diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 48171136..89c6549b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -37,7 +37,8 @@ Also include: - Your terminal, and `$TERM` inside and outside of tmux. -- Logs from tmux (see below). +- Logs from tmux (see below). Please attach logs to the issue directly rather + than using a download site or pastebin. Put in a zip file if necessary. - At most one or two screenshots, if helpful. diff --git a/.github/README.md b/.github/README.md index 2b299cc5..5590d0b2 100644 --- a/.github/README.md +++ b/.github/README.md @@ -14,8 +14,17 @@ page](https://github.com/libevent/libevent/releases/latest). It also depends on [ncurses](https://www.gnu.org/software/ncurses/), available from [this page](https://invisible-mirror.net/archives/ncurses/). +To build tmux, a C compiler (for example gcc or clang), make, pkg-config and a +suitable yacc (yacc or bison) are needed. + ## Installation +### Binary packages + +Some platforms provide binary packages for tmux, although these are sometimes +out of date. Examples are listed on +[this page](https://github.com/tmux/tmux/wiki/Installing). + ### From release tarball To build and install tmux from a release tarball, use: @@ -28,6 +37,9 @@ sudo make install tmux can use the utempter library to update utmp(5), if it is installed - run configure with `--enable-utempter` to enable this. +For more detailed instructions on building and installing tmux, see +[this page](https://github.com/tmux/tmux/wiki/Installing). + ### From version control To get and build the latest from version control - note that this requires @@ -69,7 +81,7 @@ And a bash(1) completion file at: https://github.com/imomaliev/tmux-bash-completion -For debugging, run tmux with `-v` or `-vv` to generate server and client log +For debugging, run tmux with `-v` or `-vv` to generate server and client log files in the current directory. ## Support diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 89126482..00000000 --- a/.github/lock.yml +++ /dev/null @@ -1,10 +0,0 @@ -daysUntilLock: 180 -skipCreatedBefore: false -exemptLabels: [] -lockLabel: false -lockComment: > - This thread has been automatically locked since there has not been - any recent activity after it was closed. Please open a new issue for - related bugs. -setLockReason: false -#only: issues diff --git a/.github/travis/before-install.sh b/.github/travis/before-install.sh new file mode 100644 index 00000000..a589a743 --- /dev/null +++ b/.github/travis/before-install.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ "$TRAVIS_OS_NAME" = "linux" ]; then + sudo apt-get update -qq + sudo apt-get -y install bison \ + autotools-dev \ + libncurses5-dev \ + libevent-dev \ + pkg-config \ + libutempter-dev \ + build-essential + + if [ "$BUILD" = "musl" -o "$BUILD" = "musl-static" ]; then + sudo apt-get -y install musl-dev \ + musl-tools + fi +fi + +if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then + sudo pkg install -y \ + automake \ + libevent \ + pkgconf +fi diff --git a/.github/travis/build-all.sh b/.github/travis/build-all.sh new file mode 100644 index 00000000..883868e8 --- /dev/null +++ b/.github/travis/build-all.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +BUILD=$PWD/build + +LIBEVENT=https://github.com/libevent/libevent/releases/download/release-2.1.11-stable/libevent-2.1.11-stab\ +le.tar.gz +NCURSES=https://ftp.gnu.org/gnu/ncurses/ncurses-6.2.tar.gz + +wget -4q $LIBEVENT || exit 1 +tar -zxf libevent-*.tar.gz || exit 1 +(cd libevent-*/ && + ./configure --prefix=$BUILD \ + --enable-shared \ + --disable-libevent-regress \ + --disable-samples && + make && make install) || exit 1 + +wget -4q $NCURSES || exit 1 +tar -zxf ncurses-*.tar.gz || exit 1 +(cd ncurses-*/ && + CPPFLAGS=-P ./configure --prefix=$BUILD \ + --with-shared \ + --with-termlib \ + --without-ada \ + --without-cxx \ + --without-manpages \ + --without-progs \ + --without-tests \ + --without-tack \ + --disable-database \ + --enable-termcap \ + --enable-pc-files \ + --with-pkg-config-libdir=$BUILD/lib/pkgconfig && + make && make install) || exit 1 + +sh autogen.sh || exit 1 +PKG_CONFIG_PATH=$BUILD/lib/pkgconfig ./configure --prefix=$BUILD "$@" +make && make install || (cat config.log; exit 1) diff --git a/.github/travis/build.sh b/.github/travis/build.sh new file mode 100644 index 00000000..f863d8ad --- /dev/null +++ b/.github/travis/build.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +sh autogen.sh || exit 1 +case "$BUILD" in + static) + ./configure --enable-static || exit 1 + exec make + ;; + all) + sh $(dirname $0)/build-all.sh + exec make + ;; + musl) + CC=musl-gcc sh $(dirname $0)/build-all.sh + exec make + ;; + musl-static) + CC=musl-gcc sh $(dirname $0)/build-all.sh --enable-static + exec make + ;; + *) + ./configure || exit 1 + exec make + ;; +esac diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 00000000..c6691e46 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,23 @@ +name: 'Lock Threads' + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: '30' + pr-lock-inactive-days: '60' + issue-lock-comment: > + This issue has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. + pr-lock-comment: > + This pull request has been automatically locked since there + has not been any recent activity after it was closed. + Please open a new issue for related bugs. diff --git a/.gitignore b/.gitignore index d01a0166..ec49a6de 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ configure tmux.1.* *.dSYM cmd-parse.c +fuzz/*-fuzzer +.dirstamp diff --git a/.travis.yml b/.travis.yml index fd85bf61..ea3442af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,88 @@ language: c os: - - linux - - osx + - linux + - freebsd + - osx compiler: - - gcc - - clang + - gcc + - clang + +arch: + - amd64 + - arm64 + +env: + - BUILD= + - BUILD=static + - BUILD=all + - BUILD=musl + - BUILD=musl-static + +jobs: + exclude: + # Static builds are broken on OS X (by Apple) + - os: osx + compiler: gcc + env: BUILD=static + - os: osx + compiler: clang + env: BUILD=static + # No musl on FreeBSD + - os: freebsd + compiler: gcc + env: BUILD=musl + - os: freebsd + compiler: clang + env: BUILD=musl + - os: freebsd + compiler: gcc + env: BUILD=musl-static + - os: freebsd + compiler: clang + env: BUILD=musl-static + # No musl on OS X + - os: osx + compiler: gcc + env: BUILD=musl + - os: osx + compiler: clang + env: BUILD=musl + - os: osx + compiler: gcc + env: BUILD=musl-static + - os: osx + compiler: clang + env: BUILD=musl-static + # arm64 doesn't link ncurses + - os: linux + compiler: gcc + arch: arm64 + env: BUILD=all + - os: linux + compiler: clang + arch: arm64 + env: BUILD=all + - os: linux + compiler: gcc + arch: arm64 + env: BUILD=musl + - os: linux + compiler: clang + arch: arm64 + env: BUILD=musl + - os: linux + compiler: gcc + arch: arm64 + env: BUILD=musl-static + - os: linux + compiler: clang + arch: arm64 + env: BUILD=musl-static before_install: - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update -qq; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -y install bison autotools-dev libncurses5-dev libevent-dev pkg-config libutempter-dev build-essential; fi + - sh .github/travis/before-install.sh script: - - ./autogen.sh && ./configure && make + - sh .github/travis/build.sh diff --git a/CHANGES b/CHANGES index 70bf61ae..d1dbb6ea 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,508 @@ -CHANGES FROM 3.1 TO 3.2 +CHANGES FROM 3.2a TO 3.3 -XXX +* Change so that {} is converted to tmux commands immediately when parsed. This + means it must contain valid tmux commands. For commands which expand %% and + %%%, this now only happens within string arguments. Use of nested aliases + inside {} is now forbidden. Processing of commands given in quotes remains + the same. -CHANGES FROM 3.0a to 3.1 +* Disable evports on SunOS since they are broken. + +* Do not expand the file given with tmux -f so it can contain :s. + +* Bump FORMAT_LOOP_LIMIT and add a log message when hit. + +* Add a terminal feature for the mouse (since FreeBSD termcap does not have kmous). + +* Forbid empty session names. + +* Improve error reporting when the tmux /tmp directory cannot be created or + used. + +* Give #() commands a one second grace period where the output is empty before + telling the user they aren't doing anything ("not ready"). + +* When building, pick default-terminal from the first of tmux-256color, tmux, + screen-256color, screen that is available on the build system (--with-TERM + can override). + +* Do not close popups on resize, instead adjust them to fit. + +* Add a client-active hook. + +* Make window-linked and window-unlinked window options. + +* Do not configure on macOS without the user making a choice about utf8proc + (either --enable-utf8proc or --disable-utf8proc). + +* Do not freeze output in panes when a popup is open, let them continue to + redraw. + +* Add pipe variants of the line copy commands. + +* Change copy-line and copy-end-of-line not to cancel and add -and-cancel + variants, like the other copy commands. + +* Support the OSC palette-setting sequences in popups. + +* Add a pane-colours array option to specify the defaults palette. + +* Add support for Unicode zero-width joiner. + +* Make newline a style delimiter as well so they can cross multiple lines for + readability in configuration files. + +* Change focus to be driven by events rather than scanning panes so the + ordering of in and out is consistent. + +* Add display-popup -B to open a popup without a border. + +* Add a menu for popups that can be opened with button three outside the popup + or on the left or top border. Resizing now only works on the right and bottom + borders or when using Meta. The menu allows a popup to be closed, expanded to + the full size of the client, centered in the client or changed into a pane. + +* Make command-prompt and confirm-before block by default (like run-shell). A + new -b flags runs them in the background as before. Also set return code for + confirm-before. + +* Change cursor style handling so tmux understands which sequences contain + blinking and sets the flag appropriately, means that it works whether cnorm + disables blinking or not. This now matches xterm's behaviour. + +* More accurate vi(1) word navigation in copy mode and on the status line. This + changes the meaning of the word-separators option: setting it to the empty + string is equivalent to the previous behavior. + +* Add -F for command-prompt and use it to fix "Rename" on the window menu. + +* Add different command histories for different types of prompts ("command", + "search" etc). + +CHANGES FROM 3.2 TO 3.2a + +* Add an "always" value for the "extended-keys" option; if set then tmux will + forward extended keys to applications even if they do not request them. + +* Add a "mouse" terminal feature so tmux can enable the mouse on terminals + where it is known to be supported even if terminfo(5) says otherwise. + +* Do not expand the filename given to -f so it can contain colons. + +* Fixes for problems with extended keys and modifiers, scroll region, + source-file, crosscompiling, format modifiers and other minor issues. + +CHANGES FROM 3.1c TO 3.2 + +* Add a flag to disable keys to close a message. + +* Permit shortcut keys in buffer, client, tree modes to be configured with a + format (-K flag to choose-buffer, choose-client, choose-tree). + +* Add a current_file format for the config file being parsed. + +* When display-message used in config file, show the message after the config + file finishes. + +* Add client-detached notification in control mode. + +* Improve performance of format evaluation. + +* Make jump command support UTF-8 in copy mode. + +* Support X11 colour names and other colour formats for OSC 10 and 11. + +* Add "pipe" variants of "copy-pipe" commands which do not copy. + +* Include "focused" in client flags. + +* Send Unicode directional isolate characters around horizontal pane borders if + the terminal supports UTF-8 and an extension terminfo(5) capability "Bidi" is + present. + +* Add a -S flag to new-window to make it select the existing window if one + with the given name already exists rather than failing with an error. + +* Add a format modifier to check if a window or session name exists (N/w or + N/s). + +* Add compat clock_gettime for older macOS. + +* Add a no-detached choice to detach-on-destroy which detaches only if there + are no other detached sessions to switch to. + +* Add rectangle-on and rectangle-off copy mode commands. + +* Change so that window_flags escapes # automatically. A new format + window_raw_flags contains the old unescaped version. + +* Add -N flag to never start server even if command would normally do so. + +* With incremental search, start empty and only repeat the previous search if + the user tries to search again with an empty prompt. + +* Add a value for remain-on-exit that only keeps the pane if the program + failed. + +* Add a -C flag to run-shell to use a tmux command rather than a shell command. + +* Do not list user options with show-hooks. + +* Remove current match indicator in copy mode which can't work anymore since we + only search the visible region. + +* Make synchronize-panes a pane option and add -U flag to set-option to unset + an option on all panes. + +* Make replacement of ##s consistent when drawing formats, whether followed by + [ or not. Add a flag (e) to the q: format modifier to double up #s. + +* Add -N flag to display-panes to ignore keys. + +* Change how escaping is processed for formats so that ## and # can be used in + styles. + +* Add a 'w' format modifier for string width. + +* Add support for Haiku. + +* Expand menu and popup -x and -y as formats. + +* Add numeric comparisons for formats. + +* Fire focus events even when the pane is in a mode. + +* Add -O flag to display-menu to not automatically close when all mouse buttons + are released. + +* Allow fnmatch(3) wildcards in update-environment. + +* Disable nested job expansion so that the result of #() is not expanded again. + +* Use the setal capability as well as (tmux's) Setulc. + +* Add -q flag to unbind-key to hide errors. + +* Allow -N without a command to change or add a note to an existing key. + +* Add a -w flag to set- and load-buffer to send to clipboard using OSC 52. + +* Add -F to set-environment and source-file. + +* Allow colour to be spelt as color in various places. + +* Add n: modifier to get length of a format. + +* Respond to OSC colour requests if a colour is available. + +* Add a -d option to display-message to set delay. + +* Add a way for control mode clients to subscribe to a format and be notified + of changes rather than having to poll. + +* Add some formats for search in copy mode (search_present, search_match). + +* Do not wait on shutdown for commands started with run -b. + +* Add -b flags to insert a window before (like the existing -a for after) to + break-pane, move-window, new-window. + +* Make paste -p the default for ]. + +* Add support for pausing a pane when the output buffered for a control mode + client gets too far behind. The pause-after flag with a time is set on the + pane with refresh-client -f and a paused pane may be resumed with + refresh-client -A. + +* Allow strings in configuration files to span multiple lines - newlines and + any leading whitespace are removed, as well as any following comments that + couldn't be part of a format. This allows long formats or other strings to be + annotated and indented. + +* Instead of using a custom parse function to process {} in configuration + files, treat as a set of statements the same as outside {} and convert back + to a string as the last step. This means the rules are consistent inside and + outside {}, %if and friends work at the right time, and the final result + isn't littered with unnecessary newlines. + +* Add support for extended keys - both xterm(1)'s CSI 27 ~ sequence and the + libtickit CSI u sequence are accepted; only the latter is output. tmux will + only attempt to use these if the extended-keys option is on and it can detect + that the terminal outside supports them (or is told it does with the + "extkeys" terminal feature). + +* Add an option to set the pane border lines style from a choice of single + lines (ACS or UTF-8), double or heavy (UTF-8), simple (plain ASCII) or number + (the pane numbers). Lines that won't work on a non-UTF-8 terminal are + translated back into ACS when they are output. + +* Make focus events update the latest client (like a key press). + +* Store UTF-8 characters differently to reduce memory use. + +* Fix break-pane -n when only one pane in the window. + +* Instead of sending all data to control mode clients as fast as possible, add + a limit of how much data will be sent to the client and try to use it for + panes with some degree of fairness. + +* Add an active-pane client flag (set with attach-session -f, new-session -f + or refresh-client -f). This allows a client to have an independent active + pane for interactive use (the window client pane is still used for many + things however). + +* Add a mark to copy mode, this is set with the set-mark command (bound to X) + and appears with the entire line shown using copy-mode-mark-style and the + marked character in reverse. The jump-to-mark command (bound to M-x) swaps + the mark and the cursor positions. + +* Add a -D flag to make the tmux server run in the foreground and not as a + daemon. + +* Do not loop forever in copy mode when search finds an empty match. + +* Fix the next-matching-bracket logic when using vi(1) keys. + +* Add a customize mode where options may be browsed and changed, includes + adding a brief description of each option. Bound to C-b C by default. + +* Change message log (C-b ~) so there is one for the server rather than one per + client and it remains after detach, and make it useful by logging every + command. + +* Add M-+ and M-- to tree mode to expand and collapse all. + +* Change the existing client flags for control mode to apply for any client, + use the same mechanism for the read-only flag and add an ignore-size flag. + + refresh-client -F has become -f (-F stays for backwards compatibility) and + attach-session and switch-client now have -f flags also. A new format + client_flags lists the flags and is shown by list-clients by default. + + This separates the read-only flag from "ignore size" behaviour (new + ignore-size) flag - both behaviours are useful in different circumstances. + + attach -r and switchc -r remain and set or toggle both flags together. + +* Store and restore cursor position when copy mode is resized. + +* Export TERM_PROGRAM and TERM_PROGRAM_VERSION like various other terminals. + +* Add formats for after hook command arguments: hook_arguments with all the + arguments together; hook_argument_0, hook_argument_1 and so on with + individual arguments; hook_flag_X if flag -X is present; hook_flag_X_0, + hook_flag_X_1 and so on if -X appears multiple times. + +* Try to search the entire history first for up to 200 ms so a search count can + be shown. If it takes too long, search the visible text only. + +* Use VIS_CSTYLE for paste buffers also (show \012 as \n). + +* Change default formats for tree mode, client mode and buffer mode to be more + compact and remove some clutter. + +* Add a key (e) in buffer mode to open the buffer in an editor. The buffer + contents is updated when the editor exits. + +* Add -e flag for new-session to set environment variables, like the same flag + for new-window. + +* Improve search match marking in copy mode. Two new options + copy-mode-match-style and copy-mode-current-match-style to set the style for + matches and for the current match respectively. Also a change so that if a + copy key is pressed with no selection, the current match (if any) is copied. + +* Sanitize session names like window names instead of forbidding invalid ones. + +* Check if the clear terminfo(5) capability starts with CSI and if so then + assume the terminal is VT100-like, rather than relying on the XT capability. + +* Improve command prompt tab completion and add menus both for strings and -t + and -s (when used without a trailing space). command-prompt has additional + flags for only completing a window (-W) and a target (-T), allowing C-b ' to + only show windows and C-b . only targets. + +* Change all the style options to string options so they can support formats. + Change pane-active-border-style to use this to change the border colour when + in a mode or with synchronize-panes on. This also implies a few minor changes + to existing behaviour: + + - set-option -a with a style option automatically inserts a comma between the + old value and appended text. + + - OSC 10 and 11 no longer set the window-style option, instead they store the + colour internally in the pane data and it is used as the default when the + option is evaluated. + + - status-fg and -bg now override status-style instead of the option values + being changed. + +* Add extension terminfo(5) capabilities for margins and focus reporting. + +* Try $XDG_CONFIG_HOME/tmux/tmux.conf as well as ~/.config/tmux/tmux.conf for + configuration file (the search paths are in TMUX_CONF in Makefile.am). + +* Remove the DSR 1337 iTerm2 extension and replace by the extended device + attributes sequence (CSI > q) supported by more terminals. + +* Add a -s flag to copy-mode to specify a different pane for the source + content. This means it is possible to view two places in a pane's history at + the same time in different panes, or view the history while still using the + pane. Pressing r refreshes the content from the source pane. + +* Add an argument to list-commands to show only a single command. + +* Change copy mode to make copy of the pane history so it does not need to + freeze the pane. + +* Restore pane_current_path format from portable tmux on OpenBSD. + +* Wait until the initial command sequence is done before sending a device + attributes request and other bits that prompt a reply from the terminal. This + means that stray replies are not left on the terminal if the command has + attached and then immediately detached and tmux will not be around to receive + them. + +* Add a -f filter argument to the list commands like choose-tree. + +* Move specific hooks for panes to pane options and windows for window options + rather than all hooks being session options. These hooks are now window options: + + window-layout-changed + window-linked + window-pane-changed + window-renamed + window-unlinked + + And these are now pane options: + + pane-died + pane-exited + pane-focus-in + pane-focus-out + pane-mode-changed + pane-set-clipboard + + Any existing configurations using these hooks on a session rather than + globally (that is, set-hook or set-option without -g) may need to be changed. + +* Show signal names when a process exits with remain-on-exit on platforms which + have a way to get them. + +* Start menu with top item selected if no mouse and use mode-style for the + selected item. + +* Add a copy-command option and change copy-pipe and friends to pipe to it if + used without arguments, allows all the default copy key bindings to be + changed to pipe with one option rather than needing to change each key + binding individually. + +* Tidy up the terminal detection and feature code and add named sets of + terminal features, each of which are defined in one place and map to a + builtin set of terminfo(5) capabilities. Features can be specified based on + TERM with a new terminal-features option or with the -T flag when running + tmux. tmux will also detect a few common terminals from the DA and DSR + responses. + + This is intended to make it easier to configure tmux's use of terminfo(5) + even in the presence of outdated ncurses(3) or terminfo(5) databases or for + features which do not yet have a terminfo(5) entry. Instead of having to grok + terminfo(5) capability names and what they should be set to in the + terminal-overrides option, the user can hopefully just give tmux a feature + name and let it do the right thing. + + The terminal-overrides option remains both for backwards compatibility and to + allow tweaks of individual capabilities. + +* Support mintty's application escape sequence (means tmux doesn't have to + delay to wait for Escape, so no need to reduce escape-time when using + mintty). + +* Change so main-pane-width and height can be given as a percentage. + +* Support for the iTerm2 synchronized updates feature (allows the terminal to + avoid unnecessary drawing while output is still in progress). + +* Make the mouse_word and mouse_line formats work in copy mode and enable the + default pane menu in copy mode. + +* Add a -T flag to resize-pane to trim lines below the cursor, moving lines out + of the history. + +* Add a way to mark environment variables as "hidden" so they can be used by + tmux (for example in formats) but are not set in the environment for new + panes. set-environment and show-environment have a new -h flag and there is a + new %hidden statement for the configuration file. + +* Change default position for display-menu -x and -y to centre rather than top + left. + +* Add support for per-client transient popups, similar to menus but which are + connected to an external command (like a pane). These are created with new + command display-popup. + +* Change double and triple click bindings so that only one is fired (previously + double click was fired on the way to triple click). Also add default double + and triple click bindings to copy the word or line under the cursor and + change the existing bindings in copy mode to do the same. + +* Add a default binding for button 2 to paste. + +* Add -d flag to run-shell to delay before running the command and allow it to + be used without a command so it just delays. + +* Add C-g to cancel command prompt with vi keys as well as emacs, and q in + command mode. + +* When the server socket is given with -S, create it with umask 177 instead of + 117 (because it may not be in a safe directory like the default directory in + /tmp). + +* Add a copy-mode -H flag to hide the position marker in the top right. + +* Add number operators for formats (+, -, *, / and m), + +CHANGED FROM 3.1b TO 3.1c + +* Do not write after the end of the array and overwrite the stack when + colon-separated SGR sequences contain empty arguments. + +CHANGES FROM 3.1a TO 3.1b + +* Fix build on systems without sys/queue.h. + +* Fix crash when allow-rename is on and an empty name is set. + +CHANGES FROM 3.1 TO 3.1a + +* Do not close stdout prematurely in control mode since it is needed to print + exit messages. Prevents hanging when detaching with iTerm2. + +CHANGES FROM 3.0a TO 3.1 + +* Only search the visible part of the history when marking (highlighting) + search terms. This is much faster than searching the whole history and solves + problems with large histories. The count of matches shown is now the visible + matches rather than all matches. + +* Search using regular expressions in copy mode. search-forward and + search-backward use regular expressions by default; the incremental versions + do not. + +* Turn off mouse mode 1003 as well as the rest when exiting. + +* Add selection_active format for when the selection is present but not moving + with the cursor. + +* Fix dragging with modifier keys, so binding keys such as C-MouseDrag1Pane and + C-MouseDragEnd1Pane now work. + +* Add -a to list-keys to also list keys without notes with -N. + +* Do not jump to next word end if already on a word end when selecting a word; + fixes select-word with single character words and vi(1) keys. + +* Fix top and bottom pane calculation with pane border status enabled. * Add support for adding a note to a key binding (with bind-key -N) and use this to add descriptions to the default key bindings. A new -N flag to diff --git a/Makefile.am b/Makefile.am index 380d9bf5..55bf150f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,8 +12,9 @@ dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. AM_CPPFLAGS += @XOPEN_DEFINES@ \ - -DTMUX_VERSION="\"@VERSION@\"" \ - -DTMUX_CONF="\"$(sysconfdir)/tmux.conf:~/.tmux.conf:~/.config/tmux/tmux.conf\"" + -DTMUX_VERSION='"@VERSION@"' \ + -DTMUX_CONF='"$(sysconfdir)/tmux.conf:~/.tmux.conf:$$XDG_CONFIG_HOME/tmux/tmux.conf:~/.config/tmux/tmux.conf"' \ + -DTMUX_TERM='"@DEFAULT_TERM@"' # Additional object files. LDADD = $(LIBOBJS) @@ -28,7 +29,10 @@ AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare AM_CFLAGS += -Wundef -Wbad-function-cast -Winline -Wcast-align AM_CFLAGS += -Wdeclaration-after-statement -Wno-pointer-sign -Wno-attributes -AM_CFLAGS += -Wno-unused-result +AM_CFLAGS += -Wno-unused-result -Wno-format-y2k +if IS_DARWIN +AM_CFLAGS += -Wno-deprecated-declarations -Wno-cast-align +endif AM_CPPFLAGS += -DDEBUG endif AM_CPPFLAGS += -iquote. @@ -58,6 +62,11 @@ if IS_NETBSD AM_CPPFLAGS += -D_OPENBSD_SOURCE endif +# Set flags for Haiku. +if IS_HAIKU +AM_CPPFLAGS += -D_BSD_SOURCE +endif + # List of sources. dist_tmux_SOURCES = \ alerts.c \ @@ -120,6 +129,7 @@ dist_tmux_SOURCES = \ cmd-show-environment.c \ cmd-show-messages.c \ cmd-show-options.c \ + cmd-show-prompt-history.c \ cmd-source-file.c \ cmd-split-window.c \ cmd-swap-pane.c \ @@ -136,6 +146,7 @@ dist_tmux_SOURCES = \ file.c \ format.c \ format-draw.c \ + grid-reader.c \ grid-view.c \ grid.c \ input-keys.c \ @@ -155,6 +166,7 @@ dist_tmux_SOURCES = \ options-table.c \ options.c \ paste.c \ + popup.c \ proc.c \ regsub.c \ resize.c \ @@ -171,7 +183,9 @@ dist_tmux_SOURCES = \ style.c \ tmux.c \ tmux.h \ + tmux-protocol.h \ tty-acs.c \ + tty-features.c \ tty-keys.c \ tty-term.c \ tty.c \ @@ -180,11 +194,11 @@ dist_tmux_SOURCES = \ window-client.c \ window-clock.c \ window-copy.c \ + window-customize.c \ window-tree.c \ window.c \ xmalloc.c \ - xmalloc.h \ - xterm-keys.c + xmalloc.h nodist_tmux_SOURCES = osdep-@PLATFORM@.c # Add compat file for forkpty. @@ -197,6 +211,12 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif +if NEED_FUZZING +check_PROGRAMS = fuzz/input-fuzzer +fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) +fuzz_input_fuzzer_LDADD = $(LDADD) $(tmux_OBJECTS) +endif + # Install tmux.1 in the right format. install-exec-hook: if test x@MANFORMAT@ = xmdoc; then \ diff --git a/README b/README index 4f577060..8732a027 100644 --- a/README +++ b/README @@ -16,6 +16,9 @@ It also depends on ncurses, available from: https://invisible-mirror.net/archives/ncurses/ +To build tmux, a C compiler (for example gcc or clang), make, pkg-config and a +suitable yacc (yacc or bison) are needed. + * Installation To build and install tmux from a release tarball, use: @@ -52,6 +55,14 @@ source tree with: A small example configuration is in example_tmux.conf. +Other documentation is available in the wiki: + + https://github.com/tmux/tmux/wiki + +Also see the tmux FAQ at: + + https://github.com/tmux/tmux/wiki/FAQ + A bash(1) completion file is at: https://github.com/imomaliev/tmux-bash-completion diff --git a/SYNCING b/SYNCING index b8cc4b5c..07be40c4 100644 --- a/SYNCING +++ b/SYNCING @@ -89,7 +89,7 @@ of the remote name "obsd-tmux", we can now create the master branch from % 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-openbsd" together, the fake history points added to the diff --git a/alerts.c b/alerts.c index 6fe88a09..d3c5df05 100644 --- a/alerts.c +++ b/alerts.c @@ -18,7 +18,6 @@ #include -#include #include #include "tmux.h" @@ -200,7 +199,7 @@ alerts_check_bell(struct window *w) * not check WINLINK_BELL). */ s = wl->session; - if (s->curw != wl) { + if (s->curw != wl || s->attached == 0) { wl->flags |= WINLINK_BELL; server_status_session(s); } @@ -236,7 +235,7 @@ alerts_check_activity(struct window *w) if (wl->flags & WINLINK_ACTIVITY) continue; s = wl->session; - if (s->curw != wl) { + if (s->curw != wl || s->attached == 0) { wl->flags |= WINLINK_ACTIVITY; server_status_session(s); } @@ -272,7 +271,7 @@ alerts_check_silence(struct window *w) if (wl->flags & WINLINK_SILENCE) continue; s = wl->session; - if (s->curw != wl) { + if (s->curw != wl || s->attached == 0) { wl->flags |= WINLINK_SILENCE; server_status_session(s); } @@ -315,9 +314,12 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option) tty_putcode(&c->tty, TTYC_BEL); if (visual == VISUAL_OFF) continue; - if (c->session->curw == wl) - status_message_set(c, "%s in current window", type); - else - status_message_set(c, "%s in window %d", type, wl->idx); + if (c->session->curw == wl) { + status_message_set(c, -1, 1, 0, "%s in current window", + type); + } else { + status_message_set(c, -1, 1, 0, "%s in window %d", type, + wl->idx); + } } } diff --git a/arguments.c b/arguments.c index 026272af..4b08de2c 100644 --- a/arguments.c +++ b/arguments.c @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -28,12 +29,10 @@ * Manipulate command arguments. */ -struct args_value { - char *value; - TAILQ_ENTRY(args_value) entry; -}; +/* List of argument values. */ TAILQ_HEAD(args_values, args_value); +/* Single arguments flag. */ struct args_entry { u_char flag; struct args_values values; @@ -41,6 +40,20 @@ struct args_entry { RB_ENTRY(args_entry) entry; }; +/* Parsed argument flags and values. */ +struct args { + struct args_tree tree; + u_int count; + struct args_value *values; +}; + +/* Prepared command state. */ +struct args_command_state { + struct cmd_list *cmdlist; + char *cmd; + struct cmd_parse_input pi; +}; + static struct args_entry *args_find(struct args *, u_char); static int args_cmp(struct args_entry *, struct args_entry *); @@ -55,44 +68,297 @@ args_cmp(struct args_entry *a1, struct args_entry *a2) /* Find a flag in the arguments tree. */ static struct args_entry * -args_find(struct args *args, u_char ch) +args_find(struct args *args, u_char flag) { struct args_entry entry; - entry.flag = ch; + entry.flag = flag; return (RB_FIND(args_tree, &args->tree, &entry)); } -/* Parse an argv and argc into a new argument set. */ -struct args * -args_parse(const char *template, int argc, char **argv) +/* Copy value. */ +static void +args_copy_value(struct args_value *to, struct args_value *from) { - struct args *args; - int opt; + to->type = from->type; + switch (from->type) { + case ARGS_NONE: + break; + case ARGS_COMMANDS: + to->cmdlist = from->cmdlist; + to->cmdlist->references++; + break; + case ARGS_STRING: + to->string = xstrdup(from->string); + break; + } +} + +/* Get value as string. */ +static const char * +args_value_as_string(struct args_value *value) +{ + switch (value->type) { + case ARGS_NONE: + return (""); + case ARGS_COMMANDS: + if (value->cached == NULL) + value->cached = cmd_list_print(value->cmdlist, 0); + return (value->cached); + case ARGS_STRING: + return (value->string); + } +} + +/* Create an empty arguments set. */ +struct args * +args_create(void) +{ + struct args *args; args = xcalloc(1, sizeof *args); - - optreset = 1; - optind = 1; - - while ((opt = getopt(argc, argv, template)) != -1) { - if (opt < 0) - continue; - if (opt == '?' || strchr(template, opt) == NULL) { - args_free(args); - return (NULL); - } - args_set(args, opt, optarg); - } - argc -= optind; - argv += optind; - - args->argc = argc; - args->argv = cmd_copy_argv(argc, argv); - + RB_INIT(&args->tree); return (args); } +/* Parse arguments into a new argument set. */ +struct args * +args_parse(const struct args_parse *parse, struct args_value *values, + u_int count, char **cause) +{ + struct args *args; + u_int i; + enum args_parse_type type; + struct args_value *value, *new; + u_char flag, argument; + const char *found, *string, *s; + + if (count == 0) + return (args_create()); + + args = args_create(); + for (i = 1; i < count; /* nothing */) { + value = &values[i]; + if (value->type != ARGS_STRING) + break; + + string = value->string; + if (*string++ != '-' || *string == '\0') + break; + i++; + if (string[0] == '-' && string[1] == '\0') + break; + + for (;;) { + flag = *string++; + if (flag == '\0') + break; + if (flag == '?') { + args_free(args); + return (NULL); + } + if (!isalnum(flag)) { + xasprintf(cause, "invalid flag -%c", flag); + args_free(args); + return (NULL); + } + found = strchr(parse->template, flag); + if (found == NULL) { + xasprintf(cause, "unknown flag -%c", flag); + args_free(args); + return (NULL); + } + argument = *++found; + if (argument != ':') { + log_debug("%s: -%c", __func__, flag); + args_set(args, flag, NULL); + continue; + } + new = xcalloc(1, sizeof *new); + if (*string != '\0') { + new->type = ARGS_STRING; + new->string = xstrdup(string); + } else { + if (i == count) { + xasprintf(cause, + "-%c expects an argument", + flag); + args_free(args); + return (NULL); + } + if (values[i].type != ARGS_STRING) { + xasprintf(cause, + "-%c argument must be a string", + flag); + args_free(args); + return (NULL); + } + args_copy_value(new, &values[i++]); + } + s = args_value_as_string(new); + log_debug("%s: -%c = %s", __func__, flag, s); + args_set(args, flag, new); + break; + } + } + log_debug("%s: flags end at %u of %u", __func__, i, count); + if (i != count) { + for (/* nothing */; i < count; i++) { + value = &values[i]; + + s = args_value_as_string(value); + log_debug("%s: %u = %s (type %d)", __func__, i, s, + value->type); + + if (parse->cb != NULL) { + type = parse->cb(args, args->count, cause); + if (type == ARGS_PARSE_INVALID) { + args_free(args); + return (NULL); + } + } else + type = ARGS_PARSE_STRING; + + args->values = xrecallocarray(args->values, + args->count, args->count + 1, sizeof *args->values); + new = &args->values[args->count++]; + + switch (type) { + case ARGS_PARSE_INVALID: + fatalx("unexpected argument type"); + case ARGS_PARSE_STRING: + if (value->type != ARGS_STRING) { + xasprintf(cause, + "argument %u must be \"string\"", + args->count); + args_free(args); + return (NULL); + } + args_copy_value(new, value); + break; + case ARGS_PARSE_COMMANDS_OR_STRING: + args_copy_value(new, value); + break; + case ARGS_PARSE_COMMANDS: + if (value->type != ARGS_COMMANDS) { + xasprintf(cause, + "argument %u must be { commands }", + args->count); + args_free(args); + return (NULL); + } + args_copy_value(new, value); + break; + } + } + } + + if (parse->lower != -1 && args->count < (u_int)parse->lower) { + xasprintf(cause, + "too few arguments (need at least %u)", + parse->lower); + args_free(args); + return (NULL); + } + if (parse->upper != -1 && args->count > (u_int)parse->upper) { + xasprintf(cause, + "too many arguments (need at most %u)", + parse->upper); + args_free(args); + return (NULL); + } + return (args); +} + +/* Copy and expand a value. */ +static void +args_copy_copy_value(struct args_value *to, struct args_value *from, int argc, + char **argv) +{ + char *s, *expanded; + int i; + + to->type = from->type; + switch (from->type) { + case ARGS_NONE: + break; + case ARGS_STRING: + expanded = xstrdup(from->string); + for (i = 0; i < argc; i++) { + s = cmd_template_replace(expanded, argv[i], i + 1); + free(expanded); + expanded = s; + } + to->string = expanded; + break; + case ARGS_COMMANDS: + to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv); + break; + } +} + +/* Copy an arguments set. */ +struct args * +args_copy(struct args *args, int argc, char **argv) +{ + struct args *new_args; + struct args_entry *entry; + struct args_value *value, *new_value; + u_int i; + + cmd_log_argv(argc, argv, "%s", __func__); + + new_args = args_create(); + RB_FOREACH(entry, args_tree, &args->tree) { + if (TAILQ_EMPTY(&entry->values)) { + for (i = 0; i < entry->count; i++) + args_set(new_args, entry->flag, NULL); + continue; + } + TAILQ_FOREACH(value, &entry->values, entry) { + new_value = xcalloc(1, sizeof *new_value); + args_copy_copy_value(new_value, value, argc, argv); + args_set(new_args, entry->flag, new_value); + } + } + if (args->count == 0) + return (new_args); + new_args->count = args->count; + new_args->values = xcalloc(args->count, sizeof *new_args->values); + for (i = 0; i < args->count; i++) { + new_value = &new_args->values[i]; + args_copy_copy_value(new_value, &args->values[i], argc, argv); + } + return (new_args); +} + +/* Free a value. */ +void +args_free_value(struct args_value *value) +{ + switch (value->type) { + case ARGS_NONE: + break; + case ARGS_STRING: + free(value->string); + break; + case ARGS_COMMANDS: + cmd_list_free(value->cmdlist); + break; + } + free(value->cached); +} + +/* Free values. */ +void +args_free_values(struct args_value *values, u_int count) +{ + u_int i; + + for (i = 0; i < count; i++) + args_free_value(&values[i]); +} + /* Free an arguments set. */ void args_free(struct args *args) @@ -102,13 +368,14 @@ args_free(struct args *args) struct args_value *value; struct args_value *value1; - cmd_free_argv(args->argc, args->argv); + args_free_values(args->values, args->count); + free(args->values); RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) { RB_REMOVE(args_tree, &args->tree, entry); TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) { TAILQ_REMOVE(&entry->values, value, entry); - free(value->value); + args_free_value(value); free(value); } free(entry); @@ -117,11 +384,52 @@ args_free(struct args *args) free(args); } +/* Convert arguments to vector. */ +void +args_to_vector(struct args *args, int *argc, char ***argv) +{ + char *s; + u_int i; + + *argc = 0; + *argv = NULL; + + for (i = 0; i < args->count; i++) { + switch (args->values[i].type) { + case ARGS_NONE: + break; + case ARGS_STRING: + cmd_append_argv(argc, argv, args->values[i].string); + break; + case ARGS_COMMANDS: + s = cmd_list_print(args->values[i].cmdlist, 0); + cmd_append_argv(argc, argv, s); + free(s); + break; + } + } +} + +/* Convert arguments from vector. */ +struct args_value * +args_from_vector(int argc, char **argv) +{ + struct args_value *values; + int i; + + values = xcalloc(argc, sizeof *values); + for (i = 0; i < argc; i++) { + values[i].type = ARGS_STRING; + values[i].string = xstrdup(argv[i]); + } + return (values); +} + /* Add to string. */ static void printflike(3, 4) args_print_add(char **buf, size_t *len, const char *fmt, ...) { - va_list ap; + va_list ap; char *s; size_t slen; @@ -138,43 +446,35 @@ args_print_add(char **buf, size_t *len, const char *fmt, ...) /* Add value to string. */ static void -args_print_add_value(char **buf, size_t *len, struct args_entry *entry, - struct args_value *value) +args_print_add_value(char **buf, size_t *len, struct args_value *value) { - char *escaped; - - if (**buf != '\0') - args_print_add(buf, len, " -%c ", entry->flag); - else - args_print_add(buf, len, "-%c ", entry->flag); - - escaped = args_escape(value->value); - args_print_add(buf, len, "%s", escaped); - free(escaped); -} - -/* Add argument to string. */ -static void -args_print_add_argument(char **buf, size_t *len, const char *argument) -{ - char *escaped; + char *expanded = NULL; if (**buf != '\0') args_print_add(buf, len, " "); - escaped = args_escape(argument); - args_print_add(buf, len, "%s", escaped); - free(escaped); + switch (value->type) { + case ARGS_NONE: + break; + case ARGS_COMMANDS: + expanded = cmd_list_print(value->cmdlist, 0); + args_print_add(buf, len, "{ %s }", expanded); + break; + case ARGS_STRING: + expanded = args_escape(value->string); + args_print_add(buf, len, "%s", expanded); + break; + } + free(expanded); } /* Print a set of arguments. */ char * args_print(struct args *args) { - size_t len; + size_t len; char *buf; - int i; - u_int j; + u_int i, j; struct args_entry *entry; struct args_value *value; @@ -194,13 +494,18 @@ args_print(struct args *args) /* Then the flags with arguments. */ RB_FOREACH(entry, args_tree, &args->tree) { - TAILQ_FOREACH(value, &entry->values, entry) - args_print_add_value(&buf, &len, entry, value); + TAILQ_FOREACH(value, &entry->values, entry) { + if (*buf != '\0') + args_print_add(&buf, &len, " -%c", entry->flag); + else + args_print_add(&buf, &len, "-%c", entry->flag); + args_print_add_value(&buf, &len, value); + } } /* And finally the argument vector. */ - for (i = 0; i < args->argc; i++) - args_print_add_argument(&buf, &len, args->argv[i]); + for (i = 0; i < args->count; i++) + args_print_add_value(&buf, &len, &args->values[i]); return (buf); } @@ -209,25 +514,35 @@ args_print(struct args *args) char * args_escape(const char *s) { - static const char quoted[] = " #\"';${}"; + static const char dquoted[] = " #';${}%"; + static const char squoted[] = " \""; char *escaped, *result; - int flags; + int flags, quotes = 0; + + if (*s == '\0') { + xasprintf(&result, "''"); + return (result); + } + if (s[strcspn(s, dquoted)] != '\0') + quotes = '"'; + else if (s[strcspn(s, squoted)] != '\0') + quotes = '\''; - if (*s == '\0') - return (xstrdup(s)); if (s[0] != ' ' && - (strchr(quoted, s[0]) != NULL || s[0] == '~') && - s[1] == '\0') { + s[1] == '\0' && + (quotes != 0 || s[0] == '~')) { xasprintf(&escaped, "\\%c", s[0]); return (escaped); } flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; - if (s[strcspn(s, quoted)] != '\0') + if (quotes == '"') flags |= VIS_DQ; utf8_stravis(&escaped, s, flags); - if (flags & VIS_DQ) { + if (quotes == '\'') + xasprintf(&result, "'%s'", escaped); + else if (quotes == '"') { if (*escaped == '~') xasprintf(&result, "\"\\%s\"", escaped); else @@ -244,11 +559,11 @@ args_escape(const char *s) /* Return if an argument is present. */ int -args_has(struct args *args, u_char ch) +args_has(struct args *args, u_char flag) { struct args_entry *entry; - entry = args_find(args, ch); + entry = args_find(args, flag); if (entry == NULL) return (0); return (entry->count); @@ -256,83 +571,256 @@ args_has(struct args *args, u_char ch) /* Set argument value in the arguments tree. */ void -args_set(struct args *args, u_char ch, const char *s) +args_set(struct args *args, u_char flag, struct args_value *value) { struct args_entry *entry; - struct args_value *value; - entry = args_find(args, ch); + entry = args_find(args, flag); if (entry == NULL) { entry = xcalloc(1, sizeof *entry); - entry->flag = ch; + entry->flag = flag; entry->count = 1; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); } else entry->count++; - - if (s != NULL) { - value = xcalloc(1, sizeof *value); - value->value = xstrdup(s); + if (value != NULL && value->type != ARGS_NONE) TAILQ_INSERT_TAIL(&entry->values, value, entry); - } } /* Get argument value. Will be NULL if it isn't present. */ const char * -args_get(struct args *args, u_char ch) +args_get(struct args *args, u_char flag) { struct args_entry *entry; - if ((entry = args_find(args, ch)) == NULL) + if ((entry = args_find(args, flag)) == NULL) return (NULL); - return (TAILQ_LAST(&entry->values, args_values)->value); + if (TAILQ_EMPTY(&entry->values)) + return (NULL); + return (TAILQ_LAST(&entry->values, args_values)->string); +} + +/* Get first argument. */ +u_char +args_first(struct args *args, struct args_entry **entry) +{ + *entry = RB_MIN(args_tree, &args->tree); + if (*entry == NULL) + return (0); + return ((*entry)->flag); +} + +/* Get next argument. */ +u_char +args_next(struct args_entry **entry) +{ + *entry = RB_NEXT(args_tree, &args->tree, *entry); + if (*entry == NULL) + return (0); + return ((*entry)->flag); +} + +/* Get argument count. */ +u_int +args_count(struct args *args) +{ + return (args->count); +} + +/* Get argument values. */ +struct args_value * +args_values(struct args *args) +{ + return (args->values); +} + +/* Get argument value. */ +struct args_value * +args_value(struct args *args, u_int idx) +{ + if (idx >= args->count) + return (NULL); + return (&args->values[idx]); +} + +/* Return argument as string. */ +const char * +args_string(struct args *args, u_int idx) +{ + if (idx >= args->count) + return (NULL); + return (args_value_as_string(&args->values[idx])); +} + +/* Make a command now. */ +struct cmd_list * +args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx, + int expand) +{ + struct args_command_state *state; + char *error; + struct cmd_list *cmdlist; + + state = args_make_commands_prepare(self, item, idx, NULL, 0, expand); + cmdlist = args_make_commands(state, 0, NULL, &error); + if (cmdlist == NULL) { + cmdq_error(item, "%s", error); + free(error); + } + else + cmdlist->references++; + args_make_commands_free(state); + return (cmdlist); +} + +/* Save bits to make a command later. */ +struct args_command_state * +args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx, + const char *default_command, int wait, int expand) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item); + struct args_value *value; + struct args_command_state *state; + const char *cmd; + + state = xcalloc(1, sizeof *state); + + if (idx < args->count) { + value = &args->values[idx]; + if (value->type == ARGS_COMMANDS) { + state->cmdlist = value->cmdlist; + state->cmdlist->references++; + return (state); + } + cmd = value->string; + } else { + if (default_command == NULL) + fatalx("argument out of range"); + cmd = default_command; + } + + + if (expand) + state->cmd = format_single_from_target(item, cmd); + else + state->cmd = xstrdup(cmd); + log_debug("%s: %s", __func__, state->cmd); + + if (wait) + state->pi.item = item; + cmd_get_source(self, &state->pi.file, &state->pi.line); + state->pi.c = tc; + if (state->pi.c != NULL) + state->pi.c->references++; + cmd_find_copy_state(&state->pi.fs, target); + + return (state); +} + +/* Return argument as command. */ +struct cmd_list * +args_make_commands(struct args_command_state *state, int argc, char **argv, + char **error) +{ + struct cmd_parse_result *pr; + char *cmd, *new_cmd; + int i; + + if (state->cmdlist != NULL) { + if (argc == 0) + return (state->cmdlist); + return (cmd_list_copy(state->cmdlist, argc, argv)); + } + + cmd = xstrdup(state->cmd); + for (i = 0; i < argc; i++) { + new_cmd = cmd_template_replace(cmd, argv[i], i + 1); + log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); + free(cmd); + cmd = new_cmd; + } + log_debug("%s: %s", __func__, cmd); + + pr = cmd_parse_from_string(cmd, &state->pi); + free(cmd); + switch (pr->status) { + case CMD_PARSE_ERROR: + *error = pr->error; + return (NULL); + case CMD_PARSE_SUCCESS: + return (pr->cmdlist); + } +} + +/* Free commands state. */ +void +args_make_commands_free(struct args_command_state *state) +{ + if (state->cmdlist != NULL) + cmd_list_free(state->cmdlist); + if (state->pi.c != NULL) + server_client_unref(state->pi.c); + free(state->cmd); + free(state); +} + +/* Get prepared command. */ +char * +args_make_commands_get_command(struct args_command_state *state) +{ + struct cmd *first; + int n; + char *s; + + if (state->cmdlist != NULL) { + first = cmd_list_first(state->cmdlist); + if (first == NULL) + return (xstrdup("")); + return (xstrdup(cmd_get_entry(first)->name)); + } + n = strcspn(state->cmd, " ,"); + xasprintf(&s, "%.*s", n, state->cmd); + return (s); } /* Get first value in argument. */ -const char * -args_first_value(struct args *args, u_char ch, struct args_value **value) +struct args_value * +args_first_value(struct args *args, u_char flag) { struct args_entry *entry; - if ((entry = args_find(args, ch)) == NULL) + if ((entry = args_find(args, flag)) == NULL) return (NULL); - - *value = TAILQ_FIRST(&entry->values); - if (*value == NULL) - return (NULL); - return ((*value)->value); + return (TAILQ_FIRST(&entry->values)); } /* Get next value in argument. */ -const char * -args_next_value(struct args_value **value) +struct args_value * +args_next_value(struct args_value *value) { - if (*value == NULL) - return (NULL); - *value = TAILQ_NEXT(*value, entry); - if (*value == NULL) - return (NULL); - return ((*value)->value); + return (TAILQ_NEXT(value, entry)); } /* Convert an argument value to a number. */ long long -args_strtonum(struct args *args, u_char ch, long long minval, long long maxval, - char **cause) +args_strtonum(struct args *args, u_char flag, long long minval, + long long maxval, char **cause) { const char *errstr; - long long ll; + long long ll; struct args_entry *entry; struct args_value *value; - if ((entry = args_find(args, ch)) == NULL) { + if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } value = TAILQ_LAST(&entry->values, args_values); - ll = strtonum(value->value, minval, maxval, &errstr); + ll = strtonum(value->string, minval, maxval, &errstr); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); @@ -341,3 +829,60 @@ args_strtonum(struct args *args, u_char ch, long long minval, long long maxval, *cause = NULL; return (ll); } + +/* Convert an argument to a number which may be a percentage. */ +long long +args_percentage(struct args *args, u_char flag, long long minval, + long long maxval, long long curval, char **cause) +{ + const char *value; + struct args_entry *entry; + + if ((entry = args_find(args, flag)) == NULL) { + *cause = xstrdup("missing"); + return (0); + } + value = TAILQ_LAST(&entry->values, args_values)->string; + return (args_string_percentage(value, minval, maxval, curval, cause)); +} + +/* Convert a string to a number which may be a percentage. */ +long long +args_string_percentage(const char *value, long long minval, long long maxval, + long long curval, char **cause) +{ + const char *errstr; + long long ll; + size_t valuelen = strlen(value); + char *copy; + + if (value[valuelen - 1] == '%') { + copy = xstrdup(value); + copy[valuelen - 1] = '\0'; + + ll = strtonum(copy, 0, 100, &errstr); + free(copy); + if (errstr != NULL) { + *cause = xstrdup(errstr); + return (0); + } + ll = (curval * ll) / 100; + if (ll < minval) { + *cause = xstrdup("too small"); + return (0); + } + if (ll > maxval) { + *cause = xstrdup("too large"); + return (0); + } + } else { + ll = strtonum(value, minval, maxval, &errstr); + if (errstr != NULL) { + *cause = xstrdup(errstr); + return (0); + } + } + + *cause = NULL; + return (ll); +} diff --git a/attributes.c b/attributes.c index ca88a056..b839f06d 100644 --- a/attributes.c +++ b/attributes.c @@ -31,7 +31,8 @@ attributes_tostring(int attr) if (attr == 0) return ("none"); - len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s", + len = xsnprintf(buf, sizeof buf, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (attr & GRID_ATTR_CHARSET) ? "acs," : "", (attr & GRID_ATTR_BRIGHT) ? "bright," : "", (attr & GRID_ATTR_DIM) ? "dim," : "", (attr & GRID_ATTR_UNDERSCORE) ? "underscore," : "", @@ -62,6 +63,7 @@ attributes_fromstring(const char *str) const char *name; int attr; } table[] = { + { "acs", GRID_ATTR_CHARSET }, { "bright", GRID_ATTR_BRIGHT }, { "bold", GRID_ATTR_BRIGHT }, { "dim", GRID_ATTR_DIM }, diff --git a/cfg.c b/cfg.c index 933eda4e..5b64c2aa 100644 --- a/cfg.c +++ b/cfg.c @@ -27,12 +27,15 @@ #include "tmux.h" struct client *cfg_client; -static char *cfg_file; int cfg_finished; static char **cfg_causes; static u_int cfg_ncauses; static struct cmdq_item *cfg_item; +int cfg_quiet = 1; +char **cfg_files; +u_int cfg_nfiles; + static enum cmd_retval cfg_client_done(__unused struct cmdq_item *item, __unused void *data) { @@ -59,52 +62,11 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) return (CMD_RETURN_NORMAL); } -void -set_cfg_file(const char *path) -{ - free(cfg_file); - cfg_file = xstrdup(path); -} - -static char * -expand_cfg_file(const char *path, const char *home) -{ - char *expanded, *name; - const char *end; - struct environ_entry *value; - - if (strncmp(path, "~/", 2) == 0) { - if (home == NULL) - return (NULL); - xasprintf(&expanded, "%s%s", home, path + 1); - return (expanded); - } - - if (*path == '$') { - end = strchr(path, '/'); - if (end == NULL) - name = xstrdup(path + 1); - else - name = xstrndup(path + 1, end - path - 1); - value = environ_find(global_environ, name); - free(name); - if (value == NULL) - return (NULL); - if (end == NULL) - end = ""; - xasprintf(&expanded, "%s%s", value->value, end); - return (expanded); - } - - return (xstrdup(path)); -} - void start_cfg(void) { - const char *home = find_home(); - struct client *c; - char *path, *copy, *next, *expanded; + struct client *c; + u_int i; /* * Configuration files are loaded without a client, so commands are run @@ -122,21 +84,12 @@ start_cfg(void) cmdq_append(c, cfg_item); } - if (cfg_file == NULL) { - path = copy = xstrdup(TMUX_CONF); - while ((next = strsep(&path, ":")) != NULL) { - expanded = expand_cfg_file(next, home); - if (expanded == NULL) { - log_debug("couldn't expand %s", next); - continue; - } - log_debug("expanded %s to %s", next, expanded); - load_cfg(expanded, c, NULL, CMD_PARSE_QUIET, NULL); - free(expanded); - } - free(copy); - } else - load_cfg(cfg_file, c, NULL, 0, NULL); + for (i = 0; i < cfg_nfiles; i++) { + if (cfg_quiet) + load_cfg(cfg_files[i], c, NULL, CMD_PARSE_QUIET, NULL); + else + load_cfg(cfg_files[i], c, NULL, 0, NULL); + } cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } @@ -149,6 +102,7 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, struct cmd_parse_input pi; struct cmd_parse_result *pr; struct cmdq_item *new_item0; + struct cmdq_state *state; if (new_item != NULL) *new_item = NULL; @@ -170,8 +124,6 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, pr = cmd_parse_from_file(f, &pi); fclose(f); - if (pr->status == CMD_PARSE_EMPTY) - return (0); if (pr->status == CMD_PARSE_ERROR) { cfg_add_cause("%s", pr->error); free(pr->error); @@ -182,12 +134,19 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, return (0); } - new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); + if (item != NULL) + state = cmdq_copy_state(cmdq_get_state(item)); + else + state = cmdq_new_state(NULL, NULL, 0); + cmdq_add_format(state, "current_file", "%s", pi.file); + + new_item0 = cmdq_get_command(pr->cmdlist, state); if (item != NULL) new_item0 = cmdq_insert_after(item, new_item0); else new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); + cmdq_free_state(state); if (new_item != NULL) *new_item = new_item0; @@ -202,6 +161,7 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, struct cmd_parse_input pi; struct cmd_parse_result *pr; struct cmdq_item *new_item0; + struct cmdq_state *state; if (new_item != NULL) *new_item = NULL; @@ -216,8 +176,6 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, pi.c = c; pr = cmd_parse_from_buffer(buf, len, &pi); - if (pr->status == CMD_PARSE_EMPTY) - return (0); if (pr->status == CMD_PARSE_ERROR) { cfg_add_cause("%s", pr->error); free(pr->error); @@ -228,12 +186,19 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path, return (0); } - new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); + if (item != NULL) + state = cmdq_copy_state(cmdq_get_state(item)); + else + state = cmdq_new_state(NULL, NULL, 0); + cmdq_add_format(state, "current_file", "%s", pi.file); + + new_item0 = cmdq_get_command(pr->cmdlist, state); if (item != NULL) new_item0 = cmdq_insert_after(item, new_item0); else new_item0 = cmdq_append(NULL, new_item0); cmd_list_free(pr->cmdlist); + cmdq_free_state(state); if (new_item != NULL) *new_item = new_item0; @@ -283,7 +248,7 @@ cfg_show_causes(struct session *s) wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) - window_pane_set_mode(wp, &window_view_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); for (i = 0; i < cfg_ncauses; i++) { window_copy_add(wp, "%s", cfg_causes[i]); free(cfg_causes[i]); diff --git a/client.c b/client.c index 91e084a5..8ca08524 100644 --- a/client.c +++ b/client.c @@ -18,12 +18,12 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -34,7 +34,8 @@ static struct tmuxproc *client_proc; static struct tmuxpeer *client_peer; -static int client_flags; +static uint64_t client_flags; +static int client_suspended; static enum { CLIENT_EXIT_NONE, CLIENT_EXIT_DETACHED, @@ -44,11 +45,13 @@ static enum { CLIENT_EXIT_LOST_SERVER, CLIENT_EXIT_EXITED, CLIENT_EXIT_SERVER_EXITED, + CLIENT_EXIT_MESSAGE_PROVIDED } client_exitreason = CLIENT_EXIT_NONE; static int client_exitflag; static int client_exitval; static enum msgtype client_exittype; static const char *client_exitsession; +static char *client_exitmessage; static const char *client_execshell; static const char *client_execcmd; static int client_attached; @@ -56,8 +59,10 @@ static struct client_files client_files = RB_INITIALIZER(&client_files); static __dead void client_exec(const char *,const char *); static int client_get_lock(char *); -static int client_connect(struct event_base *, const char *, int); -static void client_send_identify(const char *, const char *); +static int client_connect(struct event_base *, const char *, + uint64_t); +static void client_send_identify(const char *, const char *, + char **, u_int, const char *, int); static void client_signal(int); static void client_dispatch(struct imsg *, void *); static void client_dispatch_attached(struct imsg *); @@ -97,7 +102,7 @@ client_get_lock(char *lockfile) /* Connect client to server. */ static int -client_connect(struct event_base *base, const char *path, int start_server) +client_connect(struct event_base *base, const char *path, uint64_t flags) { struct sockaddr_un sa; size_t size; @@ -122,7 +127,9 @@ retry: log_debug("connect failed: %s", strerror(errno)); if (errno != ECONNREFUSED && errno != ENOENT) goto failed; - if (!start_server) + if (flags & CLIENT_NOSTARTSERVER) + goto failed; + if (~flags & CLIENT_STARTSERVER) goto failed; close(fd); @@ -154,7 +161,7 @@ retry: close(lockfd); return (-1); } - fd = server_start(client_proc, base, lockfd, lockfile); + fd = server_start(client_proc, flags, base, lockfd, lockfile); } if (locked && lockfd >= 0) { @@ -206,6 +213,8 @@ client_exit_message(void) return ("exited"); case CLIENT_EXIT_SERVER_EXITED: return ("server exited"); + case CLIENT_EXIT_MESSAGE_PROVIDED: + return (client_exitmessage); } return ("unknown reason"); } @@ -214,76 +223,68 @@ client_exit_message(void) static void client_exit(void) { - struct client_file *cf; - size_t left; - int waiting = 0; - - RB_FOREACH (cf, client_files, &client_files) { - if (cf->event == NULL) - continue; - left = EVBUFFER_LENGTH(cf->event->output); - if (left != 0) { - waiting++; - log_debug("file %u %zu bytes left", cf->stream, left); - } - } - if (waiting == 0) + if (!file_write_left(&client_files)) proc_exit(client_proc); } /* Client main loop. */ int -client_main(struct event_base *base, int argc, char **argv, int flags) +client_main(struct event_base *base, int argc, char **argv, uint64_t flags, + int feat) { struct cmd_parse_result *pr; - struct cmd *cmd; struct msg_command *data; - int cmdflags, fd, i; - const char *ttynam, *cwd; + int fd, i; + const char *ttynam, *termname, *cwd; pid_t ppid; enum msgtype msg; struct termios tio, saved_tio; - size_t size; + size_t size, linesize = 0; + ssize_t linelen; + char *line = NULL, **caps = NULL, *cause; + u_int ncaps = 0; + struct args_value *values; /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ signal(SIGCHLD, SIG_IGN); - /* Save the flags. */ - client_flags = flags; - /* Set up the initial command. */ - cmdflags = 0; if (shell_command != NULL) { msg = MSG_SHELL; - cmdflags = CMD_STARTSERVER; + flags |= CLIENT_STARTSERVER; } else if (argc == 0) { msg = MSG_COMMAND; - cmdflags = CMD_STARTSERVER; + flags |= CLIENT_STARTSERVER; } else { msg = MSG_COMMAND; /* - * It sucks parsing the command string twice (in client and - * later in server) but it is necessary to get the start server - * flag. + * It's annoying parsing the command string twice (in client + * and later in server) but it is necessary to get the start + * server flag. */ - pr = cmd_parse_from_arguments(argc, argv, NULL); + values = args_from_vector(argc, argv); + pr = cmd_parse_from_arguments(values, argc, NULL); if (pr->status == CMD_PARSE_SUCCESS) { - TAILQ_FOREACH(cmd, &pr->cmdlist->list, qentry) { - if (cmd->entry->flags & CMD_STARTSERVER) - cmdflags |= CMD_STARTSERVER; - } + if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER)) + flags |= CLIENT_STARTSERVER; cmd_list_free(pr->cmdlist); } else free(pr->error); + args_free_values(values, argc); + free(values); } /* Create client process structure (starts logging). */ client_proc = proc_start("client"); proc_set_signals(client_proc, client_signal); + /* Save the flags. */ + client_flags = flags; + log_debug("flags are %#llx", (unsigned long long)client_flags); + /* Initialize the client socket and start the server. */ - fd = client_connect(base, socket_path, cmdflags & CMD_STARTSERVER); + fd = client_connect(base, socket_path, client_flags); if (fd == -1) { if (errno == ECONNREFUSED) { fprintf(stderr, "no server running on %s\n", @@ -301,6 +302,8 @@ client_main(struct event_base *base, int argc, char **argv, int flags) cwd = "/"; if ((ttynam = ttyname(STDIN_FILENO)) == NULL) ttynam = ""; + if ((termname = getenv("TERM")) == NULL) + termname = ""; /* * Drop privileges for client. "proc exec" is needed for -c and for @@ -316,6 +319,16 @@ client_main(struct event_base *base, int argc, char **argv, int flags) NULL) != 0) fatal("pledge failed"); + /* Load terminfo entry if any. */ + if (isatty(STDIN_FILENO) && + *termname != '\0' && + tty_term_read_list(termname, STDIN_FILENO, &caps, &ncaps, + &cause) != 0) { + fprintf(stderr, "%s\n", cause); + free(cause); + return (1); + } + /* Free stuff that is not used in the client. */ if (ptm_fd != -1) close(ptm_fd); @@ -346,7 +359,8 @@ client_main(struct event_base *base, int argc, char **argv, int flags) } /* Send identify messages. */ - client_send_identify(ttynam, cwd); + client_send_identify(ttynam, termname, caps, ncaps, cwd, feat); + tty_term_free_list(caps, ncaps); /* Send first command. */ if (msg == MSG_COMMAND) { @@ -389,6 +403,11 @@ client_main(struct event_base *base, int argc, char **argv, int flags) client_exec(client_execshell, client_execcmd); } + /* Restore streams to blocking. */ + setblocking(STDIN_FILENO, 1); + setblocking(STDOUT_FILENO, 1); + setblocking(STDERR_FILENO, 1); + /* Print the exit message, if any, and exit. */ if (client_attached) { if (client_exitreason != CLIENT_EXIT_NONE) @@ -397,42 +416,65 @@ client_main(struct event_base *base, int argc, char **argv, int flags) ppid = getppid(); if (client_exittype == MSG_DETACHKILL && ppid > 1) kill(ppid, SIGHUP); - } else if (client_flags & CLIENT_CONTROLCONTROL) { + } else if (client_flags & CLIENT_CONTROL) { if (client_exitreason != CLIENT_EXIT_NONE) printf("%%exit %s\n", client_exit_message()); else printf("%%exit\n"); - printf("\033\\"); - tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + fflush(stdout); + if (client_flags & CLIENT_CONTROL_WAITEXIT) { + setvbuf(stdin, NULL, _IOLBF, 0); + for (;;) { + linelen = getline(&line, &linesize, stdin); + if (linelen <= 1) + break; + } + free(line); + } + if (client_flags & CLIENT_CONTROLCONTROL) { + printf("\033\\"); + fflush(stdout); + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio); + } } else if (client_exitreason != CLIENT_EXIT_NONE) fprintf(stderr, "%s\n", client_exit_message()); - setblocking(STDIN_FILENO, 1); return (client_exitval); } /* Send identify messages to server. */ static void -client_send_identify(const char *ttynam, const char *cwd) +client_send_identify(const char *ttynam, const char *termname, char **caps, + u_int ncaps, const char *cwd, int feat) { - const char *s; - char **ss; - size_t sslen; - int fd, flags = client_flags; - pid_t pid; + char **ss; + size_t sslen; + int fd, flags = client_flags; + pid_t pid; + u_int i; proc_send(client_peer, MSG_IDENTIFY_FLAGS, -1, &flags, sizeof flags); + proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -1, &client_flags, + sizeof client_flags); - if ((s = getenv("TERM")) == NULL) - s = ""; - proc_send(client_peer, MSG_IDENTIFY_TERM, -1, s, strlen(s) + 1); + proc_send(client_peer, MSG_IDENTIFY_TERM, -1, termname, + strlen(termname) + 1); + proc_send(client_peer, MSG_IDENTIFY_FEATURES, -1, &feat, sizeof feat); proc_send(client_peer, MSG_IDENTIFY_TTYNAME, -1, ttynam, strlen(ttynam) + 1); proc_send(client_peer, MSG_IDENTIFY_CWD, -1, cwd, strlen(cwd) + 1); + for (i = 0; i < ncaps; i++) { + proc_send(client_peer, MSG_IDENTIFY_TERMINFO, -1, + caps[i], strlen(caps[i]) + 1); + } + if ((fd = dup(STDIN_FILENO)) == -1) fatal("dup failed"); proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0); + if ((fd = dup(STDOUT_FILENO)) == -1) + fatal("dup failed"); + proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0); pid = getpid(); proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid); @@ -447,256 +489,6 @@ client_send_identify(const char *ttynam, const char *cwd) proc_send(client_peer, MSG_IDENTIFY_DONE, -1, NULL, 0); } -/* File write error callback. */ -static void -client_write_error_callback(__unused struct bufferevent *bev, - __unused short what, void *arg) -{ - struct client_file *cf = arg; - - log_debug("write error file %d", cf->stream); - - bufferevent_free(cf->event); - cf->event = NULL; - - close(cf->fd); - cf->fd = -1; - - if (client_exitflag) - client_exit(); -} - -/* File write callback. */ -static void -client_write_callback(__unused struct bufferevent *bev, void *arg) -{ - struct client_file *cf = arg; - - if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) { - bufferevent_free(cf->event); - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); - } - - if (client_exitflag) - client_exit(); -} - -/* Open write file. */ -static void -client_write_open(void *data, size_t datalen) -{ - struct msg_write_open *msg = data; - const char *path; - struct msg_write_ready reply; - struct client_file find, *cf; - const int flags = O_NONBLOCK|O_WRONLY|O_CREAT; - int error = 0; - - if (datalen < sizeof *msg) - fatalx("bad MSG_WRITE_OPEN size"); - if (datalen == sizeof *msg) - path = "-"; - else - path = (const char *)(msg + 1); - log_debug("open write file %d %s", msg->stream, path); - - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { - cf = file_create(NULL, msg->stream, NULL, NULL); - RB_INSERT(client_files, &client_files, cf); - } else { - error = EBADF; - goto reply; - } - if (cf->closed) { - error = EBADF; - goto reply; - } - - cf->fd = -1; - if (msg->fd == -1) - cf->fd = open(path, msg->flags|flags, 0644); - else { - if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) - errno = EBADF; - else { - cf->fd = dup(msg->fd); - if (client_flags & CLIENT_CONTROL) - close(msg->fd); /* can only be used once */ - } - } - if (cf->fd == -1) { - error = errno; - goto reply; - } - - cf->event = bufferevent_new(cf->fd, NULL, client_write_callback, - client_write_error_callback, cf); - bufferevent_enable(cf->event, EV_WRITE); - goto reply; - -reply: - reply.stream = msg->stream; - reply.error = error; - proc_send(client_peer, MSG_WRITE_READY, -1, &reply, sizeof reply); -} - -/* Write to client file. */ -static void -client_write_data(void *data, size_t datalen) -{ - struct msg_write_data *msg = data; - struct client_file find, *cf; - size_t size = datalen - sizeof *msg; - - if (datalen < sizeof *msg) - fatalx("bad MSG_WRITE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) - fatalx("unknown stream number"); - log_debug("write %zu to file %d", size, cf->stream); - - if (cf->event != NULL) - bufferevent_write(cf->event, msg + 1, size); -} - -/* Close client file. */ -static void -client_write_close(void *data, size_t datalen) -{ - struct msg_write_close *msg = data; - struct client_file find, *cf; - - if (datalen != sizeof *msg) - fatalx("bad MSG_WRITE_CLOSE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) - fatalx("unknown stream number"); - log_debug("close file %d", cf->stream); - - if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { - if (cf->event != NULL) - bufferevent_free(cf->event); - if (cf->fd != -1) - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); - } -} - -/* File read callback. */ -static void -client_read_callback(__unused struct bufferevent *bev, void *arg) -{ - struct client_file *cf = arg; - void *bdata; - size_t bsize; - struct msg_read_data *msg; - size_t msglen; - - msg = xmalloc(sizeof *msg); - for (;;) { - bdata = EVBUFFER_DATA(cf->event->input); - bsize = EVBUFFER_LENGTH(cf->event->input); - - if (bsize == 0) - break; - if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) - bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; - log_debug("read %zu from file %d", bsize, cf->stream); - - msglen = (sizeof *msg) + bsize; - msg = xrealloc(msg, msglen); - msg->stream = cf->stream; - memcpy(msg + 1, bdata, bsize); - proc_send(client_peer, MSG_READ, -1, msg, msglen); - - evbuffer_drain(cf->event->input, bsize); - } - free(msg); -} - -/* File read error callback. */ -static void -client_read_error_callback(__unused struct bufferevent *bev, - __unused short what, void *arg) -{ - struct client_file *cf = arg; - struct msg_read_done msg; - - log_debug("read error file %d", cf->stream); - - msg.stream = cf->stream; - msg.error = 0; - proc_send(client_peer, MSG_READ_DONE, -1, &msg, sizeof msg); - - bufferevent_free(cf->event); - close(cf->fd); - RB_REMOVE(client_files, &client_files, cf); - file_free(cf); -} - -/* Open read file. */ -static void -client_read_open(void *data, size_t datalen) -{ - struct msg_read_open *msg = data; - const char *path; - struct msg_read_done reply; - struct client_file find, *cf; - const int flags = O_NONBLOCK|O_RDONLY; - int error = 0; - - if (datalen < sizeof *msg) - fatalx("bad MSG_READ_OPEN size"); - if (datalen == sizeof *msg) - path = "-"; - else - path = (const char *)(msg + 1); - log_debug("open read file %d %s", msg->stream, path); - - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &client_files, &find)) == NULL) { - cf = file_create(NULL, msg->stream, NULL, NULL); - RB_INSERT(client_files, &client_files, cf); - } else { - error = EBADF; - goto reply; - } - if (cf->closed) { - error = EBADF; - goto reply; - } - - cf->fd = -1; - if (msg->fd == -1) - cf->fd = open(path, flags); - else { - if (msg->fd != STDIN_FILENO) - errno = EBADF; - else { - cf->fd = dup(msg->fd); - close(msg->fd); /* can only be used once */ - } - } - if (cf->fd == -1) { - error = errno; - goto reply; - } - - cf->event = bufferevent_new(cf->fd, client_read_callback, NULL, - client_read_error_callback, cf); - bufferevent_enable(cf->event, EV_READ); - return; - -reply: - reply.stream = msg->stream; - reply.error = error; - proc_send(client_peer, MSG_READ_DONE, -1, &reply, sizeof reply); -} - /* Run command in shell; used for -c. */ static __dead void client_exec(const char *shell, const char *shellcmd) @@ -735,6 +527,7 @@ client_signal(int sig) struct sigaction sigact; int status; + log_debug("%s: %s", __func__, strsignal(sig)); if (sig == SIGCHLD) waitpid(WAIT_ANY, &status, WNOHANG); else if (!client_attached) { @@ -748,7 +541,8 @@ client_signal(int sig) proc_send(client_peer, MSG_EXITING, -1, NULL, 0); break; case SIGTERM: - client_exitreason = CLIENT_EXIT_TERMINATED; + if (!client_suspended) + client_exitreason = CLIENT_EXIT_TERMINATED; client_exitval = 1; proc_send(client_peer, MSG_EXITING, -1, NULL, 0); break; @@ -763,18 +557,31 @@ client_signal(int sig) if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); proc_send(client_peer, MSG_WAKEUP, -1, NULL, 0); + client_suspended = 0; break; } } } +/* Callback for file write error or close. */ +static void +client_file_check_cb(__unused struct client *c, __unused const char *path, + __unused int error, __unused int closed, __unused struct evbuffer *buffer, + __unused void *data) +{ + if (client_exitflag) + client_exit(); +} + /* Callback for client read events. */ static void client_dispatch(struct imsg *imsg, __unused void *arg) { if (imsg == NULL) { - client_exitreason = CLIENT_EXIT_LOST_SERVER; - client_exitval = 1; + if (!client_exitflag) { + client_exitreason = CLIENT_EXIT_LOST_SERVER; + client_exitval = 1; + } proc_exit(client_proc); return; } @@ -785,13 +592,38 @@ client_dispatch(struct imsg *imsg, __unused void *arg) client_dispatch_wait(imsg); } +/* Process an exit message. */ +static void +client_dispatch_exit_message(char *data, size_t datalen) +{ + int retval; + + if (datalen < sizeof retval && datalen != 0) + fatalx("bad MSG_EXIT size"); + + if (datalen >= sizeof retval) { + memcpy(&retval, data, sizeof retval); + client_exitval = retval; + } + + if (datalen > sizeof retval) { + datalen -= sizeof retval; + data += sizeof retval; + + client_exitmessage = xmalloc(datalen); + memcpy(client_exitmessage, data, datalen); + client_exitmessage[datalen - 1] = '\0'; + + client_exitreason = CLIENT_EXIT_MESSAGE_PROVIDED; + } +} + /* Dispatch imsgs when in wait state (before MSG_READY). */ static void client_dispatch_wait(struct imsg *imsg) { char *data; ssize_t datalen; - int retval; static int pledge_applied; /* @@ -814,12 +646,7 @@ client_dispatch_wait(struct imsg *imsg) switch (imsg->hdr.type) { case MSG_EXIT: case MSG_SHUTDOWN: - if (datalen != sizeof retval && datalen != 0) - fatalx("bad MSG_EXIT size"); - if (datalen == sizeof retval) { - memcpy(&retval, data, sizeof retval); - client_exitval = retval; - } + client_dispatch_exit_message(data, datalen); client_exitflag = 1; client_exit(); break; @@ -840,6 +667,14 @@ client_dispatch_wait(struct imsg *imsg) client_exitval = 1; proc_exit(client_proc); break; + case MSG_FLAGS: + if (datalen != sizeof client_flags) + fatalx("bad MSG_FLAGS string"); + + memcpy(&client_flags, data, sizeof client_flags); + log_debug("new flags are %#llx", + (unsigned long long)client_flags); + break; case MSG_SHELL: if (datalen == 0 || data[datalen - 1] != '\0') fatalx("bad MSG_SHELL string"); @@ -854,16 +689,20 @@ client_dispatch_wait(struct imsg *imsg) proc_exit(client_proc); break; case MSG_READ_OPEN: - client_read_open(data, datalen); + file_read_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); break; case MSG_WRITE_OPEN: - client_write_open(data, datalen); + file_write_open(&client_files, client_peer, imsg, 1, + !(client_flags & CLIENT_CONTROL), client_file_check_cb, + NULL); break; case MSG_WRITE: - client_write_data(data, datalen); + file_write_data(&client_files, imsg); break; case MSG_WRITE_CLOSE: - client_write_close(data, datalen); + file_write_close(&client_files, imsg); break; case MSG_OLDSTDERR: case MSG_OLDSTDIN: @@ -886,6 +725,14 @@ client_dispatch_attached(struct imsg *imsg) datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { + case MSG_FLAGS: + if (datalen != sizeof client_flags) + fatalx("bad MSG_FLAGS string"); + + memcpy(&client_flags, data, sizeof client_flags); + log_debug("new flags are %#llx", + (unsigned long long)client_flags); + break; case MSG_DETACH: case MSG_DETACHKILL: if (datalen == 0 || data[datalen - 1] != '\0') @@ -910,11 +757,10 @@ client_dispatch_attached(struct imsg *imsg) proc_send(client_peer, MSG_EXITING, -1, NULL, 0); break; case MSG_EXIT: - if (datalen != 0 && datalen != sizeof (int)) - fatalx("bad MSG_EXIT size"); - + client_dispatch_exit_message(data, datalen); + if (client_exitreason == CLIENT_EXIT_NONE) + client_exitreason = CLIENT_EXIT_EXITED; proc_send(client_peer, MSG_EXITING, -1, NULL, 0); - client_exitreason = CLIENT_EXIT_EXITED; break; case MSG_EXITED: if (datalen != 0) @@ -940,6 +786,7 @@ client_dispatch_attached(struct imsg *imsg) sigact.sa_handler = SIG_DFL; if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); + client_suspended = 1; kill(getpid(), SIGTSTP); break; case MSG_LOCK: diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 477d3517..cc795b22 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -37,8 +37,9 @@ const struct cmd_entry cmd_attach_session_entry = { .name = "attach-session", .alias = "attach", - .args = { "c:dErt:x", 0, 0 }, - .usage = "[-dErx] [-c working-directory] " CMD_TARGET_SESSION_USAGE, + .args = { "c:dEf:rt:x", 0, 0, NULL }, + .usage = "[-dErx] [-c working-directory] [-f flags] " + CMD_TARGET_SESSION_USAGE, /* -t is special */ @@ -48,16 +49,17 @@ const struct cmd_entry cmd_attach_session_entry = { enum cmd_retval cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, - int xflag, int rflag, const char *cflag, int Eflag) + int xflag, int rflag, const char *cflag, int Eflag, const char *fflag) { - struct cmd_find_state *current = &item->shared->current; + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state target; enum cmd_find_type type; int flags; - struct client *c = item->client, *c_loop; + struct client *c = cmdq_get_client(item), *c_loop; struct session *s; struct winlink *wl; struct window_pane *wp; - char *cause; + char *cwd, *cause; enum msgtype msgtype; if (RB_EMPTY(&sessions)) { @@ -80,11 +82,11 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, type = CMD_FIND_SESSION; flags = CMD_FIND_PREFER_UNATTACHED; } - if (cmd_find_target(&item->target, item, tflag, type, flags) != 0) + if (cmd_find_target(&target, item, tflag, type, flags) != 0) return (CMD_RETURN_ERROR); - s = item->target.s; - wl = item->target.wl; - wp = item->target.wp; + s = target.s; + wl = target.wl; + wp = target.wp; if (wl != NULL) { if (wp != NULL) @@ -97,9 +99,14 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, } if (cflag != NULL) { + cwd = format_single(item, cflag, c, s, wl, wp); free((void *)s->cwd); - s->cwd = format_single(item, cflag, c, s, wl, wp); + s->cwd = cwd; } + if (fflag) + server_client_set_flags(c, fflag); + if (rflag) + c->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); c->last_session = c->session; if (c->session != NULL) { @@ -117,25 +124,15 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (!Eflag) environ_update(s->options, c->environ, s->environ); - c->session = s; - if (~item->shared->flags & CMDQ_SHARED_REPEAT) + server_client_set_session(c, s); + if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT) server_client_set_key_table(c, NULL); - tty_update_client_offset(c); - status_timer_start(c); - notify_client("client-session-changed", c); - session_update_activity(s, NULL); - gettimeofday(&s->last_attached_time, NULL); - server_redraw_client(c); - s->curw->flags &= ~WINLINK_ALERTFLAGS; - s->curw->window->latest = c; } else { if (server_client_open(c, &cause) != 0) { cmdq_error(item, "open terminal failed: %s", cause); free(cause); return (CMD_RETURN_ERROR); } - if (rflag) - c->flags |= CLIENT_READONLY; if (dflag || xflag) { if (xflag) @@ -151,25 +148,14 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (!Eflag) environ_update(s->options, c->environ, s->environ); - c->session = s; + server_client_set_session(c, s); server_client_set_key_table(c, NULL); - tty_update_client_offset(c); - status_timer_start(c); - notify_client("client-session-changed", c); - session_update_activity(s, NULL); - gettimeofday(&s->last_attached_time, NULL); - server_redraw_client(c); - s->curw->flags &= ~WINLINK_ALERTFLAGS; - s->curw->window->latest = c; if (~c->flags & CLIENT_CONTROL) proc_send(c->peer, MSG_READY, -1, NULL, 0); notify_client("client-attached", c); c->flags |= CLIENT_ATTACHED; } - recalculate_sizes(); - alerts_check_session(s); - server_update_socket(); return (CMD_RETURN_NORMAL); } @@ -177,9 +163,9 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, static enum cmd_retval cmd_attach_session_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); return (cmd_attach_session(item, args_get(args, 't'), args_has(args, 'd'), args_has(args, 'x'), args_has(args, 'r'), - args_get(args, 'c'), args_has(args, 'E'))); + args_get(args, 'c'), args_has(args, 'E'), args_get(args, 'f'))); } diff --git a/cmd-bind-key.c b/cmd-bind-key.c index bc6a3d40..dab03b01 100644 --- a/cmd-bind-key.c +++ b/cmd-bind-key.c @@ -27,33 +27,44 @@ * Bind a key to a command. */ -static enum cmd_retval cmd_bind_key_exec(struct cmd *, struct cmdq_item *); +static enum args_parse_type cmd_bind_key_args_parse(struct args *, u_int, + char **); +static enum cmd_retval cmd_bind_key_exec(struct cmd *, + struct cmdq_item *); const struct cmd_entry cmd_bind_key_entry = { .name = "bind-key", .alias = "bind", - .args = { "nrN:T:", 2, -1 }, + .args = { "nrN:T:", 1, -1, cmd_bind_key_args_parse }, .usage = "[-nr] [-T key-table] [-N note] key " - "command [arguments]", + "[command [arguments]]", .flags = CMD_AFTERHOOK, .exec = cmd_bind_key_exec }; +static enum args_parse_type +cmd_bind_key_args_parse(__unused struct args *args, __unused u_int idx, + __unused char **cause) +{ + return (ARGS_PARSE_COMMANDS_OR_STRING); +} + static enum cmd_retval cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); key_code key; - const char *tablename, *note; + const char *tablename, *note = args_get(args, 'N'); struct cmd_parse_result *pr; - char **argv = args->argv; - int argc = args->argc, repeat; + int repeat; + struct args_value *value; + u_int count = args_count(args); - key = key_string_lookup_string(argv[0]); + key = key_string_lookup_string(args_string(args, 0)); if (key == KEYC_NONE || key == KEYC_UNKNOWN) { - cmdq_error(item, "unknown key: %s", argv[0]); + cmdq_error(item, "unknown key: %s", args_string(args, 0)); return (CMD_RETURN_ERROR); } @@ -65,14 +76,25 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) tablename = "prefix"; repeat = args_has(args, 'r'); - if (argc == 2) - pr = cmd_parse_from_string(argv[1], NULL); - else - pr = cmd_parse_from_arguments(argc - 1, argv + 1, NULL); + if (count == 1) { + key_bindings_add(tablename, key, note, repeat, NULL); + return (CMD_RETURN_NORMAL); + } + + value = args_value(args, 1); + if (count == 2 && value->type == ARGS_COMMANDS) { + key_bindings_add(tablename, key, note, repeat, value->cmdlist); + value->cmdlist->references++; + return (CMD_RETURN_NORMAL); + } + + if (count == 2) + pr = cmd_parse_from_string(args_string(args, 1), NULL); + else { + pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1, + NULL); + } switch (pr->status) { - case CMD_PARSE_EMPTY: - cmdq_error(item, "empty command"); - return (CMD_RETURN_ERROR); case CMD_PARSE_ERROR: cmdq_error(item, "%s", pr->error); free(pr->error); @@ -80,7 +102,6 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) case CMD_PARSE_SUCCESS: break; } - note = args_get(args, 'N'); key_bindings_add(tablename, key, note, repeat, pr->cmdlist); return (CMD_RETURN_NORMAL); } diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 6c638103..4f38d4bd 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_break_pane_entry = { .name = "break-pane", .alias = "breakp", - .args = { "dPF:n:s:t:", 0, 0 }, - .usage = "[-dP] [-F format] [-n window-name] [-s src-pane] " + .args = { "abdPF:n:s:t:", 0, 0, NULL }, + .usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] " "[-t dst-window]", .source = { 's', CMD_FIND_PANE, 0 }, @@ -48,31 +48,52 @@ const struct cmd_entry cmd_break_pane_entry = { static enum cmd_retval cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; - struct client *c = cmd_find_client(item, NULL, 1); - struct winlink *wl = item->source.wl; - struct session *src_s = item->source.s; - struct session *dst_s = item->target.s; - struct window_pane *wp = item->source.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct cmd_find_state *source = cmdq_get_source(item); + struct client *tc = cmdq_get_target_client(item); + struct winlink *wl = source->wl; + struct session *src_s = source->s; + struct session *dst_s = target->s; + struct window_pane *wp = source->wp; struct window *w = wl->window; - char *name, *cause; - int idx = item->target.idx; + char *name, *cause, *cp; + int idx = target->idx, before; const char *template; - char *cp; - if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { - cmdq_error(item, "index %d already in use", idx); - return (CMD_RETURN_ERROR); - } - - if (window_count_panes(w) == 1) { - cmdq_error(item, "can't break with only one pane"); - return (CMD_RETURN_ERROR); + before = args_has(args, 'b'); + if (args_has(args, 'a') || before) { + if (target->wl != NULL) + idx = winlink_shuffle_up(dst_s, target->wl, before); + else + idx = winlink_shuffle_up(dst_s, dst_s->curw, before); + if (idx == -1) + return (CMD_RETURN_ERROR); } server_unzoom_window(w); + if (window_count_panes(w) == 1) { + if (server_link_window(src_s, wl, dst_s, idx, 0, + !args_has(args, 'd'), &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'n')) { + window_set_name(w, args_get(args, 'n')); + options_set_number(w->options, "automatic-rename", 0); + } + server_unlink_window(src_s, wl); + return (CMD_RETURN_NORMAL); + } + if (idx != -1 && winlink_find_by_index(&dst_s->windows, idx) != NULL) { + cmdq_error(item, "index in use: %d", idx); + return (CMD_RETURN_ERROR); + } + TAILQ_REMOVE(&w->panes, wp, entry); + server_client_remove_pane(wp); window_lost_pane(w, wp); layout_close_pane(wp); @@ -81,7 +102,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) wp->flags |= PANE_STYLECHANGED; TAILQ_INSERT_HEAD(&w->panes, wp, entry); w->active = wp; - w->latest = c; + w->latest = tc; if (!args_has(args, 'n')) { name = default_window_name(w); @@ -98,7 +119,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) if (idx == -1) idx = -1 - options_get_number(dst_s->options, "base-index"); wl = session_attach(dst_s, w, idx, &cause); /* can't fail */ - if (!args_has(self->args, 'd')) { + if (!args_has(args, 'd')) { session_select(dst_s, wl->idx); cmd_find_from_session(current, dst_s, 0); } @@ -113,7 +134,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = BREAK_PANE_TEMPLATE; - cp = format_single(item, template, c, dst_s, wl, wp); + cp = format_single(item, template, tc, dst_s, wl, wp); cmdq_print(item, "%s", cp); free(cp); } diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index 18be3f77..964f831e 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -39,7 +39,7 @@ const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:JNpPqS:t:", 0, 0 }, + .args = { "ab:CeE:JNpPqS:t:", 0, 0, NULL }, .usage = "[-aCeJNpPq] " CMD_BUFFER_USAGE " [-E end-line] " "[-S start-line] " CMD_TARGET_PANE_USAGE, @@ -53,7 +53,7 @@ const struct cmd_entry cmd_clear_history_entry = { .name = "clear-history", .alias = "clearhist", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -80,7 +80,7 @@ cmd_capture_pane_pending(struct args *args, struct window_pane *wp, size_t linelen; u_int i; - pending = input_pending(wp); + pending = input_pending(wp->ictx); if (pending == NULL) return (xstrdup("")); @@ -118,7 +118,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, sx = screen_size_x(&wp->base); if (args_has(args, 'a')) { - gd = wp->saved_grid; + gd = wp->base.saved_grid; if (gd == NULL) { if (!args_has(args, 'q')) { cmdq_error(item, "no alternate screen"); @@ -192,14 +192,14 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, static enum cmd_retval cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = item->client; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); + struct window_pane *wp = cmdq_get_target(item)->wp; char *buf, *cause; const char *bufname; size_t len; - if (self->entry == &cmd_clear_history_entry) { + if (cmd_get_entry(self) == &cmd_clear_history_entry) { window_pane_reset_mode_all(wp); grid_clear_history(wp->base.grid); return (CMD_RETURN_NORMAL); @@ -214,15 +214,20 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); if (args_has(args, 'p')) { - if (!file_can_print(c)) { - cmdq_error(item, "can't write output to client"); - free(buf); - return (CMD_RETURN_ERROR); - } - file_print_buffer(c, buf, len); - if (args_has(args, 'P') && len > 0) + if (len > 0 && buf[len - 1] == '\n') + len--; + if (c->flags & CLIENT_CONTROL) + control_write(c, "%.*s", (int)len, buf); + else { + if (!file_can_print(c)) { + cmdq_error(item, "can't write to client"); + free(buf); + return (CMD_RETURN_ERROR); + } + file_print_buffer(c, buf, len); file_print(c, "\n"); - free(buf); + free(buf); + } } else { bufname = NULL; if (args_has(args, 'b')) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 8178ec9f..7aa1d217 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -24,15 +24,18 @@ * Enter a mode. */ -static enum cmd_retval cmd_choose_tree_exec(struct cmd *, struct cmdq_item *); +static enum args_parse_type cmd_choose_tree_args_parse(struct args *args, + u_int idx, char **cause); +static enum cmd_retval cmd_choose_tree_exec(struct cmd *, + struct cmdq_item *); const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", .alias = NULL, - .args = { "F:Gf:NO:rst:wZ", 0, 1 }, - .usage = "[-GNrswZ] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE " [template]", + .args = { "F:f:GK:NO:rst:wZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] " + "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -44,9 +47,9 @@ const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", .alias = NULL, - .args = { "F:f:NO:rt:Z", 0, 1 }, - .usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE " [template]", + .args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " + "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -58,9 +61,9 @@ const struct cmd_entry cmd_choose_buffer_entry = { .name = "choose-buffer", .alias = NULL, - .args = { "F:f:NO:rt:Z", 0, 1 }, - .usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE " [template]", + .args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-NrZ] [-F format] [-f filter] [-K key-format] " + "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -68,24 +71,47 @@ const struct cmd_entry cmd_choose_buffer_entry = { .exec = cmd_choose_tree_exec }; +const struct cmd_entry cmd_customize_mode_entry = { + .name = "customize-mode", + .alias = NULL, + + .args = { "F:f:Nt:Z", 0, 0, NULL }, + .usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .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) +{ + return (ARGS_PARSE_COMMANDS_OR_STRING); +} + static enum cmd_retval cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *wp = target->wp; const struct window_mode *mode; - if (self->entry == &cmd_choose_buffer_entry) { + if (cmd_get_entry(self) == &cmd_choose_buffer_entry) { if (paste_get_top(NULL) == NULL) return (CMD_RETURN_NORMAL); mode = &window_buffer_mode; - } else if (self->entry == &cmd_choose_client_entry) { + } else if (cmd_get_entry(self) == &cmd_choose_client_entry) { if (server_client_how_many() == 0) return (CMD_RETURN_NORMAL); mode = &window_client_mode; - } else + } else if (cmd_get_entry(self) == &cmd_customize_mode_entry) + mode = &window_customize_mode; + else mode = &window_tree_mode; - window_pane_set_mode(wp, mode, &item->target, args); + window_pane_set_mode(wp, NULL, mode, target, args); return (CMD_RETURN_NORMAL); } diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index 9f0ea19f..a7a02702 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -29,8 +29,10 @@ * Prompt for command in client. */ -static enum cmd_retval cmd_command_prompt_exec(struct cmd *, - struct cmdq_item *); +static enum args_parse_type cmd_command_prompt_args_parse(struct args *, + u_int, char **); +static enum cmd_retval cmd_command_prompt_exec(struct cmd *, + struct cmdq_item *); static int cmd_command_prompt_callback(struct client *, void *, const char *, int); @@ -40,81 +42,111 @@ const struct cmd_entry cmd_command_prompt_entry = { .name = "command-prompt", .alias = NULL, - .args = { "1kiI:Np:t:", 0, 1 }, - .usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " " - "[template]", + .args = { "1bFkiI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, + .usage = "[-1bFkiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE + " [-T type] [template]", - .flags = 0, + .flags = CMD_CLIENT_TFLAG, .exec = cmd_command_prompt_exec }; -struct cmd_command_prompt_cdata { - int flags; - - char *inputs; - char *next_input; - - char *prompts; - char *next_prompt; - - char *template; - int idx; +struct cmd_command_prompt_prompt { + char *input; + char *prompt; }; +struct cmd_command_prompt_cdata { + struct cmdq_item *item; + struct args_command_state *state; + + int flags; + enum prompt_type prompt_type; + + struct cmd_command_prompt_prompt *prompts; + u_int count; + u_int current; + + int argc; + char **argv; +}; + +static enum args_parse_type +cmd_command_prompt_args_parse(__unused struct args *args, __unused u_int idx, + __unused char **cause) +{ + return (ARGS_PARSE_COMMANDS_OR_STRING); +} + static enum cmd_retval cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - const char *inputs, *prompts; + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); + struct cmd_find_state *target = cmdq_get_target(item); + const char *type, *s, *input; struct cmd_command_prompt_cdata *cdata; - struct client *c; - char *prompt, *ptr, *input = NULL; - size_t n; + char *tmp, *prompts, *prompt, *next_prompt; + char *inputs = NULL, *next_input; + u_int count = args_count(args); + int wait = !args_has(args, 'b'), space = 1; - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); - - if (c->prompt_string != NULL) + if (tc->prompt_string != NULL) return (CMD_RETURN_NORMAL); + if (args_has(args, 'i')) + wait = 0; cdata = xcalloc(1, sizeof *cdata); + if (wait) + cdata->item = item; + cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait, + args_has(args, 'F')); - cdata->inputs = NULL; - cdata->next_input = NULL; - - cdata->prompts = NULL; - cdata->next_prompt = NULL; - - cdata->template = NULL; - cdata->idx = 1; - - if (args->argc != 0) - cdata->template = xstrdup(args->argv[0]); - else - cdata->template = xstrdup("%1"); - - if ((prompts = args_get(args, 'p')) != NULL) - cdata->prompts = xstrdup(prompts); - else if (args->argc != 0) { - n = strcspn(cdata->template, " ,"); - xasprintf(&cdata->prompts, "(%.*s) ", (int) n, cdata->template); + if ((s = args_get(args, 'p')) == NULL) { + if (count != 0) { + tmp = args_make_commands_get_command(cdata->state); + xasprintf(&prompts, "(%s)", tmp); + free(tmp); + } else { + prompts = xstrdup(":"); + space = 0; + } + next_prompt = prompts; } else - cdata->prompts = xstrdup(":"); - - /* Get first prompt. */ - cdata->next_prompt = cdata->prompts; - ptr = strsep(&cdata->next_prompt, ","); - if (prompts == NULL) - prompt = xstrdup(ptr); + next_prompt = prompts = xstrdup (s); + if ((s = args_get(args, 'I')) != NULL) + next_input = inputs = xstrdup(s); else - xasprintf(&prompt, "%s ", ptr); + next_input = NULL; + while ((prompt = strsep(&next_prompt, ",")) != NULL) { + cdata->prompts = xreallocarray(cdata->prompts, cdata->count + 1, + sizeof *cdata->prompts); + if (!space) + tmp = xstrdup(prompt); + else + xasprintf(&tmp, "%s ", prompt); + cdata->prompts[cdata->count].prompt = tmp; - /* Get initial prompt input. */ - if ((inputs = args_get(args, 'I')) != NULL) { - cdata->inputs = xstrdup(inputs); - cdata->next_input = cdata->inputs; - input = strsep(&cdata->next_input, ","); + if (next_input != NULL) { + input = strsep(&next_input, ","); + if (input == NULL) + input = ""; + } else + input = ""; + cdata->prompts[cdata->count].input = xstrdup(input); + + cdata->count++; } + free(inputs); + free(prompts); + + if ((type = args_get(args, 'T')) != NULL) { + cdata->prompt_type = status_prompt_type(type); + if (cdata->prompt_type == PROMPT_TYPE_INVALID) { + cmdq_error(item, "unknown type: %s", type); + return (CMD_RETURN_ERROR); + } + } else + cdata->prompt_type = PROMPT_TYPE_COMMAND; if (args_has(args, '1')) cdata->flags |= PROMPT_SINGLE; @@ -124,69 +156,68 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) cdata->flags |= PROMPT_INCREMENTAL; else if (args_has(args, 'k')) cdata->flags |= PROMPT_KEY; - status_prompt_set(c, prompt, input, cmd_command_prompt_callback, - cmd_command_prompt_free, cdata, cdata->flags); - free(prompt); + status_prompt_set(tc, target, cdata->prompts[0].prompt, + cdata->prompts[0].input, cmd_command_prompt_callback, + cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type); - return (CMD_RETURN_NORMAL); + if (!wait) + return (CMD_RETURN_NORMAL); + return (CMD_RETURN_WAIT); } static int cmd_command_prompt_callback(struct client *c, void *data, const char *s, int done) { - struct cmd_command_prompt_cdata *cdata = data; - struct cmdq_item *new_item; - char *new_template, *prompt, *ptr; - char *input = NULL; - struct cmd_parse_result *pr; + struct cmd_command_prompt_cdata *cdata = data; + char *error; + struct cmdq_item *item = cdata->item, *new_item; + struct cmd_list *cmdlist; + struct cmd_command_prompt_prompt *prompt; + int argc = 0; + char **argv = NULL; if (s == NULL) - return (0); - if (done && (cdata->flags & PROMPT_INCREMENTAL)) - return (0); - - new_template = cmd_template_replace(cdata->template, s, cdata->idx); + goto out; if (done) { - free(cdata->template); - cdata->template = new_template; + if (cdata->flags & PROMPT_INCREMENTAL) + goto out; + + cmd_append_argv(&cdata->argc, &cdata->argv, s); + if (++cdata->current != cdata->count) { + prompt = &cdata->prompts[cdata->current]; + status_prompt_update(c, prompt->prompt, prompt->input); + return (1); + } } - /* - * Check if there are more prompts; if so, get its respective input - * and update the prompt data. - */ - if (done && (ptr = strsep(&cdata->next_prompt, ",")) != NULL) { - xasprintf(&prompt, "%s ", ptr); - input = strsep(&cdata->next_input, ","); - status_prompt_update(c, prompt, input); - - free(prompt); - cdata->idx++; - return (1); + argc = cdata->argc; + argv = cmd_copy_argv(cdata->argc, cdata->argv); + cmd_append_argv(&argc, &argv, s); + if (done) { + cdata->argc = argc; + cdata->argv = cmd_copy_argv(argc, argv); } - pr = cmd_parse_from_string(new_template, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - new_item = NULL; - break; - case CMD_PARSE_ERROR: - new_item = cmdq_get_error(pr->error); - free(pr->error); + cmdlist = args_make_commands(cdata->state, argc, argv, &error); + if (cmdlist == NULL) { + cmdq_append(c, cmdq_get_error(error)); + free(error); + } else if (item == NULL) { + new_item = cmdq_get_command(cmdlist, NULL); cmdq_append(c, new_item); - break; - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); - cmd_list_free(pr->cmdlist); - cmdq_append(c, new_item); - break; + } else { + new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); + cmdq_insert_after(item, new_item); } + cmd_free_argv(argc, argv); - if (!done) - free(new_template); if (c->prompt_inputcb != cmd_command_prompt_callback) return (1); + +out: + if (item != NULL) + cmdq_continue(item); return (0); } @@ -194,9 +225,14 @@ static void cmd_command_prompt_free(void *data) { struct cmd_command_prompt_cdata *cdata = data; + u_int i; - free(cdata->inputs); + for (i = 0; i < cdata->count; i++) { + free(cdata->prompts[i].prompt); + free(cdata->prompts[i].input); + } free(cdata->prompts); - free(cdata->template); + cmd_free_argv(cdata->argc, cdata->argv); + args_make_commands_free(cdata->state); free(cdata); } diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index be21a78b..95841962 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -28,8 +28,10 @@ * Asks for confirmation before executing a command. */ -static enum cmd_retval cmd_confirm_before_exec(struct cmd *, - struct cmdq_item *); +static enum args_parse_type cmd_confirm_before_args_parse(struct args *, + u_int, char **); +static enum cmd_retval cmd_confirm_before_exec(struct cmd *, + struct cmdq_item *); static int cmd_confirm_before_callback(struct client *, void *, const char *, int); @@ -39,47 +41,59 @@ const struct cmd_entry cmd_confirm_before_entry = { .name = "confirm-before", .alias = "confirm", - .args = { "p:t:", 1, 1 }, - .usage = "[-p prompt] " CMD_TARGET_CLIENT_USAGE " command", + .args = { "bp:t:", 1, 1, cmd_confirm_before_args_parse }, + .usage = "[-b] [-p prompt] " CMD_TARGET_CLIENT_USAGE " command", - .flags = 0, + .flags = CMD_CLIENT_TFLAG, .exec = cmd_confirm_before_exec }; struct cmd_confirm_before_data { - char *cmd; + struct cmdq_item *item; + struct cmd_list *cmdlist; }; +static enum args_parse_type +cmd_confirm_before_args_parse(__unused struct args *args, __unused u_int idx, + __unused char **cause) +{ + return (ARGS_PARSE_COMMANDS_OR_STRING); +} + static enum cmd_retval cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct cmd_confirm_before_data *cdata; - struct client *c; - char *cmd, *copy, *new_prompt, *ptr; - const char *prompt; + struct client *tc = cmdq_get_target_client(item); + struct cmd_find_state *target = cmdq_get_target(item); + char *new_prompt; + const char *prompt, *cmd; + int wait = !args_has(args, 'b'); - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) + cdata = xcalloc(1, sizeof *cdata); + cdata->cmdlist = args_make_commands_now(self, item, 0, 0); + if (cdata->cmdlist == NULL) return (CMD_RETURN_ERROR); + if (wait) + cdata->item = item; + if ((prompt = args_get(args, 'p')) != NULL) xasprintf(&new_prompt, "%s ", prompt); else { - ptr = copy = xstrdup(args->argv[0]); - cmd = strsep(&ptr, " \t"); + cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name; xasprintf(&new_prompt, "Confirm '%s'? (y/n) ", cmd); - free(copy); } - cdata = xmalloc(sizeof *cdata); - cdata->cmd = xstrdup(args->argv[0]); - - status_prompt_set(c, new_prompt, NULL, + status_prompt_set(tc, target, new_prompt, NULL, cmd_confirm_before_callback, cmd_confirm_before_free, cdata, - PROMPT_SINGLE); - + PROMPT_SINGLE, PROMPT_TYPE_COMMAND); free(new_prompt); - return (CMD_RETURN_NORMAL); + + if (!wait) + return (CMD_RETURN_NORMAL); + return (CMD_RETURN_WAIT); } static int @@ -87,34 +101,34 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s, __unused int done) { struct cmd_confirm_before_data *cdata = data; - struct cmdq_item *new_item; - struct cmd_parse_result *pr; + struct cmdq_item *item = cdata->item, *new_item; + int retcode = 1; if (c->flags & CLIENT_DEAD) - return (0); + goto out; if (s == NULL || *s == '\0') - return (0); + goto out; if (tolower((u_char)s[0]) != 'y' || s[1] != '\0') - return (0); + goto out; + retcode = 0; - pr = cmd_parse_from_string(cdata->cmd, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - new_item = NULL; - break; - case CMD_PARSE_ERROR: - new_item = cmdq_get_error(pr->error); - free(pr->error); + if (item == NULL) { + new_item = cmdq_get_command(cdata->cmdlist, NULL); cmdq_append(c, new_item); - break; - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); - cmd_list_free(pr->cmdlist); - cmdq_append(c, new_item); - break; + } else { + new_item = cmdq_get_command(cdata->cmdlist, + cmdq_get_state(item)); + cmdq_insert_after(item, new_item); } +out: + if (item != NULL) { + if (cmdq_get_client(item) != NULL && + cmdq_get_client(item)->session == NULL) + cmdq_get_client(item)->retval = retcode; + cmdq_continue(item); + } return (0); } @@ -123,6 +137,6 @@ cmd_confirm_before_free(void *data) { struct cmd_confirm_before_data *cdata = data; - free(cdata->cmd); + cmd_list_free(cdata->cmdlist); free(cdata); } diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index b35d0af1..8f698ce8 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -30,9 +30,10 @@ const struct cmd_entry cmd_copy_mode_entry = { .name = "copy-mode", .alias = NULL, - .args = { "Met:u", 0, 0 }, - .usage = "[-Mu] " CMD_TARGET_PANE_USAGE, + .args = { "eHMs:t:uq", 0, 0, NULL }, + .usage = "[-eHMuq] [-s src-pane] " CMD_TARGET_PANE_USAGE, + .source = { 's', CMD_FIND_PANE, 0 }, .target = { 't', CMD_FIND_PANE, 0 }, .flags = CMD_AFTERHOOK, @@ -43,7 +44,7 @@ const struct cmd_entry cmd_clock_mode_entry = { .name = "clock-mode", .alias = NULL, - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -55,29 +56,40 @@ const struct cmd_entry cmd_clock_mode_entry = { static enum cmd_retval cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmdq_shared *shared = item->shared; - struct client *c = item->client; + struct args *args = cmd_get_args(self); + struct key_event *event = cmdq_get_event(item); + struct cmd_find_state *source = cmdq_get_source(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); struct session *s; - struct window_pane *wp = item->target.wp; + struct window_pane *wp = target->wp, *swp; + + if (args_has(args, 'q')) { + window_pane_reset_mode_all(wp); + return (CMD_RETURN_NORMAL); + } if (args_has(args, 'M')) { - if ((wp = cmd_mouse_pane(&shared->mouse, &s, NULL)) == NULL) + if ((wp = cmd_mouse_pane(&event->m, &s, NULL)) == NULL) return (CMD_RETURN_NORMAL); if (c == NULL || c->session != s) return (CMD_RETURN_NORMAL); } - if (self->entry == &cmd_clock_mode_entry) { - window_pane_set_mode(wp, &window_clock_mode, NULL, NULL); + if (cmd_get_entry(self) == &cmd_clock_mode_entry) { + window_pane_set_mode(wp, NULL, &window_clock_mode, NULL, NULL); return (CMD_RETURN_NORMAL); } - if (!window_pane_set_mode(wp, &window_copy_mode, NULL, args)) { + if (args_has(args, 's')) + swp = source->wp; + else + swp = wp; + if (!window_pane_set_mode(wp, swp, &window_copy_mode, NULL, args)) { if (args_has(args, 'M')) - window_copy_start_drag(c, &shared->mouse); + window_copy_start_drag(c, &event->m); } - if (args_has(self->args, 'u')) + if (args_has(args, 'u')) window_copy_pageup(wp, 0); return (CMD_RETURN_NORMAL); diff --git a/cmd-detach-client.c b/cmd-detach-client.c index 85b9a4ed..661293ae 100644 --- a/cmd-detach-client.c +++ b/cmd-detach-client.c @@ -33,13 +33,13 @@ const struct cmd_entry cmd_detach_client_entry = { .name = "detach-client", .alias = "detach", - .args = { "aE:s:t:P", 0, 0 }, + .args = { "aE:s:t:P", 0, 0, NULL }, .usage = "[-aP] [-E shell-command] " "[-s target-session] " CMD_TARGET_CLIENT_USAGE, .source = { 's', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, - .flags = CMD_READONLY, + .flags = CMD_READONLY|CMD_CLIENT_TFLAG, .exec = cmd_detach_client_exec }; @@ -47,27 +47,25 @@ const struct cmd_entry cmd_suspend_client_entry = { .name = "suspend-client", .alias = "suspendc", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_CLIENT_USAGE, - .flags = 0, + .flags = CMD_CLIENT_TFLAG, .exec = cmd_detach_client_exec }; static enum cmd_retval cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c, *cloop; - struct session *s; - enum msgtype msgtype; - const char *cmd = args_get(args, 'E'); + struct args *args = cmd_get_args(self); + struct cmd_find_state *source = cmdq_get_source(item); + struct client *tc = cmdq_get_target_client(item), *loop; + struct session *s; + enum msgtype msgtype; + const char *cmd = args_get(args, 'E'); - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); - - if (self->entry == &cmd_suspend_client_entry) { - server_client_suspend(c); + if (cmd_get_entry(self) == &cmd_suspend_client_entry) { + server_client_suspend(tc); return (CMD_RETURN_NORMAL); } @@ -77,35 +75,35 @@ cmd_detach_client_exec(struct cmd *self, struct cmdq_item *item) msgtype = MSG_DETACH; if (args_has(args, 's')) { - s = item->source.s; + s = source->s; if (s == NULL) return (CMD_RETURN_NORMAL); - TAILQ_FOREACH(cloop, &clients, entry) { - if (cloop->session == s) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session == s) { if (cmd != NULL) - server_client_exec(cloop, cmd); + server_client_exec(loop, cmd); else - server_client_detach(cloop, msgtype); + server_client_detach(loop, msgtype); } } return (CMD_RETURN_STOP); } if (args_has(args, 'a')) { - TAILQ_FOREACH(cloop, &clients, entry) { - if (cloop->session != NULL && cloop != c) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session != NULL && loop != tc) { if (cmd != NULL) - server_client_exec(cloop, cmd); + server_client_exec(loop, cmd); else - server_client_detach(cloop, msgtype); + server_client_detach(loop, msgtype); } } return (CMD_RETURN_NORMAL); } if (cmd != NULL) - server_client_exec(c, cmd); + server_client_exec(tc, cmd); else - server_client_detach(c, msgtype); + server_client_detach(tc, msgtype); return (CMD_RETURN_STOP); } diff --git a/cmd-display-menu.c b/cmd-display-menu.c index ac7a4cfe..1a11bd01 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -27,73 +27,297 @@ * Display a menu on a client. */ -static enum cmd_retval cmd_display_menu_exec(struct cmd *, - struct cmdq_item *); +static enum args_parse_type cmd_display_menu_args_parse(struct args *, + u_int, char **); +static enum cmd_retval cmd_display_menu_exec(struct cmd *, + struct cmdq_item *); +static enum cmd_retval cmd_display_popup_exec(struct cmd *, + struct cmdq_item *); const struct cmd_entry cmd_display_menu_entry = { .name = "display-menu", .alias = "menu", - .args = { "c:t:T:x:y:", 1, -1 }, - .usage = "[-c target-client] " CMD_TARGET_PANE_USAGE " [-T title] " + .args = { "c:t:OT:x:y:", 1, -1, cmd_display_menu_args_parse }, + .usage = "[-O] [-c target-client] " CMD_TARGET_PANE_USAGE " [-T title] " "[-x position] [-y position] name key command ...", .target = { 't', CMD_FIND_PANE, 0 }, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG, .exec = cmd_display_menu_exec }; +const struct cmd_entry cmd_display_popup_entry = { + .name = "display-popup", + .alias = "popup", + + .args = { "BCc:d:Eh:t:w:x:y:", 0, -1, NULL }, + .usage = "[-BCE] [-c target-client] [-d start-directory] [-h height] " + CMD_TARGET_PANE_USAGE " [-w width] " + "[-x position] [-y position] [shell-command]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG, + .exec = cmd_display_popup_exec +}; + +static enum args_parse_type +cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause) +{ + u_int i = 0; + enum args_parse_type type = ARGS_PARSE_STRING; + + for (;;) { + type = ARGS_PARSE_STRING; + if (i == idx) + break; + if (*args_string(args, i++) == '\0') + continue; + + type = ARGS_PARSE_STRING; + if (i++ == idx) + break; + + type = ARGS_PARSE_COMMANDS_OR_STRING; + if (i++ == idx) + break; + } + return (type); +} + +static int +cmd_display_menu_get_position(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; + 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_pane *wp = target->wp; + struct style_ranges *ranges = NULL; + struct style_range *sr = NULL; + const char *xp, *yp; + char *p; + int top; + u_int line, ox, oy, sx, sy, lines, position; + long n; + struct format_tree *ft; + + /* + * Work out the position from the -x and -y arguments. This is the + * bottom-left position. + */ + + /* If the popup is too big, stop now. */ + if (w > tty->sx || h > tty->sy) + return (0); + + /* Create format with mouse position if any. */ + ft = format_create_from_target(item); + if (event->m.valid) { + format_add(ft, "popup_mouse_x", "%u", event->m.x); + format_add(ft, "popup_mouse_y", "%u", event->m.y); + } + + /* + * If there are any status lines, add this window position and the + * status line position. + */ + top = status_at_line(tc); + if (top != -1) { + lines = status_line_size(tc); + if (top == 0) + top = lines; + else + top = 0; + position = options_get_number(s->options, "status-position"); + + 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); + if (position == 0) { + format_add(ft, "popup_window_status_line_y", + "%u", line + 1 + h); + } else { + format_add(ft, "popup_window_status_line_y", + "%u", tty->sy - lines + line); + } + } + + if (position == 0) + format_add(ft, "popup_status_line_y", "%u", lines + h); + else { + format_add(ft, "popup_status_line_y", "%u", + tty->sy - lines); + } + } else + top = 0; + + /* Popup width and height. */ + format_add(ft, "popup_width", "%u", w); + format_add(ft, "popup_height", "%u", h); + + /* Position so popup is in the centre. */ + n = (long)(tty->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 = (tty->sy - 1) / 2 + h / 2; + if (n >= tty->sy) + format_add(ft, "popup_centre_y", "%u", tty->sy - h); + else + format_add(ft, "popup_centre_y", "%ld", n); + + /* Position of popup relative to mouse. */ + if (event->m.valid) { + n = (long)event->m.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 = event->m.y - h / 2; + if (n + h >= tty->sy) { + format_add(ft, "popup_mouse_centre_y", "%u", + tty->sy - h); + } else + format_add(ft, "popup_mouse_centre_y", "%ld", n); + n = (long)event->m.y + h; + if (n >= tty->sy) + format_add(ft, "popup_mouse_top", "%u", tty->sy - 1); + else + format_add(ft, "popup_mouse_top", "%ld", n); + n = event->m.y - h; + if (n < 0) + format_add(ft, "popup_mouse_bottom", "%u", 0); + else + format_add(ft, "popup_mouse_bottom", "%ld", n); + } + + /* Position in pane. */ + tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy); + n = top + wp->yoff - oy + h; + if (n >= tty->sy) + format_add(ft, "popup_pane_top", "%u", tty->sy - h); + else + format_add(ft, "popup_pane_top", "%ld", n); + format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy); + format_add(ft, "popup_pane_left", "%u", wp->xoff - ox); + n = (long)wp->xoff + wp->sx - ox - w; + if (n < 0) + format_add(ft, "popup_pane_right", "%u", 0); + else + format_add(ft, "popup_pane_right", "%ld", n); + + /* Expand horizontal position. */ + 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, "W") == 0) + xp = "#{popup_window_status_line_x}"; + p = format_expand(ft, xp); + n = strtol(p, NULL, 10); + if (n + w >= tty->sx) + n = tty->sx - w; + else if (n < 0) + n = 0; + *px = n; + log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w); + free(p); + + /* Expand vertical position */ + 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, "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 + h >= tty->sy) + n = tty->sy - h; + else if (n < 0) + n = 0; + *py = n; + log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h); + free(p); + + return (1); +} + static enum cmd_retval cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; - struct cmd_find_state *fs = &item->target; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct key_event *event = cmdq_get_event(item); + struct client *tc = cmdq_get_target_client(item); struct menu *menu = NULL; - struct style_range *sr; struct menu_item menu_item; - const char *xp, *yp, *key; - char *title, *name; - int at, flags, i; - u_int px, py, ox, oy, sx, sy; + const char *key, *name; + char *title; + int flags = 0; + u_int px, py, i, count = args_count(args); - if ((c = cmd_find_client(item, args_get(args, 'c'), 0)) == NULL) - return (CMD_RETURN_ERROR); - if (c->overlay_draw != NULL) + if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); - at = status_at_line(c); if (args_has(args, 'T')) - title = format_single(NULL, args_get(args, 'T'), c, s, wl, wp); + title = format_single_from_target(item, args_get(args, 'T')); else title = xstrdup(""); - menu = menu_create(title); - for (i = 0; i != args->argc; /* nothing */) { - name = args->argv[i++]; + for (i = 0; i != count; /* nothing */) { + name = args_string(args, i++); if (*name == '\0') { - menu_add_item(menu, NULL, item, c, fs); + menu_add_item(menu, NULL, item, tc, target); continue; } - if (args->argc - i < 2) { + if (count - i < 2) { cmdq_error(item, "not enough arguments"); free(title); menu_free(menu); return (CMD_RETURN_ERROR); } - key = args->argv[i++]; + key = args_string(args, i++); menu_item.name = name; menu_item.key = key_string_lookup_string(key); - menu_item.command = args->argv[i++]; + menu_item.command = args_string(args, i++); - menu_add_item(menu, &menu_item, item, c, fs); + menu_add_item(menu, &menu_item, item, tc, target); } free(title); if (menu == NULL) { @@ -104,75 +328,98 @@ cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item) menu_free(menu); return (CMD_RETURN_NORMAL); } - - xp = args_get(args, 'x'); - if (xp == NULL) - px = 0; - else if (strcmp(xp, "R") == 0) - px = c->tty.sx - 1; - else if (strcmp(xp, "P") == 0) { - tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); - if (wp->xoff >= ox) - px = wp->xoff - ox; - else - px = 0; - } else if (strcmp(xp, "M") == 0 && item->shared->mouse.valid) { - if (item->shared->mouse.x > (menu->width + 4) / 2) - px = item->shared->mouse.x - (menu->width + 4) / 2; - else - px = 0; + if (!cmd_display_menu_get_position(tc, item, args, &px, &py, + menu->width + 4, menu->count + 2)) { + menu_free(menu); + return (CMD_RETURN_NORMAL); } - else if (strcmp(xp, "W") == 0) { - if (at == -1) - px = 0; - else { - TAILQ_FOREACH(sr, &c->status.entries[0].ranges, entry) { - if (sr->type != STYLE_RANGE_WINDOW) - continue; - if (sr->argument == (u_int)wl->idx) - break; - } - if (sr != NULL) - px = sr->start; - else - px = 0; - } - } else - px = strtoul(xp, NULL, 10); - if (px + menu->width + 4 >= c->tty.sx) - px = c->tty.sx - menu->width - 4; - yp = args_get(args, 'y'); - if (yp == NULL) - py = 0; - else if (strcmp(yp, "P") == 0) { - tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); - if (wp->yoff + wp->sy >= oy) - py = wp->yoff + wp->sy - oy; - else - py = 0; - } else if (strcmp(yp, "M") == 0 && item->shared->mouse.valid) - py = item->shared->mouse.y + menu->count + 2; - else if (strcmp(yp, "S") == 0) { - if (at == -1) - py = c->tty.sy; - else if (at == 0) - py = status_line_size(c) + menu->count + 2; - else - py = at; - } else - py = strtoul(yp, NULL, 10); - if (py < menu->count + 2) - py = 0; - else - py -= menu->count + 2; - if (py + menu->count + 2 >= c->tty.sy) - py = c->tty.sy - menu->count - 2; - - flags = 0; - if (!item->shared->mouse.valid) + if (args_has(args, 'O')) + flags |= MENU_STAYOPEN; + if (!event->m.valid) flags |= MENU_NOMOUSE; - if (menu_display(menu, flags, item, px, py, c, fs, NULL, NULL) != 0) + if (menu_display(menu, flags, item, px, py, tc, target, NULL, + NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } + +static enum cmd_retval +cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct session *s = target->s; + struct client *tc = cmdq_get_target_client(item); + struct tty *tty = &tc->tty; + const char *value, *shell, *shellcmd = NULL; + char *cwd, *cause, **argv = NULL; + int flags = 0, argc = 0; + u_int px, py, w, h, count = args_count(args); + + if (args_has(args, 'C')) { + server_client_clear_overlay(tc); + return (CMD_RETURN_NORMAL); + } + if (tc->overlay_draw != NULL) + return (CMD_RETURN_NORMAL); + + h = tty->sy / 2; + if (args_has(args, 'h')) { + h = args_percentage(args, 'h', 1, tty->sy, tty->sy, &cause); + if (cause != NULL) { + cmdq_error(item, "height %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + + w = tty->sx / 2; + if (args_has(args, 'w')) { + w = args_percentage(args, 'w', 1, tty->sx, tty->sx, &cause); + if (cause != NULL) { + cmdq_error(item, "width %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + + if (w > tty->sx) + w = tty->sx; + if (h > tty->sy) + h = tty->sy; + if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h)) + return (CMD_RETURN_NORMAL); + + value = args_get(args, 'd'); + if (value != NULL) + cwd = format_single_from_target(item, value); + else + cwd = xstrdup(server_client_get_cwd(tc, s)); + if (count == 0) + shellcmd = options_get_string(s->options, "default-command"); + else if (count == 1) + shellcmd = args_string(args, 0); + if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) { + shellcmd = NULL; + shell = options_get_string(s->options, "default-shell"); + if (!checkshell(shell)) + shell = _PATH_BSHELL; + cmd_append_argv(&argc, &argv, shell); + } else + args_to_vector(args, &argc, &argv); + + if (args_has(args, 'E') > 1) + flags |= POPUP_CLOSEEXITZERO; + else if (args_has(args, 'E')) + flags |= POPUP_CLOSEEXIT; + if (args_has(args, 'B')) + flags |= POPUP_NOBORDER; + if (popup_display(flags, item, px, py, w, h, shellcmd, argc, argv, cwd, + tc, s, NULL, NULL) != 0) { + cmd_free_argv(argc, argv); + return (CMD_RETURN_NORMAL); + } + cmd_free_argv(argc, argv); + return (CMD_RETURN_WAIT); +} diff --git a/cmd-display-message.c b/cmd-display-message.c index 4d9bccb6..7828f694 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -39,13 +39,13 @@ const struct cmd_entry cmd_display_message_entry = { .name = "display-message", .alias = "display", - .args = { "ac:Ipt:F:v", 0, 1 }, - .usage = "[-aIpv] [-c target-client] [-F format] " + .args = { "ac:d:INpt:F:v", 0, 1, NULL }, + .usage = "[-aINpv] [-c target-client] [-d delay] [-F format] " CMD_TARGET_PANE_USAGE " [message]", - .target = { 't', CMD_FIND_PANE, 0 }, + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL, .exec = cmd_display_message_exec }; @@ -60,33 +60,51 @@ cmd_display_message_each(const char *key, const char *value, void *arg) static enum cmd_retval cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c, *target_c; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item), *c; + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane *wp = target->wp; const char *template; char *msg, *cause; + int delay = -1, flags; struct format_tree *ft; - int flags; + u_int count = args_count(args); if (args_has(args, 'I')) { - if (window_pane_start_input(wp, item, &cause) != 0) { + if (wp == NULL) + return (CMD_RETURN_NORMAL); + switch (window_pane_start_input(wp, item, &cause)) { + case -1: cmdq_error(item, "%s", cause); free(cause); return (CMD_RETURN_ERROR); + case 1: + return (CMD_RETURN_NORMAL); + case 0: + return (CMD_RETURN_WAIT); } - return (CMD_RETURN_WAIT); } - if (args_has(args, 'F') && args->argc != 0) { + if (args_has(args, 'F') && count != 0) { cmdq_error(item, "only one of -F or argument must be given"); return (CMD_RETURN_ERROR); } - template = args_get(args, 'F'); - if (args->argc != 0) - template = args->argv[0]; + if (args_has(args, 'd')) { + delay = args_strtonum(args, 'd', 0, UINT_MAX, &cause); + if (cause != NULL) { + cmdq_error(item, "delay %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + + if (count != 0) + template = args_string(args, 0); + else + template = args_get(args, 'F'); if (template == NULL) template = DISPLAY_MESSAGE_TEMPLATE; @@ -96,17 +114,18 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) * formats too, assuming it matches the session. If it doesn't, use the * best client for the session. */ - c = cmd_find_client(item, args_get(args, 'c'), 1); - if (c != NULL && c->session == s) - target_c = c; + if (tc != NULL && tc->session == s) + c = tc; + else if (s != NULL) + c = cmd_find_best_client(s); else - target_c = cmd_find_best_client(s); - if (args_has(self->args, 'v')) + c = NULL; + if (args_has(args, 'v')) flags = FORMAT_VERBOSE; else flags = 0; - ft = format_create(item->client, item, FORMAT_NONE, flags); - format_defaults(ft, target_c, s, wl, wp); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, flags); + format_defaults(ft, c, s, wl, wp); if (args_has(args, 'a')) { format_each(ft, cmd_display_message_each, item); @@ -114,10 +133,14 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) } msg = format_expand_time(ft, template); - if (args_has(self->args, 'p')) + if (cmdq_get_client(item) == NULL) + cmdq_error(item, "%s", msg); + else if (args_has(args, 'p')) cmdq_print(item, "%s", msg); - else if (c != NULL) - status_message_set(c, "%s", msg); + else if (tc != NULL) { + status_message_set(tc, delay, 0, args_has(args, 'N'), "%s", + msg); + } free(msg); format_free(ft); diff --git a/cmd-display-panes.c b/cmd-display-panes.c index df97819c..5773a2d0 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -27,25 +27,34 @@ * Display panes on a client. */ -static enum cmd_retval cmd_display_panes_exec(struct cmd *, - struct cmdq_item *); +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:t:", 0, 1 }, - .usage = "[-b] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", + .args = { "bd:Nt:", 0, 1, cmd_display_panes_args_parse }, + .usage = "[-bN] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, .exec = cmd_display_panes_exec }; struct cmd_display_panes_data { - struct cmdq_item *item; - char *command; + struct cmdq_item *item; + struct args_command_state *state; }; +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_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) @@ -55,11 +64,11 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, struct session *s = c->session; struct options *oo = s->options; struct window *w = wp->window; - struct grid_cell gc; - u_int idx, px, py, i, j, xoff, yoff, sx, sy; + struct grid_cell fgc, bgc; + u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy; int colour, active_colour; - char buf[16], *ptr; - size_t len; + char buf[16], lbuf[16], rbuf[16], *ptr; + size_t len, llen, rlen; if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx || @@ -109,31 +118,50 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, px = sx / 2; py = sy / 2; - if (window_pane_index(wp, &idx) != 0) + if (window_pane_index(wp, &pane) != 0) fatalx("index not found"); - len = xsnprintf(buf, sizeof buf, "%u", idx); + len = xsnprintf(buf, sizeof buf, "%u", pane); if (sx < len) return; colour = options_get_number(oo, "display-panes-colour"); active_colour = options_get_number(oo, "display-panes-active-colour"); + memcpy(&fgc, &grid_default_cell, sizeof fgc); + memcpy(&bgc, &grid_default_cell, sizeof bgc); + if (w->active == wp) { + fgc.fg = active_colour; + bgc.bg = active_colour; + } else { + fgc.fg = colour; + bgc.bg = colour; + } + + rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy); + if (pane > 9 && pane < 35) + llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10)); + else + llen = 0; + if (sx < len * 6 || sy < 5) { - tty_cursor(tty, xoff + px - len / 2, yoff + py); - goto draw_text; + tty_attributes(tty, &fgc, &grid_default_cell, NULL); + if (sx >= len + llen + 1) { + len += llen + 1; + tty_cursor(tty, xoff + px - len / 2, yoff + py); + tty_putn(tty, buf, len, len); + tty_putn(tty, " ", 1, 1); + tty_putn(tty, lbuf, llen, llen); + } else { + tty_cursor(tty, xoff + px - len / 2, yoff + py); + tty_putn(tty, buf, len, len); + } + goto out; } px -= len * 3; py -= 2; - memcpy(&gc, &grid_default_cell, sizeof gc); - if (w->active == wp) - gc.bg = active_colour; - else - gc.bg = colour; - gc.flags |= GRID_FLAG_NOPALETTE; - - tty_attributes(tty, &gc, wp); + tty_attributes(tty, &bgc, &grid_default_cell, NULL); for (ptr = buf; *ptr != '\0'; ptr++) { if (*ptr < '0' || *ptr > '9') continue; @@ -149,27 +177,26 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, px += 6; } - len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy); - if (sx < len || sy < 6) - return; - tty_cursor(tty, xoff + sx - len, yoff); - -draw_text: - memcpy(&gc, &grid_default_cell, sizeof gc); - if (w->active == wp) - gc.fg = active_colour; - else - gc.fg = colour; - gc.flags |= GRID_FLAG_NOPALETTE; - - tty_attributes(tty, &gc, wp); - tty_puts(tty, buf); + if (sy <= 6) + goto out; + tty_attributes(tty, &fgc, &grid_default_cell, NULL); + if (rlen != 0 && sx >= rlen) { + tty_cursor(tty, xoff + sx - rlen, yoff); + tty_putn(tty, rbuf, rlen, rlen); + } + if (llen != 0) { + tty_cursor(tty, xoff + sx / 2 + len * 3 - llen - 1, + yoff + py + 5); + tty_putn(tty, lbuf, llen, llen); + } +out: tty_cursor(tty, 0, 0); } static void -cmd_display_panes_draw(struct client *c, struct screen_redraw_ctx *ctx) +cmd_display_panes_draw(struct client *c, __unused void *data, + struct screen_redraw_ctx *ctx) { struct window *w = c->session->curw->window; struct window_pane *wp; @@ -183,55 +210,58 @@ cmd_display_panes_draw(struct client *c, struct screen_redraw_ctx *ctx) } static void -cmd_display_panes_free(struct client *c) +cmd_display_panes_free(__unused struct client *c, void *data) { - struct cmd_display_panes_data *cdata = c->overlay_data; + struct cmd_display_panes_data *cdata = data; if (cdata->item != NULL) cmdq_continue(cdata->item); - free(cdata->command); + args_make_commands_free(cdata->state); free(cdata); } static int -cmd_display_panes_key(struct client *c, struct key_event *event) +cmd_display_panes_key(struct client *c, void *data, struct key_event *event) { - struct cmd_display_panes_data *cdata = c->overlay_data; - struct cmdq_item *new_item; - char *cmd, *expanded; + struct cmd_display_panes_data *cdata = data; + char *expanded, *error; + struct cmdq_item *item = cdata->item, *new_item; + struct cmd_list *cmdlist; struct window *w = c->session->curw->window; struct window_pane *wp; - struct cmd_parse_result *pr; + u_int index; + key_code key; - if (event->key < '0' || event->key > '9') + 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, event->key - '0'); + wp = window_pane_at_index(w, index); if (wp == NULL) return (1); window_unzoom(w); xasprintf(&expanded, "%%%u", wp->id); - cmd = cmd_template_replace(cdata->command, expanded, 1); - pr = cmd_parse_from_string(cmd, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - new_item = NULL; - break; - case CMD_PARSE_ERROR: - new_item = cmdq_get_error(pr->error); - free(pr->error); + cmdlist = args_make_commands(cdata->state, 1, &expanded, &error); + if (cmdlist == NULL) { + cmdq_append(c, cmdq_get_error(error)); + free(error); + } else if (item == NULL) { + new_item = cmdq_get_command(cmdlist, NULL); cmdq_append(c, new_item); - break; - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); - cmd_list_free(pr->cmdlist); - cmdq_append(c, new_item); - break; + } else { + new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); + cmdq_insert_after(item, new_item); } - free(cmd); free(expanded); return (1); } @@ -239,18 +269,15 @@ cmd_display_panes_key(struct client *c, struct key_event *event) static enum cmd_retval cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c; - struct session *s; - u_int delay; + 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 ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); - s = c->session; - - if (c->overlay_draw != NULL) + if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); if (args_has(args, 'd')) { @@ -263,20 +290,23 @@ cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item) } else delay = options_get_number(s->options, "display-panes-time"); - cdata = xmalloc(sizeof *cdata); - if (args->argc != 0) - cdata->command = xstrdup(args->argv[0]); - else - cdata->command = xstrdup("select-pane -t '%%'"); - if (args_has(args, 'b')) - cdata->item = NULL; - else + cdata = xcalloc(1, sizeof *cdata); + if (wait) cdata->item = item; + cdata->state = args_make_commands_prepare(self, item, 0, + "select-pane -t \"%%%\"", wait, 0); - server_client_set_overlay(c, delay, cmd_display_panes_draw, - cmd_display_panes_key, cmd_display_panes_free, cdata); + 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 (args_has(args, 'b')) + if (!wait) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } diff --git a/cmd-find-window.c b/cmd-find-window.c index c29878b5..6e07537c 100644 --- a/cmd-find-window.c +++ b/cmd-find-window.c @@ -32,8 +32,8 @@ const struct cmd_entry cmd_find_window_entry = { .name = "find-window", .alias = "findw", - .args = { "CNrt:TZ", 1, 1 }, - .usage = "[-CNrTZ] " CMD_TARGET_PANE_USAGE " match-string", + .args = { "CiNrt:TZ", 1, 1, NULL }, + .usage = "[-CiNrTZ] " CMD_TARGET_PANE_USAGE " match-string", .target = { 't', CMD_FIND_PANE, 0 }, @@ -44,82 +44,70 @@ const struct cmd_entry cmd_find_window_entry = { static enum cmd_retval cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args, *new_args; - struct window_pane *wp = item->target.wp; - const char *s = args->argv[0]; - char *filter, *argv = { NULL }; + struct args *args = cmd_get_args(self), *new_args; + struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *wp = target->wp; + const char *s = args_string(args, 0), *suffix = ""; + struct args_value *filter; int C, N, T; C = args_has(args, 'C'); N = args_has(args, 'N'); T = args_has(args, 'T'); + if (args_has(args, 'r') && args_has(args, 'i')) + suffix = "/ri"; + else if (args_has(args, 'r')) + suffix = "/r"; + else if (args_has(args, 'i')) + suffix = "/i"; + if (!C && !N && !T) C = N = T = 1; - if (!args_has(args, 'r')) { - if (C && N && T) { - xasprintf(&filter, - "#{||:" - "#{C:%s},#{||:#{m:*%s*,#{window_name}}," - "#{m:*%s*,#{pane_title}}}}", - s, s, s); - } else if (C && N) { - xasprintf(&filter, - "#{||:#{C:%s},#{m:*%s*,#{window_name}}}", - s, s); - } else if (C && T) { - xasprintf(&filter, - "#{||:#{C:%s},#{m:*%s*,#{pane_title}}}", - s, s); - } else if (N && T) { - xasprintf(&filter, - "#{||:#{m:*%s*,#{window_name}}," - "#{m:*%s*,#{pane_title}}}", - s, s); - } else if (C) - xasprintf(&filter, "#{C:%s}", s); - else if (N) - xasprintf(&filter, "#{m:*%s*,#{window_name}}", s); - else - xasprintf(&filter, "#{m:*%s*,#{pane_title}}", s); + filter = xcalloc(1, sizeof *filter); + filter->type = ARGS_STRING; + + if (C && N && T) { + xasprintf(&filter->string, + "#{||:" + "#{C%s:%s},#{||:#{m%s:*%s*,#{window_name}}," + "#{m%s:*%s*,#{pane_title}}}}", + suffix, s, suffix, s, suffix, s); + } else if (C && N) { + xasprintf(&filter->string, + "#{||:#{C%s:%s},#{m%s:*%s*,#{window_name}}}", + suffix, s, suffix, s); + } else if (C && T) { + xasprintf(&filter->string, + "#{||:#{C%s:%s},#{m%s:*%s*,#{pane_title}}}", + suffix, s, suffix, s); + } else if (N && T) { + xasprintf(&filter->string, + "#{||:#{m%s:*%s*,#{window_name}}," + "#{m%s:*%s*,#{pane_title}}}", + suffix, s, suffix, s); + } else if (C) { + xasprintf(&filter->string, + "#{C%s:%s}", + suffix, s); + } else if (N) { + xasprintf(&filter->string, + "#{m%s:*%s*,#{window_name}}", + suffix, s); } else { - if (C && N && T) { - xasprintf(&filter, - "#{||:" - "#{C/r:%s},#{||:#{m/r:%s,#{window_name}}," - "#{m/r:%s,#{pane_title}}}}", - s, s, s); - } else if (C && N) { - xasprintf(&filter, - "#{||:#{C/r:%s},#{m/r:%s,#{window_name}}}", - s, s); - } else if (C && T) { - xasprintf(&filter, - "#{||:#{C/r:%s},#{m/r:%s,#{pane_title}}}", - s, s); - } else if (N && T) { - xasprintf(&filter, - "#{||:#{m/r:%s,#{window_name}}," - "#{m/r:%s,#{pane_title}}}", - s, s); - } else if (C) - xasprintf(&filter, "#{C/r:%s}", s); - else if (N) - xasprintf(&filter, "#{m/r:%s,#{window_name}}", s); - else - xasprintf(&filter, "#{m/r:%s,#{pane_title}}", s); + xasprintf(&filter->string, + "#{m%s:*%s*,#{pane_title}}", + suffix, s); } - new_args = args_parse("", 1, &argv); + new_args = args_create(); if (args_has(args, 'Z')) args_set(new_args, 'Z', NULL); args_set(new_args, 'f', filter); - window_pane_set_mode(wp, &window_tree_mode, &item->target, new_args); - + window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); args_free(new_args); - free(filter); return (CMD_RETURN_NORMAL); } diff --git a/cmd-find.c b/cmd-find.c index 154842ab..e98090d2 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -587,22 +587,22 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) return (-1); return (0); } else if (strcmp(pane, "{up-of}") == 0) { - fs->wp = window_pane_find_up(fs->w->active); + fs->wp = window_pane_find_up(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{down-of}") == 0) { - fs->wp = window_pane_find_down(fs->w->active); + fs->wp = window_pane_find_down(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{left-of}") == 0) { - fs->wp = window_pane_find_left(fs->w->active); + fs->wp = window_pane_find_left(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{right-of}") == 0) { - fs->wp = window_pane_find_right(fs->w->active); + fs->wp = window_pane_find_right(fs->current->wp); if (fs->wp == NULL) return (-1); return (0); @@ -614,7 +614,7 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) n = strtonum(pane + 1, 1, INT_MAX, NULL); else n = 1; - wp = fs->w->active; + wp = fs->current->wp; if (pane[0] == '+') fs->wp = window_pane_next_by_number(fs->w, wp, n); else @@ -866,7 +866,18 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) /* If this is an attached client, all done. */ if (c->session != NULL) { - cmd_find_from_session(fs, c->session, flags); + cmd_find_clear_state(fs, flags); + + fs->wp = server_client_get_pane(c); + if (fs->wp == NULL) { + cmd_find_from_session(fs, c->session, flags); + return (0); + } + fs->s = c->session; + fs->wl = fs->s->curw; + fs->w = fs->wl->window; + + cmd_find_log_state(__func__, fs); return (0); } cmd_find_clear_state(fs, flags); @@ -960,10 +971,11 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, if (server_check_marked() && (flags & CMD_FIND_DEFAULT_MARKED)) { fs->current = &marked_pane; log_debug("%s: current is marked pane", __func__); - } else if (cmd_find_valid_state(&item->shared->current)) { - fs->current = &item->shared->current; + } else if (cmd_find_valid_state(cmdq_get_current(item))) { + fs->current = cmdq_get_current(item); log_debug("%s: current is from queue", __func__); - } else if (cmd_find_from_client(¤t, item->client, flags) == 0) { + } else if (cmd_find_from_client(¤t, cmdq_get_client(item), + flags) == 0) { fs->current = ¤t; log_debug("%s: current is from client", __func__); } else { @@ -980,7 +992,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, /* Mouse target is a plain = or {mouse}. */ if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) { - m = &item->shared->mouse; + m = &cmdq_get_event(item)->m; switch (type) { case CMD_FIND_PANE: fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl); @@ -1230,29 +1242,31 @@ no_pane: static struct client * cmd_find_current_client(struct cmdq_item *item, int quiet) { - struct client *c; + struct client *c = NULL, *found; struct session *s; struct window_pane *wp; struct cmd_find_state fs; - if (item->client != NULL && item->client->session != NULL) - return (item->client); + if (item != NULL) + c = cmdq_get_client(item); + if (c != NULL && c->session != NULL) + return (c); - c = NULL; - if ((wp = cmd_find_inside_pane(item->client)) != NULL) { + found = NULL; + if (c != NULL && (wp = cmd_find_inside_pane(c)) != NULL) { cmd_find_clear_state(&fs, CMD_FIND_QUIET); fs.w = wp->window; if (cmd_find_best_session_with_window(&fs) == 0) - c = cmd_find_best_client(fs.s); + found = cmd_find_best_client(fs.s); } else { s = cmd_find_best_session(NULL, 0, CMD_FIND_QUIET); if (s != NULL) - c = cmd_find_best_client(s); + found = cmd_find_best_client(s); } - if (c == NULL && !quiet) + if (found == NULL && item != NULL && !quiet) cmdq_error(item, "no current client"); - log_debug("%s: no target, return %p", __func__, c); - return (c); + log_debug("%s: no target, return %p", __func__, found); + return (found); } /* Find the target client or report an error and return NULL. */ diff --git a/cmd-if-shell.c b/cmd-if-shell.c index 2befbc0c..211e08d6 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -29,16 +30,19 @@ * Executes a tmux command if a shell command returns true or false. */ -static enum cmd_retval cmd_if_shell_exec(struct cmd *, struct cmdq_item *); +static enum args_parse_type cmd_if_shell_args_parse(struct args *, u_int, + char **); +static enum cmd_retval cmd_if_shell_exec(struct cmd *, + struct cmdq_item *); -static void cmd_if_shell_callback(struct job *); -static void cmd_if_shell_free(void *); +static void cmd_if_shell_callback(struct job *); +static void cmd_if_shell_free(void *); const struct cmd_entry cmd_if_shell_entry = { .name = "if-shell", .alias = "if", - .args = { "bFt:", 2, 3 }, + .args = { "bFt:", 2, 3, cmd_if_shell_args_parse }, .usage = "[-bF] " CMD_TARGET_PANE_USAGE " shell-command command " "[command]", @@ -49,102 +53,75 @@ const struct cmd_entry cmd_if_shell_entry = { }; struct cmd_if_shell_data { - struct cmd_parse_input input; + struct args_command_state *cmd_if; + struct args_command_state *cmd_else; - char *cmd_if; - char *cmd_else; - - struct client *client; - struct cmdq_item *item; - struct mouse_event mouse; + struct client *client; + struct cmdq_item *item; }; +static enum args_parse_type +cmd_if_shell_args_parse(__unused struct args *args, u_int idx, + __unused char **cause) +{ + if (idx == 1 || idx == 2) + return (ARGS_PARSE_COMMANDS_OR_STRING); + return (ARGS_PARSE_STRING); +} + static enum cmd_retval cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct mouse_event *m = &item->shared->mouse; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); struct cmd_if_shell_data *cdata; - char *shellcmd, *cmd; struct cmdq_item *new_item; - struct cmd_find_state *fs = &item->target; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = fs->s; - struct winlink *wl = fs->wl; - struct window_pane *wp = fs->wp; - struct cmd_parse_input pi; - struct cmd_parse_result *pr; + char *shellcmd; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct cmd_list *cmdlist; + u_int count = args_count(args); + int wait = !args_has(args, 'b'); - shellcmd = format_single(item, args->argv[0], c, s, wl, wp); + shellcmd = format_single_from_target(item, args_string(args, 0)); if (args_has(args, 'F')) { if (*shellcmd != '0' && *shellcmd != '\0') - cmd = args->argv[1]; - else if (args->argc == 3) - cmd = args->argv[2]; - else - cmd = NULL; - free(shellcmd); - if (cmd == NULL) + cmdlist = args_make_commands_now(self, item, 1, 0); + else if (count == 3) + cmdlist = args_make_commands_now(self, item, 2, 0); + else { + free(shellcmd); return (CMD_RETURN_NORMAL); - - memset(&pi, 0, sizeof pi); - if (self->file != NULL) - pi.file = self->file; - pi.line = self->line; - pi.item = item; - pi.c = c; - cmd_find_copy_state(&pi.fs, fs); - - pr = cmd_parse_from_string(cmd, &pi); - switch (pr->status) { - case CMD_PARSE_EMPTY: - break; - case CMD_PARSE_ERROR: - cmdq_error(item, "%s", pr->error); - free(pr->error); - return (CMD_RETURN_ERROR); - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, fs, m, 0); - cmdq_insert_after(item, new_item); - cmd_list_free(pr->cmdlist); - break; } + free(shellcmd); + if (cmdlist == NULL) + return (CMD_RETURN_ERROR); + new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); + cmdq_insert_after(item, new_item); return (CMD_RETURN_NORMAL); } cdata = xcalloc(1, sizeof *cdata); - cdata->cmd_if = xstrdup(args->argv[1]); - if (args->argc == 3) - cdata->cmd_else = xstrdup(args->argv[2]); - else - cdata->cmd_else = NULL; - memcpy(&cdata->mouse, m, sizeof cdata->mouse); + cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait, + 0); + if (count == 3) { + cdata->cmd_else = args_make_commands_prepare(self, item, 2, + NULL, wait, 0); + } - if (!args_has(args, 'b')) - cdata->client = item->client; - else - cdata->client = c; + if (wait) { + cdata->client = cmdq_get_client(item); + cdata->item = item; + } else + cdata->client = tc; if (cdata->client != NULL) cdata->client->references++; - if (!args_has(args, 'b')) - cdata->item = item; - else - cdata->item = NULL; - - memset(&cdata->input, 0, sizeof cdata->input); - if (self->file != NULL) - cdata->input.file = xstrdup(self->file); - cdata->input.line = self->line; - cdata->input.item = cdata->item; - cdata->input.c = c; - if (cdata->input.c != NULL) - cdata->input.c->references++; - cmd_find_copy_state(&cdata->input.fs, fs); - - if (job_run(shellcmd, s, server_client_get_cwd(item->client, s), NULL, - cmd_if_shell_callback, cmd_if_shell_free, cdata, 0) == NULL) { + if (job_run(shellcmd, 0, NULL, s, + server_client_get_cwd(cmdq_get_client(item), s), NULL, + cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1, + -1) == NULL) { cmdq_error(item, "failed to run command: %s", shellcmd); free(shellcmd); free(cdata); @@ -152,7 +129,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) } free(shellcmd); - if (args_has(args, 'b')) + if (!wait) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } @@ -162,39 +139,34 @@ cmd_if_shell_callback(struct job *job) { struct cmd_if_shell_data *cdata = job_get_data(job); struct client *c = cdata->client; - struct mouse_event *m = &cdata->mouse; - struct cmdq_item *new_item = NULL; - char *cmd; + struct cmdq_item *item = cdata->item, *new_item; + struct args_command_state *state; + struct cmd_list *cmdlist; + char *error; int status; - struct cmd_parse_result *pr; status = job_get_status(job); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - cmd = cdata->cmd_else; + state = cdata->cmd_else; else - cmd = cdata->cmd_if; - if (cmd == NULL) + state = cdata->cmd_if; + if (state == NULL) goto out; - pr = cmd_parse_from_string(cmd, &cdata->input); - switch (pr->status) { - case CMD_PARSE_EMPTY: - break; - case CMD_PARSE_ERROR: - if (cdata->item != NULL) - cmdq_error(cdata->item, "%s", pr->error); - free(pr->error); - break; - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, NULL, m, 0); - cmd_list_free(pr->cmdlist); - break; - } - if (new_item != NULL) { - if (cdata->item == NULL) - cmdq_append(c, new_item); - else - cmdq_insert_after(cdata->item, new_item); + cmdlist = args_make_commands(state, 0, NULL, &error); + if (cmdlist == NULL) { + if (cdata->item == NULL) { + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, "%s", error); + } else + cmdq_error(cdata->item, "%s", error); + free(error); + } else if (item == NULL) { + new_item = cmdq_get_command(cmdlist, NULL); + cmdq_append(c, new_item); + } else { + new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); + cmdq_insert_after(item, new_item); } out: @@ -210,12 +182,9 @@ cmd_if_shell_free(void *data) if (cdata->client != NULL) server_client_unref(cdata->client); - free(cdata->cmd_else); - free(cdata->cmd_if); - - if (cdata->input.c != NULL) - server_client_unref(cdata->input.c); - free((void *)cdata->input.file); + if (cdata->cmd_else != NULL) + args_make_commands_free(cdata->cmd_else); + args_make_commands_free(cdata->cmd_if); free(cdata); } diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 4b767e3e..cb3fb343 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -35,7 +35,7 @@ const struct cmd_entry cmd_join_pane_entry = { .name = "join-pane", .alias = "joinp", - .args = { "bdfhvp:l:s:t:", 0, 0 }, + .args = { "bdfhvp:l:s:t:", 0, 0, NULL }, .usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, @@ -49,8 +49,8 @@ const struct cmd_entry cmd_move_pane_entry = { .name = "move-pane", .alias = "movep", - .args = { "bdhvp:l:s:t:", 0, 0 }, - .usage = "[-bdhv] [-p percentage|-l size] " CMD_SRCDST_PANE_USAGE, + .args = { "bdfhvp:l:s:t:", 0, 0, NULL }, + .usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .target = { 't', CMD_FIND_PANE, 0 }, @@ -62,42 +62,33 @@ const struct cmd_entry cmd_move_pane_entry = { static enum cmd_retval cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct cmd_find_state *source = cmdq_get_source(item); struct session *dst_s; struct winlink *src_wl, *dst_wl; struct window *src_w, *dst_w; struct window_pane *src_wp, *dst_wp; - char *cause, *copy; - const char *errstr, *p; - size_t plen; - int size, percentage, dst_idx, not_same_window; + char *cause = NULL; + int size, percentage, dst_idx; int flags; enum layout_type type; struct layout_cell *lc; - if (self->entry == &cmd_join_pane_entry) - not_same_window = 1; - else - not_same_window = 0; - - dst_s = item->target.s; - dst_wl = item->target.wl; - dst_wp = item->target.wp; + dst_s = target->s; + dst_wl = target->wl; + dst_wp = target->wp; dst_w = dst_wl->window; dst_idx = dst_wl->idx; server_unzoom_window(dst_w); - src_wl = item->source.wl; - src_wp = item->source.wp; + src_wl = source->wl; + src_wp = source->wp; src_w = src_wl->window; server_unzoom_window(src_w); - if (not_same_window && src_w == dst_w) { - cmdq_error(item, "can't join a pane to its own window"); - return (CMD_RETURN_ERROR); - } - if (!not_same_window && src_wp == dst_wp) { + if (src_wp == dst_wp) { cmdq_error(item, "source and target panes must be different"); return (CMD_RETURN_ERROR); } @@ -107,40 +98,27 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) type = LAYOUT_LEFTRIGHT; size = -1; - if ((p = args_get(args, 'l')) != NULL) { - plen = strlen(p); - if (p[plen - 1] == '%') { - copy = xstrdup(p); - copy[plen - 1] = '\0'; - percentage = strtonum(copy, 0, INT_MAX, &errstr); - free(copy); - if (errstr != NULL) { - cmdq_error(item, "percentage %s", errstr); - return (CMD_RETURN_ERROR); - } + if (args_has(args, 'l')) { + if (type == LAYOUT_TOPBOTTOM) { + size = args_percentage(args, 'l', 0, INT_MAX, + dst_wp->sy, &cause); + } else { + size = args_percentage(args, 'l', 0, INT_MAX, + dst_wp->sx, &cause); + } + } else if (args_has(args, 'p')) { + percentage = args_strtonum(args, 'p', 0, 100, &cause); + if (cause == NULL) { if (type == LAYOUT_TOPBOTTOM) size = (dst_wp->sy * percentage) / 100; else size = (dst_wp->sx * percentage) / 100; - } else { - size = args_strtonum(args, 'l', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } } - } else if (args_has(args, 'p')) { - percentage = args_strtonum(args, 'p', 0, 100, &cause); - if (cause != NULL) { - cmdq_error(item, "percentage %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } - if (type == LAYOUT_TOPBOTTOM) - size = (dst_wp->sy * percentage) / 100; - else - size = (dst_wp->sx * percentage) / 100; + } + if (cause != NULL) { + cmdq_error(item, "size %s", cause); + free(cause); + return (CMD_RETURN_ERROR); } flags = 0; @@ -157,14 +135,18 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) layout_close_pane(src_wp); + server_client_remove_pane(src_wp); window_lost_pane(src_w, src_wp); TAILQ_REMOVE(&src_w->panes, src_wp, entry); src_wp->window = dst_w; options_set_parent(src_wp->options, dst_w->options); src_wp->flags |= PANE_STYLECHANGED; - TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); - layout_assign_pane(lc, src_wp); + if (flags & SPAWN_BEFORE) + TAILQ_INSERT_BEFORE(dst_wp, src_wp, entry); + else + TAILQ_INSERT_AFTER(&dst_w->panes, dst_wp, src_wp, entry); + layout_assign_pane(lc, src_wp, 0); recalculate_sizes(); @@ -180,7 +162,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_status_session(dst_s); if (window_count_panes(src_w) == 0) - server_kill_window(src_w); + server_kill_window(src_w, 1); else notify_window("window-layout-changed", src_w); notify_window("window-layout-changed", dst_w); diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index f0aacb2a..e1134a1e 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -32,7 +32,7 @@ const struct cmd_entry cmd_kill_pane_entry = { .name = "kill-pane", .alias = "killp", - .args = { "at:", 0, 0 }, + .args = { "at:", 0, 0, NULL }, .usage = "[-a] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -44,14 +44,17 @@ const struct cmd_entry cmd_kill_pane_entry = { static enum cmd_retval cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct winlink *wl = item->target.wl; - struct window_pane *loopwp, *tmpwp, *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct window_pane *loopwp, *tmpwp, *wp = target->wp; - if (args_has(self->args, 'a')) { + if (args_has(args, 'a')) { server_unzoom_window(wl->window); TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { if (loopwp == wp) continue; + server_client_remove_pane(loopwp); layout_close_pane(loopwp); window_remove_pane(wl->window, loopwp); } diff --git a/cmd-kill-server.c b/cmd-kill-server.c index d7eba692..7bb79e3d 100644 --- a/cmd-kill-server.c +++ b/cmd-kill-server.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_kill_server_entry = { .name = "kill-server", .alias = NULL, - .args = { "", 0, 0 }, + .args = { "", 0, 0, NULL }, .usage = "", .flags = 0, @@ -44,7 +44,7 @@ const struct cmd_entry cmd_start_server_entry = { .name = "start-server", .alias = "start", - .args = { "", 0, 0 }, + .args = { "", 0, 0, NULL }, .usage = "", .flags = CMD_STARTSERVER, @@ -54,7 +54,7 @@ const struct cmd_entry cmd_start_server_entry = { static enum cmd_retval cmd_kill_server_exec(struct cmd *self, __unused struct cmdq_item *item) { - if (self->entry == &cmd_kill_server_entry) + if (cmd_get_entry(self) == &cmd_kill_server_entry) kill(getpid(), SIGTERM); return (CMD_RETURN_NORMAL); diff --git a/cmd-kill-session.c b/cmd-kill-session.c index dcef8097..19a8d495 100644 --- a/cmd-kill-session.c +++ b/cmd-kill-session.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_kill_session_entry = { .name = "kill-session", .alias = NULL, - .args = { "aCt:", 0, 0 }, + .args = { "aCt:", 0, 0, NULL }, .usage = "[-aC] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -45,11 +45,10 @@ const struct cmd_entry cmd_kill_session_entry = { static enum cmd_retval cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct session *s, *sloop, *stmp; - struct winlink *wl; - - s = item->target.s; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct session *s = target->s, *sloop, *stmp; + struct winlink *wl; if (args_has(args, 'C')) { RB_FOREACH(wl, winlinks, &s->windows) { diff --git a/cmd-kill-window.c b/cmd-kill-window.c index 50df83ee..f5ff05f8 100644 --- a/cmd-kill-window.c +++ b/cmd-kill-window.c @@ -30,7 +30,7 @@ const struct cmd_entry cmd_kill_window_entry = { .name = "kill-window", .alias = "killw", - .args = { "at:", 0, 0 }, + .args = { "at:", 0, 0, NULL }, .usage = "[-a] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -43,7 +43,7 @@ const struct cmd_entry cmd_unlink_window_entry = { .name = "unlink-window", .alias = "unlinkw", - .args = { "kt:", 0, 0 }, + .args = { "kt:", 0, 0, NULL }, .usage = "[-k] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -55,27 +55,56 @@ const struct cmd_entry cmd_unlink_window_entry = { static enum cmd_retval cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct winlink *wl = item->target.wl, *wl2, *wl3; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl, *loop; struct window *w = wl->window; - struct session *s = item->target.s; + struct session *s = target->s; + u_int found; - if (self->entry == &cmd_unlink_window_entry) { - if (!args_has(self->args, 'k') && !session_is_linked(s, w)) { + if (cmd_get_entry(self) == &cmd_unlink_window_entry) { + if (!args_has(args, 'k') && !session_is_linked(s, w)) { cmdq_error(item, "window only linked to one session"); return (CMD_RETURN_ERROR); } server_unlink_window(s, wl); - } else { - if (args_has(args, 'a')) { - RB_FOREACH_SAFE(wl2, winlinks, &s->windows, wl3) { - if (wl != wl2) - server_kill_window(wl2->window); - } - } else - server_kill_window(wl->window); + recalculate_sizes(); + return (CMD_RETURN_NORMAL); } - recalculate_sizes(); + if (args_has(args, 'a')) { + if (RB_PREV(winlinks, &s->windows, wl) == NULL && + RB_NEXT(winlinks, &s->windows, wl) == NULL) + return (CMD_RETURN_NORMAL); + + /* Kill all windows except the current one. */ + do { + found = 0; + RB_FOREACH(loop, winlinks, &s->windows) { + if (loop->window != wl->window) { + server_kill_window(loop->window, 0); + found++; + break; + } + } + } while (found != 0); + + /* + * If the current window appears in the session more than once, + * kill it as well. + */ + found = 0; + RB_FOREACH(loop, winlinks, &s->windows) { + if (loop->window == wl->window) + found++; + } + if (found > 1) + server_kill_window(wl->window, 0); + + server_renumber_all(); + return (CMD_RETURN_NORMAL); + } + + server_kill_window(wl->window, 1); return (CMD_RETURN_NORMAL); } diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c index 0457a62d..8b12f0b3 100644 --- a/cmd-list-buffers.c +++ b/cmd-list-buffers.c @@ -36,8 +36,8 @@ const struct cmd_entry cmd_list_buffers_entry = { .name = "list-buffers", .alias = "lsb", - .args = { "F:", 0, 0 }, - .usage = "[-F format]", + .args = { "F:f:", 0, 0, NULL }, + .usage = "[-F format] [-f filter]", .flags = CMD_AFTERHOOK, .exec = cmd_list_buffers_exec @@ -46,23 +46,33 @@ const struct cmd_entry cmd_list_buffers_entry = { static enum cmd_retval cmd_list_buffers_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct paste_buffer *pb; struct format_tree *ft; - char *line; - const char *template; + const char *template, *filter; + char *line, *expanded; + int flag; if ((template = args_get(args, 'F')) == NULL) template = LIST_BUFFERS_TEMPLATE; + filter = args_get(args, 'f'); pb = NULL; while ((pb = paste_walk(pb)) != NULL) { - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_defaults_paste_buffer(ft, pb); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); } diff --git a/cmd-list-clients.c b/cmd-list-clients.c index 9fab8f84..a5b7d147 100644 --- a/cmd-list-clients.c +++ b/cmd-list-clients.c @@ -28,10 +28,10 @@ * List all clients. */ -#define LIST_CLIENTS_TEMPLATE \ - "#{client_name}: #{session_name} " \ - "[#{client_width}x#{client_height} #{client_termname}]" \ - "#{?client_utf8, (utf8),} #{?client_readonly, (ro),}" +#define LIST_CLIENTS_TEMPLATE \ + "#{client_name}: #{session_name} " \ + "[#{client_width}x#{client_height} #{client_termname}] " \ + "#{?client_flags,(,}#{client_flags}#{?client_flags,),}" static enum cmd_retval cmd_list_clients_exec(struct cmd *, struct cmdq_item *); @@ -39,7 +39,7 @@ const struct cmd_entry cmd_list_clients_entry = { .name = "list-clients", .alias = "lsc", - .args = { "F:t:", 0, 0 }, + .args = { "F:t:", 0, 0, NULL }, .usage = "[-F format] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -51,7 +51,8 @@ const struct cmd_entry cmd_list_clients_entry = { static enum cmd_retval cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); struct client *c; struct session *s; struct format_tree *ft; @@ -60,7 +61,7 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) char *line; if (args_has(args, 't')) - s = item->target.s; + s = target->s; else s = NULL; @@ -72,7 +73,7 @@ cmd_list_clients_exec(struct cmd *self, struct cmdq_item *item) if (c->session == NULL || (s != NULL && s != c->session)) continue; - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", idx); format_defaults(ft, c, NULL, NULL, NULL); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 34ed43bf..ae9f995c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -36,7 +36,7 @@ const struct cmd_entry cmd_list_keys_entry = { .name = "list-keys", .alias = "lsk", - .args = { "1aNP:T:", 0, 1 }, + .args = { "1aNP:T:", 0, 1, NULL }, .usage = "[-1aN] [-P prefix-string] [-T key-table] [key]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, @@ -47,8 +47,8 @@ const struct cmd_entry cmd_list_commands_entry = { .name = "list-commands", .alias = "lscm", - .args = { "F:", 0, 0 }, - .usage = "[-F format]", + .args = { "F:", 0, 1, NULL }, + .usage = "[-F format] [command]", .flags = CMD_STARTSERVER|CMD_AFTERHOOK, .exec = cmd_list_keys_exec @@ -68,11 +68,12 @@ cmd_list_keys_get_width(const char *tablename, key_code only) while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - bd->note == NULL) { + bd->note == NULL || + *bd->note == '\0') { bd = key_bindings_next(table, bd); continue; } - width = utf8_cstrwidth(key_string_lookup_key(bd->key)); + width = utf8_cstrwidth(key_string_lookup_key(bd->key, 0)); if (width > keywidth) keywidth = width; @@ -85,7 +86,7 @@ static int cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, const char *tablename, u_int keywidth, key_code only, const char *prefix) { - struct client *c = cmd_find_client(item, NULL, 1); + struct client *tc = cmdq_get_target_client(item); struct key_table *table; struct key_binding *bd; const char *key; @@ -99,21 +100,23 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - (bd->note == NULL && !args_has(args, 'a'))) { + ((bd->note == NULL || *bd->note == '\0') && + !args_has(args, 'a'))) { bd = key_bindings_next(table, bd); continue; } found = 1; - key = key_string_lookup_key(bd->key); + key = key_string_lookup_key(bd->key, 0); - if (bd->note == NULL) + if (bd->note == NULL || *bd->note == '\0') note = cmd_list_print(bd->cmdlist, 1); else note = xstrdup(bd->note); tmp = utf8_padcstr(key, keywidth + 1); - if (args_has(args, '1') && c != NULL) - status_message_set(c, "%s%s%s", prefix, tmp, note); - else + if (args_has(args, '1') && tc != NULL) { + status_message_set(tc, -1, 1, 0, "%s%s%s", prefix, tmp, + note); + } else cmdq_print(item, "%s%s%s", prefix, tmp, note); free(tmp); free(note); @@ -133,7 +136,7 @@ cmd_list_keys_get_prefix(struct args *args, key_code *prefix) *prefix = options_get_number(global_s_options, "prefix"); if (!args_has(args, 'P')) { if (*prefix != KEYC_NONE) - xasprintf(&s, "%s ", key_string_lookup_key(*prefix)); + xasprintf(&s, "%s ", key_string_lookup_key(*prefix, 0)); else s = xstrdup(""); } else @@ -144,24 +147,25 @@ cmd_list_keys_get_prefix(struct args *args, key_code *prefix) static enum cmd_retval cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct key_table *table; struct key_binding *bd; - const char *tablename, *r; + const char *tablename, *r, *keystr; char *key, *cp, *tmp, *start, *empty; key_code prefix, only = KEYC_UNKNOWN; int repeat, width, tablewidth, keywidth, found = 0; size_t tmpsize, tmpused, cplen; - if (self->entry == &cmd_list_commands_entry) + if (cmd_get_entry(self) == &cmd_list_commands_entry) return (cmd_list_keys_commands(self, item)); - if (args->argc != 0) { - only = key_string_lookup_string(args->argv[0]); + if ((keystr = args_string(args, 0)) != NULL) { + only = key_string_lookup_string(keystr); if (only == KEYC_UNKNOWN) { - cmdq_error(item, "invalid key: %s", args->argv[0]); + cmdq_error(item, "invalid key: %s", keystr); return (CMD_RETURN_ERROR); } + only &= (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS); } tablename = args_get(args, 'T'); @@ -207,7 +211,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) repeat = 0; tablewidth = keywidth = 0; - table = key_bindings_first_table (); + table = key_bindings_first_table(); while (table != NULL) { if (tablename != NULL && strcmp(table->name, tablename) != 0) { table = key_bindings_next_table(table); @@ -219,7 +223,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) bd = key_bindings_next(table, bd); continue; } - key = args_escape(key_string_lookup_key(bd->key)); + key = args_escape(key_string_lookup_key(bd->key, 0)); if (bd->flags & KEY_BINDING_REPEAT) repeat = 1; @@ -240,7 +244,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) tmpsize = 256; tmp = xmalloc(tmpsize); - table = key_bindings_first_table (); + table = key_bindings_first_table(); while (table != NULL) { if (tablename != NULL && strcmp(table->name, tablename) != 0) { table = key_bindings_next_table(table); @@ -253,7 +257,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) continue; } found = 1; - key = args_escape(key_string_lookup_key(bd->key)); + key = args_escape(key_string_lookup_key(bd->key, 0)); if (!repeat) r = ""; @@ -269,7 +273,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) tmpsize *= 2; tmp = xrealloc(tmp, tmpsize); } - tmpused = strlcat(tmp, cp, tmpsize); + strlcat(tmp, cp, tmpsize); tmpused = strlcat(tmp, " ", tmpsize); free(cp); @@ -279,7 +283,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) tmpsize *= 2; tmp = xrealloc(tmp, tmpsize); } - tmpused = strlcat(tmp, cp, tmpsize); + strlcat(tmp, cp, tmpsize); tmpused = strlcat(tmp, " ", tmpsize); free(cp); @@ -304,7 +308,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) out: if (only != KEYC_UNKNOWN && !found) { - cmdq_error(item, "unknown key: %s", args->argv[0]); + cmdq_error(item, "unknown key: %s", args_string(args, 0)); return (CMD_RETURN_ERROR); } return (CMD_RETURN_NORMAL); @@ -313,11 +317,11 @@ out: static enum cmd_retval cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); const struct cmd_entry **entryp; const struct cmd_entry *entry; struct format_tree *ft; - const char *template, *s; + const char *template, *s, *command; char *line; if ((template = args_get(args, 'F')) == NULL) { @@ -326,11 +330,17 @@ cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item) "#{command_list_usage}"; } - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_defaults(ft, NULL, NULL, NULL, NULL); + command = args_string(args, 0); for (entryp = cmd_table; *entryp != NULL; entryp++) { entry = *entryp; + if (command != NULL && + (strcmp(entry->name, command) != 0 && + (entry->alias == NULL || + strcmp(entry->alias, command) != 0))) + continue; format_add(ft, "command_list_name", "%s", entry->name); if (entry->alias != NULL) diff --git a/cmd-list-panes.c b/cmd-list-panes.c index 7f6994bd..a29a4032 100644 --- a/cmd-list-panes.c +++ b/cmd-list-panes.c @@ -38,8 +38,8 @@ const struct cmd_entry cmd_list_panes_entry = { .name = "list-panes", .alias = "lsp", - .args = { "asF:t:", 0, 0 }, - .usage = "[-as] [-F format] " CMD_TARGET_WINDOW_USAGE, + .args = { "asF:f:t:", 0, 0, NULL }, + .usage = "[-as] [-F format] [-f filter] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -50,9 +50,10 @@ const struct cmd_entry cmd_list_panes_entry = { static enum cmd_retval cmd_list_panes_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct session *s = target->s; + struct winlink *wl = target->wl; if (args_has(args, 'a')) cmd_list_panes_server(self, item); @@ -87,12 +88,13 @@ static void cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl, struct cmdq_item *item, int type) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct window_pane *wp; u_int n; struct format_tree *ft; - const char *template; - char *line; + const char *template, *filter; + char *line, *expanded; + int flag; template = args_get(args, 'F'); if (template == NULL) { @@ -120,16 +122,25 @@ cmd_list_panes_window(struct cmd *self, struct session *s, struct winlink *wl, break; } } + filter = args_get(args, 'f'); n = 0; TAILQ_FOREACH(wp, &wl->window->panes, entry) { - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", n); format_defaults(ft, NULL, s, wl, wp); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); n++; diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c index 72ff47e8..e448524e 100644 --- a/cmd-list-sessions.c +++ b/cmd-list-sessions.c @@ -42,8 +42,8 @@ const struct cmd_entry cmd_list_sessions_entry = { .name = "list-sessions", .alias = "ls", - .args = { "F:", 0, 0 }, - .usage = "[-F format]", + .args = { "F:f:", 0, 0, NULL }, + .usage = "[-F format] [-f filter]", .flags = CMD_AFTERHOOK, .exec = cmd_list_sessions_exec @@ -52,25 +52,35 @@ const struct cmd_entry cmd_list_sessions_entry = { static enum cmd_retval cmd_list_sessions_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct session *s; u_int n; struct format_tree *ft; - const char *template; - char *line; + const char *template, *filter; + char *line, *expanded; + int flag; if ((template = args_get(args, 'F')) == NULL) template = LIST_SESSIONS_TEMPLATE; + filter = args_get(args, 'f'); n = 0; RB_FOREACH(s, sessions, &sessions) { - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", n); format_defaults(ft, NULL, s, NULL, NULL); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); n++; diff --git a/cmd-list-windows.c b/cmd-list-windows.c index 46ee6f0c..035471d4 100644 --- a/cmd-list-windows.c +++ b/cmd-list-windows.c @@ -28,14 +28,14 @@ */ #define LIST_WINDOWS_TEMPLATE \ - "#{window_index}: #{window_name}#{window_flags} " \ + "#{window_index}: #{window_name}#{window_raw_flags} " \ "(#{window_panes} panes) " \ "[#{window_width}x#{window_height}] " \ "[layout #{window_layout}] #{window_id}" \ "#{?window_active, (active),}"; #define LIST_WINDOWS_WITH_SESSION_TEMPLATE \ "#{session_name}:" \ - "#{window_index}: #{window_name}#{window_flags} " \ + "#{window_index}: #{window_name}#{window_raw_flags} " \ "(#{window_panes} panes) " \ "[#{window_width}x#{window_height}] " @@ -49,8 +49,8 @@ const struct cmd_entry cmd_list_windows_entry = { .name = "list-windows", .alias = "lsw", - .args = { "F:at:", 0, 0 }, - .usage = "[-a] [-F format] " CMD_TARGET_SESSION_USAGE, + .args = { "F:f:at:", 0, 0, NULL }, + .usage = "[-a] [-F format] [-f filter] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -61,12 +61,13 @@ const struct cmd_entry cmd_list_windows_entry = { static enum cmd_retval cmd_list_windows_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); if (args_has(args, 'a')) cmd_list_windows_server(self, item); else - cmd_list_windows_session(self, item->target.s, item, 0); + cmd_list_windows_session(self, target->s, item, 0); return (CMD_RETURN_NORMAL); } @@ -84,12 +85,13 @@ static void cmd_list_windows_session(struct cmd *self, struct session *s, struct cmdq_item *item, int type) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct winlink *wl; u_int n; struct format_tree *ft; - const char *template; - char *line; + const char *template, *filter; + char *line, *expanded; + int flag; template = args_get(args, 'F'); if (template == NULL) { @@ -102,16 +104,25 @@ cmd_list_windows_session(struct cmd *self, struct session *s, break; } } + filter = args_get(args, 'f'); n = 0; RB_FOREACH(wl, winlinks, &s->windows) { - ft = format_create(item->client, item, FORMAT_NONE, 0); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); format_add(ft, "line", "%u", n); format_defaults(ft, NULL, s, wl, NULL); - line = format_expand(ft, template); - cmdq_print(item, "%s", line); - free(line); + if (filter != NULL) { + expanded = format_expand(ft, filter); + flag = format_true(expanded); + free(expanded); + } else + flag = 1; + if (flag) { + line = format_expand(ft, template); + cmdq_print(item, "%s", line); + free(line); + } format_free(ft); n++; diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index 5e930126..59810dea 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -37,14 +37,15 @@ const struct cmd_entry cmd_load_buffer_entry = { .name = "load-buffer", .alias = "loadb", - .args = { "b:", 1, 1 }, - .usage = CMD_BUFFER_USAGE " path", + .args = { "b:t:w", 1, 1, NULL }, + .usage = CMD_BUFFER_USAGE " " CMD_TARGET_CLIENT_USAGE " path", - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG|CMD_CLIENT_CANFAIL, .exec = cmd_load_buffer_exec }; struct cmd_load_buffer_data { + struct client *client; struct cmdq_item *item; char *name; }; @@ -54,6 +55,7 @@ cmd_load_buffer_done(__unused struct client *c, const char *path, int error, int closed, struct evbuffer *buffer, void *data) { struct cmd_load_buffer_data *cdata = data; + struct client *tc = cdata->client; struct cmdq_item *item = cdata->item; void *bdata = EVBUFFER_DATA(buffer); size_t bsize = EVBUFFER_LENGTH(buffer); @@ -72,7 +74,12 @@ cmd_load_buffer_done(__unused struct client *c, const char *path, int error, cmdq_error(item, "%s", cause); free(cause); free(copy); - } + } else if (tc != NULL && + tc->session != NULL && + (~tc->flags & CLIENT_DEAD)) + tty_set_selection(&tc->tty, copy, bsize); + if (tc != NULL) + server_client_unref(tc); } cmdq_continue(item); @@ -83,24 +90,23 @@ cmd_load_buffer_done(__unused struct client *c, const char *path, int error, static enum cmd_retval cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); struct cmd_load_buffer_data *cdata; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; const char *bufname = args_get(args, 'b'); char *path; - cdata = xmalloc(sizeof *cdata); + cdata = xcalloc(1, sizeof *cdata); cdata->item = item; if (bufname != NULL) cdata->name = xstrdup(bufname); - else - cdata->name = NULL; + if (args_has(args, 'w') && tc != NULL) { + cdata->client = tc; + cdata->client->references++; + } - path = format_single(item, args->argv[0], c, s, wl, wp); - file_read(item->client, path, cmd_load_buffer_done, cdata); + path = format_single_from_target(item, args_string(args, 0)); + file_read(cmdq_get_client(item), path, cmd_load_buffer_done, cdata); free(path); return (CMD_RETURN_WAIT); diff --git a/cmd-lock-server.c b/cmd-lock-server.c index 524fa451..bd61dcff 100644 --- a/cmd-lock-server.c +++ b/cmd-lock-server.c @@ -30,7 +30,7 @@ const struct cmd_entry cmd_lock_server_entry = { .name = "lock-server", .alias = "lock", - .args = { "", 0, 0 }, + .args = { "", 0, 0, NULL }, .usage = "", .flags = CMD_AFTERHOOK, @@ -41,7 +41,7 @@ const struct cmd_entry cmd_lock_session_entry = { .name = "lock-session", .alias = "locks", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -54,28 +54,25 @@ const struct cmd_entry cmd_lock_client_entry = { .name = "lock-client", .alias = "lockc", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_CLIENT_USAGE, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, .exec = cmd_lock_server_exec }; static enum cmd_retval cmd_lock_server_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c; + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item); - if (self->entry == &cmd_lock_server_entry) + if (cmd_get_entry(self) == &cmd_lock_server_entry) server_lock(); - else if (self->entry == &cmd_lock_session_entry) - server_lock_session(item->target.s); - else { - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); - server_lock_client(c); - } + else if (cmd_get_entry(self) == &cmd_lock_session_entry) + server_lock_session(target->s); + else + server_lock_client(tc); recalculate_sizes(); return (CMD_RETURN_NORMAL); diff --git a/cmd-move-window.c b/cmd-move-window.c index cb64d1e0..4b90e70f 100644 --- a/cmd-move-window.c +++ b/cmd-move-window.c @@ -32,8 +32,8 @@ const struct cmd_entry cmd_move_window_entry = { .name = "move-window", .alias = "movew", - .args = { "adkrs:t:", 0, 0 }, - .usage = "[-dkr] " CMD_SRCDST_WINDOW_USAGE, + .args = { "abdkrs:t:", 0, 0, NULL }, + .usage = "[-abdkr] " CMD_SRCDST_WINDOW_USAGE, .source = { 's', CMD_FIND_WINDOW, 0 }, /* -t is special */ @@ -46,8 +46,8 @@ const struct cmd_entry cmd_link_window_entry = { .name = "link-window", .alias = "linkw", - .args = { "adks:t:", 0, 0 }, - .usage = "[-dk] " CMD_SRCDST_WINDOW_USAGE, + .args = { "abdks:t:", 0, 0, NULL }, + .usage = "[-abdk] " CMD_SRCDST_WINDOW_USAGE, .source = { 's', CMD_FIND_WINDOW, 0 }, /* -t is special */ @@ -59,49 +59,53 @@ const struct cmd_entry cmd_link_window_entry = { static enum cmd_retval cmd_move_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - const char *tflag = args_get(args, 't'); - struct session *src; - struct session *dst; - struct winlink *wl; - char *cause; - int idx, kflag, dflag, sflag; + struct args *args = cmd_get_args(self); + struct cmd_find_state *source = cmdq_get_source(item); + struct cmd_find_state target; + const char *tflag = args_get(args, 't'); + struct session *src = source->s; + struct session *dst; + struct winlink *wl = source->wl; + char *cause; + int idx, kflag, dflag, sflag, before; if (args_has(args, 'r')) { - if (cmd_find_target(&item->target, item, tflag, - CMD_FIND_SESSION, CMD_FIND_QUIET) != 0) + if (cmd_find_target(&target, item, tflag, CMD_FIND_SESSION, + CMD_FIND_QUIET) != 0) return (CMD_RETURN_ERROR); - session_renumber_windows(item->target.s); + session_renumber_windows(target.s); recalculate_sizes(); - server_status_session(item->target.s); + server_status_session(target.s); return (CMD_RETURN_NORMAL); } - if (cmd_find_target(&item->target, item, tflag, CMD_FIND_WINDOW, + if (cmd_find_target(&target, item, tflag, CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX) != 0) return (CMD_RETURN_ERROR); - src = item->source.s; - dst = item->target.s; - wl = item->source.wl; - idx = item->target.idx; + dst = target.s; + idx = target.idx; - kflag = args_has(self->args, 'k'); - dflag = args_has(self->args, 'd'); - sflag = args_has(self->args, 's'); + kflag = args_has(args, 'k'); + dflag = args_has(args, 'd'); + sflag = args_has(args, 's'); - if (args_has(self->args, 'a')) { - if ((idx = winlink_shuffle_up(dst, dst->curw)) == -1) + before = args_has(args, 'b'); + if (args_has(args, 'a') || before) { + if (target.wl != NULL) + idx = winlink_shuffle_up(dst, target.wl, before); + else + idx = winlink_shuffle_up(dst, dst->curw, before); + if (idx == -1) return (CMD_RETURN_ERROR); } - if (server_link_window(src, wl, dst, idx, kflag, !dflag, - &cause) != 0) { - cmdq_error(item, "can't link window: %s", cause); + if (server_link_window(src, wl, dst, idx, kflag, !dflag, &cause) != 0) { + cmdq_error(item, "%s", cause); free(cause); return (CMD_RETURN_ERROR); } - if (self->entry == &cmd_move_window_entry) + if (cmd_get_entry(self) == &cmd_move_window_entry) server_unlink_window(src, wl); /* diff --git a/cmd-new-session.c b/cmd-new-session.c index c76b564e..cb9abfb3 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -39,10 +39,11 @@ const struct cmd_entry cmd_new_session_entry = { .name = "new-session", .alias = "new", - .args = { "Ac:dDEF:n:Ps:t:x:Xy:", 0, -1 }, - .usage = "[-AdDEPX] [-c start-directory] [-F format] [-n window-name] " - "[-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] " - "[-y height] [command]", + .args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1, NULL }, + .usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] " + "[-f flags] [-n window-name] [-s session-name] " + CMD_TARGET_SESSION_USAGE " [-x width] [-y height] " + "[shell-command]", .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, @@ -54,7 +55,7 @@ const struct cmd_entry cmd_has_session_entry = { .name = "has-session", .alias = "has", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -66,22 +67,26 @@ const struct cmd_entry cmd_has_session_entry = { static enum cmd_retval cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = item->client; - struct session *s, *as, *groupwith; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); + struct session *s, *as, *groupwith = NULL; struct environ *env; struct options *oo; struct termios tio, *tiop; - struct session_group *sg; - const char *errstr, *template, *group, *prefix, *tmp; + struct session_group *sg = NULL; + const char *errstr, *template, *group, *tmp; char *cause, *cwd = NULL, *cp, *newname = NULL; + char *name, *prefix = NULL; int detached, already_attached, is_control = 0; - u_int sx, sy, dsx, dsy; - struct spawn_context sc; + u_int sx, sy, dsx, dsy, count = args_count(args); + struct spawn_context sc = { 0 }; enum cmd_retval retval; struct cmd_find_state fs; + struct args_value *av; - if (self->entry == &cmd_has_session_entry) { + if (cmd_get_entry(self) == &cmd_has_session_entry) { /* * cmd_find_target() will fail if the session cannot be found, * so always return success here. @@ -89,28 +94,31 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args_has(args, 't') && (args->argc != 0 || args_has(args, 'n'))) { + if (args_has(args, 't') && (count != 0 || args_has(args, 'n'))) { cmdq_error(item, "command or window name given with target"); return (CMD_RETURN_ERROR); } tmp = args_get(args, 's'); if (tmp != NULL) { - newname = format_single(item, tmp, c, NULL, NULL, NULL); - if (!session_check_name(newname)) { - cmdq_error(item, "bad session name: %s", newname); - goto fail; + name = format_single(item, tmp, c, NULL, NULL, NULL); + newname = session_check_name(name); + if (newname == NULL) { + cmdq_error(item, "invalid session: %s", name); + free(name); + return (CMD_RETURN_ERROR); } + free(name); } if (args_has(args, 'A')) { if (newname != NULL) as = session_find(newname); else - as = item->target.s; + as = target->s; if (as != NULL) { retval = cmd_attach_session(item, as->name, args_has(args, 'D'), args_has(args, 'X'), 0, NULL, - args_has(args, 'E')); + args_has(args, 'E'), args_get(args, 'f')); free(newname); return (retval); } @@ -123,25 +131,23 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) /* Is this going to be part of a session group? */ group = args_get(args, 't'); if (group != NULL) { - groupwith = item->target.s; - if (groupwith == NULL) { - if (!session_check_name(group)) { - cmdq_error(item, "bad group name: %s", group); - goto fail; - } + groupwith = target->s; + if (groupwith == NULL) sg = session_group_find(group); - } else + else sg = session_group_contains(groupwith); if (sg != NULL) - prefix = sg->name; + prefix = xstrdup(sg->name); else if (groupwith != NULL) - prefix = groupwith->name; - else - prefix = group; - } else { - groupwith = NULL; - sg = NULL; - prefix = NULL; + prefix = xstrdup(groupwith->name); + else { + prefix = session_check_name(group); + if (prefix == NULL) { + cmdq_error(item, "invalid session group: %s", + group); + goto fail; + } + } } /* Set -d if no client. */ @@ -171,13 +177,16 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) * the terminal as that calls tcsetattr() to prepare for tmux taking * over. */ - if (!detached && !already_attached && c->tty.fd != -1) { - if (server_client_check_nested(item->client)) { + if (!detached && + !already_attached && + c->fd != -1 && + (~c->flags & CLIENT_CONTROL)) { + if (server_client_check_nested(cmdq_get_client(item))) { cmdq_error(item, "sessions should be nested with care, " "unset $TMUX to force"); goto fail; } - if (tcgetattr(c->tty.fd, &tio) != 0) + if (tcgetattr(c->fd, &tio) != 0) fatal("tcgetattr failed"); tiop = &tio; } else @@ -207,7 +216,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) goto fail; } } - } + } else + dsx = 80; if (args_has(args, 'y')) { tmp = args_get(args, 'y'); if (strcmp(tmp, "-") == 0) { @@ -222,7 +232,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) goto fail; } } - } + } else + dsy = 24; /* Find new session size. */ if (!detached && !is_control) { @@ -233,13 +244,14 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) } else { tmp = options_get_string(global_s_options, "default-size"); if (sscanf(tmp, "%ux%u", &sx, &sy) != 2) { - sx = 80; - sy = 24; - } - if (args_has(args, 'x')) sx = dsx; - if (args_has(args, 'y')) sy = dsy; + } else { + if (args_has(args, 'x')) + sx = dsx; + if (args_has(args, 'y')) + sy = dsy; + } } if (sx == 0) sx = 1; @@ -258,17 +270,21 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) env = environ_create(); if (c != NULL && !args_has(args, 'E')) environ_update(global_s_options, c->environ, env); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(env, av->string, 0); + av = args_next_value(av); + } s = session_create(prefix, newname, cwd, env, oo, tiop); /* Spawn the initial window. */ - memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; - sc.c = c; + if (!detached) + sc.tc = c; sc.name = args_get(args, 'n'); - sc.argc = args->argc; - sc.argv = args->argv; + args_to_vector(args, &sc.argc, &sc.argv); sc.idx = -1; sc.cwd = args_get(args, 'c'); @@ -305,23 +321,17 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) * taking this session and needs to get MSG_READY and stay around. */ if (!detached) { + if (args_has(args, 'f')) + server_client_set_flags(c, args_get(args, 'f')); if (!already_attached) { if (~c->flags & CLIENT_CONTROL) proc_send(c->peer, MSG_READY, -1, NULL, 0); } else if (c->session != NULL) c->last_session = c->session; - c->session = s; - if (~item->shared->flags & CMDQ_SHARED_REPEAT) + server_client_set_session(c, s); + if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT) server_client_set_key_table(c, NULL); - tty_update_client_offset(c); - status_timer_start(c); - notify_client("client-session-changed", c); - session_update_activity(s, NULL); - gettimeofday(&s->last_attached_time, NULL); - server_redraw_client(c); } - recalculate_sizes(); - server_update_socket(); /* * If there are still configuration file errors to display, put the new @@ -334,25 +344,31 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = NEW_SESSION_TEMPLATE; - cp = format_single(item, template, c, s, NULL, NULL); + cp = format_single(item, template, c, s, s->curw, NULL); cmdq_print(item, "%s", cp); free(cp); } - if (!detached) { + if (!detached) c->flags |= CLIENT_ATTACHED; - cmd_find_from_session(&item->shared->current, s, 0); - } + if (!args_has(args, 'd')) + cmd_find_from_session(current, s, 0); cmd_find_from_session(&fs, s, 0); cmdq_insert_hook(s, item, &fs, "after-new-session"); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); free(cwd); free(newname); + free(prefix); return (CMD_RETURN_NORMAL); fail: + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); free(cwd); free(newname); + free(prefix); return (CMD_RETURN_ERROR); } diff --git a/cmd-new-window.c b/cmd-new-window.c index 9a1025e9..e7f0868f 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -38,9 +38,9 @@ const struct cmd_entry cmd_new_window_entry = { .name = "new-window", .alias = "neww", - .args = { "ac:de:F:kn:Pt:", 0, -1 }, - .usage = "[-adkP] [-c start-directory] [-e environment] [-F format] " - "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [command]", + .args = { "abc:de:F:kn:PSt:", 0, -1, NULL }, + .usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] " + "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [shell-command]", .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, @@ -51,38 +51,67 @@ const struct cmd_entry cmd_new_window_entry = { static enum cmd_retval cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; - struct spawn_context sc; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - int idx = item->target.idx; - struct winlink *new_wl; + struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl, *new_wl = NULL; + int idx = target->idx, before; char *cause = NULL, *cp; - const char *template, *add; + const char *template, *name; struct cmd_find_state fs; - struct args_value *value; + struct args_value *av; - if (args_has(args, 'a') && (idx = winlink_shuffle_up(s, wl)) == -1) { - cmdq_error(item, "couldn't get a window index"); - return (CMD_RETURN_ERROR); + /* + * If -S and -n are given and -t is not and a single window with this + * name already exists, select it. + */ + name = args_get(args, 'n'); + if (args_has(args, 'S') && name != NULL && target->idx == -1) { + RB_FOREACH(wl, winlinks, &s->windows) { + if (strcmp(wl->window->name, name) != 0) + continue; + if (new_wl == NULL) { + new_wl = wl; + continue; + } + cmdq_error(item, "multiple windows named %s", name); + return (CMD_RETURN_ERROR); + } + if (new_wl != NULL) { + if (args_has(args, 'd')) + return (CMD_RETURN_NORMAL); + if (session_set_current(s, new_wl) == 0) + server_redraw_session(s); + if (c != NULL && c->session != NULL) + s->curw->window->latest = c; + recalculate_sizes(); + return (CMD_RETURN_NORMAL); + } + } + + before = args_has(args, 'b'); + if (args_has(args, 'a') || before) { + idx = winlink_shuffle_up(s, wl, before); + if (idx == -1) + idx = target->idx; } - memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; - sc.c = c; + sc.tc = tc; sc.name = args_get(args, 'n'); - sc.argc = args->argc; - sc.argv = args->argv; + args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); - add = args_first_value(args, 'e', &value); - while (add != NULL) { - environ_put(sc.environ, add); - add = args_next_value(&value); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); } sc.idx = idx; @@ -97,6 +126,9 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) if ((new_wl = spawn_window(&sc, &cause)) == NULL) { cmdq_error(item, "create window failed: %s", cause); free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); return (CMD_RETURN_ERROR); } if (!args_has(args, 'd') || new_wl == s->curw) { @@ -108,7 +140,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = NEW_WINDOW_TEMPLATE; - cp = format_single(item, template, c, s, new_wl, NULL); + cp = format_single(item, template, tc, s, new_wl, + new_wl->window->active); cmdq_print(item, "%s", cp); free(cp); } @@ -116,6 +149,8 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) cmd_find_from_winlink(&fs, new_wl, 0); cmdq_insert_hook(s, item, &fs, "after-new-window"); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); return (CMD_RETURN_NORMAL); } diff --git a/cmd-parse.y b/cmd-parse.y index 2375370b..1d692770 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -26,6 +26,7 @@ #include #include #include +#include #include "tmux.h" @@ -41,14 +42,27 @@ struct cmd_parse_scope { TAILQ_ENTRY (cmd_parse_scope) entry; }; +enum cmd_parse_argument_type { + CMD_PARSE_STRING, + CMD_PARSE_COMMANDS, + CMD_PARSE_PARSED_COMMANDS +}; + +struct cmd_parse_argument { + enum cmd_parse_argument_type type; + char *string; + struct cmd_parse_commands *commands; + struct cmd_list *cmdlist; + + TAILQ_ENTRY(cmd_parse_argument) entry; +}; +TAILQ_HEAD(cmd_parse_arguments, cmd_parse_argument); + struct cmd_parse_command { - char *name; - u_int line; + u_int line; + struct cmd_parse_arguments arguments; - int argc; - char **argv; - - TAILQ_ENTRY(cmd_parse_command) entry; + TAILQ_ENTRY(cmd_parse_command) entry; }; TAILQ_HEAD(cmd_parse_commands, cmd_parse_command); @@ -77,7 +91,9 @@ static char *cmd_parse_get_error(const char *, u_int, const char *); static void cmd_parse_free_command(struct cmd_parse_command *); static struct cmd_parse_commands *cmd_parse_new_commands(void); static void cmd_parse_free_commands(struct cmd_parse_commands *); -static void cmd_parse_print_commands(struct cmd_parse_input *, u_int, +static void cmd_parse_build_commands(struct cmd_parse_commands *, + struct cmd_parse_input *, struct cmd_parse_result *); +static void cmd_parse_print_commands(struct cmd_parse_input *, struct cmd_list *); %} @@ -85,10 +101,8 @@ static void cmd_parse_print_commands(struct cmd_parse_input *, u_int, %union { char *token; - struct { - int argc; - char **argv; - } arguments; + struct cmd_parse_arguments *arguments; + struct cmd_parse_argument *argument; int flag; struct { int flag; @@ -99,17 +113,20 @@ static void cmd_parse_print_commands(struct cmd_parse_input *, u_int, } %token ERROR +%token HIDDEN %token IF %token ELSE %token ELIF %token ENDIF %token FORMAT TOKEN EQUALS -%type argument expanded format +%type expanded format %type arguments +%type argument %type if_open if_elif %type elif elif1 -%type statements statement commands condition condition1 +%type argument_statements statements statement +%type commands condition condition1 %type command %% @@ -134,6 +151,11 @@ statements : statement '\n' } statement : /* empty */ + { + $$ = xmalloc (sizeof *$$); + TAILQ_INIT($$); + } + | hidden_assignment { $$ = xmalloc (sizeof *$$); TAILQ_INIT($$); @@ -204,10 +226,21 @@ assignment : EQUALS if ((~flags & CMD_PARSE_PARSEONLY) && (ps->scope == NULL || ps->scope->flag)) - environ_put(global_environ, $1); + environ_put(global_environ, $1, 0); free($1); } +hidden_assignment : HIDDEN EQUALS + { + struct cmd_parse_state *ps = &parse_state; + int flags = ps->input->flags; + + if ((~flags & CMD_PARSE_PARSEONLY) && + (ps->scope == NULL || ps->scope->flag)) + environ_put(global_environ, $2, ENVIRON_HIDDEN); + free($2); + } + if_open : IF expanded { struct cmd_parse_state *ps = &parse_state; @@ -341,7 +374,7 @@ commands : command struct cmd_parse_state *ps = &parse_state; $$ = cmd_parse_new_commands(); - if ($1->name != NULL && + if (!TAILQ_EMPTY(&$1->arguments) && (ps->scope == NULL || ps->scope->flag)) TAILQ_INSERT_TAIL($$, $1, entry); else @@ -361,7 +394,7 @@ commands : command { struct cmd_parse_state *ps = &parse_state; - if ($3->name != NULL && + if (!TAILQ_EMPTY(&$3->arguments) && (ps->scope == NULL || ps->scope->flag)) { $$ = $1; TAILQ_INSERT_TAIL($$, $3, entry); @@ -381,28 +414,39 @@ command : assignment struct cmd_parse_state *ps = &parse_state; $$ = xcalloc(1, sizeof *$$); - $$->name = NULL; $$->line = ps->input->line; + TAILQ_INIT(&$$->arguments); } | optional_assignment TOKEN { - struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_argument *arg; $$ = xcalloc(1, sizeof *$$); - $$->name = $2; $$->line = ps->input->line; + TAILQ_INIT(&$$->arguments); + arg = xcalloc(1, sizeof *arg); + arg->type = CMD_PARSE_STRING; + arg->string = $2; + TAILQ_INSERT_HEAD(&$$->arguments, arg, entry); } | optional_assignment TOKEN arguments { - struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_argument *arg; $$ = xcalloc(1, sizeof *$$); - $$->name = $2; $$->line = ps->input->line; + TAILQ_INIT(&$$->arguments); - $$->argc = $3.argc; - $$->argv = $3.argv; + TAILQ_CONCAT(&$$->arguments, $3, entry); + free($3); + + arg = xcalloc(1, sizeof *arg); + arg->type = CMD_PARSE_STRING; + arg->string = $2; + TAILQ_INSERT_HEAD(&$$->arguments, arg, entry); } condition1 : if_open commands if_close @@ -486,26 +530,46 @@ elif1 : if_elif commands arguments : argument { - $$.argc = 1; - $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv); + $$ = xcalloc(1, sizeof *$$); + TAILQ_INIT($$); - $$.argv[0] = $1; + TAILQ_INSERT_HEAD($$, $1, entry); } | argument arguments { - cmd_prepend_argv(&$2.argc, &$2.argv, $1); - free($1); + TAILQ_INSERT_HEAD($2, $1, entry); $$ = $2; } argument : TOKEN { - $$ = $1; + $$ = xcalloc(1, sizeof *$$); + $$->type = CMD_PARSE_STRING; + $$->string = $1; } | EQUALS { - $$ = $1; + $$ = xcalloc(1, sizeof *$$); + $$->type = CMD_PARSE_STRING; + $$->string = $1; } + | '{' argument_statements + { + $$ = xcalloc(1, sizeof *$$); + $$->type = CMD_PARSE_COMMANDS; + $$->commands = $2; + } + +argument_statements : statement '}' + { + $$ = $1; + } + | statements statement '}' + { + $$ = $1; + TAILQ_CONCAT($$, $2, entry); + free($2); + } %% @@ -517,31 +581,57 @@ cmd_parse_get_error(const char *file, u_int line, const char *error) if (file == NULL) s = xstrdup(error); else - xasprintf (&s, "%s:%u: %s", file, line, error); + xasprintf(&s, "%s:%u: %s", file, line, error); return (s); } static void -cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line, - struct cmd_list *cmdlist) +cmd_parse_print_commands(struct cmd_parse_input *pi, struct cmd_list *cmdlist) { char *s; - if (pi->item != NULL && (pi->flags & CMD_PARSE_VERBOSE)) { - s = cmd_list_print(cmdlist, 0); - if (pi->file != NULL) - cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s); - else - cmdq_print(pi->item, "%u: %s", line, s); - free(s); + if (pi->item == NULL || (~pi->flags & CMD_PARSE_VERBOSE)) + return; + s = cmd_list_print(cmdlist, 0); + if (pi->file != NULL) + cmdq_print(pi->item, "%s:%u: %s", pi->file, pi->line, s); + else + cmdq_print(pi->item, "%u: %s", pi->line, s); + free(s); +} + +static void +cmd_parse_free_argument(struct cmd_parse_argument *arg) +{ + switch (arg->type) { + case CMD_PARSE_STRING: + free(arg->string); + break; + case CMD_PARSE_COMMANDS: + cmd_parse_free_commands(arg->commands); + break; + case CMD_PARSE_PARSED_COMMANDS: + cmd_list_free(arg->cmdlist); + break; + } + free(arg); +} + +static void +cmd_parse_free_arguments(struct cmd_parse_arguments *args) +{ + struct cmd_parse_argument *arg, *arg1; + + TAILQ_FOREACH_SAFE(arg, args, entry, arg1) { + TAILQ_REMOVE(args, arg, entry); + cmd_parse_free_argument(arg); } } static void cmd_parse_free_command(struct cmd_parse_command *cmd) { - free(cmd->name); - cmd_free_argv(cmd->argc, cmd->argv); + cmd_parse_free_arguments(&cmd->arguments); free(cmd); } @@ -551,7 +641,7 @@ cmd_parse_new_commands(void) struct cmd_parse_commands *cmds; cmds = xmalloc(sizeof *cmds); - TAILQ_INIT (cmds); + TAILQ_INIT(cmds); return (cmds); } @@ -616,120 +706,212 @@ cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi, return (cmd_parse_run_parser(cause)); } -static struct cmd_parse_result * -cmd_parse_build_commands(struct cmd_parse_commands *cmds, - struct cmd_parse_input *pi) +static void +cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix) { - static struct cmd_parse_result pr; - struct cmd_parse_commands *cmds2; - struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after; - u_int line = UINT_MAX; - int i; - struct cmd_list *cmdlist = NULL, *result; + struct cmd_parse_command *cmd; + struct cmd_parse_argument *arg; + u_int i, j; + char *s; + + i = 0; + TAILQ_FOREACH(cmd, cmds, entry) { + j = 0; + TAILQ_FOREACH(arg, &cmd->arguments, entry) { + switch (arg->type) { + case CMD_PARSE_STRING: + log_debug("%s %u:%u: %s", prefix, i, j, + arg->string); + break; + case CMD_PARSE_COMMANDS: + xasprintf(&s, "%s %u:%u", prefix, i, j); + cmd_parse_log_commands(arg->commands, s); + free(s); + break; + case CMD_PARSE_PARSED_COMMANDS: + s = cmd_list_print(arg->cmdlist, 0); + log_debug("%s %u:%u: %s", prefix, i, j, s); + free(s); + break; + } + j++; + } + i++; + } +} + +static int +cmd_parse_expand_alias(struct cmd_parse_command *cmd, + struct cmd_parse_input *pi, struct cmd_parse_result *pr) +{ + struct cmd_parse_argument *arg, *arg1, *first; + struct cmd_parse_commands *cmds; + struct cmd_parse_command *last; + char *alias, *name, *cause; + + if (pi->flags & CMD_PARSE_NOALIAS) + return (0); + memset(pr, 0, sizeof *pr); + + first = TAILQ_FIRST(&cmd->arguments); + if (first == NULL || first->type != CMD_PARSE_STRING) { + pr->status = CMD_PARSE_SUCCESS; + pr->cmdlist = cmd_list_new(); + return (1); + } + name = first->string; + + alias = cmd_get_alias(name); + if (alias == NULL) + return (0); + log_debug("%s: %u alias %s = %s", __func__, pi->line, name, alias); + + cmds = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause); + free(alias); + if (cmds == NULL) { + pr->status = CMD_PARSE_ERROR; + pr->error = cause; + return (1); + } + + last = TAILQ_LAST(cmds, cmd_parse_commands); + if (last == NULL) { + pr->status = CMD_PARSE_SUCCESS; + pr->cmdlist = cmd_list_new(); + return (1); + } + + TAILQ_REMOVE(&cmd->arguments, first, entry); + cmd_parse_free_argument(first); + + TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) { + TAILQ_REMOVE(&cmd->arguments, arg, entry); + TAILQ_INSERT_TAIL(&last->arguments, arg, entry); + } + cmd_parse_log_commands(cmds, __func__); + + pi->flags |= CMD_PARSE_NOALIAS; + cmd_parse_build_commands(cmds, pi, pr); + pi->flags &= ~CMD_PARSE_NOALIAS; + return (1); +} + +static void +cmd_parse_build_command(struct cmd_parse_command *cmd, + struct cmd_parse_input *pi, struct cmd_parse_result *pr) +{ + struct cmd_parse_argument *arg; struct cmd *add; - char *alias, *cause, *s; + char *cause; + struct args_value *values = NULL; + u_int count = 0, idx; + + memset(pr, 0, sizeof *pr); + + if (cmd_parse_expand_alias(cmd, pi, pr)) + return; + + TAILQ_FOREACH(arg, &cmd->arguments, entry) { + values = xrecallocarray(values, count, count + 1, + sizeof *values); + switch (arg->type) { + case CMD_PARSE_STRING: + values[count].type = ARGS_STRING; + values[count].string = xstrdup(arg->string); + break; + case CMD_PARSE_COMMANDS: + cmd_parse_build_commands(arg->commands, pi, pr); + if (pr->status != CMD_PARSE_SUCCESS) + goto out; + values[count].type = ARGS_COMMANDS; + values[count].cmdlist = pr->cmdlist; + break; + case CMD_PARSE_PARSED_COMMANDS: + values[count].type = ARGS_COMMANDS; + values[count].cmdlist = arg->cmdlist; + values[count].cmdlist->references++; + break; + } + count++; + } + + add = cmd_parse(values, count, pi->file, pi->line, &cause); + if (add == NULL) { + pr->status = CMD_PARSE_ERROR; + pr->error = cmd_parse_get_error(pi->file, pi->line, cause); + free(cause); + goto out; + } + pr->status = CMD_PARSE_SUCCESS; + pr->cmdlist = cmd_list_new(); + cmd_list_append(pr->cmdlist, add); + +out: + for (idx = 0; idx < count; idx++) + args_free_value(&values[idx]); + free(values); +} + +static void +cmd_parse_build_commands(struct cmd_parse_commands *cmds, + struct cmd_parse_input *pi, struct cmd_parse_result *pr) +{ + struct cmd_parse_command *cmd; + u_int line = UINT_MAX; + struct cmd_list *current = NULL, *result; + char *s; + + memset(pr, 0, sizeof *pr); /* Check for an empty list. */ if (TAILQ_EMPTY(cmds)) { - cmd_parse_free_commands(cmds); - pr.status = CMD_PARSE_EMPTY; - return (&pr); - } - - /* - * Walk the commands and expand any aliases. Each alias is parsed - * individually to a new command list, any trailing arguments appended - * to the last command, and all commands inserted into the original - * command list. - */ - TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) { - alias = cmd_get_alias(cmd->name); - if (alias == NULL) - continue; - - line = cmd->line; - log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias); - - pi->line = line; - cmds2 = cmd_parse_do_buffer(alias, strlen(alias), pi, &cause); - free(alias); - if (cmds2 == NULL) { - pr.status = CMD_PARSE_ERROR; - pr.error = cause; - goto out; - } - - cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands); - if (cmd2 == NULL) { - TAILQ_REMOVE(cmds, cmd, entry); - cmd_parse_free_command(cmd); - continue; - } - for (i = 0; i < cmd->argc; i++) - cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]); - - after = cmd; - TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) { - cmd2->line = line; - TAILQ_REMOVE(cmds2, cmd2, entry); - TAILQ_INSERT_AFTER(cmds, after, cmd2, entry); - after = cmd2; - } - cmd_parse_free_commands(cmds2); - - TAILQ_REMOVE(cmds, cmd, entry); - cmd_parse_free_command(cmd); + pr->status = CMD_PARSE_SUCCESS; + pr->cmdlist = cmd_list_new(); + return; } + cmd_parse_log_commands(cmds, __func__); /* * Parse each command into a command list. Create a new command list - * for each line so they get a new group (so the queue knows which ones - * to remove if a command fails when executed). + * for each line (unless the flag is set) so they get a new group (so + * the queue knows which ones to remove if a command fails when + * executed). */ result = cmd_list_new(); TAILQ_FOREACH(cmd, cmds, entry) { - log_debug("%s: %u %s", __func__, cmd->line, cmd->name); - cmd_log_argv(cmd->argc, cmd->argv, __func__); - - if (cmdlist == NULL || cmd->line != line) { - if (cmdlist != NULL) { - cmd_parse_print_commands(pi, line, cmdlist); - cmd_list_move(result, cmdlist); - cmd_list_free(cmdlist); + if (((~pi->flags & CMD_PARSE_ONEGROUP) && cmd->line != line)) { + if (current != NULL) { + cmd_parse_print_commands(pi, current); + cmd_list_move(result, current); + cmd_list_free(current); } - cmdlist = cmd_list_new(); + current = cmd_list_new(); } - line = cmd->line; + if (current == NULL) + current = cmd_list_new(); + line = pi->line = cmd->line; - cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name); - add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause); - if (add == NULL) { + cmd_parse_build_command(cmd, pi, pr); + if (pr->status != CMD_PARSE_SUCCESS) { cmd_list_free(result); - pr.status = CMD_PARSE_ERROR; - pr.error = cmd_parse_get_error(pi->file, line, cause); - free(cause); - cmd_list_free(cmdlist); - goto out; + cmd_list_free(current); + return; } - cmd_list_append(cmdlist, add); + cmd_list_append_all(current, pr->cmdlist); + cmd_list_free(pr->cmdlist); } - if (cmdlist != NULL) { - cmd_parse_print_commands(pi, line, cmdlist); - cmd_list_move(result, cmdlist); - cmd_list_free(cmdlist); + if (current != NULL) { + cmd_parse_print_commands(pi, current); + cmd_list_move(result, current); + cmd_list_free(current); } s = cmd_list_print(result, 0); log_debug("%s: %s", __func__, s); free(s); - pr.status = CMD_PARSE_SUCCESS; - pr.cmdlist = result; - -out: - cmd_parse_free_commands(cmds); - - return (&pr); + pr->status = CMD_PARSE_SUCCESS; + pr->cmdlist = result; } struct cmd_parse_result * @@ -752,15 +934,79 @@ cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi) pr.error = cause; return (&pr); } - return (cmd_parse_build_commands(cmds, pi)); + cmd_parse_build_commands(cmds, pi, &pr); + cmd_parse_free_commands(cmds); + return (&pr); + } struct cmd_parse_result * cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) { + struct cmd_parse_input input; + + if (pi == NULL) { + memset(&input, 0, sizeof input); + pi = &input; + } + + /* + * When parsing a string, put commands in one group even if there are + * multiple lines. This means { a \n b } is identical to "a ; b" when + * given as an argument to another command. + */ + pi->flags |= CMD_PARSE_ONEGROUP; return (cmd_parse_from_buffer(s, strlen(s), pi)); } +enum cmd_parse_status +cmd_parse_and_insert(const char *s, struct cmd_parse_input *pi, + struct cmdq_item *after, struct cmdq_state *state, char **error) +{ + struct cmd_parse_result *pr; + struct cmdq_item *item; + + pr = cmd_parse_from_string(s, pi); + switch (pr->status) { + case CMD_PARSE_ERROR: + if (error != NULL) + *error = pr->error; + else + free(pr->error); + break; + case CMD_PARSE_SUCCESS: + item = cmdq_get_command(pr->cmdlist, state); + cmdq_insert_after(after, item); + cmd_list_free(pr->cmdlist); + break; + } + return (pr->status); +} + +enum cmd_parse_status +cmd_parse_and_append(const char *s, struct cmd_parse_input *pi, + struct client *c, struct cmdq_state *state, char **error) +{ + struct cmd_parse_result *pr; + struct cmdq_item *item; + + pr = cmd_parse_from_string(s, pi); + switch (pr->status) { + case CMD_PARSE_ERROR: + if (error != NULL) + *error = pr->error; + else + free(pr->error); + break; + case CMD_PARSE_SUCCESS: + item = cmdq_get_command(pr->cmdlist, state); + cmdq_append(c, item); + cmd_list_free(pr->cmdlist); + break; + } + return (pr->status); +} + struct cmd_parse_result * cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi) { @@ -776,9 +1022,8 @@ cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi) memset(&pr, 0, sizeof pr); if (len == 0) { - pr.status = CMD_PARSE_EMPTY; - pr.cmdlist = NULL; - pr.error = NULL; + pr.status = CMD_PARSE_SUCCESS; + pr.cmdlist = cmd_list_new(); return (&pr); } @@ -788,18 +1033,24 @@ cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi) pr.error = cause; return (&pr); } - return (cmd_parse_build_commands(cmds, pi)); + cmd_parse_build_commands(cmds, pi, &pr); + cmd_parse_free_commands(cmds); + return (&pr); } struct cmd_parse_result * -cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi) +cmd_parse_from_arguments(struct args_value *values, u_int count, + struct cmd_parse_input *pi) { - struct cmd_parse_input input; - struct cmd_parse_commands *cmds; - struct cmd_parse_command *cmd; - char **copy, **new_argv; - size_t size; - int i, last, new_argc; + static struct cmd_parse_result pr; + struct cmd_parse_input input; + struct cmd_parse_commands *cmds; + struct cmd_parse_command *cmd; + struct cmd_parse_argument *arg; + u_int i; + char *copy; + size_t size; + int end; /* * The commands are already split up into arguments, so just separate @@ -810,64 +1061,55 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi) memset(&input, 0, sizeof input); pi = &input; } - cmd_log_argv(argc, argv, "%s", __func__); + memset(&pr, 0, sizeof pr); cmds = cmd_parse_new_commands(); - copy = cmd_copy_argv(argc, argv); - last = 0; - for (i = 0; i < argc; i++) { - size = strlen(copy[i]); - if (size == 0 || copy[i][size - 1] != ';') - continue; - copy[i][--size] = '\0'; - if (size > 0 && copy[i][size - 1] == '\\') { - copy[i][size - 1] = ';'; - continue; - } - - new_argc = i - last; - new_argv = copy + last; - if (size != 0) - new_argc++; - - if (new_argc != 0) { - cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__, - i); - - cmd = xcalloc(1, sizeof *cmd); - cmd->name = xstrdup(new_argv[0]); - cmd->line = pi->line; - - cmd->argc = new_argc - 1; - cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); + cmd = xcalloc(1, sizeof *cmd); + cmd->line = pi->line; + TAILQ_INIT(&cmd->arguments); + for (i = 0; i < count; i++) { + end = 0; + if (values[i].type == ARGS_STRING) { + copy = xstrdup(values[i].string); + size = strlen(copy); + if (size != 0 && copy[size - 1] == ';') { + copy[--size] = '\0'; + if (size > 0 && copy[size - 1] == '\\') + copy[size - 1] = ';'; + else + end = 1; + } + if (!end || size != 0) { + arg = xcalloc(1, sizeof *arg); + arg->type = CMD_PARSE_STRING; + arg->string = copy; + TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); + } + } else if (values[i].type == ARGS_COMMANDS) { + arg = xcalloc(1, sizeof *arg); + arg->type = CMD_PARSE_PARSED_COMMANDS; + arg->cmdlist = values[i].cmdlist; + arg->cmdlist->references++; + TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry); + } else + fatalx("unknown argument type"); + if (end) { TAILQ_INSERT_TAIL(cmds, cmd, entry); - } - - last = i + 1; - } - if (last != argc) { - new_argv = copy + last; - new_argc = argc - last; - - if (new_argc != 0) { - cmd_log_argv(new_argc, new_argv, "%s: at %u", __func__, - last); - cmd = xcalloc(1, sizeof *cmd); - cmd->name = xstrdup(new_argv[0]); cmd->line = pi->line; - - cmd->argc = new_argc - 1; - cmd->argv = cmd_copy_argv(new_argc - 1, new_argv + 1); - - TAILQ_INSERT_TAIL(cmds, cmd, entry); + TAILQ_INIT(&cmd->arguments); } } + if (!TAILQ_EMPTY(&cmd->arguments)) + TAILQ_INSERT_TAIL(cmds, cmd, entry); + else + free(cmd); - cmd_free_argv(argc, copy); - return (cmd_parse_build_commands(cmds, pi)); + cmd_parse_build_commands(cmds, pi, &pr); + cmd_parse_free_commands(cmds); + return (&pr); } static int printflike(1, 2) @@ -1038,11 +1280,11 @@ yylex(void) return ('\n'); } - if (ch == ';') { + if (ch == ';' || ch == '{' || ch == '}') { /* - * A semicolon is itself. + * A semicolon or { or } is itself. */ - return (';'); + return (ch); } if (ch == '#') { @@ -1079,6 +1321,10 @@ yylex(void) if (*cp == '\0') return (TOKEN); ps->condition = 1; + if (strcmp(yylval.token, "%hidden") == 0) { + free(yylval.token); + return (HIDDEN); + } if (strcmp(yylval.token, "%if") == 0) { free(yylval.token); return (IF); @@ -1163,10 +1409,9 @@ error: static int yylex_token_escape(char **buf, size_t *len) { - int ch, type, o2, o3; - u_int size, i, tmp; - char s[9]; - struct utf8_data ud; + int ch, type, o2, o3, mlen; + u_int size, i, tmp; + char s[9], m[MB_LEN_MAX]; ch = yylex_getc(); @@ -1181,7 +1426,7 @@ yylex_token_escape(char **buf, size_t *len) if (o3 >= '0' && o3 <= '7') { ch = 64 * (ch - '0') + 8 * (o2 - '0') + - (o3 - '0'); + (o3 - '0'); yylex_append1(buf, len, ch); return (1); } @@ -1251,11 +1496,12 @@ unicode: yyerror("invalid \\%c argument", type); return (0); } - if (utf8_split(tmp, &ud) != UTF8_DONE) { + mlen = wctomb(m, tmp); + if (mlen <= 0 || mlen > (int)sizeof m) { yyerror("invalid \\%c argument", type); return (0); } - yylex_append(buf, len, ud.data, ud.size); + yylex_append(buf, len, m, mlen); return (1); } @@ -1303,7 +1549,7 @@ yylex_token_variable(char **buf, size_t *len) name[namelen] = '\0'; envent = environ_find(global_environ, name); - if (envent != NULL) { + if (envent != NULL && envent->value != NULL) { value = envent->value; log_debug("%s: %s -> %s", __func__, name, value); yylex_append(buf, len, value, strlen(value)); @@ -1353,119 +1599,6 @@ yylex_token_tilde(char **buf, size_t *len) return (1); } -static int -yylex_token_brace(char **buf, size_t *len) -{ - struct cmd_parse_state *ps = &parse_state; - int ch, lines = 0, nesting = 1, escape = 0; - int quote = '\0', token = 0; - - /* - * Extract a string up to the matching unquoted '}', including newlines - * and handling nested braces. - * - * To detect the final and intermediate braces which affect the nesting - * depth, we scan the input as if it was a tmux config file, and ignore - * braces which would be considered quoted, escaped, or in a comment. - * - * We update the token state after every character because '#' begins a - * comment only when it begins a token. For simplicity, we treat an - * unquoted directive format as comment. - * - * The result is verbatim copy of the input excluding the final brace. - */ - - for (ch = yylex_getc1(); ch != EOF; ch = yylex_getc1()) { - yylex_append1(buf, len, ch); - if (ch == '\n') - lines++; - - /* - * If the previous character was a backslash (escape is set), - * escape anything if unquoted or in double quotes, otherwise - * escape only '\n' and '\\'. - */ - if (escape && - (quote == '\0' || - quote == '"' || - ch == '\n' || - ch == '\\')) { - escape = 0; - if (ch != '\n') - token = 1; - continue; - } - - /* - * The character is not escaped. If it is a backslash, set the - * escape flag. - */ - if (ch == '\\') { - escape = 1; - continue; - } - escape = 0; - - /* A newline always resets to unquoted. */ - if (ch == '\n') { - quote = token = 0; - continue; - } - - if (quote) { - /* - * Inside quotes or comment. Check if this is the - * closing quote. - */ - if (ch == quote && quote != '#') - quote = 0; - token = 1; /* token continues regardless */ - } else { - /* Not inside quotes or comment. */ - switch (ch) { - case '"': - case '\'': - case '#': - /* Beginning of quote or maybe comment. */ - if (ch != '#' || !token) - quote = ch; - token = 1; - break; - case ' ': - case '\t': - case ';': - /* Delimiter - token resets. */ - token = 0; - break; - case '{': - nesting++; - token = 0; /* new commands set - token resets */ - break; - case '}': - nesting--; - token = 1; /* same as after quotes */ - if (nesting == 0) { - (*len)--; /* remove closing } */ - ps->input->line += lines; - return (1); - } - break; - default: - token = 1; - break; - } - } - } - - /* - * Update line count after error as reporting the opening line is more - * useful than EOF. - */ - yyerror("unterminated brace string"); - ps->input->line += lines; - return (0); -} - static char * yylex_token(int ch) { @@ -1480,23 +1613,37 @@ yylex_token(int ch) buf = xmalloc(1); for (;;) { - /* - * EOF or \n are always the end of the token. If inside quotes - * they are an error. - */ - if (ch == EOF || ch == '\n') { - if (state != NONE) - goto error; + /* EOF or \n are always the end of the token. */ + if (ch == EOF || (state == NONE && ch == '\n')) break; + + /* Whitespace or ; or } ends a token unless inside quotes. */ + if ((ch == ' ' || ch == '\t' || ch == ';' || ch == '}') && + state == NONE) + break; + + /* + * Spaces and comments inside quotes after \n are removed but + * the \n is left. + */ + if (ch == '\n' && state != NONE) { + yylex_append1(&buf, &len, '\n'); + while ((ch = yylex_getc()) == ' ' || ch == '\t') + /* nothing */; + if (ch != '#') + continue; + ch = yylex_getc(); + if (strchr(",#{}:", ch) != NULL) { + yylex_ungetc(ch); + ch = '#'; + } else { + while ((ch = yylex_getc()) != '\n' && ch != EOF) + /* nothing */; + } + continue; } - /* Whitespace or ; ends a token unless inside quotes. */ - if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE) - break; - - /* - * \ ~ and $ are expanded except in single quotes. - */ + /* \ ~ and $ are expanded except in single quotes. */ if (ch == '\\' && state != SINGLE_QUOTES) { if (!yylex_token_escape(&buf, &len)) goto error; @@ -1512,17 +1659,10 @@ yylex_token(int ch) goto error; goto skip; } - if (ch == '{' && state == NONE) { - if (!yylex_token_brace(&buf, &len)) - goto error; - goto skip; - } if (ch == '}' && state == NONE) goto error; /* unmatched (matched ones were handled) */ - /* - * ' and " starts or end quotes (and is consumed). - */ + /* ' and " starts or end quotes (and is consumed). */ if (ch == '\'') { if (state == NONE) { state = SINGLE_QUOTES; @@ -1544,9 +1684,7 @@ yylex_token(int ch) } } - /* - * Otherwise add the character to the buffer. - */ + /* Otherwise add the character to the buffer. */ yylex_append1(&buf, &len, ch); skip: diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c index 2b5db825..36326e18 100644 --- a/cmd-paste-buffer.c +++ b/cmd-paste-buffer.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_paste_buffer_entry = { .name = "paste-buffer", .alias = "pasteb", - .args = { "db:prs:t:", 0, 0 }, + .args = { "db:prs:t:", 0, 0, NULL }, .usage = "[-dpr] [-s separator] " CMD_BUFFER_USAGE " " CMD_TARGET_PANE_USAGE, @@ -46,8 +46,9 @@ const struct cmd_entry cmd_paste_buffer_entry = { static enum cmd_retval cmd_paste_buffer_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *wp = target->wp; struct paste_buffer *pb; const char *sepstr, *bufname, *bufdata, *bufend, *line; size_t seplen, bufsize; diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index ce1b3d37..20318492 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -43,8 +43,8 @@ const struct cmd_entry cmd_pipe_pane_entry = { .name = "pipe-pane", .alias = "pipep", - .args = { "IOot:", 0, 1 }, - .usage = "[-IOo] " CMD_TARGET_PANE_USAGE " [command]", + .args = { "IOot:", 0, 1, NULL }, + .usage = "[-IOo] " CMD_TARGET_PANE_USAGE " [shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -55,15 +55,17 @@ const struct cmd_entry cmd_pipe_pane_entry = { static enum cmd_retval cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct window_pane *wp = item->target.wp; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - char *cmd; - int old_fd, pipe_fd[2], null_fd, in, out; - struct format_tree *ft; - sigset_t set, oldset; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item); + struct window_pane *wp = target->wp; + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane_offset *wpo = &wp->pipe_offset; + char *cmd; + int old_fd, pipe_fd[2], null_fd, in, out; + struct format_tree *ft; + sigset_t set, oldset; /* Destroy the old pipe. */ old_fd = wp->pipe_fd; @@ -79,7 +81,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) } /* If no pipe command, that is enough. */ - if (args->argc == 0 || *args->argv[0] == '\0') + if (args_count(args) == 0 || *args_string(args, 0) == '\0') return (CMD_RETURN_NORMAL); /* @@ -88,13 +90,13 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) * * bind ^p pipep -o 'cat >>~/output' */ - if (args_has(self->args, 'o') && old_fd != -1) + if (args_has(args, 'o') && old_fd != -1) return (CMD_RETURN_NORMAL); /* What do we want to do? Neither -I or -O is -O. */ - if (args_has(self->args, 'I')) { + if (args_has(args, 'I')) { in = 1; - out = args_has(self->args, 'O'); + out = args_has(args, 'O'); } else { in = 0; out = 1; @@ -107,9 +109,9 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) } /* Expand the command. */ - ft = format_create(item->client, item, FORMAT_NONE, 0); - format_defaults(ft, c, s, wl, wp); - cmd = format_expand_time(ft, args->argv[0]); + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + format_defaults(ft, tc, s, wl, wp); + cmd = format_expand_time(ft, args_string(args, 0)); format_free(ft); /* Fork the child. */ @@ -157,10 +159,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) close(pipe_fd[1]); wp->pipe_fd = pipe_fd[0]; - if (wp->fd != -1) - wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); - else - wp->pipe_off = 0; + memcpy(wpo, &wp->offset, sizeof *wpo); setblocking(wp->pipe_fd, 0); wp->pipe_event = bufferevent_new(wp->pipe_fd, diff --git a/cmd-queue.c b/cmd-queue.c index 59c7a35c..4fbdc4e7 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -25,8 +25,69 @@ #include "tmux.h" -/* Global command queue. */ -static struct cmdq_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue); +/* Command queue flags. */ +#define CMDQ_FIRED 0x1 +#define CMDQ_WAITING 0x2 + +/* Command queue item type. */ +enum cmdq_type { + CMDQ_COMMAND, + CMDQ_CALLBACK, +}; + +/* Command queue item. */ +struct cmdq_item { + char *name; + struct cmdq_list *queue; + struct cmdq_item *next; + + struct client *client; + struct client *target_client; + + enum cmdq_type type; + u_int group; + + u_int number; + time_t time; + + int flags; + + struct cmdq_state *state; + struct cmd_find_state source; + struct cmd_find_state target; + + struct cmd_list *cmdlist; + struct cmd *cmd; + + cmdq_cb cb; + void *data; + + TAILQ_ENTRY(cmdq_item) entry; +}; +TAILQ_HEAD(cmdq_item_list, cmdq_item); + +/* + * Command queue state. This is the context for commands on the command queue. + * It holds information about how the commands were fired (the key and flags), + * any additional formats for the commands, and the current default target. + * Multiple commands can share the same state and a command may update the + * default target. + */ +struct cmdq_state { + int references; + int flags; + + struct format_tree *formats; + + struct key_event event; + struct cmd_find_state current; +}; + +/* Command queue. */ +struct cmdq_list { + struct cmdq_item *item; + struct cmdq_item_list list; +}; /* Get command queue name. */ static const char * @@ -47,9 +108,188 @@ cmdq_name(struct client *c) static struct cmdq_list * cmdq_get(struct client *c) { - if (c == NULL) - return (&global_queue); - return (&c->queue); + static struct cmdq_list *global_queue; + + if (c == NULL) { + if (global_queue == NULL) + global_queue = cmdq_new(); + return (global_queue); + } + return (c->queue); +} + +/* Create a queue. */ +struct cmdq_list * +cmdq_new(void) +{ + struct cmdq_list *queue; + + queue = xcalloc (1, sizeof *queue); + TAILQ_INIT (&queue->list); + return (queue); +} + +/* Free a queue. */ +void +cmdq_free(struct cmdq_list *queue) +{ + if (!TAILQ_EMPTY(&queue->list)) + fatalx("queue not empty"); + free(queue); +} + +/* Get item name. */ +const char * +cmdq_get_name(struct cmdq_item *item) +{ + return (item->name); +} + +/* Get item client. */ +struct client * +cmdq_get_client(struct cmdq_item *item) +{ + return (item->client); +} + +/* Get item target client. */ +struct client * +cmdq_get_target_client(struct cmdq_item *item) +{ + return (item->target_client); +} + +/* Get item state. */ +struct cmdq_state * +cmdq_get_state(struct cmdq_item *item) +{ + return (item->state); +} + +/* Get item target. */ +struct cmd_find_state * +cmdq_get_target(struct cmdq_item *item) +{ + return (&item->target); +} + +/* Get item source. */ +struct cmd_find_state * +cmdq_get_source(struct cmdq_item *item) +{ + return (&item->source); +} + +/* Get state event. */ +struct key_event * +cmdq_get_event(struct cmdq_item *item) +{ + return (&item->state->event); +} + +/* Get state current target. */ +struct cmd_find_state * +cmdq_get_current(struct cmdq_item *item) +{ + return (&item->state->current); +} + +/* Get state flags. */ +int +cmdq_get_flags(struct cmdq_item *item) +{ + return (item->state->flags); +} + +/* Create a new state. */ +struct cmdq_state * +cmdq_new_state(struct cmd_find_state *current, struct key_event *event, + int flags) +{ + struct cmdq_state *state; + + state = xcalloc(1, sizeof *state); + state->references = 1; + state->flags = flags; + + if (event != NULL) + memcpy(&state->event, event, sizeof state->event); + else + state->event.key = KEYC_NONE; + if (current != NULL && cmd_find_valid_state(current)) + cmd_find_copy_state(&state->current, current); + else + cmd_find_clear_state(&state->current, 0); + + return (state); +} + +/* Add a reference to a state. */ +struct cmdq_state * +cmdq_link_state(struct cmdq_state *state) +{ + state->references++; + return (state); +} + +/* Make a copy of a state. */ +struct cmdq_state * +cmdq_copy_state(struct cmdq_state *state) +{ + return (cmdq_new_state(&state->current, &state->event, state->flags)); +} + +/* Free a state. */ +void +cmdq_free_state(struct cmdq_state *state) +{ + if (--state->references != 0) + return; + + if (state->formats != NULL) + format_free(state->formats); + free(state); +} + +/* Add a format to command queue. */ +void +cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) +{ + va_list ap; + char *value; + + va_start(ap, fmt); + xvasprintf(&value, fmt, ap); + va_end(ap); + + if (state->formats == NULL) + state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); + format_add(state->formats, key, "%s", value); + + free(value); +} + +/* Add formats to command queue. */ +void +cmdq_add_formats(struct cmdq_state *state, struct format_tree *ft) +{ + if (state->formats == NULL) + state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); + format_merge(state->formats, ft); +} + +/* Merge formats from item. */ +void +cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) +{ + const struct cmd_entry *entry; + + if (item->cmd != NULL) { + entry = cmd_get_entry(item->cmd); + format_add(ft, "command", "%s", entry->name); + } + if (item->state->formats != NULL) + format_merge(ft, item->state->formats); } /* Append an item. */ @@ -59,10 +299,6 @@ cmdq_append(struct client *c, struct cmdq_item *item) struct cmdq_list *queue = cmdq_get(c); struct cmdq_item *next; - TAILQ_FOREACH(next, queue, entry) { - log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), - next->name, next->group); - } do { next = item->next; item->next = NULL; @@ -72,12 +308,12 @@ cmdq_append(struct client *c, struct cmdq_item *item) item->client = c; item->queue = queue; - TAILQ_INSERT_TAIL(queue, item, entry); + TAILQ_INSERT_TAIL(&queue->list, item, entry); log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); item = next; } while (item != NULL); - return (TAILQ_LAST(queue, cmdq_list)); + return (TAILQ_LAST(&queue->list, cmdq_item_list)); } /* Insert an item. */ @@ -88,10 +324,6 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) struct cmdq_list *queue = after->queue; struct cmdq_item *next; - TAILQ_FOREACH(next, queue, entry) { - log_debug("%s %s: queue %s (%u)", __func__, cmdq_name(c), - next->name, next->group); - } do { next = item->next; item->next = after->next; @@ -102,7 +334,7 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) item->client = c; item->queue = queue; - TAILQ_INSERT_AFTER(queue, after, item, entry); + TAILQ_INSERT_AFTER(&queue->list, after, item, entry); log_debug("%s %s: %s after %s", __func__, cmdq_name(c), item->name, after->name); @@ -115,17 +347,25 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) /* Insert a hook. */ void cmdq_insert_hook(struct session *s, struct cmdq_item *item, - struct cmd_find_state *fs, const char *fmt, ...) + struct cmd_find_state *current, const char *fmt, ...) { + struct cmdq_state *state = item->state; + struct cmd *cmd = item->cmd; + struct args *args = cmd_get_args(cmd); + struct args_entry *ae; + struct args_value *av; struct options *oo; va_list ap; - char *name; + char *name, tmp[32], flag, *arguments; + u_int i; + const char *value; struct cmdq_item *new_item; + struct cmdq_state *new_state; struct options_entry *o; struct options_array_item *a; struct cmd_list *cmdlist; - if (item->flags & CMDQ_NOHOOKS) + if (item->state->flags & CMDQ_STATE_NOHOOKS) return; if (s == NULL) oo = global_s_options; @@ -143,24 +383,58 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, } log_debug("running hook %s (parent %p)", name, item); + /* + * The hooks get a new state because they should not update the current + * target or formats for any subsequent commands. + */ + new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); + cmdq_add_format(new_state, "hook", "%s", name); + + arguments = args_print(args); + cmdq_add_format(new_state, "hook_arguments", "%s", arguments); + free(arguments); + + for (i = 0; i < args_count(args); i++) { + xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); + cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); + } + flag = args_first(args, &ae); + while (flag != 0) { + value = args_get(args, flag); + if (value == NULL) { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); + cmdq_add_format(new_state, tmp, "1"); + } else { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); + cmdq_add_format(new_state, tmp, "%s", value); + } + + i = 0; + av = args_first_value(args, flag); + while (av != NULL) { + xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); + cmdq_add_format(new_state, tmp, "%s", av->string); + i++; + av = args_next_value(av); + } + + flag = args_next(&ae); + } + a = options_array_first(o); while (a != NULL) { cmdlist = options_array_item_value(a)->cmdlist; - if (cmdlist == NULL) { - a = options_array_next(a); - continue; + if (cmdlist != NULL) { + new_item = cmdq_get_command(cmdlist, new_state); + if (item != NULL) + item = cmdq_insert_after(item, new_item); + else + item = cmdq_append(NULL, new_item); } - - new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS); - cmdq_format(new_item, "hook", "%s", name); - if (item != NULL) - item = cmdq_insert_after(item, new_item); - else - item = cmdq_append(NULL, new_item); - a = options_array_next(a); } + cmdq_free_state(new_state); free(name); } @@ -175,19 +449,13 @@ cmdq_continue(struct cmdq_item *item) static void cmdq_remove(struct cmdq_item *item) { - if (item->shared != NULL && --item->shared->references == 0) { - if (item->shared->formats != NULL) - format_free(item->shared->formats); - free(item->shared); - } - if (item->client != NULL) server_client_unref(item->client); - if (item->cmdlist != NULL) cmd_list_free(item->cmdlist); + cmdq_free_state(item->state); - TAILQ_REMOVE(item->queue, item, entry); + TAILQ_REMOVE(&item->queue->list, item, entry); free(item->name); free(item); @@ -210,50 +478,57 @@ cmdq_remove_group(struct cmdq_item *item) } } +/* Empty command callback. */ +static enum cmd_retval +cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data) +{ + return (CMD_RETURN_NORMAL); +} + /* Get a command for the command queue. */ struct cmdq_item * -cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current, - struct mouse_event *m, int flags) +cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) { struct cmdq_item *item, *first = NULL, *last = NULL; struct cmd *cmd; - struct cmdq_shared *shared = NULL; - u_int group = 0; + const struct cmd_entry *entry; + int created = 0; - TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { - if (cmd->group != group) { - shared = xcalloc(1, sizeof *shared); - if (current != NULL) - cmd_find_copy_state(&shared->current, current); - else - cmd_find_clear_state(&shared->current, 0); - if (m != NULL) - memcpy(&shared->mouse, m, sizeof shared->mouse); - group = cmd->group; - } + if ((cmd = cmd_list_first(cmdlist)) == NULL) + return (cmdq_get_callback(cmdq_empty_command, NULL)); + + if (state == NULL) { + state = cmdq_new_state(NULL, NULL, 0); + created = 1; + } + + while (cmd != NULL) { + entry = cmd_get_entry(cmd); item = xcalloc(1, sizeof *item); - xasprintf(&item->name, "[%s/%p]", cmd->entry->name, item); + xasprintf(&item->name, "[%s/%p]", entry->name, item); item->type = CMDQ_COMMAND; - item->group = cmd->group; - item->flags = flags; + item->group = cmd_get_group(cmd); + item->state = cmdq_link_state(state); - item->shared = shared; item->cmdlist = cmdlist; item->cmd = cmd; - log_debug("%s: %s group %u", __func__, item->name, item->group); - - shared->references++; cmdlist->references++; + log_debug("%s: %s group %u", __func__, item->name, item->group); if (first == NULL) first = item; if (last != NULL) last->next = item; last = item; + + cmd = cmd_list_next(cmd); } + + if (created) + cmdq_free_state(state); return (first); } @@ -265,11 +540,11 @@ cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, const char *value; if (flag->flag == 0) { - cmd_find_clear_state(fs, 0); + cmd_find_from_client(fs, item->target_client, 0); return (CMD_RETURN_NORMAL); } - value = args_get(item->cmd->args, flag->flag); + value = args_get(cmd_get_args(item->cmd), flag->flag); if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { cmd_find_clear_state(fs, 0); return (CMD_RETURN_ERROR); @@ -277,31 +552,75 @@ cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, return (CMD_RETURN_NORMAL); } +/* Add message with command. */ +static void +cmdq_add_message(struct cmdq_item *item) +{ + struct client *c = item->client; + struct cmdq_state *state = item->state; + const char *name, *key; + char *tmp; + + tmp = cmd_print(item->cmd); + if (c != NULL) { + name = c->name; + if (c->session != NULL && state->event.key != KEYC_NONE) { + key = key_string_lookup_key(state->event.key, 0); + server_add_message("%s key %s: %s", name, key, tmp); + } else + server_add_message("%s command: %s", name, tmp); + } else + server_add_message("command: %s", tmp); + free(tmp); +} + /* Fire command on command queue. */ static enum cmd_retval cmdq_fire_command(struct cmdq_item *item) { - struct client *c = item->client; - const char *name = cmdq_name(c); - struct cmdq_shared *shared = item->shared; + const char *name = cmdq_name(item->client); + struct cmdq_state *state = item->state; struct cmd *cmd = item->cmd; - const struct cmd_entry *entry = cmd->entry; + struct args *args = cmd_get_args(cmd); + const struct cmd_entry *entry = cmd_get_entry(cmd); + struct client *tc, *saved = item->client; enum cmd_retval retval; struct cmd_find_state *fsp, fs; - int flags; + int flags, quiet = 0; char *tmp; + if (cfg_finished) + cmdq_add_message(item); if (log_get_level() > 1) { tmp = cmd_print(cmd); log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); free(tmp); } - flags = !!(shared->flags & CMDQ_SHARED_CONTROL); + flags = !!(state->flags & CMDQ_STATE_CONTROL); cmdq_guard(item, "begin", flags); if (item->client == NULL) item->client = cmd_find_client(item, NULL, 1); + + if (entry->flags & CMD_CLIENT_CANFAIL) + quiet = 1; + if (entry->flags & CMD_CLIENT_CFLAG) { + tc = cmd_find_client(item, args_get(args, 'c'), quiet); + if (tc == NULL && !quiet) { + retval = CMD_RETURN_ERROR; + goto out; + } + } else if (entry->flags & CMD_CLIENT_TFLAG) { + tc = cmd_find_client(item, args_get(args, 't'), quiet); + if (tc == NULL && !quiet) { + retval = CMD_RETURN_ERROR; + goto out; + } + } else + tc = cmd_find_client(item, NULL, 1); + item->target_client = tc; + retval = cmdq_find_flag(item, &item->source, &entry->source); if (retval == CMD_RETURN_ERROR) goto out; @@ -316,8 +635,8 @@ cmdq_fire_command(struct cmdq_item *item) if (entry->flags & CMD_AFTERHOOK) { if (cmd_find_valid_state(&item->target)) fsp = &item->target; - else if (cmd_find_valid_state(&item->shared->current)) - fsp = &item->shared->current; + else if (cmd_find_valid_state(&item->state->current)) + fsp = &item->state->current; else if (cmd_find_from_client(&fs, item->client, 0) == 0) fsp = &fs; else @@ -326,7 +645,7 @@ cmdq_fire_command(struct cmdq_item *item) } out: - item->client = c; + item->client = saved; if (retval == CMD_RETURN_ERROR) cmdq_guard(item, "error", flags); else @@ -345,7 +664,7 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) item->type = CMDQ_CALLBACK; item->group = 0; - item->flags = 0; + item->state = cmdq_new_state(NULL, NULL, 0); item->cb = cb; item->data = data; @@ -379,25 +698,6 @@ cmdq_fire_callback(struct cmdq_item *item) return (item->cb(item, item->data)); } -/* Add a format to command queue. */ -void -cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...) -{ - struct cmdq_shared *shared = item->shared; - va_list ap; - char *value; - - va_start(ap, fmt); - xvasprintf(&value, fmt, ap); - va_end(ap); - - if (shared->formats == NULL) - shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0); - format_add(shared->formats, key, "%s", value); - - free(value); -} - /* Process next item on command queue. */ u_int cmdq_next(struct client *c) @@ -409,18 +709,18 @@ cmdq_next(struct client *c) u_int items = 0; static u_int number; - if (TAILQ_EMPTY(queue)) { + if (TAILQ_EMPTY(&queue->list)) { log_debug("%s %s: empty", __func__, name); return (0); } - if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { + if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) { log_debug("%s %s: waiting", __func__, name); return (0); } log_debug("%s %s: enter", __func__, name); for (;;) { - item = TAILQ_FIRST(queue); + item = queue->item = TAILQ_FIRST(&queue->list); if (item == NULL) break; log_debug("%s %s: %s (%d), flags %x", __func__, name, @@ -470,6 +770,7 @@ cmdq_next(struct client *c) } cmdq_remove(item); } + queue->item = NULL; log_debug("%s %s: exit (empty)", __func__, name); return (items); @@ -479,6 +780,19 @@ waiting: return (items); } +/* Get running item if any. */ +struct cmdq_item * +cmdq_running(struct client *c) +{ + struct cmdq_list *queue = cmdq_get(c); + + if (queue->item == NULL) + return (NULL); + if (queue->item->flags & CMDQ_WAITING) + return (NULL); + return (queue->item); +} + /* Print a guard line. */ void cmdq_guard(struct cmdq_item *item, const char *guard, int flags) @@ -488,7 +802,7 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags) u_int number = item->number; if (c != NULL && (c->flags & CLIENT_CONTROL)) - file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); + control_write(c, "%%%s %ld %u %d", guard, t, number, flags); } /* Show message from command. */ @@ -515,12 +829,17 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) msg = utf8_sanitize(tmp); free(tmp); } - file_print(c, "%s\n", msg); + if (c->flags & CLIENT_CONTROL) + control_write(c, "%s", msg); + else + file_print(c, "%s\n", msg); } else { - wp = c->session->curw->window->active; + wp = server_client_get_pane(c); wme = TAILQ_FIRST(&wp->modes); - if (wme == NULL || wme->mode != &window_view_mode) - window_pane_set_mode(wp, &window_view_mode, NULL, NULL); + if (wme == NULL || wme->mode != &window_view_mode) { + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, + NULL); + } window_copy_add(wp, "%s", msg); } @@ -534,8 +853,9 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) struct client *c = item->client; struct cmd *cmd = item->cmd; va_list ap; - char *msg; - char *tmp; + char *msg, *tmp; + const char *file; + u_int line; va_start(ap, fmt); xvasprintf(&msg, fmt, ap); @@ -543,22 +863,24 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...) log_debug("%s: %s", __func__, msg); - if (c == NULL) - cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg); - else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + if (c == NULL) { + cmd_get_source(cmd, &file, &line); + cfg_add_cause("%s:%u: %s", file, line, msg); + } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + server_add_message("%s message: %s", c->name, msg); if (~c->flags & CLIENT_UTF8) { tmp = msg; msg = utf8_sanitize(tmp); free(tmp); } if (c->flags & CLIENT_CONTROL) - file_print(c, "%s\n", msg); + control_write(c, "%s", msg); else file_error(c, "%s\n", msg); c->retval = 1; } else { *msg = toupper((u_char) *msg); - status_message_set(c, "%s", msg); + status_message_set(c, -1, 1, 0, "%s", msg); } free(msg); diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index b4c5e844..821558ae 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -34,28 +34,144 @@ const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", .alias = "refresh", - .args = { "cC:DF:lLRSt:U", 0, 1 }, - .usage = "[-cDlLRSU] [-C XxY] [-F flags] " CMD_TARGET_CLIENT_USAGE - " [adjustment]", + .args = { "A:B:cC:Df:F:lLRSt:U", 0, 1, NULL }, + .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] " + "[-C XxY] [-f flags] " CMD_TARGET_CLIENT_USAGE " [adjustment]", - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, .exec = cmd_refresh_client_exec }; +static void +cmd_refresh_client_update_subscription(struct client *tc, const char *value) +{ + char *copy, *split, *name, *what; + enum control_sub_type subtype; + int subid = -1; + + copy = name = xstrdup(value); + if ((split = strchr(copy, ':')) == NULL) { + control_remove_sub(tc, copy); + goto out; + } + *split++ = '\0'; + + what = split; + if ((split = strchr(what, ':')) == NULL) + goto out; + *split++ = '\0'; + + if (strcmp(what, "%*") == 0) + subtype = CONTROL_SUB_ALL_PANES; + else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0) + subtype = CONTROL_SUB_PANE; + else if (strcmp(what, "@*") == 0) + subtype = CONTROL_SUB_ALL_WINDOWS; + else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0) + subtype = CONTROL_SUB_WINDOW; + else + subtype = CONTROL_SUB_SESSION; + control_add_sub(tc, name, subtype, subid, split); + +out: + free(copy); +} + +static enum cmd_retval +cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); + const char *size = args_get(args, 'C'); + u_int w, x, y; + struct client_window *cw; + + if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) { + if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || + y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { + cmdq_error(item, "size too small or too big"); + return (CMD_RETURN_ERROR); + } + log_debug("%s: client %s window @%u: size %ux%u", __func__, + tc->name, w, x, y); + cw = server_client_add_client_window(tc, w); + cw->sx = x; + cw->sy = y; + tc->flags |= CLIENT_WINDOWSIZECHANGED; + recalculate_sizes_now(1); + return (CMD_RETURN_NORMAL); + } + if (sscanf(size, "@%u:", &w) == 1) { + cw = server_client_get_client_window(tc, w); + if (cw != NULL) { + log_debug("%s: client %s window @%u: no size", __func__, + tc->name, w); + cw->sx = 0; + cw->sy = 0; + recalculate_sizes_now(1); + } + return (CMD_RETURN_NORMAL); + } + + if (sscanf(size, "%u,%u", &x, &y) != 2 && + sscanf(size, "%ux%u", &x, &y) != 2) { + cmdq_error(item, "bad size argument"); + return (CMD_RETURN_ERROR); + } + if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || + y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { + cmdq_error(item, "size too small or too big"); + return (CMD_RETURN_ERROR); + } + tty_set_size(&tc->tty, x, y, 0, 0); + tc->flags |= CLIENT_SIZECHANGED; + recalculate_sizes_now(1); + return (CMD_RETURN_NORMAL); +} + +static void +cmd_refresh_client_update_offset(struct client *tc, const char *value) +{ + struct window_pane *wp; + char *copy, *split; + u_int pane; + + if (*value != '%') + return; + copy = xstrdup(value); + if ((split = strchr(copy, ':')) == NULL) + goto out; + *split++ = '\0'; + + if (sscanf(copy, "%%%u", &pane) != 1) + goto out; + wp = window_pane_find_by_id(pane); + if (wp == NULL) + goto out; + + if (strcmp(split, "on") == 0) + control_set_pane_on(tc, wp); + else if (strcmp(split, "off") == 0) + control_set_pane_off(tc, wp); + else if (strcmp(split, "continue") == 0) + control_continue_pane(tc, wp); + else if (strcmp(split, "pause") == 0) + control_pause_pane(tc, wp); + +out: + free(copy); +} + static enum cmd_retval cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c; - struct tty *tty; - struct window *w; - const char *size, *errstr; - char *copy, *next, *s; - u_int x, y, adjust; - - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); - tty = &c->tty; + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); + struct tty *tty = &tc->tty; + struct window *w; + const char *errstr; + u_int adjust; + struct args_value *av; if (args_has(args, 'c') || args_has(args, 'L') || @@ -63,10 +179,11 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) args_has(args, 'U') || args_has(args, 'D')) { - if (args->argc == 0) + if (args_count(args) == 0) adjust = 1; else { - adjust = strtonum(args->argv[0], 1, INT_MAX, &errstr); + adjust = strtonum(args_string(args, 0), 1, INT_MAX, + &errstr); if (errstr != NULL) { cmdq_error(item, "adjustment %s", errstr); return (CMD_RETURN_ERROR); @@ -74,88 +191,85 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(args, 'c')) - c->pan_window = NULL; + tc->pan_window = NULL; else { - w = c->session->curw->window; - if (c->pan_window != w) { - c->pan_window = w; - c->pan_ox = tty->oox; - c->pan_oy = tty->ooy; + w = tc->session->curw->window; + if (tc->pan_window != w) { + tc->pan_window = w; + tc->pan_ox = tty->oox; + tc->pan_oy = tty->ooy; } if (args_has(args, 'L')) { - if (c->pan_ox > adjust) - c->pan_ox -= adjust; + if (tc->pan_ox > adjust) + tc->pan_ox -= adjust; else - c->pan_ox = 0; + tc->pan_ox = 0; } else if (args_has(args, 'R')) { - c->pan_ox += adjust; - if (c->pan_ox > w->sx - tty->osx) - c->pan_ox = w->sx - tty->osx; + tc->pan_ox += adjust; + if (tc->pan_ox > w->sx - tty->osx) + tc->pan_ox = w->sx - tty->osx; } else if (args_has(args, 'U')) { - if (c->pan_oy > adjust) - c->pan_oy -= adjust; + if (tc->pan_oy > adjust) + tc->pan_oy -= adjust; else - c->pan_oy = 0; + tc->pan_oy = 0; } else if (args_has(args, 'D')) { - c->pan_oy += adjust; - if (c->pan_oy > w->sy - tty->osy) - c->pan_oy = w->sy - tty->osy; + tc->pan_oy += adjust; + if (tc->pan_oy > w->sy - tty->osy) + tc->pan_oy = w->sy - tty->osy; } } - tty_update_client_offset(c); - server_redraw_client(c); + tty_update_client_offset(tc); + server_redraw_client(tc); return (CMD_RETURN_NORMAL); } if (args_has(args, 'l')) { - if (c->session != NULL) - tty_putcode_ptr2(&c->tty, TTYC_MS, "", "?"); + tty_putcode_ptr2(&tc->tty, TTYC_MS, "", "?"); return (CMD_RETURN_NORMAL); } - if (args_has(args, 'C') || args_has(args, 'F')) { - if (args_has(args, 'C')) { - if (!(c->flags & CLIENT_CONTROL)) { - cmdq_error(item, "not a control client"); - return (CMD_RETURN_ERROR); - } - size = args_get(args, 'C'); - if (sscanf(size, "%u,%u", &x, &y) != 2 && - sscanf(size, "%ux%u", &x, &y) != 2) { - cmdq_error(item, "bad size argument"); - return (CMD_RETURN_ERROR); - } - if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || - y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { - cmdq_error(item, "size too small or too big"); - return (CMD_RETURN_ERROR); - } - tty_set_size(&c->tty, x, y, 0, 0); - c->flags |= CLIENT_SIZECHANGED; - recalculate_sizes(); - } - if (args_has(args, 'F')) { - if (!(c->flags & CLIENT_CONTROL)) { - cmdq_error(item, "not a control client"); - return (CMD_RETURN_ERROR); - } - s = copy = xstrdup(args_get(args, 'F')); - while ((next = strsep(&s, ",")) != NULL) { - /* Unknown flags are ignored. */ - if (strcmp(next, "no-output") == 0) - c->flags |= CLIENT_CONTROL_NOOUTPUT; - } - free(copy); + if (args_has(args, 'F')) /* -F is an alias for -f */ + server_client_set_flags(tc, args_get(args, 'F')); + if (args_has(args, 'f')) + server_client_set_flags(tc, args_get(args, 'f')); + + if (args_has(args, 'A')) { + if (~tc->flags & CLIENT_CONTROL) + goto not_control_client; + av = args_first_value(args, 'A'); + while (av != NULL) { + cmd_refresh_client_update_offset(tc, av->string); + av = args_next_value(av); } return (CMD_RETURN_NORMAL); } + if (args_has(args, 'B')) { + if (~tc->flags & CLIENT_CONTROL) + goto not_control_client; + av = args_first_value(args, 'B'); + while (av != NULL) { + cmd_refresh_client_update_subscription(tc, av->string); + av = args_next_value(av); + } + return (CMD_RETURN_NORMAL); + } + if (args_has(args, 'C')) { + if (~tc->flags & CLIENT_CONTROL) + goto not_control_client; + return (cmd_refresh_client_control_client_size(self, item)); + } if (args_has(args, 'S')) { - c->flags |= CLIENT_STATUSFORCE; - server_status_client(c); + tc->flags |= CLIENT_STATUSFORCE; + server_status_client(tc); } else { - c->flags |= CLIENT_STATUSFORCE; - server_redraw_client(c); + tc->flags |= CLIENT_STATUSFORCE; + server_redraw_client(tc); } return (CMD_RETURN_NORMAL); + +not_control_client: + cmdq_error(item, "not a control client"); + return (CMD_RETURN_ERROR); } diff --git a/cmd-rename-session.c b/cmd-rename-session.c index 8385434a..694f3c97 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -34,7 +34,7 @@ const struct cmd_entry cmd_rename_session_entry = { .name = "rename-session", .alias = "rename", - .args = { "t:", 1, 1 }, + .args = { "t:", 1, 1, NULL }, .usage = CMD_TARGET_SESSION_USAGE " new-name", .target = { 't', CMD_FIND_SESSION, 0 }, @@ -46,22 +46,23 @@ const struct cmd_entry cmd_rename_session_entry = { static enum cmd_retval cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - char *newname; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct session *s = target->s; + char *newname, *tmp; - newname = format_single(item, args->argv[0], c, s, NULL, NULL); + tmp = format_single_from_target(item, args_string(args, 0)); + newname = session_check_name(tmp); + if (newname == NULL) { + cmdq_error(item, "invalid session: %s", tmp); + free(tmp); + return (CMD_RETURN_ERROR); + } + free(tmp); if (strcmp(newname, s->name) == 0) { free(newname); return (CMD_RETURN_NORMAL); } - - if (!session_check_name(newname)) { - cmdq_error(item, "bad session name: %s", newname); - free(newname); - return (CMD_RETURN_ERROR); - } if (session_find(newname) != NULL) { cmdq_error(item, "duplicate session: %s", newname); free(newname); diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 4d2ebb75..472b571b 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_rename_window_entry = { .name = "rename-window", .alias = "renamew", - .args = { "t:", 1, 1 }, + .args = { "t:", 1, 1, NULL }, .usage = CMD_TARGET_WINDOW_USAGE " new-name", .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -45,16 +45,16 @@ const struct cmd_entry cmd_rename_window_entry = { static enum cmd_retval cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - char *newname; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + char *newname; - newname = format_single(item, args->argv[0], c, s, wl, NULL); + newname = format_single_from_target(item, args_string(args, 0)); window_set_name(wl->window, newname); options_set_number(wl->window->options, "automatic-rename", 0); + server_redraw_window_borders(wl->window); server_status_window(wl->window); free(newname); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 3962546d..81744f72 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -36,8 +36,8 @@ const struct cmd_entry cmd_resize_pane_entry = { .name = "resize-pane", .alias = "resizep", - .args = { "DLMRt:Ux:y:Z", 0, 1 }, - .usage = "[-DLMRUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " " + .args = { "DLMRTt:Ux:y:Z", 0, 1, NULL }, + .usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " " "[adjustment]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -49,26 +49,39 @@ const struct cmd_entry cmd_resize_pane_entry = { static enum cmd_retval cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmdq_shared *shared = item->shared; - struct window_pane *wp = item->target.wp; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct key_event *event = cmdq_get_event(item); + struct window_pane *wp = target->wp; + struct winlink *wl = target->wl; struct window *w = wl->window; - struct client *c = item->client; - struct session *s = item->target.s; - const char *errstr, *p; - char *cause, *copy; + struct client *c = cmdq_get_client(item); + struct session *s = target->s; + const char *errstr; + char *cause; u_int adjust; - int x, y, percentage; - size_t plen; + int x, y; + struct grid *gd = wp->base.grid; + + if (args_has(args, 'T')) { + if (!TAILQ_EMPTY(&wp->modes)) + return (CMD_RETURN_NORMAL); + adjust = screen_size_y(&wp->base) - 1 - wp->base.cy; + if (adjust > gd->hsize) + adjust = gd->hsize; + grid_remove_history(gd, adjust); + wp->base.cy += adjust; + wp->flags |= PANE_REDRAW; + return (CMD_RETURN_NORMAL); + } if (args_has(args, 'M')) { - if (cmd_mouse_window(&shared->mouse, &s) == NULL) + if (!event->m.valid || cmd_mouse_window(&event->m, &s) == NULL) return (CMD_RETURN_NORMAL); if (c == NULL || c->session != s) return (CMD_RETURN_NORMAL); c->tty.mouse_drag_update = cmd_resize_pane_mouse_update; - cmd_resize_pane_mouse_update(c, &shared->mouse); + cmd_resize_pane_mouse_update(c, &event->m); return (CMD_RETURN_NORMAL); } @@ -78,73 +91,35 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) else window_zoom(wp); server_redraw_window(w); - server_status_window(w); return (CMD_RETURN_NORMAL); } server_unzoom_window(w); - if (args->argc == 0) + if (args_count(args) == 0) adjust = 1; else { - adjust = strtonum(args->argv[0], 1, INT_MAX, &errstr); + adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr); if (errstr != NULL) { cmdq_error(item, "adjustment %s", errstr); return (CMD_RETURN_ERROR); } } - if ((p = args_get(args, 'x')) != NULL) { - plen = strlen(p); - if (p[plen - 1] == '%') { - copy = xstrdup(p); - copy[plen - 1] = '\0'; - percentage = strtonum(copy, 0, INT_MAX, &errstr); - free(copy); - if (errstr != NULL) { - cmdq_error(item, "width %s", errstr); - return (CMD_RETURN_ERROR); - } - x = (w->sx * percentage) / 100; - if (x < PANE_MINIMUM) - x = PANE_MINIMUM; - if (x > INT_MAX) - x = INT_MAX; - } else { - x = args_strtonum(args, 'x', PANE_MINIMUM, INT_MAX, - &cause); - if (cause != NULL) { - cmdq_error(item, "width %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } + if (args_has(args, 'x')) { + x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause); + if (cause != NULL) { + cmdq_error(item, "width %s", cause); + free(cause); + return (CMD_RETURN_ERROR); } layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); } - if ((p = args_get(args, 'y')) != NULL) { - plen = strlen(p); - if (p[plen - 1] == '%') { - copy = xstrdup(p); - copy[plen - 1] = '\0'; - percentage = strtonum(copy, 0, INT_MAX, &errstr); - free(copy); - if (errstr != NULL) { - cmdq_error(item, "height %s", errstr); - return (CMD_RETURN_ERROR); - } - y = (w->sy * percentage) / 100; - if (y < PANE_MINIMUM) - y = PANE_MINIMUM; - if (y > INT_MAX) - y = INT_MAX; - } - else { - y = args_strtonum(args, 'y', PANE_MINIMUM, INT_MAX, - &cause); - if (cause != NULL) { - cmdq_error(item, "height %s", cause); - free(cause); - return (CMD_RETURN_ERROR); - } + if (args_has(args, 'y')) { + y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause); + if (cause != NULL) { + cmdq_error(item, "height %s", cause); + free(cause); + return (CMD_RETURN_ERROR); } layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); } diff --git a/cmd-resize-window.c b/cmd-resize-window.c index 9cc74e82..ad739165 100644 --- a/cmd-resize-window.c +++ b/cmd-resize-window.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_resize_window_entry = { .name = "resize-window", .alias = "resizew", - .args = { "aADLRt:Ux:y:", 0, 1 }, + .args = { "aADLRt:Ux:y:", 0, 1, NULL }, .usage = "[-aADLRU] [-x width] [-y height] " CMD_TARGET_WINDOW_USAGE " " "[adjustment]", @@ -46,19 +46,20 @@ const struct cmd_entry cmd_resize_window_entry = { static enum cmd_retval cmd_resize_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; struct window *w = wl->window; - struct session *s = item->target.s; + struct session *s = target->s; const char *errstr; char *cause; u_int adjust, sx, sy; int xpixel = -1, ypixel = -1; - if (args->argc == 0) + if (args_count(args) == 0) adjust = 1; else { - adjust = strtonum(args->argv[0], 1, INT_MAX, &errstr); + adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr); if (errstr != NULL) { cmdq_error(item, "adjustment %s", errstr); return (CMD_RETURN_ERROR); @@ -107,7 +108,9 @@ cmd_resize_window_exec(struct cmd *self, struct cmdq_item *item) } options_set_number(w->options, "window-size", WINDOW_SIZE_MANUAL); - resize_window(w, sx, sy, xpixel, ypixel); + w->manual_sx = sx; + w->manual_sy = sy; + recalculate_size(w, 1); return (CMD_RETURN_NORMAL); } diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index 5e23fa15..6d60002e 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -34,9 +34,9 @@ const struct cmd_entry cmd_respawn_pane_entry = { .name = "respawn-pane", .alias = "respawnp", - .args = { "c:e:kt:", 0, -1 }, + .args = { "c:e:kt:", 0, -1, NULL }, .usage = "[-k] [-c start-directory] [-e environment] " - CMD_TARGET_PANE_USAGE " [command]", + CMD_TARGET_PANE_USAGE " [shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -47,32 +47,28 @@ const struct cmd_entry cmd_respawn_pane_entry = { static enum cmd_retval cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct spawn_context sc; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane *wp = target->wp; char *cause = NULL; - const char *add; - struct args_value *value; + struct args_value *av; - memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; sc.wl = wl; sc.wp0 = wp; - sc.lc = NULL; - sc.name = NULL; - sc.argc = args->argc; - sc.argv = args->argv; + args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); - add = args_first_value(args, 'e', &value); - while (add != NULL) { - environ_put(sc.environ, add); - add = args_next_value(&value); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); } sc.idx = -1; @@ -85,12 +81,18 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item) if (spawn_pane(&sc, &cause) == NULL) { cmdq_error(item, "respawn pane failed: %s", cause); free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); return (CMD_RETURN_ERROR); } wp->flags |= PANE_REDRAW; + server_redraw_window_borders(wp->window); server_status_window(wp->window); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); return (CMD_RETURN_NORMAL); } diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c index 497e401e..9a1a02c9 100644 --- a/cmd-respawn-window.c +++ b/cmd-respawn-window.c @@ -34,9 +34,9 @@ const struct cmd_entry cmd_respawn_window_entry = { .name = "respawn-window", .alias = "respawnw", - .args = { "c:e:kt:", 0, -1 }, + .args = { "c:e:kt:", 0, -1, NULL }, .usage = "[-k] [-c start-directory] [-e environment] " - CMD_TARGET_WINDOW_USAGE " [command]", + CMD_TARGET_WINDOW_USAGE " [shell-command]", .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -47,29 +47,27 @@ const struct cmd_entry cmd_respawn_window_entry = { static enum cmd_retval cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct spawn_context sc; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; char *cause = NULL; - const char *add; - struct args_value *value; + struct args_value *av; - memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; sc.wl = wl; - sc.c = cmd_find_client(item, NULL, 1); + sc.tc = tc; - sc.name = NULL; - sc.argc = args->argc; - sc.argv = args->argv; + args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); - add = args_first_value(args, 'e', &value); - while (add != NULL) { - environ_put(sc.environ, add); - add = args_next_value(&value); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); } sc.idx = -1; @@ -82,11 +80,16 @@ cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item) if (spawn_window(&sc, &cause) == NULL) { cmdq_error(item, "respawn window failed: %s", cause); free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); return (CMD_RETURN_ERROR); } server_redraw_window(wl->window); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); return (CMD_RETURN_NORMAL); } diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index cc661163..0e2ed852 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -31,7 +31,7 @@ const struct cmd_entry cmd_rotate_window_entry = { .name = "rotate-window", .alias = "rotatew", - .args = { "Dt:UZ", 0, 0 }, + .args = { "Dt:UZ", 0, 0, NULL }, .usage = "[-DUZ] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -43,16 +43,18 @@ const struct cmd_entry cmd_rotate_window_entry = { static enum cmd_retval cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) { - struct cmd_find_state *current = &item->shared->current; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; struct window *w = wl->window; struct window_pane *wp, *wp2; struct layout_cell *lc; u_int sx, sy, xoff, yoff; - window_push_zoom(w, args_has(self->args, 'Z')); + window_push_zoom(w, 0, args_has(args, 'Z')); - if (args_has(self->args, 'D')) { + if (args_has(args, 'D')) { wp = TAILQ_LAST(&w->panes, window_panes); TAILQ_REMOVE(&w->panes, wp, entry); TAILQ_INSERT_HEAD(&w->panes, wp, entry); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 2f45f492..bf43d313 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -29,8 +30,12 @@ * Runs a command without a window. */ -static enum cmd_retval cmd_run_shell_exec(struct cmd *, struct cmdq_item *); +static enum args_parse_type cmd_run_shell_args_parse(struct args *, u_int, + char **); +static enum cmd_retval cmd_run_shell_exec(struct cmd *, + struct cmdq_item *); +static void cmd_run_shell_timer(int, short, void *); static void cmd_run_shell_callback(struct job *); static void cmd_run_shell_free(void *); static void cmd_run_shell_print(struct job *, const char *); @@ -39,8 +44,8 @@ const struct cmd_entry cmd_run_shell_entry = { .name = "run-shell", .alias = "run", - .args = { "bt:", 1, 1 }, - .usage = "[-b] " CMD_TARGET_PANE_USAGE " shell-command", + .args = { "bd:Ct:", 0, 1, cmd_run_shell_args_parse }, + .usage = "[-bC] [-d delay] " CMD_TARGET_PANE_USAGE " [shell-command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -49,11 +54,26 @@ const struct cmd_entry cmd_run_shell_entry = { }; struct cmd_run_shell_data { - char *cmd; - struct cmdq_item *item; - int wp_id; + struct client *client; + char *cmd; + struct args_command_state *state; + char *cwd; + struct cmdq_item *item; + struct session *s; + int wp_id; + struct event timer; + int flags; }; +static enum args_parse_type +cmd_run_shell_args_parse(struct args *args, __unused u_int idx, + __unused char **cause) +{ + if (args_has(args, 'C')) + return (ARGS_PARSE_COMMANDS_OR_STRING); + return (ARGS_PARSE_STRING); +} + static void cmd_run_shell_print(struct job *job, const char *msg) { @@ -78,48 +98,130 @@ 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, &window_view_mode, NULL, NULL); + window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); window_copy_add(wp, "%s", msg); } static enum cmd_retval cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); struct cmd_run_shell_data *cdata; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct window_pane *wp = target->wp; + const char *delay, *cmd; + double d; + struct timeval tv; + char *end; + int wait = !args_has(args, 'b'); + + if ((delay = args_get(args, 'd')) != NULL) { + d = strtod(delay, &end); + if (*end != '\0') { + cmdq_error(item, "invalid delay time: %s", delay); + return (CMD_RETURN_ERROR); + } + } else if (args_count(args) == 0) + return (CMD_RETURN_NORMAL); cdata = xcalloc(1, sizeof *cdata); - cdata->cmd = format_single(item, args->argv[0], c, s, wl, wp); + if (!args_has(args, 'C')) { + cmd = args_string(args, 0); + if (cmd != NULL) + cdata->cmd = format_single_from_target(item, cmd); + } else { + cdata->state = args_make_commands_prepare(self, item, 0, NULL, + wait, 1); + } if (args_has(args, 't') && wp != NULL) cdata->wp_id = wp->id; else cdata->wp_id = -1; - if (!args_has(args, 'b')) + if (wait) { + cdata->client = cmdq_get_client(item); cdata->item = item; - - if (job_run(cdata->cmd, s, server_client_get_cwd(item->client, s), NULL, - cmd_run_shell_callback, cmd_run_shell_free, cdata, 0) == NULL) { - cmdq_error(item, "failed to run command: %s", cdata->cmd); - free(cdata); - return (CMD_RETURN_ERROR); + } else { + cdata->client = tc; + cdata->flags |= JOB_NOWAIT; } + if (cdata->client != NULL) + cdata->client->references++; - if (args_has(args, 'b')) + cdata->cwd = xstrdup(server_client_get_cwd(cmdq_get_client(item), s)); + + cdata->s = s; + if (s != NULL) + session_add_ref(s, __func__); + + evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata); + if (delay != NULL) { + timerclear(&tv); + tv.tv_sec = (time_t)d; + tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U; + evtimer_add(&cdata->timer, &tv); + } else + event_active(&cdata->timer, EV_TIMEOUT, 1); + + if (!wait) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } +static void +cmd_run_shell_timer(__unused int fd, __unused short events, void* arg) +{ + struct cmd_run_shell_data *cdata = arg; + struct client *c = cdata->client; + const char *cmd = cdata->cmd; + struct cmdq_item *item = cdata->item, *new_item; + struct cmd_list *cmdlist; + char *error; + + if (cdata->state == NULL) { + if (cmd == NULL) { + if (cdata->item != NULL) + cmdq_continue(cdata->item); + cmd_run_shell_free(cdata); + return; + } + if (job_run(cmd, 0, NULL, cdata->s, cdata->cwd, NULL, + cmd_run_shell_callback, cmd_run_shell_free, cdata, + cdata->flags, -1, -1) == NULL) + cmd_run_shell_free(cdata); + return; + } + + cmdlist = args_make_commands(cdata->state, 0, NULL, &error); + if (cmdlist == NULL) { + if (cdata->item == NULL) { + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, "%s", error); + } else + cmdq_error(cdata->item, "%s", error); + free(error); + } else if (item == NULL) { + new_item = cmdq_get_command(cmdlist, NULL); + cmdq_append(c, new_item); + } else { + new_item = cmdq_get_command(cmdlist, cmdq_get_state(item)); + cmdq_insert_after(item, new_item); + } + + if (cdata->item != NULL) + cmdq_continue(cdata->item); + cmd_run_shell_free(cdata); +} + static void cmd_run_shell_callback(struct job *job) { struct cmd_run_shell_data *cdata = job_get_data(job); struct bufferevent *event = job_get_event(job); + struct cmdq_item *item = cdata->item; char *cmd = cdata->cmd, *msg = NULL, *line; size_t size; int retcode, status; @@ -149,13 +251,19 @@ cmd_run_shell_callback(struct job *job) } else if (WIFSIGNALED(status)) { retcode = WTERMSIG(status); xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode); - } + retcode += 128; + } else + retcode = 0; if (msg != NULL) cmd_run_shell_print(job, msg); free(msg); - if (cdata->item != NULL) - cmdq_continue(cdata->item); + if (item != NULL) { + if (cmdq_get_client(item) != NULL && + cmdq_get_client(item)->session == NULL) + cmdq_get_client(item)->retval = retcode; + cmdq_continue(item); + } } static void @@ -163,6 +271,14 @@ cmd_run_shell_free(void *data) { struct cmd_run_shell_data *cdata = data; + evtimer_del(&cdata->timer); + if (cdata->s != NULL) + session_remove_ref(cdata->s, __func__); + if (cdata->client != NULL) + server_client_unref(cdata->client); + if (cdata->state != NULL) + args_make_commands_free(cdata->state); + free(cdata->cwd); free(cdata->cmd); free(cdata); } diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 84e50242..7d678372 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -37,7 +37,7 @@ const struct cmd_entry cmd_save_buffer_entry = { .name = "save-buffer", .alias = "saveb", - .args = { "ab:", 1, 1 }, + .args = { "ab:", 1, 1, NULL }, .usage = "[-a] " CMD_BUFFER_USAGE " path", .flags = CMD_AFTERHOOK, @@ -48,7 +48,7 @@ const struct cmd_entry cmd_show_buffer_entry = { .name = "show-buffer", .alias = "showb", - .args = { "b:", 0, 0 }, + .args = { "b:", 0, 0, NULL }, .usage = CMD_BUFFER_USAGE, .flags = CMD_AFTERHOOK, @@ -72,16 +72,13 @@ cmd_save_buffer_done(__unused struct client *c, const char *path, int error, static enum cmd_retval cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; + struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); struct paste_buffer *pb; int flags; const char *bufname = args_get(args, 'b'), *bufdata; size_t bufsize; - char *path; + char *path, *tmp; if (bufname == NULL) { if ((pb = paste_get_top(NULL)) == NULL) { @@ -97,15 +94,22 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) } bufdata = paste_buffer_data(pb, &bufsize); - if (self->entry == &cmd_show_buffer_entry) + if (cmd_get_entry(self) == &cmd_show_buffer_entry) { + if (c->session != NULL || (c->flags & CLIENT_CONTROL)) { + utf8_stravisx(&tmp, bufdata, bufsize, + VIS_OCTAL|VIS_CSTYLE|VIS_TAB); + cmdq_print(item, "%s", tmp); + free(tmp); + return (CMD_RETURN_NORMAL); + } path = xstrdup("-"); - else - path = format_single(item, args->argv[0], c, s, wl, wp); - if (args_has(self->args, 'a')) + } else + path = format_single_from_target(item, args_string(args, 0)); + if (args_has(args, 'a')) flags = O_APPEND; else - flags = 0; - file_write(item->client, path, flags, bufdata, bufsize, + flags = O_TRUNC; + file_write(cmdq_get_client(item), path, flags, bufdata, bufsize, cmd_save_buffer_done, item); free(path); diff --git a/cmd-select-layout.c b/cmd-select-layout.c index 775d32c5..c857a0e1 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_select_layout_entry = { .name = "select-layout", .alias = "selectl", - .args = { "Enopt:", 0, 1 }, + .args = { "Enopt:", 0, 1, NULL }, .usage = "[-Enop] " CMD_TARGET_PANE_USAGE " [layout-name]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -46,7 +46,7 @@ const struct cmd_entry cmd_next_layout_entry = { .name = "next-layout", .alias = "nextl", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -59,7 +59,7 @@ const struct cmd_entry cmd_previous_layout_entry = { .name = "previous-layout", .alias = "prevl", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -71,20 +71,21 @@ const struct cmd_entry cmd_previous_layout_entry = { static enum cmd_retval cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; struct window *w = wl->window; - struct window_pane *wp = item->target.wp; + struct window_pane *wp = target->wp; const char *layoutname; char *oldlayout; int next, previous, layout; server_unzoom_window(w); - next = self->entry == &cmd_next_layout_entry; + next = (cmd_get_entry(self) == &cmd_next_layout_entry); if (args_has(args, 'n')) next = 1; - previous = self->entry == &cmd_previous_layout_entry; + previous = (cmd_get_entry(self) == &cmd_previous_layout_entry); if (args_has(args, 'p')) previous = 1; @@ -104,24 +105,24 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) goto changed; } + if (args_count(args) != 0) + layoutname = args_string(args, 0); + else if (args_has(args, 'o')) + layoutname = oldlayout; + else + layoutname = NULL; + if (!args_has(args, 'o')) { - if (args->argc == 0) + if (layoutname == NULL) layout = w->lastlayout; else - layout = layout_set_lookup(args->argv[0]); + layout = layout_set_lookup(layoutname); if (layout != -1) { layout_set_select(w, layout); goto changed; } } - if (args->argc != 0) - layoutname = args->argv[0]; - else if (args_has(args, 'o')) - layoutname = oldlayout; - else - layoutname = NULL; - if (layoutname != NULL) { if (layout_parse(w, layoutname) == -1) { cmdq_error(item, "can't set layout: %s", layoutname); diff --git a/cmd-select-pane.c b/cmd-select-pane.c index c63c7e61..ae21d4ce 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_select_pane_entry = { .name = "select-pane", .alias = "selectp", - .args = { "DdegLlMmP:RT:t:UZ", 0, 0 }, /* -P and -g deprecated */ + .args = { "DdegLlMmP:RT:t:UZ", 0, 0, NULL }, /* -P and -g deprecated */ .usage = "[-DdeLlMmRUZ] [-T title] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -46,7 +46,7 @@ const struct cmd_entry cmd_last_pane_entry = { .name = "last-pane", .alias = "lastp", - .args = { "det:Z", 0, 0 }, + .args = { "det:Z", 0, 0, NULL }, .usage = "[-deZ] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -83,19 +83,21 @@ cmd_select_pane_redraw(struct window *w) static enum cmd_retval cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; - struct client *c = cmd_find_client(item, NULL, 1); - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + const struct cmd_entry *entry = cmd_get_entry(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); + struct winlink *wl = target->wl; struct window *w = wl->window; - struct session *s = item->target.s; - struct window_pane *wp = item->target.wp, *lastwp, *markedwp; - char *pane_title; + struct session *s = target->s; + struct window_pane *wp = target->wp, *activewp, *lastwp, *markedwp; + struct options *oo = wp->options; + char *title; const char *style; - struct style *sy; struct options_entry *o; - if (self->entry == &cmd_last_pane_entry || args_has(args, 'l')) { + if (entry == &cmd_last_pane_entry || args_has(args, 'l')) { lastwp = w->last; if (lastwp == NULL && window_count_panes(w) == 2) { lastwp = TAILQ_PREV(w->active, window_panes, entry); @@ -106,12 +108,16 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "no last pane"); return (CMD_RETURN_ERROR); } - if (args_has(self->args, 'e')) + if (args_has(args, 'e')) { lastwp->flags &= ~PANE_INPUTOFF; - else if (args_has(self->args, 'd')) + server_redraw_window_borders(lastwp->window); + server_status_window(lastwp->window); + } else if (args_has(args, 'd')) { lastwp->flags |= PANE_INPUTOFF; - else { - if (window_push_zoom(w, args_has(self->args, 'Z'))) + server_redraw_window_borders(lastwp->window); + server_status_window(lastwp->window); + } else { + if (window_push_zoom(w, 0, args_has(args, 'Z'))) server_redraw_window(w); window_redraw_active_switch(w, lastwp); if (window_set_active_pane(w, lastwp, 1)) { @@ -127,7 +133,10 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'm') || args_has(args, 'M')) { if (args_has(args, 'm') && !window_pane_visible(wp)) return (CMD_RETURN_NORMAL); - lastwp = marked_pane.wp; + if (server_check_marked()) + lastwp = marked_pane.wp; + else + lastwp = NULL; if (args_has(args, 'M') || server_is_marked(s, wl, wp)) server_clear_marked(); @@ -136,83 +145,92 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) markedwp = marked_pane.wp; if (lastwp != NULL) { + lastwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); server_redraw_window_borders(lastwp->window); server_status_window(lastwp->window); } if (markedwp != NULL) { + markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); server_redraw_window_borders(markedwp->window); server_status_window(markedwp->window); } return (CMD_RETURN_NORMAL); } - if (args_has(self->args, 'P') || args_has(self->args, 'g')) { - if ((style = args_get(args, 'P')) != NULL) { - o = options_set_style(wp->options, "window-style", 0, - style); - if (o == NULL) { - cmdq_error(item, "bad style: %s", style); - return (CMD_RETURN_ERROR); - } - options_set_style(wp->options, "window-active-style", 0, - style); - wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); - } - if (args_has(self->args, 'g')) { - sy = options_get_style(wp->options, "window-style"); - cmdq_print(item, "%s", style_tostring(sy)); + style = args_get(args, 'P'); + if (style != NULL) { + o = options_set_string(oo, "window-style", 0, "%s", style); + if (o == NULL) { + cmdq_error(item, "bad style: %s", style); + return (CMD_RETURN_ERROR); } + options_set_string(oo, "window-active-style", 0, "%s", style); + wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + } + if (args_has(args, 'g')) { + cmdq_print(item, "%s", options_get_string(oo, "window-style")); return (CMD_RETURN_NORMAL); } - if (args_has(self->args, 'L')) { - window_push_zoom(w, 1); + if (args_has(args, 'L')) { + window_push_zoom(w, 0, 1); wp = window_pane_find_left(wp); window_pop_zoom(w); - } else if (args_has(self->args, 'R')) { - window_push_zoom(w, 1); + } else if (args_has(args, 'R')) { + window_push_zoom(w, 0, 1); wp = window_pane_find_right(wp); window_pop_zoom(w); - } else if (args_has(self->args, 'U')) { - window_push_zoom(w, 1); + } else if (args_has(args, 'U')) { + window_push_zoom(w, 0, 1); wp = window_pane_find_up(wp); window_pop_zoom(w); - } else if (args_has(self->args, 'D')) { - window_push_zoom(w, 1); + } else if (args_has(args, 'D')) { + window_push_zoom(w, 0, 1); wp = window_pane_find_down(wp); window_pop_zoom(w); } if (wp == NULL) return (CMD_RETURN_NORMAL); - if (args_has(self->args, 'e')) { + if (args_has(args, 'e')) { wp->flags &= ~PANE_INPUTOFF; + server_redraw_window_borders(wp->window); + server_status_window(wp->window); return (CMD_RETURN_NORMAL); } - if (args_has(self->args, 'd')) { + if (args_has(args, 'd')) { wp->flags |= PANE_INPUTOFF; + server_redraw_window_borders(wp->window); + server_status_window(wp->window); return (CMD_RETURN_NORMAL); } - if (args_has(self->args, 'T')) { - pane_title = format_single(item, args_get(self->args, 'T'), - c, s, wl, wp); - if (screen_set_title(&wp->base, pane_title)) + if (args_has(args, 'T')) { + title = format_single_from_target(item, args_get(args, 'T')); + if (screen_set_title(&wp->base, title)) { + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); server_status_window(wp->window); - free(pane_title); + } + free(title); return (CMD_RETURN_NORMAL); } - if (wp == w->active) + if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) + activewp = server_client_get_pane(c); + else + activewp = w->active; + if (wp == activewp) return (CMD_RETURN_NORMAL); - if (window_push_zoom(w, args_has(self->args, 'Z'))) + if (window_push_zoom(w, 0, args_has(args, 'Z'))) server_redraw_window(w); window_redraw_active_switch(w, wp); - if (window_set_active_pane(w, wp, 1)) { + if (c != NULL && c->session != NULL && (c->flags & CLIENT_ACTIVEPANE)) + server_client_set_pane(c, wp); + else if (window_set_active_pane(w, wp, 1)) cmd_find_from_winlink_pane(current, wl, wp, 0); - cmdq_insert_hook(s, item, current, "after-select-pane"); - cmd_select_pane_redraw(w); - } + cmdq_insert_hook(s, item, current, "after-select-pane"); + cmd_select_pane_redraw(w); if (window_pop_zoom(w)) server_redraw_window(w); diff --git a/cmd-select-window.c b/cmd-select-window.c index 54965e89..4aca3e60 100644 --- a/cmd-select-window.c +++ b/cmd-select-window.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_select_window_entry = { .name = "select-window", .alias = "selectw", - .args = { "lnpTt:", 0, 0 }, + .args = { "lnpTt:", 0, 0, NULL }, .usage = "[-lnpT] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -46,7 +46,7 @@ const struct cmd_entry cmd_next_window_entry = { .name = "next-window", .alias = "next", - .args = { "at:", 0, 0 }, + .args = { "at:", 0, 0, NULL }, .usage = "[-a] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -59,7 +59,7 @@ const struct cmd_entry cmd_previous_window_entry = { .name = "previous-window", .alias = "prev", - .args = { "at:", 0, 0 }, + .args = { "at:", 0, 0, NULL }, .usage = "[-a] " CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -72,7 +72,7 @@ const struct cmd_entry cmd_last_window_entry = { .name = "last-window", .alias = "last", - .args = { "t:", 0, 0 }, + .args = { "t:", 0, 0, NULL }, .usage = CMD_TARGET_SESSION_USAGE, .target = { 't', CMD_FIND_SESSION, 0 }, @@ -84,23 +84,26 @@ const struct cmd_entry cmd_last_window_entry = { static enum cmd_retval cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) { - struct cmd_find_state *current = &item->shared->current; - struct winlink *wl = item->target.wl; - struct session *s = item->target.s; + struct args *args = cmd_get_args(self); + struct client *c = cmdq_get_client(item); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct winlink *wl = target->wl; + struct session *s = target->s; int next, previous, last, activity; - next = self->entry == &cmd_next_window_entry; - if (args_has(self->args, 'n')) + next = (cmd_get_entry(self) == &cmd_next_window_entry); + if (args_has(args, 'n')) next = 1; - previous = self->entry == &cmd_previous_window_entry; - if (args_has(self->args, 'p')) + previous = (cmd_get_entry(self) == &cmd_previous_window_entry); + if (args_has(args, 'p')) previous = 1; - last = self->entry == &cmd_last_window_entry; - if (args_has(self->args, 'l')) + last = (cmd_get_entry(self) == &cmd_last_window_entry); + if (args_has(args, 'l')) last = 1; if (next || previous || last) { - activity = args_has(self->args, 'a'); + activity = args_has(args, 'a'); if (next) { if (session_next(s, activity) != 0) { cmdq_error(item, "no next window"); @@ -125,7 +128,7 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) * If -T and select-window is invoked on same window as * current, switch to previous window. */ - if (args_has(self->args, 'T') && wl == s->curw) { + if (args_has(args, 'T') && wl == s->curw) { if (session_last(s) != 0) { cmdq_error(item, "no last window"); return (-1); @@ -139,6 +142,8 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) } cmdq_insert_hook(s, item, current, "after-select-window"); } + if (c != NULL && c->session != NULL) + s->curw->window->latest = c; recalculate_sizes(); return (CMD_RETURN_NORMAL); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index cc04a73f..47fa1caa 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -33,7 +33,7 @@ const struct cmd_entry cmd_send_keys_entry = { .name = "send-keys", .alias = "send", - .args = { "FHlMN:Rt:X", 0, -1 }, + .args = { "FHlMN:Rt:X", 0, -1, NULL }, .usage = "[-FHlMRX] [-N repeat-count] " CMD_TARGET_PANE_USAGE " key ...", @@ -47,7 +47,7 @@ const struct cmd_entry cmd_send_prefix_entry = { .name = "send-prefix", .alias = NULL, - .args = { "2t:", 0, 0 }, + .args = { "2t:", 0, 0, NULL }, .usage = "[-2] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -57,43 +57,42 @@ const struct cmd_entry cmd_send_prefix_entry = { }; static struct cmdq_item * -cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, - struct cmdq_item *item, key_code key) +cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after, + key_code key) { - struct session *s = fs->s; - struct winlink *wl = fs->wl; - struct window_pane *wp = fs->wp; + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane *wp = target->wp; struct window_mode_entry *wme; struct key_table *table; struct key_binding *bd; - wme = TAILQ_FIRST(&fs->wp->modes); + wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode->key_table == NULL) { - if (options_get_number(fs->wp->window->options, "xterm-keys")) - key |= KEYC_XTERM; - if (window_pane_key(wp, item->client, s, wl, key, NULL) != 0) + if (window_pane_key(wp, tc, s, wl, key, NULL) != 0) return (NULL); return (item); } table = key_bindings_get_table(wme->mode->key_table(wme), 1); - bd = key_bindings_get(table, key & ~KEYC_XTERM); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); if (bd != NULL) { table->references++; - item = key_bindings_dispatch(bd, item, c, NULL, &item->target); + after = key_bindings_dispatch(bd, after, tc, NULL, target); key_bindings_unref_table(table); } - return (item); + return (after); } static struct cmdq_item * -cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, - struct cmdq_item *item, struct args *args, int i) +cmd_send_keys_inject_string(struct cmdq_item *item, struct cmdq_item *after, + struct args *args, int i) { - const char *s = args->argv[i]; - struct cmdq_item *new_item; - struct utf8_data *ud, *uc; - wchar_t wc; + const char *s = args_string(args, i); + struct utf8_data *ud, *loop; + utf8_char uc; key_code key; char *endptr; long n; @@ -103,45 +102,52 @@ cmd_send_keys_inject_string(struct client *c, struct cmd_find_state *fs, n = strtol(s, &endptr, 16); if (*s =='\0' || n < 0 || n > 0xff || *endptr != '\0') return (item); - return (cmd_send_keys_inject_key(c, fs, item, KEYC_LITERAL|n)); + return (cmd_send_keys_inject_key(item, after, KEYC_LITERAL|n)); } literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); if (key != KEYC_NONE && key != KEYC_UNKNOWN) { - new_item = cmd_send_keys_inject_key(c, fs, item, key); - if (new_item != NULL) - return (new_item); + after = cmd_send_keys_inject_key(item, after, key); + if (after != NULL) + return (after); } literal = 1; } if (literal) { ud = utf8_fromcstr(s); - for (uc = ud; uc->size != 0; uc++) { - if (utf8_combine(uc, &wc) != UTF8_DONE) - continue; - item = cmd_send_keys_inject_key(c, fs, item, wc); + for (loop = ud; loop->size != 0; loop++) { + if (loop->size == 1 && loop->data[0] <= 0x7f) + key = loop->data[0]; + else { + if (utf8_from_data(loop, &uc) != UTF8_DONE) + continue; + key = uc; + } + after = cmd_send_keys_inject_key(item, after, key); } free(ud); } - return (item); + return (after); } static enum cmd_retval cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c = cmd_find_client(item, NULL, 1); - struct cmd_find_state *fs = &item->target; - struct window_pane *wp = item->target.wp; - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct mouse_event *m = &item->shared->mouse; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane *wp = target->wp; + struct key_event *event = cmdq_get_event(item); + struct mouse_event *m = &event->m; struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); - int i; + struct cmdq_item *after = item; key_code key; - u_int np = 1; + u_int i, np = 1; + u_int count = args_count(args); char *cause = NULL; if (args_has(args, 'N')) { @@ -151,8 +157,8 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } - if (wme != NULL && (args_has(args, 'X') || args->argc == 0)) { - if (wme == NULL || wme->mode->command == NULL) { + if (wme != NULL && (args_has(args, 'X') || count == 0)) { + if (wme->mode->command == NULL) { cmdq_error(item, "not in a mode"); return (CMD_RETURN_ERROR); } @@ -167,7 +173,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) } if (!m->valid) m = NULL; - wme->mode->command(wme, c, s, wl, args, m); + wme->mode->command(wme, tc, s, wl, args, m); return (CMD_RETURN_NORMAL); } @@ -177,27 +183,36 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "no mouse target"); return (CMD_RETURN_ERROR); } - window_pane_key(wp, item->client, s, wl, m->key, m); + window_pane_key(wp, tc, s, wl, m->key, m); return (CMD_RETURN_NORMAL); } - if (self->entry == &cmd_send_prefix_entry) { + if (cmd_get_entry(self) == &cmd_send_prefix_entry) { if (args_has(args, '2')) key = options_get_number(s->options, "prefix2"); else key = options_get_number(s->options, "prefix"); - cmd_send_keys_inject_key(c, fs, item, key); + cmd_send_keys_inject_key(item, item, key); return (CMD_RETURN_NORMAL); } if (args_has(args, 'R')) { - window_pane_reset_palette(wp); - input_reset(wp, 1); + colour_palette_clear(&wp->palette); + input_reset(wp->ictx, 1); + wp->flags |= (PANE_STYLECHANGED|PANE_REDRAW); + } + + if (count == 0) { + for (; np != 0; np--) + cmd_send_keys_inject_key(item, NULL, event->key); + return (CMD_RETURN_NORMAL); } for (; np != 0; np--) { - for (i = 0; i < args->argc; i++) - item = cmd_send_keys_inject_string(c, fs, item, args, i); + for (i = 0; i < count; i++) { + after = cmd_send_keys_inject_string(item, after, args, + i); + } } return (CMD_RETURN_NORMAL); diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c index 96fdf450..9112683f 100644 --- a/cmd-set-buffer.c +++ b/cmd-set-buffer.c @@ -33,10 +33,11 @@ const struct cmd_entry cmd_set_buffer_entry = { .name = "set-buffer", .alias = "setb", - .args = { "ab:n:", 0, 1 }, - .usage = "[-a] " CMD_BUFFER_USAGE " [-n new-buffer-name] data", + .args = { "ab:t:n:w", 0, 1, NULL }, + .usage = "[-aw] " CMD_BUFFER_USAGE " [-n new-buffer-name] " + CMD_TARGET_CLIENT_USAGE " data", - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG|CMD_CLIENT_CANFAIL, .exec = cmd_set_buffer_exec }; @@ -44,7 +45,7 @@ const struct cmd_entry cmd_delete_buffer_entry = { .name = "delete-buffer", .alias = "deleteb", - .args = { "b:", 0, 0 }, + .args = { "b:", 0, 0, NULL }, .usage = CMD_BUFFER_USAGE, .flags = CMD_AFTERHOOK, @@ -54,7 +55,8 @@ const struct cmd_entry cmd_delete_buffer_entry = { static enum cmd_retval cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); struct paste_buffer *pb; char *bufdata, *cause; const char *bufname, *olddata; @@ -66,7 +68,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) else pb = paste_get_name(bufname); - if (self->entry == &cmd_delete_buffer_entry) { + if (cmd_get_entry(self) == &cmd_delete_buffer_entry) { if (pb == NULL) pb = paste_get_top(&bufname); if (pb == NULL) { @@ -92,11 +94,11 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (args->argc != 1) { + if (args_count(args) != 1) { cmdq_error(item, "no data specified"); return (CMD_RETURN_ERROR); } - if ((newsize = strlen(args->argv[0])) == 0) + if ((newsize = strlen(args_string(args, 0))) == 0) return (CMD_RETURN_NORMAL); bufsize = 0; @@ -109,7 +111,7 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) } bufdata = xrealloc(bufdata, bufsize + newsize); - memcpy(bufdata + bufsize, args->argv[0], newsize); + memcpy(bufdata + bufsize, args_string(args, 0), newsize); bufsize += newsize; if (paste_set(bufdata, bufsize, bufname, &cause) != 0) { @@ -118,6 +120,8 @@ cmd_set_buffer_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } + if (args_has(args, 'w') && tc != NULL) + tty_set_selection(&tc->tty, bufdata, bufsize); return (CMD_RETURN_NORMAL); } diff --git a/cmd-set-environment.c b/cmd-set-environment.c index a80acd01..cec1f3e3 100644 --- a/cmd-set-environment.c +++ b/cmd-set-environment.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_set_environment_entry = { .name = "set-environment", .alias = "setenv", - .args = { "grt:u", 1, 2 }, - .usage = "[-gru] " CMD_TARGET_SESSION_USAGE " name [value]", + .args = { "Fhgrt:u", 1, 2, NULL }, + .usage = "[-Fhgru] " CMD_TARGET_SESSION_USAGE " name [value]", .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, @@ -46,11 +46,14 @@ const struct cmd_entry cmd_set_environment_entry = { static enum cmd_retval cmd_set_environment_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct environ *env; - const char *name, *value, *target; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct environ *env; + const char *name = args_string(args, 0), *value; + const char *tflag; + char *expanded = NULL; + enum cmd_retval retval = CMD_RETURN_NORMAL; - name = args->argv[0]; if (*name == '\0') { cmdq_error(item, "empty variable name"); return (CMD_RETURN_ERROR); @@ -60,44 +63,57 @@ cmd_set_environment_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - if (args->argc < 2) + if (args_count(args) < 2) value = NULL; else - value = args->argv[1]; - - if (args_has(self->args, 'g')) + value = args_string(args, 1); + if (value != NULL && args_has(args, 'F')) { + expanded = format_single_from_target(item, value); + value = expanded; + } + if (args_has(args, 'g')) env = global_environ; else { - if (item->target.s == NULL) { - target = args_get(args, 't'); - if (target != NULL) - cmdq_error(item, "no such session: %s", target); + if (target->s == NULL) { + tflag = args_get(args, 't'); + if (tflag != NULL) + cmdq_error(item, "no such session: %s", tflag); else cmdq_error(item, "no current session"); - return (CMD_RETURN_ERROR); + retval = CMD_RETURN_ERROR; + goto out; } - env = item->target.s->environ; + env = target->s->environ; } - if (args_has(self->args, 'u')) { + if (args_has(args, 'u')) { if (value != NULL) { cmdq_error(item, "can't specify a value with -u"); - return (CMD_RETURN_ERROR); + retval = CMD_RETURN_ERROR; + goto out; } environ_unset(env, name); - } else if (args_has(self->args, 'r')) { + } else if (args_has(args, 'r')) { if (value != NULL) { cmdq_error(item, "can't specify a value with -r"); - return (CMD_RETURN_ERROR); + retval = CMD_RETURN_ERROR; + goto out; } environ_clear(env, name); } else { if (value == NULL) { cmdq_error(item, "no value specified"); - return (CMD_RETURN_ERROR); + retval = CMD_RETURN_ERROR; + goto out; } - environ_set(env, name, "%s", value); + + if (args_has(args, 'h')) + environ_set(env, name, ENVIRON_HIDDEN, "%s", value); + else + environ_set(env, name, 0, "%s", value); } - return (CMD_RETURN_NORMAL); +out: + free(expanded); + return (retval); } diff --git a/cmd-set-option.c b/cmd-set-option.c index 23b45230..fbe32cfa 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -18,7 +18,6 @@ #include -#include #include #include @@ -28,23 +27,17 @@ * Set an option. */ -static enum cmd_retval cmd_set_option_exec(struct cmd *, struct cmdq_item *); - -static int cmd_set_option_set(struct cmd *, struct cmdq_item *, - struct options *, struct options_entry *, const char *); -static int cmd_set_option_flag(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); -static int cmd_set_option_choice(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); +static enum args_parse_type cmd_set_option_args_parse(struct args *, + u_int, char **); +static enum cmd_retval cmd_set_option_exec(struct cmd *, + struct cmdq_item *); const struct cmd_entry cmd_set_option_entry = { .name = "set-option", .alias = "set", - .args = { "aFgopqst:uw", 1, 2 }, - .usage = "[-aFgopqsuw] " CMD_TARGET_PANE_USAGE " option [value]", + .args = { "aFgopqst:uUw", 1, 2, cmd_set_option_args_parse }, + .usage = "[-aFgopqsuUw] " CMD_TARGET_PANE_USAGE " option [value]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -56,7 +49,7 @@ const struct cmd_entry cmd_set_window_option_entry = { .name = "set-window-option", .alias = "setw", - .args = { "aFgoqt:u", 1, 2 }, + .args = { "aFgoqt:u", 1, 2, cmd_set_option_args_parse }, .usage = "[-aFgoqu] " CMD_TARGET_WINDOW_USAGE " option [value]", .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, @@ -69,41 +62,46 @@ const struct cmd_entry cmd_set_hook_entry = { .name = "set-hook", .alias = NULL, - .args = { "agRt:u", 1, 2 }, - .usage = "[-agRu] " CMD_TARGET_SESSION_USAGE " hook [command]", + .args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse }, + .usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]", - .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = CMD_AFTERHOOK, .exec = cmd_set_option_exec }; +static enum args_parse_type +cmd_set_option_args_parse(__unused struct args *args, u_int idx, + __unused char **cause) +{ + if (idx == 1) + return (ARGS_PARSE_COMMANDS_OR_STRING); + return (ARGS_PARSE_STRING); +} + static enum cmd_retval cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); int append = args_has(args, 'a'); - struct cmd_find_state *fs = &item->target; - struct client *c, *loop; - struct session *s = fs->s; - struct winlink *wl = fs->wl; - struct window *w; - struct window_pane *wp; + struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *loop; struct options *oo; - struct options_entry *parent, *o; - char *name, *argument, *value = NULL, *cause; + struct options_entry *parent, *o, *po; + char *name, *argument, *expanded = NULL; + char *cause; + const char *value; int window, idx, already, error, ambiguous; int scope; - struct style *sy; - window = (self->entry == &cmd_set_window_option_entry); + window = (cmd_get_entry(self) == &cmd_set_window_option_entry); /* Expand argument. */ - c = cmd_find_client(item, NULL, 1); - argument = format_single(item, args->argv[0], c, s, wl, NULL); + argument = format_single_from_target(item, args_string(args, 0)); /* If set-hook -R, fire the hook straight away. */ - if (self->entry == &cmd_set_hook_entry && args_has(args, 'R')) { + if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) { notify_hook(item, argument); free(argument); return (CMD_RETURN_NORMAL); @@ -120,15 +118,18 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "invalid option: %s", argument); goto fail; } - if (args->argc < 2) + if (args_count(args) < 2) value = NULL; - else if (args_has(args, 'F')) - value = format_single(item, args->argv[1], c, s, wl, NULL); else - value = xstrdup(args->argv[1]); + value = args_string(args, 1); + if (value != NULL && args_has(args, 'F')) { + expanded = format_single_from_target(item, value); + value = expanded; + } /* Get the scope and table for the option .*/ - scope = options_scope_from_name(args, window, name, fs, &oo, &cause); + scope = options_scope_from_name(args, window, name, target, &oo, + &cause); if (scope == OPTIONS_TABLE_NONE) { if (args_has(args, 'q')) goto out; @@ -140,7 +141,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) parent = options_get(oo, name); /* Check that array options and indexes match up. */ - if (idx != -1 && (*name == '@' || !options_isarray(parent))) { + if (idx != -1 && (*name == '@' || !options_is_array(parent))) { cmdq_error(item, "not an array: %s", argument); goto fail; } @@ -164,19 +165,22 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } /* Change the option. */ - if (args_has(args, 'u')) { + if (args_has(args, 'U') && scope == OPTIONS_TABLE_WINDOW) { + TAILQ_FOREACH(loop, &target->w->panes, entry) { + po = options_get_only(loop->options, name); + if (po == NULL) + continue; + if (options_remove_or_default(po, idx, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + goto fail; + } + } + } + if (args_has(args, 'u') || args_has(args, 'U')) { if (o == NULL) goto out; - if (idx == -1) { - if (*name == '@') - options_remove(o); - else if (oo == global_options || - oo == global_s_options || - oo == global_w_options) - options_default(oo, options_table_entry(o)); - else - options_remove(o); - } else if (options_array_set(o, idx, NULL, 0, &cause) != 0) { + if (options_remove_or_default(o, idx, &cause) != 0) { cmdq_error(item, "%s", cause); free(cause); goto fail; @@ -187,10 +191,15 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) goto fail; } options_set_string(oo, name, append, "%s", value); - } else if (idx == -1 && !options_isarray(parent)) { - error = cmd_set_option_set(self, item, oo, parent, value); - if (error != 0) + } else if (idx == -1 && !options_is_array(parent)) { + error = options_from_string(oo, options_table_entry(parent), + options_table_entry(parent)->name, value, + args_has(args, 'a'), &cause); + if (error != 0) { + cmdq_error(item, "%s", cause); + free(cause); goto fail; + } } else { if (value == NULL) { cmdq_error(item, "empty value"); @@ -214,198 +223,17 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } } - /* Update timers and so on for various options. */ - if (strcmp(name, "automatic-rename") == 0) { - RB_FOREACH(w, windows, &windows) { - if (w->active == NULL) - continue; - if (options_get_number(w->options, "automatic-rename")) - w->active->flags |= PANE_CHANGED; - } - } - if (strcmp(name, "key-table") == 0) { - TAILQ_FOREACH(loop, &clients, entry) - server_client_set_key_table(loop, NULL); - } - if (strcmp(name, "user-keys") == 0) { - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->tty.flags & TTY_OPENED) - tty_keys_build(&loop->tty); - } - } - if (strcmp(name, "status-fg") == 0 || strcmp(name, "status-bg") == 0) { - sy = options_get_style(oo, "status-style"); - sy->gc.fg = options_get_number(oo, "status-fg"); - sy->gc.bg = options_get_number(oo, "status-bg"); - } - if (strcmp(name, "status-style") == 0) { - sy = options_get_style(oo, "status-style"); - options_set_number(oo, "status-fg", sy->gc.fg); - options_set_number(oo, "status-bg", sy->gc.bg); - } - if (strcmp(name, "status") == 0 || - strcmp(name, "status-interval") == 0) - status_timer_start_all(); - if (strcmp(name, "monitor-silence") == 0) - alerts_reset_all(); - if (strcmp(name, "window-style") == 0 || - strcmp(name, "window-active-style") == 0) { - RB_FOREACH(wp, window_pane_tree, &all_window_panes) - wp->flags |= PANE_STYLECHANGED; - } - if (strcmp(name, "pane-border-status") == 0) { - RB_FOREACH(w, windows, &windows) - layout_fix_panes(w); - } - RB_FOREACH(s, sessions, &sessions) - status_update_cache(s); - - /* - * Update sizes and redraw. May not always be necessary but do it - * anyway. - */ - recalculate_sizes(); - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->session != NULL) - server_redraw_client(loop); - } + options_push_changes(name); out: free(argument); - free(value); + free(expanded); free(name); return (CMD_RETURN_NORMAL); fail: free(argument); - free(value); + free(expanded); free(name); return (CMD_RETURN_ERROR); } - -static int -cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, - struct options_entry *parent, const char *value) -{ - const struct options_table_entry *oe; - struct args *args = self->args; - int append = args_has(args, 'a'); - struct options_entry *o; - long long number; - const char *errstr, *new; - char *old; - key_code key; - - oe = options_table_entry(parent); - if (value == NULL && - oe->type != OPTIONS_TABLE_FLAG && - oe->type != OPTIONS_TABLE_CHOICE) { - cmdq_error(item, "empty value"); - return (-1); - } - - switch (oe->type) { - case OPTIONS_TABLE_STRING: - old = xstrdup(options_get_string(oo, oe->name)); - options_set_string(oo, oe->name, append, "%s", value); - new = options_get_string(oo, oe->name); - if (oe->pattern != NULL && fnmatch(oe->pattern, new, 0) != 0) { - options_set_string(oo, oe->name, 0, "%s", old); - free(old); - cmdq_error(item, "value is invalid: %s", value); - return (-1); - } - free(old); - return (0); - case OPTIONS_TABLE_NUMBER: - number = strtonum(value, oe->minimum, oe->maximum, &errstr); - if (errstr != NULL) { - cmdq_error(item, "value is %s: %s", errstr, value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_KEY: - key = key_string_lookup_string(value); - if (key == KEYC_UNKNOWN) { - cmdq_error(item, "bad key: %s", value); - return (-1); - } - options_set_number(oo, oe->name, key); - return (0); - case OPTIONS_TABLE_COLOUR: - if ((number = colour_fromstring(value)) == -1) { - cmdq_error(item, "bad colour: %s", value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_FLAG: - return (cmd_set_option_flag(item, oe, oo, value)); - case OPTIONS_TABLE_CHOICE: - return (cmd_set_option_choice(item, oe, oo, value)); - case OPTIONS_TABLE_STYLE: - o = options_set_style(oo, oe->name, append, value); - if (o == NULL) { - cmdq_error(item, "bad style: %s", value); - return (-1); - } - return (0); - case OPTIONS_TABLE_COMMAND: - break; - } - return (-1); -} - -static int -cmd_set_option_flag(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - int flag; - - if (value == NULL || *value == '\0') - flag = !options_get_number(oo, oe->name); - else if (strcmp(value, "1") == 0 || - strcasecmp(value, "on") == 0 || - strcasecmp(value, "yes") == 0) - flag = 1; - else if (strcmp(value, "0") == 0 || - strcasecmp(value, "off") == 0 || - strcasecmp(value, "no") == 0) - flag = 0; - else { - cmdq_error(item, "bad value: %s", value); - return (-1); - } - options_set_number(oo, oe->name, flag); - return (0); -} - -static int -cmd_set_option_choice(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - const char **cp; - int n, choice = -1; - - if (value == NULL) { - choice = options_get_number(oo, oe->name); - if (choice < 2) - choice = !choice; - } else { - n = 0; - for (cp = oe->choices; *cp != NULL; cp++) { - if (strcmp(*cp, value) == 0) - choice = n; - n++; - } - if (choice == -1) { - cmdq_error(item, "unknown value: %s", value); - return (-1); - } - } - options_set_number(oo, oe->name, choice); - return (0); -} diff --git a/cmd-show-environment.c b/cmd-show-environment.c index eb19cf20..b52db366 100644 --- a/cmd-show-environment.c +++ b/cmd-show-environment.c @@ -38,8 +38,8 @@ const struct cmd_entry cmd_show_environment_entry = { .name = "show-environment", .alias = "showenv", - .args = { "gst:", 0, 1 }, - .usage = "[-gs] " CMD_TARGET_SESSION_USAGE " [name]", + .args = { "hgst:", 0, 1, NULL }, + .usage = "[-hgs] " CMD_TARGET_SESSION_USAGE " [name]", .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, @@ -69,9 +69,15 @@ static void cmd_show_environment_print(struct cmd *self, struct cmdq_item *item, struct environ_entry *envent) { - char *escaped; + struct args *args = cmd_get_args(self); + char *escaped; - if (!args_has(self->args, 's')) { + if (!args_has(args, 'h') && (envent->flags & ENVIRON_HIDDEN)) + return; + if (args_has(args, 'h') && (~envent->flags & ENVIRON_HIDDEN)) + return; + + if (!args_has(args, 's')) { if (envent->value != NULL) cmdq_print(item, "%s=%s", envent->name, envent->value); else @@ -91,36 +97,37 @@ cmd_show_environment_print(struct cmd *self, struct cmdq_item *item, static enum cmd_retval cmd_show_environment_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); struct environ *env; struct environ_entry *envent; - const char *target; + const char *tflag, *name = args_string(args, 0); - if ((target = args_get(args, 't')) != NULL) { - if (item->target.s == NULL) { - cmdq_error(item, "no such session: %s", target); + if ((tflag = args_get(args, 't')) != NULL) { + if (target->s == NULL) { + cmdq_error(item, "no such session: %s", tflag); return (CMD_RETURN_ERROR); } } - if (args_has(self->args, 'g')) + if (args_has(args, 'g')) env = global_environ; else { - if (item->target.s == NULL) { - target = args_get(args, 't'); - if (target != NULL) - cmdq_error(item, "no such session: %s", target); + if (target->s == NULL) { + tflag = args_get(args, 't'); + if (tflag != NULL) + cmdq_error(item, "no such session: %s", tflag); else cmdq_error(item, "no current session"); return (CMD_RETURN_ERROR); } - env = item->target.s->environ; + env = target->s->environ; } - if (args->argc != 0) { - envent = environ_find(env, args->argv[0]); + if (name != NULL) { + envent = environ_find(env, name); if (envent == NULL) { - cmdq_error(item, "unknown variable: %s", args->argv[0]); + cmdq_error(item, "unknown variable: %s", name); return (CMD_RETURN_ERROR); } cmd_show_environment_print(self, item, envent); diff --git a/cmd-show-messages.c b/cmd-show-messages.c index 8da12374..dca3ab31 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -18,16 +18,19 @@ #include +#include #include -#include #include #include "tmux.h" /* - * Show client message log. + * Show message log. */ +#define SHOW_MESSAGES_TEMPLATE \ + "#{t/p:message_time}: #{message_text}" + static enum cmd_retval cmd_show_messages_exec(struct cmd *, struct cmdq_item *); @@ -35,29 +38,31 @@ const struct cmd_entry cmd_show_messages_entry = { .name = "show-messages", .alias = "showmsgs", - .args = { "JTt:", 0, 0 }, + .args = { "JTt:", 0, 0, NULL }, .usage = "[-JT] " CMD_TARGET_CLIENT_USAGE, - .flags = CMD_AFTERHOOK, + .flags = CMD_AFTERHOOK|CMD_CLIENT_TFLAG, .exec = cmd_show_messages_exec }; -static int cmd_show_messages_terminals(struct cmdq_item *, int); - static int -cmd_show_messages_terminals(struct cmdq_item *item, int blank) +cmd_show_messages_terminals(struct cmd *self, struct cmdq_item *item, int blank) { + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); struct tty_term *term; u_int i, n; n = 0; LIST_FOREACH(term, &tty_terms, entry) { + if (args_has(args, 't') && term != tc->tty.term) + continue; if (blank) { cmdq_print(item, "%s", ""); blank = 0; } - cmdq_print(item, "Terminal %u: %s [references=%u, flags=0x%x]:", - n, term->name, term->references, term->flags); + cmdq_print(item, "Terminal %u: %s for %s, flags=0x%x:", n, + term->name, term->tty->client->name, term->flags); n++; for (i = 0; i < tty_term_ncodes(); i++) cmdq_print(item, "%s", tty_term_describe(term, i)); @@ -68,18 +73,15 @@ cmd_show_messages_terminals(struct cmdq_item *item, int blank) static enum cmd_retval cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct client *c; + struct args *args = cmd_get_args(self); struct message_entry *msg; - char *tim; + char *s; int done, blank; - - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); + struct format_tree *ft; done = blank = 0; if (args_has(args, 'T')) { - blank = cmd_show_messages_terminals(item, blank); + blank = cmd_show_messages_terminals(self, item, blank); done = 1; } if (args_has(args, 'J')) { @@ -89,12 +91,17 @@ cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) if (done) return (CMD_RETURN_NORMAL); - TAILQ_FOREACH(msg, &c->message_log, entry) { - tim = ctime(&msg->msg_time); - *strchr(tim, '\n') = '\0'; + ft = format_create_from_target(item); + TAILQ_FOREACH_REVERSE(msg, &message_log, message_list, entry) { + format_add(ft, "message_text", "%s", msg->msg); + format_add(ft, "message_number", "%u", msg->msg_num); + format_add_tv(ft, "message_time", &msg->msg_time); - cmdq_print(item, "%s %s", tim, msg->msg); + s = format_expand(ft, SHOW_MESSAGES_TEMPLATE); + cmdq_print(item, "%s", s); + free(s); } + format_free(ft); return (CMD_RETURN_NORMAL); } diff --git a/cmd-show-options.c b/cmd-show-options.c index da481139..0e973ea0 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -38,7 +38,7 @@ const struct cmd_entry cmd_show_options_entry = { .name = "show-options", .alias = "show", - .args = { "AgHpqst:vw", 0, 1 }, + .args = { "AgHpqst:vw", 0, 1, NULL }, .usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -51,7 +51,7 @@ const struct cmd_entry cmd_show_window_options_entry = { .name = "show-window-options", .alias = "showw", - .args = { "gvt:", 0, 1 }, + .args = { "gvt:", 0, 1, NULL }, .usage = "[-gv] " CMD_TARGET_WINDOW_USAGE " [option]", .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, @@ -64,10 +64,10 @@ const struct cmd_entry cmd_show_hooks_entry = { .name = "show-hooks", .alias = NULL, - .args = { "gt:", 0, 1 }, - .usage = "[-g] " CMD_TARGET_SESSION_USAGE, + .args = { "gpt:w", 0, 1, NULL }, + .usage = "[-gpw] " CMD_TARGET_PANE_USAGE, - .target = { 't', CMD_FIND_SESSION, 0 }, + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = CMD_AFTERHOOK, .exec = cmd_show_options_exec @@ -76,20 +76,18 @@ const struct cmd_entry cmd_show_hooks_entry = { static enum cmd_retval cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *fs = &item->target; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); struct options *oo; char *argument, *name = NULL, *cause; int window, idx, ambiguous, parent, scope; struct options_entry *o; - window = (self->entry == &cmd_show_window_options_entry); + window = (cmd_get_entry(self) == &cmd_show_window_options_entry); - if (args->argc == 0) { - scope = options_scope_from_flags(args, window, fs, &oo, &cause); + if (args_count(args) == 0) { + scope = options_scope_from_flags(args, window, target, &oo, + &cause); if (scope == OPTIONS_TABLE_NONE) { if (args_has(args, 'q')) return (CMD_RETURN_NORMAL); @@ -99,7 +97,7 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) } return (cmd_show_options_all(self, item, scope, oo)); } - argument = format_single(item, args->argv[0], c, s, wl, NULL); + argument = format_single_from_target(item, args_string(args, 0)); name = options_match(argument, &idx, &ambiguous); if (name == NULL) { @@ -111,7 +109,8 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "invalid option: %s", argument); goto fail; } - scope = options_scope_from_name(args, window, name, fs, &oo, &cause); + scope = options_scope_from_name(args, window, name, target, &oo, + &cause); if (scope == OPTIONS_TABLE_NONE) { if (args_has(args, 'q')) goto fail; @@ -142,6 +141,7 @@ static void cmd_show_options_print(struct cmd *self, struct cmdq_item *item, struct options_entry *o, int idx, int parent) { + struct args *args = cmd_get_args(self); struct options_array_item *a; const char *name = options_name(o); char *value, *tmp = NULL, *escaped; @@ -150,10 +150,10 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, xasprintf(&tmp, "%s[%d]", name, idx); name = tmp; } else { - if (options_isarray(o)) { + if (options_is_array(o)) { a = options_array_first(o); if (a == NULL) { - if (!args_has(self->args, 'v')) + if (!args_has(args, 'v')) cmdq_print(item, "%s", name); return; } @@ -167,10 +167,10 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, } } - value = options_tostring(o, idx, 0); - if (args_has(self->args, 'v')) + value = options_to_string(o, idx, 0); + if (args_has(args, 'v')) cmdq_print(item, "%s", value); - else if (options_isstring(o)) { + else if (options_is_string(o)) { escaped = args_escape(value); if (parent) cmdq_print(item, "%s* %s", name, escaped); @@ -192,6 +192,7 @@ static enum cmd_retval cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, struct options *oo) { + struct args *args = cmd_get_args(self); const struct options_table_entry *oe; struct options_entry *o; struct options_array_item *a; @@ -199,28 +200,28 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, u_int idx; int parent; - o = options_first(oo); - while (o != NULL) { - if (options_table_entry(o) == NULL) - cmd_show_options_print(self, item, o, -1, 0); - o = options_next(o); + if (cmd_get_entry(self) != &cmd_show_hooks_entry) { + o = options_first(oo); + while (o != NULL) { + if (options_table_entry(o) == NULL) + cmd_show_options_print(self, item, o, -1, 0); + o = options_next(o); + } } for (oe = options_table; oe->name != NULL; oe++) { if (~oe->scope & scope) continue; - if ((self->entry != &cmd_show_hooks_entry && - !args_has(self->args, 'H') && - oe != NULL && + if ((cmd_get_entry(self) != &cmd_show_hooks_entry && + !args_has(args, 'H') && (oe->flags & OPTIONS_TABLE_IS_HOOK)) || - (self->entry == &cmd_show_hooks_entry && - (oe == NULL || - (~oe->flags & OPTIONS_TABLE_IS_HOOK)))) + (cmd_get_entry(self) == &cmd_show_hooks_entry && + (~oe->flags & OPTIONS_TABLE_IS_HOOK))) continue; o = options_get_only(oo, oe->name); if (o == NULL) { - if (!args_has(self->args, 'A')) + if (!args_has(args, 'A')) continue; o = options_get(oo, oe->name); if (o == NULL) @@ -229,10 +230,10 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, } else parent = 0; - if (!options_isarray(o)) + if (!options_is_array(o)) cmd_show_options_print(self, item, o, -1, parent); else if ((a = options_array_first(o)) == NULL) { - if (!args_has(self->args, 'v')) { + if (!args_has(args, 'v')) { name = options_name(o); if (parent) cmdq_print(item, "%s*", name); diff --git a/cmd-show-prompt-history.c b/cmd-show-prompt-history.c new file mode 100644 index 00000000..c85950b0 --- /dev/null +++ b/cmd-show-prompt-history.c @@ -0,0 +1,108 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Anindya Mukherjee + * + * 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 "tmux.h" + +#include + +/* + * Show or clear prompt history. + */ + +static enum cmd_retval cmd_show_prompt_history_exec(struct cmd *, + struct cmdq_item *); + +const struct cmd_entry cmd_show_prompt_history_entry = { + .name = "show-prompt-history", + .alias = "showphist", + + .args = { "T:", 0, 0, NULL }, + .usage = "[-T type]", + + .flags = CMD_AFTERHOOK, + .exec = cmd_show_prompt_history_exec +}; + +const struct cmd_entry cmd_clear_prompt_history_entry = { + .name = "clear-prompt-history", + .alias = "clearphist", + + .args = { "T:", 0, 0, NULL }, + .usage = "[-T type]", + + .flags = CMD_AFTERHOOK, + .exec = cmd_show_prompt_history_exec +}; + +static enum cmd_retval +cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + const char *typestr = args_get(args, 'T'); + enum prompt_type type; + u_int tidx, hidx; + + if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) { + if (typestr == NULL) { + for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { + free(status_prompt_hlist[tidx]); + status_prompt_hlist[tidx] = NULL; + status_prompt_hsize[tidx] = 0; + } + } else { + type = status_prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + cmdq_error(item, "invalid type: %s", typestr); + return (CMD_RETURN_ERROR); + } + free(status_prompt_hlist[type]); + status_prompt_hlist[type] = NULL; + status_prompt_hsize[type] = 0; + } + + return (CMD_RETURN_NORMAL); + } + + if (typestr == NULL) { + for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) { + cmdq_print(item, "History for %s:\n", + status_prompt_type_string(tidx)); + for (hidx = 0; hidx < status_prompt_hsize[tidx]; + hidx++) { + cmdq_print(item, "%d: %s", hidx + 1, + status_prompt_hlist[tidx][hidx]); + } + cmdq_print(item, "%s", ""); + } + } else { + type = status_prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + cmdq_error(item, "invalid type: %s", typestr); + return (CMD_RETURN_ERROR); + } + cmdq_print(item, "History for %s:\n", + status_prompt_type_string(type)); + for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) { + cmdq_print(item, "%d: %s", hidx + 1, + status_prompt_hlist[type][hidx]); + } + cmdq_print(item, "%s", ""); + } + + return (CMD_RETURN_NORMAL); +} diff --git a/cmd-source-file.c b/cmd-source-file.c index afe45a54..255d443e 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -35,8 +35,8 @@ const struct cmd_entry cmd_source_file_entry = { .name = "source-file", .alias = "source", - .args = { "nqv", 1, -1 }, - .usage = "[-nqv] path ...", + .args = { "Fnqv", 1, -1, NULL }, + .usage = "[-Fnqv] path ...", .flags = 0, .exec = cmd_source_file_exec @@ -65,14 +65,19 @@ static void cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata) { struct cmdq_item *new_item; + u_int i; if (cfg_finished) { - if (cdata->retval == CMD_RETURN_ERROR && c->session == NULL) + if (cdata->retval == CMD_RETURN_ERROR && + c != NULL && + c->session == NULL) c->retval = 1; new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL); cmdq_insert_after(cdata->after, new_item); } + for (i = 0; i < cdata->nfiles; i++) + free(cdata->files[i]); free(cdata->files); free(cdata); } @@ -122,15 +127,15 @@ cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path) static enum cmd_retval cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); struct cmd_source_file_data *cdata; - struct client *c = item->client; + struct client *c = cmdq_get_client(item); enum cmd_retval retval = CMD_RETURN_NORMAL; - char *pattern, *cwd; + char *pattern, *cwd, *expanded = NULL; const char *path, *error; glob_t g; - int i, result; - u_int j; + int result; + u_int i, j; cdata = xcalloc(1, sizeof *cdata); cdata->item = item; @@ -144,8 +149,13 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) utf8_stravis(&cwd, server_client_get_cwd(c, NULL), VIS_GLOB); - for (i = 0; i < args->argc; i++) { - path = args->argv[i]; + for (i = 0; i < args_count(args); i++) { + path = args_string(args, i); + if (args_has(args, 'F')) { + free(expanded); + expanded = format_single_from_target(item, path); + path = expanded; + } if (strcmp(path, "-") == 0) { cmd_source_file_add(cdata, "-"); continue; @@ -169,6 +179,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) cmdq_error(item, "%s: %s", path, error); retval = CMD_RETURN_ERROR; } + globfree(&g); free(pattern); continue; } @@ -176,7 +187,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) for (j = 0; j < g.gl_pathc; j++) cmd_source_file_add(cdata, g.gl_pathv[j]); + globfree(&g); } + free(expanded); cdata->after = item; cdata->retval = retval; diff --git a/cmd-split-window.c b/cmd-split-window.c index eaf1f74c..888d4106 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,9 +39,10 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:hIl:p:Pt:v", 0, -1 }, - .usage = "[-bdefhIPv] [-c start-directory] [-e environment] " - "[-F format] [-l size] " CMD_TARGET_PANE_USAGE " [command]", + .args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL }, + .usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] " + "[-F format] [-l size] " CMD_TARGET_PANE_USAGE + "[shell-command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -52,21 +53,23 @@ const struct cmd_entry cmd_split_window_entry = { static enum cmd_retval cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - struct cmd_find_state *current = &item->shared->current; - struct spawn_context sc; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp, *new_wp; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct spawn_context sc = { 0 }; + struct client *tc = cmdq_get_target_client(item); + struct session *s = target->s; + struct winlink *wl = target->wl; + struct window_pane *wp = target->wp, *new_wp; enum layout_type type; struct layout_cell *lc; struct cmd_find_state fs; int size, percentage, flags, input; - const char *template, *add, *errstr, *p; + const char *template, *errstr, *p; char *cause, *cp, *copy; size_t plen; - struct args_value *value; + struct args_value *av; + u_int count = args_count(args); if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; @@ -109,15 +112,15 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) } else size = -1; - server_unzoom_window(wp->window); - input = (args_has(args, 'I') && args->argc == 0); + window_push_zoom(wp->window, 1, args_has(args, 'Z')); + input = (args_has(args, 'I') && count == 0); flags = 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; - if (input || (args->argc == 1 && *args->argv[0] == '\0')) + if (input || (count == 1 && *args_string(args, 0) == '\0')) flags |= SPAWN_EMPTY; lc = layout_split_pane(wp, type, size, flags); @@ -126,7 +129,6 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - memset(&sc, 0, sizeof sc); sc.item = item; sc.s = s; sc.wl = wl; @@ -134,15 +136,13 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) sc.wp0 = wp; sc.lc = lc; - sc.name = NULL; - sc.argc = args->argc; - sc.argv = args->argv; + args_to_vector(args, &sc.argc, &sc.argv); sc.environ = environ_create(); - add = args_first_value(args, 'e', &value); - while (add != NULL) { - environ_put(sc.environ, add); - add = args_next_value(&value); + av = args_first_value(args, 'e'); + while (av != NULL) { + environ_put(sc.environ, av->string, 0); + av = args_next_value(av); } sc.idx = -1; @@ -151,29 +151,44 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) 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) { - layout_close_pane(new_wp); cmdq_error(item, "create pane failed: %s", cause); free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); return (CMD_RETURN_ERROR); } - if (input && window_pane_start_input(new_wp, item, &cause) != 0) { - layout_close_pane(new_wp); - window_remove_pane(wp->window, new_wp); - cmdq_error(item, "%s", cause); - free(cause); - return (CMD_RETURN_ERROR); + if (input) { + switch (window_pane_start_input(new_wp, item, &cause)) { + case -1: + server_client_remove_pane(new_wp); + layout_close_pane(new_wp); + window_remove_pane(wp->window, new_wp); + cmdq_error(item, "%s", cause); + free(cause); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); + environ_free(sc.environ); + return (CMD_RETURN_ERROR); + case 1: + input = 0; + break; + } } if (!args_has(args, 'd')) cmd_find_from_winlink_pane(current, wl, new_wp, 0); + window_pop_zoom(wp->window); server_redraw_window(wp->window); server_status_session(s); if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = SPLIT_WINDOW_TEMPLATE; - cp = format_single(item, template, c, s, wl, new_wp); + cp = format_single(item, template, tc, s, wl, new_wp); cmdq_print(item, "%s", cp); free(cp); } @@ -181,6 +196,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); cmdq_insert_hook(s, item, &fs, "after-split-window"); + if (sc.argv != NULL) + cmd_free_argv(sc.argc, sc.argv); environ_free(sc.environ); if (input) return (CMD_RETURN_WAIT); diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 3e0e6e60..7d477739 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -32,7 +32,7 @@ const struct cmd_entry cmd_swap_pane_entry = { .name = "swap-pane", .alias = "swapp", - .args = { "dDs:t:UZ", 0, 0 }, + .args = { "dDs:t:UZ", 0, 0, NULL }, .usage = "[-dDUZ] " CMD_SRCDST_PANE_USAGE, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, @@ -45,18 +45,20 @@ const struct cmd_entry cmd_swap_pane_entry = { static enum cmd_retval cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *source = cmdq_get_source(item); + struct cmd_find_state *target = cmdq_get_target(item); struct window *src_w, *dst_w; struct window_pane *tmp_wp, *src_wp, *dst_wp; struct layout_cell *src_lc, *dst_lc; u_int sx, sy, xoff, yoff; - dst_w = item->target.wl->window; - dst_wp = item->target.wp; - src_w = item->source.wl->window; - src_wp = item->source.wp; + dst_w = target->wl->window; + dst_wp = target->wp; + src_w = source->wl->window; + src_wp = source->wp; - if (window_push_zoom(dst_w, args_has(args, 'Z'))) + if (window_push_zoom(dst_w, 0, args_has(args, 'Z'))) server_redraw_window(dst_w); if (args_has(args, 'D')) { @@ -71,12 +73,15 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp = TAILQ_LAST(&dst_w->panes, window_panes); } - if (src_w != dst_w && window_push_zoom(src_w, args_has(args, 'Z'))) + if (src_w != dst_w && window_push_zoom(src_w, 0, args_has(args, 'Z'))) server_redraw_window(src_w); if (src_wp == dst_wp) goto out; + server_client_remove_pane(src_wp); + server_client_remove_pane(dst_wp); + tmp_wp = TAILQ_PREV(dst_wp, window_panes, entry); TAILQ_REMOVE(&dst_w->panes, dst_wp, entry); TAILQ_REPLACE(&src_w->panes, src_wp, dst_wp, entry); diff --git a/cmd-swap-window.c b/cmd-swap-window.c index 0c15479d..b765112b 100644 --- a/cmd-swap-window.c +++ b/cmd-swap-window.c @@ -32,7 +32,7 @@ const struct cmd_entry cmd_swap_window_entry = { .name = "swap-window", .alias = "swapw", - .args = { "ds:t:", 0, 0 }, + .args = { "ds:t:", 0, 0, NULL }, .usage = "[-d] " CMD_SRCDST_WINDOW_USAGE, .source = { 's', CMD_FIND_WINDOW, CMD_FIND_DEFAULT_MARKED }, @@ -45,20 +45,20 @@ const struct cmd_entry cmd_swap_window_entry = { static enum cmd_retval cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item) { - struct session *src, *dst; + struct args *args = cmd_get_args(self); + struct cmd_find_state *source = cmdq_get_source(item); + struct cmd_find_state *target = cmdq_get_target(item); + struct session *src = source->s, *dst = target->s; struct session_group *sg_src, *sg_dst; - struct winlink *wl_src, *wl_dst; + struct winlink *wl_src = source->wl, *wl_dst = target->wl; struct window *w_src, *w_dst; - wl_src = item->source.wl; - src = item->source.s; sg_src = session_group_contains(src); - - wl_dst = item->target.wl; - dst = item->target.s; sg_dst = session_group_contains(dst); - if (src != dst && sg_src != NULL && sg_dst != NULL && + if (src != dst && + sg_src != NULL && + sg_dst != NULL && sg_src == sg_dst) { cmdq_error(item, "can't move window, sessions are grouped"); return (CMD_RETURN_ERROR); @@ -77,7 +77,7 @@ cmd_swap_window_exec(struct cmd *self, struct cmdq_item *item) wl_src->window = w_dst; TAILQ_INSERT_TAIL(&w_dst->winlinks, wl_src, wentry); - if (args_has(self->args, 'd')) { + if (args_has(args, 'd')) { session_select(dst, wl_dst->idx); if (src != dst) session_select(src, wl_src->idx); diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 309a7e7c..dc1b621a 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -34,24 +34,26 @@ const struct cmd_entry cmd_switch_client_entry = { .name = "switch-client", .alias = "switchc", - .args = { "lc:Enpt:rT:Z", 0, 0 }, + .args = { "lc:EFnpt:rT:Z", 0, 0, NULL }, .usage = "[-ElnprZ] [-c target-client] [-t target-session] " "[-T key-table]", /* -t is special */ - .flags = CMD_READONLY, + .flags = CMD_READONLY|CMD_CLIENT_CFLAG, .exec = cmd_switch_client_exec }; static enum cmd_retval cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); + struct cmd_find_state *current = cmdq_get_current(item); + struct cmd_find_state target; const char *tflag = args_get(args, 't'); enum cmd_find_type type; int flags; - struct client *c; + struct client *tc = cmdq_get_target_client(item); struct session *s; struct winlink *wl; struct window *w; @@ -59,9 +61,6 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) const char *tablename; struct key_table *table; - if ((c = cmd_find_client(item, args_get(args, 'c'), 0)) == NULL) - return (CMD_RETURN_ERROR); - if (tflag != NULL && tflag[strcspn(tflag, ":.%")] != '\0') { type = CMD_FIND_PANE; flags = 0; @@ -69,15 +68,18 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) type = CMD_FIND_SESSION; flags = CMD_FIND_PREFER_UNATTACHED; } - if (cmd_find_target(&item->target, item, tflag, type, flags) != 0) + if (cmd_find_target(&target, item, tflag, type, flags) != 0) return (CMD_RETURN_ERROR); - s = item->target.s; - wl = item->target.wl; - w = wl->window; - wp = item->target.wp; + s = target.s; + wl = target.wl; + wp = target.wp; - if (args_has(args, 'r')) - c->flags ^= CLIENT_READONLY; + if (args_has(args, 'r')) { + if (tc->flags & CLIENT_READONLY) + tc->flags &= ~(CLIENT_READONLY|CLIENT_IGNORESIZE); + else + tc->flags |= (CLIENT_READONLY|CLIENT_IGNORESIZE); + } tablename = args_get(args, 'T'); if (tablename != NULL) { @@ -87,24 +89,24 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } table->references++; - key_bindings_unref_table(c->keytable); - c->keytable = table; + key_bindings_unref_table(tc->keytable); + tc->keytable = table; return (CMD_RETURN_NORMAL); } if (args_has(args, 'n')) { - if ((s = session_next_session(c->session)) == NULL) { + if ((s = session_next_session(tc->session)) == NULL) { cmdq_error(item, "can't find next session"); return (CMD_RETURN_ERROR); } } else if (args_has(args, 'p')) { - if ((s = session_previous_session(c->session)) == NULL) { + if ((s = session_previous_session(tc->session)) == NULL) { cmdq_error(item, "can't find previous session"); return (CMD_RETURN_ERROR); } } else if (args_has(args, 'l')) { - if (c->last_session != NULL && session_alive(c->last_session)) - s = c->last_session; + if (tc->last_session != NULL && session_alive(tc->last_session)) + s = tc->last_session; else s = NULL; if (s == NULL) { @@ -112,10 +114,11 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } } else { - if (item->client == NULL) + if (cmdq_get_client(item) == NULL) return (CMD_RETURN_NORMAL); - if (wl != NULL && wp != NULL) { - if (window_push_zoom(w, args_has(self->args, 'Z'))) + if (wl != NULL && wp != NULL && wp != wl->window->active) { + w = wl->window; + if (window_push_zoom(w, 0, args_has(args, 'Z'))) server_redraw_window(w); window_redraw_active_switch(w, wp); window_set_active_pane(w, wp, 1); @@ -124,29 +127,16 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) } if (wl != NULL) { session_set_current(s, wl); - cmd_find_from_session(&item->shared->current, s, 0); + cmd_find_from_session(current, s, 0); } } if (!args_has(args, 'E')) - environ_update(s->options, c->environ, s->environ); + environ_update(s->options, tc->environ, s->environ); - if (c->session != NULL && c->session != s) - c->last_session = c->session; - c->session = s; - if (~item->shared->flags & CMDQ_SHARED_REPEAT) - server_client_set_key_table(c, NULL); - tty_update_client_offset(c); - status_timer_start(c); - notify_client("client-session-changed", c); - session_update_activity(s, NULL); - gettimeofday(&s->last_attached_time, NULL); - - recalculate_sizes(); - server_check_unattached(); - server_redraw_client(c); - s->curw->flags &= ~WINLINK_ALERTFLAGS; - alerts_check_session(s); + server_client_set_session(tc, s); + if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT) + server_client_set_key_table(tc, NULL); return (CMD_RETURN_NORMAL); } diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c index d65ac91a..6d91d7cc 100644 --- a/cmd-unbind-key.c +++ b/cmd-unbind-key.c @@ -32,8 +32,8 @@ const struct cmd_entry cmd_unbind_key_entry = { .name = "unbind-key", .alias = "unbind", - .args = { "anT:", 0, 1 }, - .usage = "[-an] [-T key-table] key", + .args = { "anqT:", 0, 1, NULL }, + .usage = "[-anq] [-T key-table] key", .flags = CMD_AFTERHOOK, .exec = cmd_unbind_key_exec @@ -42,47 +42,57 @@ const struct cmd_entry cmd_unbind_key_entry = { static enum cmd_retval cmd_unbind_key_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; + struct args *args = cmd_get_args(self); key_code key; - const char *tablename; + const char *tablename, *keystr = args_string(args, 0); + int quiet = args_has(args, 'q'); - if (!args_has(args, 'a')) { - if (args->argc != 1) { - cmdq_error(item, "missing key"); + if (args_has(args, 'a')) { + if (keystr != NULL) { + if (!quiet) + cmdq_error(item, "key given with -a"); return (CMD_RETURN_ERROR); } - key = key_string_lookup_string(args->argv[0]); - if (key == KEYC_NONE || key == KEYC_UNKNOWN) { - cmdq_error(item, "unknown key: %s", args->argv[0]); - return (CMD_RETURN_ERROR); - } - } else { - if (args->argc != 0) { - cmdq_error(item, "key given with -a"); - return (CMD_RETURN_ERROR); - } - key = KEYC_UNKNOWN; - } - if (key == KEYC_UNKNOWN) { tablename = args_get(args, 'T'); if (tablename == NULL) { - key_bindings_remove_table("root"); - key_bindings_remove_table("prefix"); - return (CMD_RETURN_NORMAL); + if (args_has(args, 'n')) + tablename = "root"; + else + tablename = "prefix"; } if (key_bindings_get_table(tablename, 0) == NULL) { - cmdq_error(item, "table %s doesn't exist", tablename); + if (!quiet) { + cmdq_error(item, "table %s doesn't exist" , + tablename); + } return (CMD_RETURN_ERROR); } + key_bindings_remove_table(tablename); return (CMD_RETURN_NORMAL); } + if (keystr == NULL) { + if (!quiet) + cmdq_error(item, "missing key"); + return (CMD_RETURN_ERROR); + } + + key = key_string_lookup_string(keystr); + if (key == KEYC_NONE || key == KEYC_UNKNOWN) { + if (!quiet) + cmdq_error(item, "unknown key: %s", keystr); + return (CMD_RETURN_ERROR); + } + if (args_has(args, 'T')) { tablename = args_get(args, 'T'); if (key_bindings_get_table(tablename, 0) == NULL) { - cmdq_error(item, "table %s doesn't exist", tablename); + if (!quiet) { + cmdq_error(item, "table %s doesn't exist" , + tablename); + } return (CMD_RETURN_ERROR); } } else if (args_has(args, 'n')) diff --git a/cmd-wait-for.c b/cmd-wait-for.c index 4f438a7f..8a6aa259 100644 --- a/cmd-wait-for.c +++ b/cmd-wait-for.c @@ -34,7 +34,7 @@ const struct cmd_entry cmd_wait_for_entry = { .name = "wait-for", .alias = "wait", - .args = { "LSU", 1, 1 }, + .args = { "LSU", 1, 1, NULL }, .usage = "[-L|-S|-U] channel", .flags = 0, @@ -120,12 +120,12 @@ cmd_wait_for_remove(struct wait_channel *wc) static enum cmd_retval cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - const char *name = args->argv[0]; - struct wait_channel *wc, wc0; + struct args *args = cmd_get_args(self); + const char *name = args_string(args, 0); + struct wait_channel *wc, find; - wc0.name = name; - wc = RB_FIND(wait_channels, &wait_channels, &wc0); + find.name = name; + wc = RB_FIND(wait_channels, &wait_channels, &find); if (args_has(args, 'S')) return (cmd_wait_for_signal(item, name, wc)); @@ -167,7 +167,7 @@ static enum cmd_retval cmd_wait_for_wait(struct cmdq_item *item, const char *name, struct wait_channel *wc) { - struct client *c = item->client; + struct client *c = cmdq_get_client(item); struct wait_item *wi; if (c == NULL) { @@ -198,7 +198,7 @@ cmd_wait_for_lock(struct cmdq_item *item, const char *name, { struct wait_item *wi; - if (item->client == NULL) { + if (cmdq_get_client(item) == NULL) { cmdq_error(item, "not able to lock"); return (CMD_RETURN_ERROR); } diff --git a/cmd.c b/cmd.c index 380eedcf..f8027dfe 100644 --- a/cmd.c +++ b/cmd.c @@ -35,14 +35,17 @@ extern const struct cmd_entry cmd_choose_buffer_entry; extern const struct cmd_entry cmd_choose_client_entry; extern const struct cmd_entry cmd_choose_tree_entry; extern const struct cmd_entry cmd_clear_history_entry; +extern const struct cmd_entry cmd_clear_prompt_history_entry; extern const struct cmd_entry cmd_clock_mode_entry; extern const struct cmd_entry cmd_command_prompt_entry; extern const struct cmd_entry cmd_confirm_before_entry; extern const struct cmd_entry cmd_copy_mode_entry; +extern const struct cmd_entry cmd_customize_mode_entry; extern const struct cmd_entry cmd_delete_buffer_entry; extern const struct cmd_entry cmd_detach_client_entry; extern const struct cmd_entry cmd_display_menu_entry; extern const struct cmd_entry cmd_display_message_entry; +extern const struct cmd_entry cmd_display_popup_entry; extern const struct cmd_entry cmd_display_panes_entry; extern const struct cmd_entry cmd_down_pane_entry; extern const struct cmd_entry cmd_find_window_entry; @@ -102,6 +105,7 @@ extern const struct cmd_entry cmd_show_environment_entry; extern const struct cmd_entry cmd_show_hooks_entry; extern const struct cmd_entry cmd_show_messages_entry; extern const struct cmd_entry cmd_show_options_entry; +extern const struct cmd_entry cmd_show_prompt_history_entry; extern const struct cmd_entry cmd_show_window_options_entry; extern const struct cmd_entry cmd_source_file_entry; extern const struct cmd_entry cmd_split_window_entry; @@ -124,14 +128,17 @@ const struct cmd_entry *cmd_table[] = { &cmd_choose_client_entry, &cmd_choose_tree_entry, &cmd_clear_history_entry, + &cmd_clear_prompt_history_entry, &cmd_clock_mode_entry, &cmd_command_prompt_entry, &cmd_confirm_before_entry, &cmd_copy_mode_entry, + &cmd_customize_mode_entry, &cmd_delete_buffer_entry, &cmd_detach_client_entry, &cmd_display_menu_entry, &cmd_display_message_entry, + &cmd_display_popup_entry, &cmd_display_panes_entry, &cmd_find_window_entry, &cmd_has_session_entry, @@ -190,6 +197,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_show_hooks_entry, &cmd_show_messages_entry, &cmd_show_options_entry, + &cmd_show_prompt_history_entry, &cmd_show_window_options_entry, &cmd_source_file_entry, &cmd_split_window_entry, @@ -204,8 +212,23 @@ const struct cmd_entry *cmd_table[] = { NULL }; +/* Instance of a command. */ +struct cmd { + const struct cmd_entry *entry; + struct args *args; + u_int group; + + char *file; + u_int line; + + TAILQ_ENTRY(cmd) qentry; +}; +TAILQ_HEAD(cmds, cmd); + +/* Next group number for new command list. */ static u_int cmd_list_next_group = 1; +/* Log an argument vector. */ void printflike(3, 4) cmd_log_argv(int argc, char **argv, const char *fmt, ...) { @@ -222,8 +245,9 @@ cmd_log_argv(int argc, char **argv, const char *fmt, ...) free(prefix); } +/* Prepend to an argument vector. */ void -cmd_prepend_argv(int *argc, char ***argv, char *arg) +cmd_prepend_argv(int *argc, char ***argv, const char *arg) { char **new_argv; int i; @@ -238,13 +262,15 @@ cmd_prepend_argv(int *argc, char ***argv, char *arg) (*argc)++; } +/* Append to an argument vector. */ void -cmd_append_argv(int *argc, char ***argv, char *arg) +cmd_append_argv(int *argc, char ***argv, const char *arg) { *argv = xreallocarray(*argv, (*argc) + 1, sizeof **argv); (*argv)[(*argc)++] = xstrdup(arg); } +/* Pack an argument vector up into a buffer. */ int cmd_pack_argv(int argc, char **argv, char *buf, size_t len) { @@ -267,6 +293,7 @@ cmd_pack_argv(int argc, char **argv, char *buf, size_t len) return (0); } +/* Unpack an argument vector from a packed buffer. */ int cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv) { @@ -295,6 +322,7 @@ cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv) return (0); } +/* Copy an argument vector, ensuring it is terminated by NULL. */ char ** cmd_copy_argv(int argc, char **argv) { @@ -311,6 +339,7 @@ cmd_copy_argv(int argc, char **argv) return (new_argv); } +/* Free an argument vector. */ void cmd_free_argv(int argc, char **argv) { @@ -323,32 +352,67 @@ cmd_free_argv(int argc, char **argv) free(argv); } +/* Convert argument vector to a string. */ char * cmd_stringify_argv(int argc, char **argv) { - char *buf; + char *buf = NULL, *s; + size_t len = 0; int i; - size_t len; if (argc == 0) return (xstrdup("")); - len = 0; - buf = NULL; - for (i = 0; i < argc; i++) { - len += strlen(argv[i]) + 1; + s = args_escape(argv[i]); + log_debug("%s: %u %s = %s", __func__, i, argv[i], s); + + len += strlen(s) + 1; buf = xrealloc(buf, len); if (i == 0) *buf = '\0'; else strlcat(buf, " ", len); - strlcat(buf, argv[i], len); + strlcat(buf, s, len); + + free(s); } return (buf); } +/* Get entry for command. */ +const struct cmd_entry * +cmd_get_entry(struct cmd *cmd) +{ + return (cmd->entry); +} + +/* Get arguments for command. */ +struct args * +cmd_get_args(struct cmd *cmd) +{ + return (cmd->args); +} + +/* Get group for command. */ +u_int +cmd_get_group(struct cmd *cmd) +{ + return (cmd->group); +} + +/* Get file and line for command. */ +void +cmd_get_source(struct cmd *cmd, const char **file, u_int *line) +{ + if (file != NULL) + *file = cmd->file; + if (line != NULL) + *line = cmd->line; +} + +/* Look for an alias for a command. */ char * cmd_get_alias(const char *name) { @@ -379,6 +443,7 @@ cmd_get_alias(const char *name) return (NULL); } +/* Look up a command entry by name. */ static const struct cmd_entry * cmd_find(const char *name, char **cause) { @@ -428,32 +493,34 @@ ambiguous: return (NULL); } +/* Parse a single command from an argument vector. */ struct cmd * -cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) +cmd_parse(struct args_value *values, u_int count, const char *file, u_int line, + char **cause) { const struct cmd_entry *entry; - const char *name; struct cmd *cmd; struct args *args; + char *error = NULL; - if (argc == 0) { + if (count == 0 || values[0].type != ARGS_STRING) { xasprintf(cause, "no command"); return (NULL); } - name = argv[0]; - - entry = cmd_find(name, cause); + entry = cmd_find(values[0].string, cause); if (entry == NULL) return (NULL); - cmd_log_argv(argc, argv, "%s: %s", __func__, entry->name); - args = args_parse(entry->args.template, argc, argv); - if (args == NULL) - goto usage; - if (entry->args.lower != -1 && args->argc < entry->args.lower) - goto usage; - if (entry->args.upper != -1 && args->argc > entry->args.upper) - goto usage; + args = args_parse(&entry->args, values, count, &error); + if (args == NULL && error == NULL) { + xasprintf(cause, "usage: %s %s", entry->name, entry->usage); + return (NULL); + } + if (args == NULL) { + xasprintf(cause, "command %s: %s", entry->name, error); + free(error); + return (NULL); + } cmd = xcalloc(1, sizeof *cmd); cmd->entry = entry; @@ -463,31 +530,37 @@ cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) cmd->file = xstrdup(file); cmd->line = line; - cmd->alias = NULL; - cmd->argc = argc; - cmd->argv = cmd_copy_argv(argc, argv); - return (cmd); - -usage: - if (args != NULL) - args_free(args); - xasprintf(cause, "usage: %s %s", entry->name, entry->usage); - return (NULL); } +/* Free a command. */ void cmd_free(struct cmd *cmd) { - free(cmd->alias); - cmd_free_argv(cmd->argc, cmd->argv); - free(cmd->file); args_free(cmd->args); free(cmd); } +/* Copy a command. */ +struct cmd * +cmd_copy(struct cmd *cmd, int argc, char **argv) +{ + struct cmd *new_cmd; + + new_cmd = xcalloc(1, sizeof *new_cmd); + new_cmd->entry = cmd->entry; + new_cmd->args = args_copy(cmd->args, argc, argv); + + if (cmd->file != NULL) + new_cmd->file = xstrdup(cmd->file); + new_cmd->line = cmd->line; + + return (new_cmd); +} + +/* Get a command as a string. */ char * cmd_print(struct cmd *cmd) { @@ -503,6 +576,7 @@ cmd_print(struct cmd *cmd) return (out); } +/* Create a new command list. */ struct cmd_list * cmd_list_new(void) { @@ -511,29 +585,39 @@ cmd_list_new(void) cmdlist = xcalloc(1, sizeof *cmdlist); cmdlist->references = 1; cmdlist->group = cmd_list_next_group++; - TAILQ_INIT(&cmdlist->list); + cmdlist->list = xcalloc(1, sizeof *cmdlist->list); + TAILQ_INIT(cmdlist->list); return (cmdlist); } +/* Append a command to a command list. */ void cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd) { cmd->group = cmdlist->group; - TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry); + TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry); } +/* Append all commands from one list to another. */ +void +cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from) +{ + struct cmd *cmd; + + TAILQ_FOREACH(cmd, from->list, qentry) + cmd->group = cmdlist->group; + TAILQ_CONCAT(cmdlist->list, from->list, qentry); +} + +/* Move all commands from one command list to another. */ void cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from) { - struct cmd *cmd, *cmd1; - - TAILQ_FOREACH_SAFE(cmd, &from->list, qentry, cmd1) { - TAILQ_REMOVE(&from->list, cmd, qentry); - TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry); - } + TAILQ_CONCAT(cmdlist->list, from->list, qentry); cmdlist->group = cmd_list_next_group++; } +/* Free a command list. */ void cmd_list_free(struct cmd_list *cmdlist) { @@ -542,36 +626,77 @@ cmd_list_free(struct cmd_list *cmdlist) if (--cmdlist->references != 0) return; - TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) { - TAILQ_REMOVE(&cmdlist->list, cmd, qentry); + TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) { + TAILQ_REMOVE(cmdlist->list, cmd, qentry); cmd_free(cmd); } - + free(cmdlist->list); free(cmdlist); } +/* Copy a command list, expanding %s in arguments. */ +struct cmd_list * +cmd_list_copy(struct cmd_list *cmdlist, int argc, char **argv) +{ + struct cmd *cmd; + struct cmd_list *new_cmdlist; + struct cmd *new_cmd; + u_int group = cmdlist->group; + char *s; + + s = cmd_list_print(cmdlist, 0); + log_debug("%s: %s", __func__, s); + free(s); + + new_cmdlist = cmd_list_new(); + TAILQ_FOREACH(cmd, cmdlist->list, qentry) { + if (cmd->group != group) { + new_cmdlist->group = cmd_list_next_group++; + group = cmd->group; + } + new_cmd = cmd_copy(cmd, argc, argv); + cmd_list_append(new_cmdlist, new_cmd); + } + + s = cmd_list_print(new_cmdlist, 0); + log_debug("%s: %s", __func__, s); + free(s); + + return (new_cmdlist); +} + +/* Get a command list as a string. */ char * cmd_list_print(struct cmd_list *cmdlist, int escaped) { - struct cmd *cmd; + struct cmd *cmd, *next; char *buf, *this; size_t len; len = 1; buf = xcalloc(1, len); - TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { + TAILQ_FOREACH(cmd, cmdlist->list, qentry) { this = cmd_print(cmd); - len += strlen(this) + 4; + len += strlen(this) + 6; buf = xrealloc(buf, len); strlcat(buf, this, len); - if (TAILQ_NEXT(cmd, qentry) != NULL) { - if (escaped) - strlcat(buf, " \\; ", len); - else - strlcat(buf, " ; ", len); + + next = TAILQ_NEXT(cmd, qentry); + if (next != NULL) { + if (cmd->group != next->group) { + if (escaped) + strlcat(buf, " \\;\\; ", len); + else + strlcat(buf, " ;; ", len); + } else { + if (escaped) + strlcat(buf, " \\; ", len); + else + strlcat(buf, " ; ", len); + } } free(this); @@ -580,6 +705,46 @@ cmd_list_print(struct cmd_list *cmdlist, int escaped) return (buf); } +/* Get first command in list. */ +struct cmd * +cmd_list_first(struct cmd_list *cmdlist) +{ + return (TAILQ_FIRST(cmdlist->list)); +} + +/* Get next command in list. */ +struct cmd * +cmd_list_next(struct cmd *cmd) +{ + return (TAILQ_NEXT(cmd, qentry)); +} + +/* Do all of the commands in this command list have this flag? */ +int +cmd_list_all_have(struct cmd_list *cmdlist, int flag) +{ + struct cmd *cmd; + + TAILQ_FOREACH(cmd, cmdlist->list, qentry) { + if (~cmd->entry->flags & flag) + return (0); + } + return (1); +} + +/* Do any of the commands in this command list have this flag? */ +int +cmd_list_any_have(struct cmd_list *cmdlist, int flag) +{ + struct cmd *cmd; + + TAILQ_FOREACH(cmd, cmdlist->list, qentry) { + if (cmd->entry->flags & flag) + return (1); + } + return (0); +} + /* Adjust current mouse position for a pane. */ int cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, diff --git a/colour.c b/colour.c index c7972878..9ac07415 100644 --- a/colour.c +++ b/colour.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "tmux.h" @@ -111,6 +112,9 @@ colour_tostring(int c) static char s[32]; u_char r, g, b; + if (c == -1) + return ("invalid"); + if (c & COLOUR_FLAG_RGB) { colour_split_rgb(c, &r, &g, &b); xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b); @@ -189,6 +193,12 @@ colour_fromstring(const char *s) return (-1); return (n | COLOUR_FLAG_256); } + if (strncasecmp(s, "color", (sizeof "color") - 1) == 0) { + n = strtonum(s + (sizeof "color") - 1, 0, 255, &errstr); + if (errstr != NULL) + return (-1); + return (n | COLOUR_FLAG_256); + } if (strcasecmp(s, "default") == 0) return (8); @@ -227,7 +237,7 @@ colour_fromstring(const char *s) return (96); if (strcasecmp(s, "brightwhite") == 0 || strcmp(s, "97") == 0) return (97); - return (-1); + return (colour_byname(s)); } /* Convert 256 colour to RGB colour. */ @@ -329,3 +339,720 @@ colour_256to16(int c) return (table[c & 0xff]); } + +/* Get colour by X11 colour name. */ +int +colour_byname(const char *name) +{ + static const struct { + const char *name; + int c; + } colours[] = { + { "AliceBlue", 0xf0f8ff }, + { "AntiqueWhite", 0xfaebd7 }, + { "AntiqueWhite1", 0xffefdb }, + { "AntiqueWhite2", 0xeedfcc }, + { "AntiqueWhite3", 0xcdc0b0 }, + { "AntiqueWhite4", 0x8b8378 }, + { "BlanchedAlmond", 0xffebcd }, + { "BlueViolet", 0x8a2be2 }, + { "CadetBlue", 0x5f9ea0 }, + { "CadetBlue1", 0x98f5ff }, + { "CadetBlue2", 0x8ee5ee }, + { "CadetBlue3", 0x7ac5cd }, + { "CadetBlue4", 0x53868b }, + { "CornflowerBlue", 0x6495ed }, + { "DarkBlue", 0x00008b }, + { "DarkCyan", 0x008b8b }, + { "DarkGoldenrod", 0xb8860b }, + { "DarkGoldenrod1", 0xffb90f }, + { "DarkGoldenrod2", 0xeead0e }, + { "DarkGoldenrod3", 0xcd950c }, + { "DarkGoldenrod4", 0x8b6508 }, + { "DarkGray", 0xa9a9a9 }, + { "DarkGreen", 0x006400 }, + { "DarkGrey", 0xa9a9a9 }, + { "DarkKhaki", 0xbdb76b }, + { "DarkMagenta", 0x8b008b }, + { "DarkOliveGreen", 0x556b2f }, + { "DarkOliveGreen1", 0xcaff70 }, + { "DarkOliveGreen2", 0xbcee68 }, + { "DarkOliveGreen3", 0xa2cd5a }, + { "DarkOliveGreen4", 0x6e8b3d }, + { "DarkOrange", 0xff8c00 }, + { "DarkOrange1", 0xff7f00 }, + { "DarkOrange2", 0xee7600 }, + { "DarkOrange3", 0xcd6600 }, + { "DarkOrange4", 0x8b4500 }, + { "DarkOrchid", 0x9932cc }, + { "DarkOrchid1", 0xbf3eff }, + { "DarkOrchid2", 0xb23aee }, + { "DarkOrchid3", 0x9a32cd }, + { "DarkOrchid4", 0x68228b }, + { "DarkRed", 0x8b0000 }, + { "DarkSalmon", 0xe9967a }, + { "DarkSeaGreen", 0x8fbc8f }, + { "DarkSeaGreen1", 0xc1ffc1 }, + { "DarkSeaGreen2", 0xb4eeb4 }, + { "DarkSeaGreen3", 0x9bcd9b }, + { "DarkSeaGreen4", 0x698b69 }, + { "DarkSlateBlue", 0x483d8b }, + { "DarkSlateGray", 0x2f4f4f }, + { "DarkSlateGray1", 0x97ffff }, + { "DarkSlateGray2", 0x8deeee }, + { "DarkSlateGray3", 0x79cdcd }, + { "DarkSlateGray4", 0x528b8b }, + { "DarkSlateGrey", 0x2f4f4f }, + { "DarkTurquoise", 0x00ced1 }, + { "DarkViolet", 0x9400d3 }, + { "DeepPink", 0xff1493 }, + { "DeepPink1", 0xff1493 }, + { "DeepPink2", 0xee1289 }, + { "DeepPink3", 0xcd1076 }, + { "DeepPink4", 0x8b0a50 }, + { "DeepSkyBlue", 0x00bfff }, + { "DeepSkyBlue1", 0x00bfff }, + { "DeepSkyBlue2", 0x00b2ee }, + { "DeepSkyBlue3", 0x009acd }, + { "DeepSkyBlue4", 0x00688b }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1e90ff }, + { "DodgerBlue1", 0x1e90ff }, + { "DodgerBlue2", 0x1c86ee }, + { "DodgerBlue3", 0x1874cd }, + { "DodgerBlue4", 0x104e8b }, + { "FloralWhite", 0xfffaf0 }, + { "ForestGreen", 0x228b22 }, + { "GhostWhite", 0xf8f8ff }, + { "GreenYellow", 0xadff2f }, + { "HotPink", 0xff69b4 }, + { "HotPink1", 0xff6eb4 }, + { "HotPink2", 0xee6aa7 }, + { "HotPink3", 0xcd6090 }, + { "HotPink4", 0x8b3a62 }, + { "IndianRed", 0xcd5c5c }, + { "IndianRed1", 0xff6a6a }, + { "IndianRed2", 0xee6363 }, + { "IndianRed3", 0xcd5555 }, + { "IndianRed4", 0x8b3a3a }, + { "LavenderBlush", 0xfff0f5 }, + { "LavenderBlush1", 0xfff0f5 }, + { "LavenderBlush2", 0xeee0e5 }, + { "LavenderBlush3", 0xcdc1c5 }, + { "LavenderBlush4", 0x8b8386 }, + { "LawnGreen", 0x7cfc00 }, + { "LemonChiffon", 0xfffacd }, + { "LemonChiffon1", 0xfffacd }, + { "LemonChiffon2", 0xeee9bf }, + { "LemonChiffon3", 0xcdc9a5 }, + { "LemonChiffon4", 0x8b8970 }, + { "LightBlue", 0xadd8e6 }, + { "LightBlue1", 0xbfefff }, + { "LightBlue2", 0xb2dfee }, + { "LightBlue3", 0x9ac0cd }, + { "LightBlue4", 0x68838b }, + { "LightCoral", 0xf08080 }, + { "LightCyan", 0xe0ffff }, + { "LightCyan1", 0xe0ffff }, + { "LightCyan2", 0xd1eeee }, + { "LightCyan3", 0xb4cdcd }, + { "LightCyan4", 0x7a8b8b }, + { "LightGoldenrod", 0xeedd82 }, + { "LightGoldenrod1", 0xffec8b }, + { "LightGoldenrod2", 0xeedc82 }, + { "LightGoldenrod3", 0xcdbe70 }, + { "LightGoldenrod4", 0x8b814c }, + { "LightGoldenrodYellow", 0xfafad2 }, + { "LightGray", 0xd3d3d3 }, + { "LightGreen", 0x90ee90 }, + { "LightGrey", 0xd3d3d3 }, + { "LightPink", 0xffb6c1 }, + { "LightPink1", 0xffaeb9 }, + { "LightPink2", 0xeea2ad }, + { "LightPink3", 0xcd8c95 }, + { "LightPink4", 0x8b5f65 }, + { "LightSalmon", 0xffa07a }, + { "LightSalmon1", 0xffa07a }, + { "LightSalmon2", 0xee9572 }, + { "LightSalmon3", 0xcd8162 }, + { "LightSalmon4", 0x8b5742 }, + { "LightSeaGreen", 0x20b2aa }, + { "LightSkyBlue", 0x87cefa }, + { "LightSkyBlue1", 0xb0e2ff }, + { "LightSkyBlue2", 0xa4d3ee }, + { "LightSkyBlue3", 0x8db6cd }, + { "LightSkyBlue4", 0x607b8b }, + { "LightSlateBlue", 0x8470ff }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xb0c4de }, + { "LightSteelBlue1", 0xcae1ff }, + { "LightSteelBlue2", 0xbcd2ee }, + { "LightSteelBlue3", 0xa2b5cd }, + { "LightSteelBlue4", 0x6e7b8b }, + { "LightYellow", 0xffffe0 }, + { "LightYellow1", 0xffffe0 }, + { "LightYellow2", 0xeeeed1 }, + { "LightYellow3", 0xcdcdb4 }, + { "LightYellow4", 0x8b8b7a }, + { "LimeGreen", 0x32cd32 }, + { "MediumAquamarine", 0x66cdaa }, + { "MediumBlue", 0x0000cd }, + { "MediumOrchid", 0xba55d3 }, + { "MediumOrchid1", 0xe066ff }, + { "MediumOrchid2", 0xd15fee }, + { "MediumOrchid3", 0xb452cd }, + { "MediumOrchid4", 0x7a378b }, + { "MediumPurple", 0x9370db }, + { "MediumPurple1", 0xab82ff }, + { "MediumPurple2", 0x9f79ee }, + { "MediumPurple3", 0x8968cd }, + { "MediumPurple4", 0x5d478b }, + { "MediumSeaGreen", 0x3cb371 }, + { "MediumSlateBlue", 0x7b68ee }, + { "MediumSpringGreen", 0x00fa9a }, + { "MediumTurquoise", 0x48d1cc }, + { "MediumVioletRed", 0xc71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xf5fffa }, + { "MistyRose", 0xffe4e1 }, + { "MistyRose1", 0xffe4e1 }, + { "MistyRose2", 0xeed5d2 }, + { "MistyRose3", 0xcdb7b5 }, + { "MistyRose4", 0x8b7d7b }, + { "NavajoWhite", 0xffdead }, + { "NavajoWhite1", 0xffdead }, + { "NavajoWhite2", 0xeecfa1 }, + { "NavajoWhite3", 0xcdb38b }, + { "NavajoWhite4", 0x8b795e }, + { "NavyBlue", 0x000080 }, + { "OldLace", 0xfdf5e6 }, + { "OliveDrab", 0x6b8e23 }, + { "OliveDrab1", 0xc0ff3e }, + { "OliveDrab2", 0xb3ee3a }, + { "OliveDrab3", 0x9acd32 }, + { "OliveDrab4", 0x698b22 }, + { "OrangeRed", 0xff4500 }, + { "OrangeRed1", 0xff4500 }, + { "OrangeRed2", 0xee4000 }, + { "OrangeRed3", 0xcd3700 }, + { "OrangeRed4", 0x8b2500 }, + { "PaleGoldenrod", 0xeee8aa }, + { "PaleGreen", 0x98fb98 }, + { "PaleGreen1", 0x9aff9a }, + { "PaleGreen2", 0x90ee90 }, + { "PaleGreen3", 0x7ccd7c }, + { "PaleGreen4", 0x548b54 }, + { "PaleTurquoise", 0xafeeee }, + { "PaleTurquoise1", 0xbbffff }, + { "PaleTurquoise2", 0xaeeeee }, + { "PaleTurquoise3", 0x96cdcd }, + { "PaleTurquoise4", 0x668b8b }, + { "PaleVioletRed", 0xdb7093 }, + { "PaleVioletRed1", 0xff82ab }, + { "PaleVioletRed2", 0xee799f }, + { "PaleVioletRed3", 0xcd6889 }, + { "PaleVioletRed4", 0x8b475d }, + { "PapayaWhip", 0xffefd5 }, + { "PeachPuff", 0xffdab9 }, + { "PeachPuff1", 0xffdab9 }, + { "PeachPuff2", 0xeecbad }, + { "PeachPuff3", 0xcdaf95 }, + { "PeachPuff4", 0x8b7765 }, + { "PowderBlue", 0xb0e0e6 }, + { "RebeccaPurple", 0x663399 }, + { "RosyBrown", 0xbc8f8f }, + { "RosyBrown1", 0xffc1c1 }, + { "RosyBrown2", 0xeeb4b4 }, + { "RosyBrown3", 0xcd9b9b }, + { "RosyBrown4", 0x8b6969 }, + { "RoyalBlue", 0x4169e1 }, + { "RoyalBlue1", 0x4876ff }, + { "RoyalBlue2", 0x436eee }, + { "RoyalBlue3", 0x3a5fcd }, + { "RoyalBlue4", 0x27408b }, + { "SaddleBrown", 0x8b4513 }, + { "SandyBrown", 0xf4a460 }, + { "SeaGreen", 0x2e8b57 }, + { "SeaGreen1", 0x54ff9f }, + { "SeaGreen2", 0x4eee94 }, + { "SeaGreen3", 0x43cd80 }, + { "SeaGreen4", 0x2e8b57 }, + { "SkyBlue", 0x87ceeb }, + { "SkyBlue1", 0x87ceff }, + { "SkyBlue2", 0x7ec0ee }, + { "SkyBlue3", 0x6ca6cd }, + { "SkyBlue4", 0x4a708b }, + { "SlateBlue", 0x6a5acd }, + { "SlateBlue1", 0x836fff }, + { "SlateBlue2", 0x7a67ee }, + { "SlateBlue3", 0x6959cd }, + { "SlateBlue4", 0x473c8b }, + { "SlateGray", 0x708090 }, + { "SlateGray1", 0xc6e2ff }, + { "SlateGray2", 0xb9d3ee }, + { "SlateGray3", 0x9fb6cd }, + { "SlateGray4", 0x6c7b8b }, + { "SlateGrey", 0x708090 }, + { "SpringGreen", 0x00ff7f }, + { "SpringGreen1", 0x00ff7f }, + { "SpringGreen2", 0x00ee76 }, + { "SpringGreen3", 0x00cd66 }, + { "SpringGreen4", 0x008b45 }, + { "SteelBlue", 0x4682b4 }, + { "SteelBlue1", 0x63b8ff }, + { "SteelBlue2", 0x5cacee }, + { "SteelBlue3", 0x4f94cd }, + { "SteelBlue4", 0x36648b }, + { "VioletRed", 0xd02090 }, + { "VioletRed1", 0xff3e96 }, + { "VioletRed2", 0xee3a8c }, + { "VioletRed3", 0xcd3278 }, + { "VioletRed4", 0x8b2252 }, + { "WebGray", 0x808080 }, + { "WebGreen", 0x008000 }, + { "WebGrey", 0x808080 }, + { "WebMaroon", 0x800000 }, + { "WebPurple", 0x800080 }, + { "WhiteSmoke", 0xf5f5f5 }, + { "X11Gray", 0xbebebe }, + { "X11Green", 0x00ff00 }, + { "X11Grey", 0xbebebe }, + { "X11Maroon", 0xb03060 }, + { "X11Purple", 0xa020f0 }, + { "YellowGreen", 0x9acd32 }, + { "alice blue", 0xf0f8ff }, + { "antique white", 0xfaebd7 }, + { "aqua", 0x00ffff }, + { "aquamarine", 0x7fffd4 }, + { "aquamarine1", 0x7fffd4 }, + { "aquamarine2", 0x76eec6 }, + { "aquamarine3", 0x66cdaa }, + { "aquamarine4", 0x458b74 }, + { "azure", 0xf0ffff }, + { "azure1", 0xf0ffff }, + { "azure2", 0xe0eeee }, + { "azure3", 0xc1cdcd }, + { "azure4", 0x838b8b }, + { "beige", 0xf5f5dc }, + { "bisque", 0xffe4c4 }, + { "bisque1", 0xffe4c4 }, + { "bisque2", 0xeed5b7 }, + { "bisque3", 0xcdb79e }, + { "bisque4", 0x8b7d6b }, + { "black", 0x000000 }, + { "blanched almond", 0xffebcd }, + { "blue violet", 0x8a2be2 }, + { "blue", 0x0000ff }, + { "blue1", 0x0000ff }, + { "blue2", 0x0000ee }, + { "blue3", 0x0000cd }, + { "blue4", 0x00008b }, + { "brown", 0xa52a2a }, + { "brown1", 0xff4040 }, + { "brown2", 0xee3b3b }, + { "brown3", 0xcd3333 }, + { "brown4", 0x8b2323 }, + { "burlywood", 0xdeb887 }, + { "burlywood1", 0xffd39b }, + { "burlywood2", 0xeec591 }, + { "burlywood3", 0xcdaa7d }, + { "burlywood4", 0x8b7355 }, + { "cadet blue", 0x5f9ea0 }, + { "chartreuse", 0x7fff00 }, + { "chartreuse1", 0x7fff00 }, + { "chartreuse2", 0x76ee00 }, + { "chartreuse3", 0x66cd00 }, + { "chartreuse4", 0x458b00 }, + { "chocolate", 0xd2691e }, + { "chocolate1", 0xff7f24 }, + { "chocolate2", 0xee7621 }, + { "chocolate3", 0xcd661d }, + { "chocolate4", 0x8b4513 }, + { "coral", 0xff7f50 }, + { "coral1", 0xff7256 }, + { "coral2", 0xee6a50 }, + { "coral3", 0xcd5b45 }, + { "coral4", 0x8b3e2f }, + { "cornflower blue", 0x6495ed }, + { "cornsilk", 0xfff8dc }, + { "cornsilk1", 0xfff8dc }, + { "cornsilk2", 0xeee8cd }, + { "cornsilk3", 0xcdc8b1 }, + { "cornsilk4", 0x8b8878 }, + { "crimson", 0xdc143c }, + { "cyan", 0x00ffff }, + { "cyan1", 0x00ffff }, + { "cyan2", 0x00eeee }, + { "cyan3", 0x00cdcd }, + { "cyan4", 0x008b8b }, + { "dark blue", 0x00008b }, + { "dark cyan", 0x008b8b }, + { "dark goldenrod", 0xb8860b }, + { "dark gray", 0xa9a9a9 }, + { "dark green", 0x006400 }, + { "dark grey", 0xa9a9a9 }, + { "dark khaki", 0xbdb76b }, + { "dark magenta", 0x8b008b }, + { "dark olive green", 0x556b2f }, + { "dark orange", 0xff8c00 }, + { "dark orchid", 0x9932cc }, + { "dark red", 0x8b0000 }, + { "dark salmon", 0xe9967a }, + { "dark sea green", 0x8fbc8f }, + { "dark slate blue", 0x483d8b }, + { "dark slate gray", 0x2f4f4f }, + { "dark slate grey", 0x2f4f4f }, + { "dark turquoise", 0x00ced1 }, + { "dark violet", 0x9400d3 }, + { "deep pink", 0xff1493 }, + { "deep sky blue", 0x00bfff }, + { "dim gray", 0x696969 }, + { "dim grey", 0x696969 }, + { "dodger blue", 0x1e90ff }, + { "firebrick", 0xb22222 }, + { "firebrick1", 0xff3030 }, + { "firebrick2", 0xee2c2c }, + { "firebrick3", 0xcd2626 }, + { "firebrick4", 0x8b1a1a }, + { "floral white", 0xfffaf0 }, + { "forest green", 0x228b22 }, + { "fuchsia", 0xff00ff }, + { "gainsboro", 0xdcdcdc }, + { "ghost white", 0xf8f8ff }, + { "gold", 0xffd700 }, + { "gold1", 0xffd700 }, + { "gold2", 0xeec900 }, + { "gold3", 0xcdad00 }, + { "gold4", 0x8b7500 }, + { "goldenrod", 0xdaa520 }, + { "goldenrod1", 0xffc125 }, + { "goldenrod2", 0xeeb422 }, + { "goldenrod3", 0xcd9b1d }, + { "goldenrod4", 0x8b6914 }, + { "green yellow", 0xadff2f }, + { "green", 0x00ff00 }, + { "green1", 0x00ff00 }, + { "green2", 0x00ee00 }, + { "green3", 0x00cd00 }, + { "green4", 0x008b00 }, + { "honeydew", 0xf0fff0 }, + { "honeydew1", 0xf0fff0 }, + { "honeydew2", 0xe0eee0 }, + { "honeydew3", 0xc1cdc1 }, + { "honeydew4", 0x838b83 }, + { "hot pink", 0xff69b4 }, + { "indian red", 0xcd5c5c }, + { "indigo", 0x4b0082 }, + { "ivory", 0xfffff0 }, + { "ivory1", 0xfffff0 }, + { "ivory2", 0xeeeee0 }, + { "ivory3", 0xcdcdc1 }, + { "ivory4", 0x8b8b83 }, + { "khaki", 0xf0e68c }, + { "khaki1", 0xfff68f }, + { "khaki2", 0xeee685 }, + { "khaki3", 0xcdc673 }, + { "khaki4", 0x8b864e }, + { "lavender blush", 0xfff0f5 }, + { "lavender", 0xe6e6fa }, + { "lawn green", 0x7cfc00 }, + { "lemon chiffon", 0xfffacd }, + { "light blue", 0xadd8e6 }, + { "light coral", 0xf08080 }, + { "light cyan", 0xe0ffff }, + { "light goldenrod yellow", 0xfafad2 }, + { "light goldenrod", 0xeedd82 }, + { "light gray", 0xd3d3d3 }, + { "light green", 0x90ee90 }, + { "light grey", 0xd3d3d3 }, + { "light pink", 0xffb6c1 }, + { "light salmon", 0xffa07a }, + { "light sea green", 0x20b2aa }, + { "light sky blue", 0x87cefa }, + { "light slate blue", 0x8470ff }, + { "light slate gray", 0x778899 }, + { "light slate grey", 0x778899 }, + { "light steel blue", 0xb0c4de }, + { "light yellow", 0xffffe0 }, + { "lime green", 0x32cd32 }, + { "lime", 0x00ff00 }, + { "linen", 0xfaf0e6 }, + { "magenta", 0xff00ff }, + { "magenta1", 0xff00ff }, + { "magenta2", 0xee00ee }, + { "magenta3", 0xcd00cd }, + { "magenta4", 0x8b008b }, + { "maroon", 0xb03060 }, + { "maroon1", 0xff34b3 }, + { "maroon2", 0xee30a7 }, + { "maroon3", 0xcd2990 }, + { "maroon4", 0x8b1c62 }, + { "medium aquamarine", 0x66cdaa }, + { "medium blue", 0x0000cd }, + { "medium orchid", 0xba55d3 }, + { "medium purple", 0x9370db }, + { "medium sea green", 0x3cb371 }, + { "medium slate blue", 0x7b68ee }, + { "medium spring green", 0x00fa9a }, + { "medium turquoise", 0x48d1cc }, + { "medium violet red", 0xc71585 }, + { "midnight blue", 0x191970 }, + { "mint cream", 0xf5fffa }, + { "misty rose", 0xffe4e1 }, + { "moccasin", 0xffe4b5 }, + { "navajo white", 0xffdead }, + { "navy blue", 0x000080 }, + { "navy", 0x000080 }, + { "old lace", 0xfdf5e6 }, + { "olive drab", 0x6b8e23 }, + { "olive", 0x808000 }, + { "orange red", 0xff4500 }, + { "orange", 0xffa500 }, + { "orange1", 0xffa500 }, + { "orange2", 0xee9a00 }, + { "orange3", 0xcd8500 }, + { "orange4", 0x8b5a00 }, + { "orchid", 0xda70d6 }, + { "orchid1", 0xff83fa }, + { "orchid2", 0xee7ae9 }, + { "orchid3", 0xcd69c9 }, + { "orchid4", 0x8b4789 }, + { "pale goldenrod", 0xeee8aa }, + { "pale green", 0x98fb98 }, + { "pale turquoise", 0xafeeee }, + { "pale violet red", 0xdb7093 }, + { "papaya whip", 0xffefd5 }, + { "peach puff", 0xffdab9 }, + { "peru", 0xcd853f }, + { "pink", 0xffc0cb }, + { "pink1", 0xffb5c5 }, + { "pink2", 0xeea9b8 }, + { "pink3", 0xcd919e }, + { "pink4", 0x8b636c }, + { "plum", 0xdda0dd }, + { "plum1", 0xffbbff }, + { "plum2", 0xeeaeee }, + { "plum3", 0xcd96cd }, + { "plum4", 0x8b668b }, + { "powder blue", 0xb0e0e6 }, + { "purple", 0xa020f0 }, + { "purple1", 0x9b30ff }, + { "purple2", 0x912cee }, + { "purple3", 0x7d26cd }, + { "purple4", 0x551a8b }, + { "rebecca purple", 0x663399 }, + { "red", 0xff0000 }, + { "red1", 0xff0000 }, + { "red2", 0xee0000 }, + { "red3", 0xcd0000 }, + { "red4", 0x8b0000 }, + { "rosy brown", 0xbc8f8f }, + { "royal blue", 0x4169e1 }, + { "saddle brown", 0x8b4513 }, + { "salmon", 0xfa8072 }, + { "salmon1", 0xff8c69 }, + { "salmon2", 0xee8262 }, + { "salmon3", 0xcd7054 }, + { "salmon4", 0x8b4c39 }, + { "sandy brown", 0xf4a460 }, + { "sea green", 0x2e8b57 }, + { "seashell", 0xfff5ee }, + { "seashell1", 0xfff5ee }, + { "seashell2", 0xeee5de }, + { "seashell3", 0xcdc5bf }, + { "seashell4", 0x8b8682 }, + { "sienna", 0xa0522d }, + { "sienna1", 0xff8247 }, + { "sienna2", 0xee7942 }, + { "sienna3", 0xcd6839 }, + { "sienna4", 0x8b4726 }, + { "silver", 0xc0c0c0 }, + { "sky blue", 0x87ceeb }, + { "slate blue", 0x6a5acd }, + { "slate gray", 0x708090 }, + { "slate grey", 0x708090 }, + { "snow", 0xfffafa }, + { "snow1", 0xfffafa }, + { "snow2", 0xeee9e9 }, + { "snow3", 0xcdc9c9 }, + { "snow4", 0x8b8989 }, + { "spring green", 0x00ff7f }, + { "steel blue", 0x4682b4 }, + { "tan", 0xd2b48c }, + { "tan1", 0xffa54f }, + { "tan2", 0xee9a49 }, + { "tan3", 0xcd853f }, + { "tan4", 0x8b5a2b }, + { "teal", 0x008080 }, + { "thistle", 0xd8bfd8 }, + { "thistle1", 0xffe1ff }, + { "thistle2", 0xeed2ee }, + { "thistle3", 0xcdb5cd }, + { "thistle4", 0x8b7b8b }, + { "tomato", 0xff6347 }, + { "tomato1", 0xff6347 }, + { "tomato2", 0xee5c42 }, + { "tomato3", 0xcd4f39 }, + { "tomato4", 0x8b3626 }, + { "turquoise", 0x40e0d0 }, + { "turquoise1", 0x00f5ff }, + { "turquoise2", 0x00e5ee }, + { "turquoise3", 0x00c5cd }, + { "turquoise4", 0x00868b }, + { "violet red", 0xd02090 }, + { "violet", 0xee82ee }, + { "web gray", 0x808080 }, + { "web green", 0x008000 }, + { "web grey", 0x808080 }, + { "web maroon", 0x800000 }, + { "web purple", 0x800080 }, + { "wheat", 0xf5deb3 }, + { "wheat1", 0xffe7ba }, + { "wheat2", 0xeed8ae }, + { "wheat3", 0xcdba96 }, + { "wheat4", 0x8b7e66 }, + { "white smoke", 0xf5f5f5 }, + { "white", 0xffffff }, + { "x11 gray", 0xbebebe }, + { "x11 green", 0x00ff00 }, + { "x11 grey", 0xbebebe }, + { "x11 maroon", 0xb03060 }, + { "x11 purple", 0xa020f0 }, + { "yellow green", 0x9acd32 }, + { "yellow", 0xffff00 }, + { "yellow1", 0xffff00 }, + { "yellow2", 0xeeee00 }, + { "yellow3", 0xcdcd00 }, + { "yellow4", 0x8b8b00 } + }; + u_int i; + int c; + + if (strncmp(name, "grey", 4) == 0 || strncmp(name, "gray", 4) == 0) { + if (!isdigit((u_char)name[4])) + return (0xbebebe|COLOUR_FLAG_RGB); + c = round(2.55 * atoi(name + 4)); + if (c < 0 || c > 255) + return (-1); + return (colour_join_rgb(c, c, c)); + } + for (i = 0; i < nitems(colours); i++) { + if (strcasecmp(colours[i].name, name) == 0) + return (colours[i].c|COLOUR_FLAG_RGB); + } + return (-1); +} + +/* Initialize palette. */ +void +colour_palette_init(struct colour_palette *p) +{ + p->fg = 8; + p->bg = 8; + p->palette = NULL; + p->default_palette = NULL; +} + +/* Clear palette. */ +void +colour_palette_clear(struct colour_palette *p) +{ + if (p != NULL) { + p->fg = 8; + p->bg = 8; + free(p->palette); + p->palette = NULL; + } +} + +/* Free a palette. */ +void +colour_palette_free(struct colour_palette *p) +{ + if (p != NULL) { + free(p->palette); + p->palette = NULL; + free(p->default_palette); + p->default_palette = NULL; + } +} + +/* Get a colour from a palette. */ +int +colour_palette_get(struct colour_palette *p, int c) +{ + if (p == NULL) + return (-1); + + if (c >= 90 && c <= 97) + c = 8 + c - 90; + else if (c & COLOUR_FLAG_256) + c &= ~COLOUR_FLAG_256; + else if (c >= 8) + return (-1); + + if (p->palette != NULL && p->palette[c] != -1) + return (p->palette[c]); + if (p->default_palette != NULL && p->default_palette[c] != -1) + return (p->default_palette[c]); + return (-1); +} + +/* Set a colour in a palette. */ +int +colour_palette_set(struct colour_palette *p, int n, int c) +{ + u_int i; + + if (p == NULL || n > 255) + return (0); + + if (c == -1 && p->palette == NULL) + return (0); + + if (c != -1 && p->palette == NULL) { + if (p->palette == NULL) + p->palette = xcalloc(256, sizeof *p->palette); + for (i = 0; i < 256; i++) + p->palette[i] = -1; + } + p->palette[n] = c; + return (1); +} + +/* Build palette defaults from an option. */ +void +colour_palette_from_option(struct colour_palette *p, struct options *oo) +{ + struct options_entry *o; + struct options_array_item *a; + u_int i, n; + int c; + + if (p == NULL) + return; + + o = options_get(oo, "pane-colours"); + if ((a = options_array_first(o)) == NULL) { + if (p->default_palette != NULL) { + free(p->default_palette); + p->default_palette = NULL; + } + return; + } + if (p->default_palette == NULL) + p->default_palette = xcalloc(256, sizeof *p->default_palette); + for (i = 0; i < 256; i++) + p->default_palette[i] = -1; + while (a != NULL) { + n = options_array_item_index(a); + if (n < 256) { + c = options_array_item_value(a)->number; + p->default_palette[n] = c; + } + a = options_array_next(a); + } + +} diff --git a/compat.h b/compat.h index 70801d0d..13334ad7 100644 --- a/compat.h +++ b/compat.h @@ -27,10 +27,35 @@ #include #include +#ifdef HAVE_EVENT2_EVENT_H +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#ifdef HAVE_MALLOC_TRIM +#include +#endif + +#ifdef HAVE_UTF8PROC +#include +#endif + #ifndef __GNUC__ #define __attribute__(a) #endif +#ifdef BROKEN___DEAD +#undef __dead +#endif + #ifndef __unused #define __unused __attribute__ ((__unused__)) #endif @@ -40,6 +65,9 @@ #ifndef __packed #define __packed __attribute__ ((__packed__)) #endif +#ifndef __weak +#define __weak __attribute__ ((__weak__)) +#endif #ifndef ECHOPRT #define ECHOPRT 0 @@ -90,10 +118,18 @@ void warnx(const char *, ...); #define _PATH_DEFPATH "/usr/bin:/bin" #endif +#ifndef _PATH_VI +#define _PATH_VI "/usr/bin/vi" +#endif + #ifndef __OpenBSD__ #define pledge(s, p) (0) #endif +#ifndef IMAXBEL +#define IMAXBEL 0 +#endif + #ifdef HAVE_STDINT_H #include #else @@ -229,6 +265,13 @@ void warnx(const char *, ...); #define HOST_NAME_MAX 255 #endif +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + #ifndef HAVE_FLOCK #define LOCK_SH 0 #define LOCK_EX 0 @@ -306,6 +349,11 @@ const char *getprogname(void); void setproctitle(const char *, ...); #endif +#ifndef HAVE_CLOCK_GETTIME +/* clock_gettime.c */ +int clock_gettime(int, struct timespec *); +#endif + #ifndef HAVE_B64_NTOP /* base64.c */ #undef b64_ntop @@ -337,6 +385,11 @@ int vasprintf(char **, const char *, va_list); char *fgetln(FILE *, size_t *); #endif +#ifndef HAVE_GETLINE +/* getline.c */ +ssize_t getline(char **, size_t *, FILE *); +#endif + #ifndef HAVE_SETENV /* setenv.c */ int setenv(const char *, const char *, int); @@ -370,7 +423,11 @@ int utf8proc_mbtowc(wchar_t *, const char *, size_t); int utf8proc_wctomb(char *, wchar_t); #endif -#ifndef HAVE_GETOPT +#ifdef NEED_FUZZING +/* tmux.c */ +#define main __weak main +#endif + /* getopt.c */ extern int BSDopterr; extern int BSDoptind; @@ -384,6 +441,5 @@ int BSDgetopt(int, char *const *, const char *); #define optopt BSDoptopt #define optreset BSDoptreset #define optarg BSDoptarg -#endif #endif /* COMPAT_H */ diff --git a/compat/clock_gettime.c b/compat/clock_gettime.c new file mode 100644 index 00000000..8290e75c --- /dev/null +++ b/compat/clock_gettime.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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 "compat.h" + +#ifndef TIMEVAL_TO_TIMESPEC +#define TIMEVAL_TO_TIMESPEC(tv, ts) do { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} while (0) +#endif + +int +clock_gettime(int clock, struct timespec *ts) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, ts); + return 0; +} diff --git a/compat/closefrom.c b/compat/closefrom.c index 7915cde4..be008239 100644 --- a/compat/closefrom.c +++ b/compat/closefrom.c @@ -44,6 +44,9 @@ # include # endif #endif +#if defined(HAVE_LIBPROC_H) +# include +#endif #include "compat.h" @@ -55,39 +58,15 @@ __unused static const char rcsid[] = "$Sudo: closefrom.c,v 1.11 2006/08/17 15:26:54 millert Exp $"; #endif /* lint */ +#ifndef HAVE_FCNTL_CLOSEM /* * Close all file descriptors greater than or equal to lowfd. */ -#ifdef HAVE_FCNTL_CLOSEM -void -closefrom(int lowfd) +static void +closefrom_fallback(int lowfd) { - (void) fcntl(lowfd, F_CLOSEM, 0); -} -#else -void -closefrom(int lowfd) -{ - long fd, maxfd; -#if defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) - char fdpath[PATH_MAX], *endp; - struct dirent *dent; - DIR *dirp; - int len; + long fd, maxfd; - /* Check for a /proc/$$/fd directory. */ - len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); - if (len > 0 && (size_t)len <= sizeof(fdpath) && (dirp = opendir(fdpath))) { - while ((dent = readdir(dirp)) != NULL) { - fd = strtol(dent->d_name, &endp, 10); - if (dent->d_name != endp && *endp == '\0' && - fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) - (void) close((int) fd); - } - (void) closedir(dirp); - } else -#endif - { /* * Fall back on sysconf() or getdtablesize(). We avoid checking * resource limits since it is possible to open a file descriptor @@ -99,11 +78,78 @@ closefrom(int lowfd) maxfd = getdtablesize(); #endif /* HAVE_SYSCONF */ if (maxfd < 0) - maxfd = OPEN_MAX; + maxfd = OPEN_MAX; for (fd = lowfd; fd < maxfd; fd++) - (void) close((int) fd); + (void) close((int) fd); +} +#endif /* HAVE_FCNTL_CLOSEM */ + +#ifdef HAVE_FCNTL_CLOSEM +void +closefrom(int lowfd) +{ + (void) fcntl(lowfd, F_CLOSEM, 0); +} +#elif defined(HAVE_LIBPROC_H) && defined(HAVE_PROC_PIDINFO) +void +closefrom(int lowfd) +{ + int i, r, sz; + pid_t pid = getpid(); + struct proc_fdinfo *fdinfo_buf = NULL; + + sz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (sz == 0) + return; /* no fds, really? */ + else if (sz == -1) + goto fallback; + if ((fdinfo_buf = malloc(sz)) == NULL) + goto fallback; + r = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdinfo_buf, sz); + if (r < 0 || r > sz) + goto fallback; + for (i = 0; i < r / (int)PROC_PIDLISTFD_SIZE; i++) { + if (fdinfo_buf[i].proc_fd >= lowfd) + close(fdinfo_buf[i].proc_fd); + } + free(fdinfo_buf); + return; + fallback: + free(fdinfo_buf); + closefrom_fallback(lowfd); + return; +} +#elif defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) +void +closefrom(int lowfd) +{ + long fd; + char fdpath[PATH_MAX], *endp; + struct dirent *dent; + DIR *dirp; + int len; + + /* Check for a /proc/$$/fd directory. */ + len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); + if (len > 0 && (size_t)len < sizeof(fdpath) && (dirp = opendir(fdpath))) { + while ((dent = readdir(dirp)) != NULL) { + fd = strtol(dent->d_name, &endp, 10); + if (dent->d_name != endp && *endp == '\0' && + fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) + (void) close((int) fd); + } + (void) closedir(dirp); + return; } + /* /proc/$$/fd strategy failed, fall back to brute force closure */ + closefrom_fallback(lowfd); +} +#else +void +closefrom(int lowfd) +{ + closefrom_fallback(lowfd); } #endif /* !HAVE_FCNTL_CLOSEM */ #endif /* HAVE_CLOSEFROM */ diff --git a/compat/forkpty-haiku.c b/compat/forkpty-haiku.c new file mode 100644 index 00000000..6112164c --- /dev/null +++ b/compat/forkpty-haiku.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2008 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 +#include + +#include "compat.h" + +void fatal(const char *, ...); +void fatalx(const char *, ...); + +pid_t +forkpty(int *master, char *name, struct termios *tio, struct winsize *ws) +{ + int slave = -1; + char *path; + pid_t pid; + + if ((*master = open("/dev/ptmx", O_RDWR|O_NOCTTY)) == -1) + return (-1); + if (grantpt(*master) != 0) + goto out; + if (unlockpt(*master) != 0) + goto out; + + if ((path = ptsname(*master)) == NULL) + goto out; + if (name != NULL) + strlcpy(name, path, TTY_NAME_MAX); + if ((slave = open(path, O_RDWR|O_NOCTTY)) == -1) + goto out; + + switch (pid = fork()) { + case -1: + goto out; + case 0: + close(*master); + + setsid(); + if (ioctl(slave, TIOCSCTTY, NULL) == -1) + fatal("ioctl failed"); + + if (tio != NULL && tcsetattr(slave, TCSAFLUSH, tio) == -1) + fatal("tcsetattr failed"); + if (ioctl(slave, TIOCSWINSZ, ws) == -1) + fatal("ioctl failed"); + + dup2(slave, 0); + dup2(slave, 1); + dup2(slave, 2); + if (slave > 2) + close(slave); + return (0); + } + + close(slave); + return (pid); + +out: + if (*master != -1) + close(*master); + if (slave != -1) + close(slave); + return (-1); +} diff --git a/compat/getdtablesize.c b/compat/getdtablesize.c new file mode 100644 index 00000000..fa82577f --- /dev/null +++ b/compat/getdtablesize.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 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 "compat.h" + +#ifdef HAVE_SYSCONF +int +getdtablesize(void) +{ + return (sysconf(_SC_OPEN_MAX)); +} +#endif diff --git a/compat/getline.c b/compat/getline.c new file mode 100644 index 00000000..90437ccb --- /dev/null +++ b/compat/getline.c @@ -0,0 +1,93 @@ +/* $NetBSD: getline.c,v 1.1.1.6 2015/01/02 20:34:27 christos Exp $ */ + +/* NetBSD: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp */ + +/*- + * Copyright (c) 2011 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* NETBSD ORIGINAL: external/bsd/file/dist/src/getline.c */ + +#include + +#include +#include +#include +#include +#include + +#include "tmux.h" + +static ssize_t +getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) +{ + char *ptr, *eptr; + + + if (*buf == NULL || *bufsiz == 0) { + if ((*buf = malloc(BUFSIZ)) == NULL) + return -1; + *bufsiz = BUFSIZ; + } + + for (ptr = *buf, eptr = *buf + *bufsiz;;) { + int c = fgetc(fp); + if (c == -1) { + if (feof(fp)) { + ssize_t diff = (ssize_t)(ptr - *buf); + if (diff != 0) { + *ptr = '\0'; + return diff; + } + } + return -1; + } + *ptr++ = c; + if (c == delimiter) { + *ptr = '\0'; + return ptr - *buf; + } + if (ptr + 2 >= eptr) { + char *nbuf; + size_t nbufsiz = *bufsiz * 2; + ssize_t d = ptr - *buf; + if ((nbuf = realloc(*buf, nbufsiz)) == NULL) + return -1; + *buf = nbuf; + *bufsiz = nbufsiz; + eptr = nbuf + nbufsiz; + ptr = nbuf + d; + } + } +} + +ssize_t +getline(char **buf, size_t *bufsiz, FILE *fp) +{ + return getdelim(buf, bufsiz, '\n', fp); +} diff --git a/compat/imsg-buffer.c b/compat/imsg-buffer.c index 814591f4..67d4c705 100644 --- a/compat/imsg-buffer.c +++ b/compat/imsg-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg-buffer.c,v 1.11 2017/12/14 09:27:44 kettenis Exp $ */ +/* $OpenBSD: imsg-buffer.c,v 1.12 2019/01/20 02:50:03 bcook Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -70,7 +70,7 @@ ibuf_dynamic(size_t len, size_t max) static int ibuf_realloc(struct ibuf *buf, size_t len) { - u_char *b; + unsigned char *b; /* on static buffers max is eq size and so the following fails */ if (buf->wpos + len > buf->max) { diff --git a/compat/imsg.h b/compat/imsg.h index 8bf94147..5b092cfc 100644 --- a/compat/imsg.h +++ b/compat/imsg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg.h,v 1.4 2017/03/24 09:34:12 nicm Exp $ */ +/* $OpenBSD: imsg.h,v 1.5 2019/01/20 02:50:03 bcook Exp $ */ /* * Copyright (c) 2006, 2007 Pierre-Yves Ritschard @@ -21,13 +21,15 @@ #ifndef _IMSG_H_ #define _IMSG_H_ +#include + #define IBUF_READ_SIZE 65535 #define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) #define MAX_IMSGSIZE 16384 struct ibuf { TAILQ_ENTRY(ibuf) entry; - u_char *buf; + unsigned char *buf; size_t size; size_t max; size_t wpos; @@ -42,8 +44,8 @@ struct msgbuf { }; struct ibuf_read { - u_char buf[IBUF_READ_SIZE]; - u_char *rptr; + unsigned char buf[IBUF_READ_SIZE]; + unsigned char *rptr; size_t wpos; }; diff --git a/configure.ac b/configure.ac index 380eea73..26bd1a98 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT([tmux], next-3.2) +AC_INIT([tmux], next-3.4) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) @@ -21,6 +21,26 @@ SAVED_CFLAGS="$CFLAGS" SAVED_CPPFLAGS="$CPPFLAGS" SAVED_LDFLAGS="$LDFLAGS" +# Is this oss-fuzz build? +AC_ARG_ENABLE( + fuzzing, + AS_HELP_STRING(--enable-fuzzing, build fuzzers) +) +AC_ARG_VAR( + FUZZING_LIBS, + AS_HELP_STRING(libraries to link fuzzing targets with) +) + +# Set up convenient fuzzing defaults before initializing compiler. +if test "x$enable_fuzzing" = xyes; then + AC_DEFINE(NEED_FUZZING) + test "x$CC" = x && CC=clang + test "x$FUZZING_LIBS" = x && \ + FUZZING_LIBS="-fsanitize=fuzzer" + test "x$SAVED_CFLAGS" = x && \ + AM_CFLAGS="-g -fsanitize=fuzzer-no-link,address" +fi + # Set up the compiler in two different ways and say yes we may want to install. AC_PROG_CC AM_PROG_CC_C_O @@ -39,14 +59,14 @@ test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc case "x$VERSION" in xnext*) enable_debug=yes;; esac AC_ARG_ENABLE( debug, - AC_HELP_STRING(--enable-debug, enable debug build flags), + AS_HELP_STRING(--enable-debug, enable debug build flags), ) AM_CONDITIONAL(IS_DEBUG, test "x$enable_debug" = xyes) # Is this a static build? AC_ARG_ENABLE( static, - AC_HELP_STRING(--enable-static, create a static build) + AS_HELP_STRING(--enable-static, create a static build) ) if test "x$enable_static" = xyes; then test "x$PKG_CONFIG" != x && PKG_CONFIG="$PKG_CONFIG --static" @@ -54,8 +74,26 @@ if test "x$enable_static" = xyes; then LDFLAGS="$AM_LDFLAGS $SAVED_LDFLAGS" fi +# Allow default TERM to be set. +AC_ARG_WITH( + TERM, + AS_HELP_STRING(--with-TERM, set default TERM), + [DEFAULT_TERM=$withval], + [DEFAULT_TERM=] +) +case "x$DEFAULT_TERM" in + xscreen*|xtmux*|x) + ;; + *) + AC_MSG_ERROR("unsuitable TERM (must be screen* or tmux*)") + ;; +esac + +# Do we need fuzzers? +AM_CONDITIONAL(NEED_FUZZING, test "x$enable_fuzzing" = xyes) + # Is this gcc? -AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes) +AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes -a "x$enable_fuzzing" != xyes) # Is this Sun CC? AC_EGREP_CPP( @@ -76,6 +114,7 @@ AC_CHECK_HEADERS([ \ dirent.h \ fcntl.h \ inttypes.h \ + libproc.h \ libutil.h \ ndir.h \ paths.h \ @@ -87,6 +126,12 @@ AC_CHECK_HEADERS([ \ util.h \ ]) +# Look for sys_signame. +AC_SEARCH_LIBS(sys_signame, , AC_DEFINE(HAVE_SYS_SIGNAME)) + +# Look for fmod. +AC_CHECK_LIB(m, fmod) + # Look for library needed for flock. AC_SEARCH_LIBS(flock, bsd) @@ -95,22 +140,24 @@ AC_CHECK_FUNCS([ \ dirfd \ flock \ prctl \ - sysconf \ + proc_pidinfo \ + sysconf ]) # Check for functions with a compatibility implementation. AC_REPLACE_FUNCS([ \ asprintf \ cfmakeraw \ + clock_gettime \ closefrom \ explicit_bzero \ fgetln \ freezero \ getdtablecount \ + getdtablesize \ + getline \ getprogname \ memmem \ - recallocarray \ - reallocarray \ setenv \ setproctitle \ strcasestr \ @@ -118,95 +165,158 @@ AC_REPLACE_FUNCS([ \ strlcpy \ strndup \ strsep \ - strtonum \ ]) AC_FUNC_STRNLEN +# Check if strtonum works. +AC_MSG_CHECKING([for working strtonum]) +AC_RUN_IFELSE([AC_LANG_PROGRAM( + [#include ], + [return (strtonum("0", 0, 1, NULL) == 0 ? 0 : 1);] + )], + [AC_DEFINE(HAVE_STRTONUM) AC_MSG_RESULT(yes)], + [AC_LIBOBJ(strtonum) AC_MSG_RESULT(no)], + [AC_LIBOBJ(strtonum) AC_MSG_RESULT(no)] +) + +# Clang sanitizers wrap reallocarray even if it isn't available on the target +# system. When compiled it always returns NULL and crashes the program. To +# detect this we need a more complicated test. +AC_MSG_CHECKING([for working reallocarray]) +AC_RUN_IFELSE([AC_LANG_PROGRAM( + [#include ], + [return (reallocarray(NULL, 1, 1) == NULL);] + )], + AC_MSG_RESULT(yes), + [AC_LIBOBJ(reallocarray) AC_MSG_RESULT([no])], + [AC_LIBOBJ(reallocarray) AC_MSG_RESULT([no])] +) +AC_MSG_CHECKING([for working recallocarray]) +AC_RUN_IFELSE([AC_LANG_PROGRAM( + [#include ], + [return (recallocarray(NULL, 1, 1, 1) == NULL);] + )], + AC_MSG_RESULT(yes), + [AC_LIBOBJ(recallocarray) AC_MSG_RESULT([no])], + [AC_LIBOBJ(recallocarray) AC_MSG_RESULT([no])] +) + # Look for clock_gettime. Must come before event_init. AC_SEARCH_LIBS(clock_gettime, rt) -# Look for libevent. +# Always use our getopt because 1) glibc's doesn't enforce argument order 2) +# musl does not set optarg to NULL for flags without arguments (although it is +# not required to, but it is helpful) 3) there are probably other weird +# implementations. +AC_LIBOBJ(getopt) + +# Look for libevent. Try libevent_core or libevent with pkg-config first then +# look for the library. PKG_CHECK_MODULES( - LIBEVENT, - libevent, + LIBEVENT_CORE, + [libevent_core >= 2], [ - AM_CFLAGS="$LIBEVENT_CFLAGS $AM_CFLAGS" - CFLAGS="$AM_CFLAGS $SAVED_CFLAGS" - LIBS="$LIBEVENT_LIBS $LIBS" + AM_CPPFLAGS="$LIBEVENT_CORE_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBEVENT_CORE_LIBS $LIBS" found_libevent=yes ], + found_libevent=no +) +if test x$found_libevent = xno; then + PKG_CHECK_MODULES( + LIBEVENT, + [libevent >= 2], + [ + AM_CPPFLAGS="$LIBEVENT_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBEVENT_LIBS $LIBS" + found_libevent=yes + ], + found_libevent=no + ) +fi +if test x$found_libevent = xno; then + AC_SEARCH_LIBS( + event_init, + [event_core event event-1.4], + found_libevent=yes, + found_libevent=no + ) +fi +AC_CHECK_HEADER( + event2/event.h, + AC_DEFINE(HAVE_EVENT2_EVENT_H), [ - AC_SEARCH_LIBS( - event_init, - [event event-1.4 event2], - found_libevent=yes, + AC_CHECK_HEADER( + event.h, + AC_DEFINE(HAVE_EVENT_H), found_libevent=no ) ] ) -AC_CHECK_HEADER( - event.h, - , - found_libevent=no -) if test "x$found_libevent" = xno; then AC_MSG_ERROR("libevent not found") fi -# Look for ncurses. +# Look for ncurses or curses. Try pkg-config first then directly for the +# library. PKG_CHECK_MODULES( LIBTINFO, tinfo, - found_ncurses=yes, + [ + AM_CPPFLAGS="$LIBTINFO_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBTINFO_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBTINFO_LIBS $LIBS" + found_ncurses=yes + ], found_ncurses=no ) if test "x$found_ncurses" = xno; then PKG_CHECK_MODULES( LIBNCURSES, ncurses, - found_ncurses=yes, + [ + AM_CPPFLAGS="$LIBNCURSES_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSES_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBNCURSES_LIBS $LIBS" + found_ncurses=yes + ], found_ncurses=no ) fi if test "x$found_ncurses" = xno; then PKG_CHECK_MODULES( - LIBNCURSES, + LIBNCURSESW, ncursesw, - found_ncurses=yes, + [ + AM_CPPFLAGS="$LIBNCURSESW_CFLAGS $AM_CPPFLAGS" + CPPFLAGS="$LIBNCURSESW_CFLAGS $SAVED_CPPFLAGS" + LIBS="$LIBNCURSESW_LIBS $LIBS" + found_ncurses=yes + ], found_ncurses=no ) fi -if test "x$found_ncurses" = xyes; then - AM_CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $AM_CFLAGS" - CFLAGS="$LIBNCURSES_CFLAGS $LIBTINFO_CFLAGS $CFLAGS" - LIBS="$LIBNCURSES_LIBS $LIBTINFO_LIBS $LIBS" -else - # pkg-config didn't work, try ncurses. - AC_CHECK_LIB( - tinfo, +if test "x$found_ncurses" = xno; then + AC_SEARCH_LIBS( setupterm, + [tinfo ncurses ncursesw], found_ncurses=yes, found_ncurses=no ) - if test "x$found_ncurses" = xno; then - AC_CHECK_LIB( - ncurses, - setupterm, - found_ncurses=yes, - found_ncurses=no - ) - fi if test "x$found_ncurses" = xyes; then AC_CHECK_HEADER( ncurses.h, LIBS="$LIBS -lncurses", - found_ncurses=no) + found_ncurses=no + ) fi fi if test "x$found_ncurses" = xyes; then + CPPFLAGS="$CPPFLAGS -DHAVE_NCURSES_H" AC_DEFINE(HAVE_NCURSES_H) else - # No ncurses, try curses. AC_CHECK_LIB( curses, setupterm, @@ -216,9 +326,11 @@ else AC_CHECK_HEADER( curses.h, , - found_curses=no) + found_curses=no + ) if test "x$found_curses" = xyes; then LIBS="$LIBS -lcurses" + CPPFLAGS="$CPPFLAGS -DHAVE_CURSES_H" AC_DEFINE(HAVE_CURSES_H) else AC_MSG_ERROR("curses not found") @@ -228,7 +340,7 @@ fi # Look for utempter. AC_ARG_ENABLE( utempter, - AC_HELP_STRING(--enable-utempter, use utempter if it is installed) + AS_HELP_STRING(--enable-utempter, use utempter if it is installed) ) if test "x$enable_utempter" = xyes; then AC_CHECK_HEADER(utempter.h, enable_utempter=yes, enable_utempter=no) @@ -250,7 +362,7 @@ fi # Look for utf8proc. AC_ARG_ENABLE( utf8proc, - AC_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed) + AS_HELP_STRING(--enable-utf8proc, use utf8proc if it is installed) ) if test "x$enable_utf8proc" = xyes; then AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no) @@ -272,41 +384,58 @@ AM_CONDITIONAL(HAVE_UTF8PROC, [test "x$enable_utf8proc" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) -AC_TRY_LINK( + AC_LINK_IFELSE([AC_LANG_PROGRAM( [ #include #include #include ], - [b64_ntop(NULL, 0, NULL, 0);], + [ + b64_ntop(NULL, 0, NULL, 0); + ])], found_b64_ntop=yes, found_b64_ntop=no ) +AC_MSG_RESULT($found_b64_ntop) +OLD_LIBS="$LIBS" if test "x$found_b64_ntop" = xno; then - AC_MSG_RESULT(no) - AC_MSG_CHECKING(for b64_ntop with -lresolv) - OLD_LIBS="$LIBS" - LIBS="$LIBS -lresolv" - AC_TRY_LINK( + LIBS="$OLD_LIBS -lresolv" + AC_LINK_IFELSE([AC_LANG_PROGRAM( [ #include #include #include ], - [b64_ntop(NULL, 0, NULL, 0);], + [ + b64_ntop(NULL, 0, NULL, 0); + ])], found_b64_ntop=yes, found_b64_ntop=no ) - if test "x$found_b64_ntop" = xno; then - LIBS="$OLD_LIBS" - AC_MSG_RESULT(no) - fi + AC_MSG_RESULT($found_b64_ntop) +fi +if test "x$found_b64_ntop" = xno; then + AC_MSG_CHECKING(for b64_ntop with -lnetwork) + LIBS="$OLD_LIBS -lnetwork" + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [ + #include + #include + #include + ], + [ + b64_ntop(NULL, 0, NULL, 0); + ])], + found_b64_ntop=yes, + found_b64_ntop=no + ) + AC_MSG_RESULT($found_b64_ntop) fi if test "x$found_b64_ntop" = xyes; then AC_DEFINE(HAVE_B64_NTOP) - AC_MSG_RESULT(yes) else + LIBS="$OLD_LIBS" AC_LIBOBJ(base64) fi @@ -315,8 +444,34 @@ AC_SEARCH_LIBS(inet_ntoa, nsl) AC_SEARCH_LIBS(socket, socket) AC_CHECK_LIB(xnet, socket) -# Check for CMSG_DATA. Some platforms require _XOPEN_SOURCE_EXTENDED (for -# example see xopen_networking(7) on HP-UX). +# Check if using glibc and have malloc_trim(3). The glibc free(3) is pretty bad +# about returning memory to the kernel unless the application tells it when to +# with malloc_trim(3). +AC_MSG_CHECKING(if free doesn't work very well) +AC_LINK_IFELSE([AC_LANG_SOURCE( + [ + #include + #ifdef __GLIBC__ + #include + int main(void) { + malloc_trim (0); + exit(0); + } + #else + no + #endif + ])], + found_malloc_trim=yes, + found_malloc_trim=no +) +AC_MSG_RESULT($found_malloc_trim) +if test "x$found_malloc_trim" = xyes; then + AC_DEFINE(HAVE_MALLOC_TRIM) +fi + +# Check for CMSG_DATA. On some platforms like HP-UX this requires UNIX 95 +# (_XOPEN_SOURCE and _XOPEN_SOURCE_EXTENDED) (see xopen_networking(7)). On +# others, UNIX 03 (_XOPEN_SOURCE 600, see standards(7) on Solaris). XOPEN_DEFINES= AC_MSG_CHECKING(for CMSG_DATA) AC_EGREP_CPP( @@ -349,6 +504,25 @@ if test "x$found_cmsg_data" = xno; then AC_MSG_RESULT($found_cmsg_data) if test "x$found_cmsg_data" = xyes; then XOPEN_DEFINES="-D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED" + fi +fi +if test "x$found_cmsg_data" = xno; then + AC_MSG_CHECKING(if CMSG_DATA needs _XOPEN_SOURCE 600) + AC_EGREP_CPP( + yes, + [ + #define _XOPEN_SOURCE 600 + #include + #ifdef CMSG_DATA + yes + #endif + ], + found_cmsg_data=yes, + found_cmsg_data=no + ) + AC_MSG_RESULT($found_cmsg_data) + if test "x$found_cmsg_data" = xyes; then + XOPEN_DEFINES="-D_XOPEN_SOURCE=600" else AC_MSG_ERROR("CMSG_DATA not found") fi @@ -424,40 +598,6 @@ else AC_LIBOBJ(unvis) fi -# Look for getopt. glibc's getopt does not enforce argument order and the ways -# of making it do so are stupid, so just use our own instead. -AC_CHECK_FUNC(getopt, found_getopt=yes, found_getopt=no) -if test "x$found_getopt" != xno; then - AC_MSG_CHECKING(if getopt is suitable) - AC_EGREP_CPP( - yes, - [ - #include - #ifdef __GLIBC__ - yes - #endif - ], - [ - found_getopt=no - AC_MSG_RESULT(no) - ], - AC_MSG_RESULT(yes)) -fi -if test "x$found_getopt" != xno; then - AC_CHECK_DECLS( - [optarg, optind, optreset], - , - found_getopt=no, - [ - #include - ]) -fi -if test "x$found_getopt" != xno; then - AC_DEFINE(HAVE_GETOPT) -else - AC_LIBOBJ(getopt) -fi - # Look for fdforkpty and forkpty in libutil. AC_SEARCH_LIBS(fdforkpty, util, found_fdforkpty=yes, found_fdforkpty=no) if test "x$found_fdforkpty" = xyes; then @@ -483,7 +623,7 @@ AC_CHECK_DECL( ) AC_CHECK_DECL( TAILQ_PREV, - found_queue_h=yes, + , found_queue_h=no, [#include ] ) @@ -556,6 +696,74 @@ else AC_MSG_RESULT(no) fi +# Try to figure out what the best value for TERM might be. +if test "x$DEFAULT_TERM" = x; then + DEFAULT_TERM=screen + AC_MSG_CHECKING(TERM) + AC_RUN_IFELSE([AC_LANG_SOURCE( + [ + #include + #include + #if defined(HAVE_CURSES_H) + #include + #elif defined(HAVE_NCURSES_H) + #include + #endif + #include + int main(void) { + if (setupterm("screen-256color", -1, NULL) != OK) + exit(1); + exit(0); + } + ])], + [DEFAULT_TERM=screen-256color], + , + [DEFAULT_TERM=screen] + ) + AC_RUN_IFELSE([AC_LANG_SOURCE( + [ + #include + #include + #if defined(HAVE_CURSES_H) + #include + #elif defined(HAVE_NCURSES_H) + #include + #endif + #include + int main(void) { + if (setupterm("tmux", -1, NULL) != OK) + exit(1); + exit(0); + } + ])], + [DEFAULT_TERM=tmux], + , + [DEFAULT_TERM=screen] + ) + AC_RUN_IFELSE([AC_LANG_SOURCE( + [ + #include + #include + #if defined(HAVE_CURSES_H) + #include + #elif defined(HAVE_NCURSES_H) + #include + #endif + #include + int main(void) { + if (setupterm("tmux-256color", -1, NULL) != OK) + exit(1); + exit(0); + } + ])], + [DEFAULT_TERM=tmux-256color], + , + [DEFAULT_TERM=screen] + ) + AC_MSG_RESULT($DEFAULT_TERM) +fi +AC_SUBST(DEFAULT_TERM) + # Man page defaults to mdoc. MANFORMAT=mdoc AC_SUBST(MANFORMAT) @@ -571,13 +779,32 @@ case "$host_os" in AC_MSG_RESULT(darwin) PLATFORM=darwin # - # OS X CMSG_FIRSTHDR is broken, so redefine it with a working - # one. daemon works but has some stupid side effects, so use - # our internal version which has a workaround. + # macOS uses __dead2 instead of __dead, like FreeBSD. But it defines + # __dead away so it needs to be removed before we can replace it. + # + AC_DEFINE(BROKEN___DEAD) + # + # macOS CMSG_FIRSTHDR is broken, so redefine it with a working one. + # daemon works but has some stupid side effects, so use our internal + # version which has a workaround. # AC_DEFINE(BROKEN_CMSG_FIRSTHDR) AC_LIBOBJ(daemon) AC_LIBOBJ(daemon-darwin) + # + # macOS wcwidth(3) is bad, so complain and suggest using utf8proc + # instead. + # + if test "x$enable_utf8proc" = x; then + AC_MSG_NOTICE([]) + AC_MSG_NOTICE([ macOS library support for Unicode is very poor,]) + AC_MSG_NOTICE([ particularly for complex codepoints like emojis;]) + AC_MSG_NOTICE([ to use these correctly, configuring with]) + AC_MSG_NOTICE([ --enable-utf8proc is recommended. To build]) + AC_MSG_NOTICE([ without anyway, use --disable-utf8proc]) + AC_MSG_NOTICE([]) + AC_MSG_ERROR([must give --enable-utf8proc or --disable-utf8proc]) + fi ;; *dragonfly*) AC_MSG_RESULT(dragonfly) @@ -625,6 +852,10 @@ case "$host_os" in AC_MSG_RESULT(cygwin) PLATFORM=cygwin ;; + *haiku*) + AC_MSG_RESULT(haiku) + PLATFORM=haiku + ;; *) AC_MSG_RESULT(unknown) PLATFORM=unknown @@ -640,6 +871,7 @@ AM_CONDITIONAL(IS_NETBSD, test "x$PLATFORM" = xnetbsd) AM_CONDITIONAL(IS_OPENBSD, test "x$PLATFORM" = xopenbsd) AM_CONDITIONAL(IS_SUNOS, test "x$PLATFORM" = xsunos) AM_CONDITIONAL(IS_HPUX, test "x$PLATFORM" = xhpux) +AM_CONDITIONAL(IS_HAIKU, test "x$PLATFORM" = xhaiku) AM_CONDITIONAL(IS_UNKNOWN, test "x$PLATFORM" = xunknown) # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user @@ -652,4 +884,5 @@ AC_SUBST(AM_LDFLAGS) LDFLAGS="$SAVED_LDFLAGS" # autoconf should create a Makefile. -AC_OUTPUT(Makefile) +AC_CONFIG_FILES(Makefile) +AC_OUTPUT diff --git a/control-notify.c b/control-notify.c index babfcf2d..6ff0e436 100644 --- a/control-notify.c +++ b/control-notify.c @@ -26,39 +26,6 @@ #define CONTROL_SHOULD_NOTIFY_CLIENT(c) \ ((c) != NULL && ((c)->flags & CLIENT_CONTROL)) -void -control_notify_input(struct client *c, struct window_pane *wp, - const u_char *buf, size_t len) -{ - struct evbuffer *message; - u_int i; - - if (c->session == NULL) - return; - - if (c->flags & CLIENT_CONTROL_NOOUTPUT) - return; - - /* - * Only write input if the window pane is linked to a window belonging - * to the client's session. - */ - if (winlink_find_by_window(&c->session->windows, wp->window) != NULL) { - message = evbuffer_new(); - if (message == NULL) - fatalx("out of memory"); - evbuffer_add_printf(message, "%%output %%%u ", wp->id); - for (i = 0; i < len; i++) { - if (buf[i] < ' ' || buf[i] == '\\') - evbuffer_add_printf(message, "\\%03o", buf[i]); - else - evbuffer_add_printf(message, "%c", buf[i]); - } - control_write(c, "%s", EVBUFFER_DATA(message)); - evbuffer_free(message); - } -} - void control_notify_pane_mode_changed(int pane) { @@ -82,7 +49,7 @@ control_notify_window_layout_changed(struct window *w) char *cp; template = "%layout-change #{window_id} #{window_layout} " - "#{window_visible_layout} #{window_flags}"; + "#{window_visible_layout} #{window_raw_flags}"; TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL) @@ -204,6 +171,17 @@ control_notify_client_session_changed(struct client *cc) } } +void +control_notify_client_detached(struct client *cc) +{ + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + if (CONTROL_SHOULD_NOTIFY_CLIENT(c)) + control_write(c, "%%client-detached %s", cc->name); + } +} + void control_notify_session_renamed(struct session *s) { diff --git a/control.c b/control.c index b8a16e73..73286e00 100644 --- a/control.c +++ b/control.c @@ -19,30 +19,509 @@ #include -#include #include #include #include +#include #include "tmux.h" +/* + * Block of data to output. Each client has one "all" queue of blocks and + * another queue for each pane (in struct client_offset). %output blocks are + * added to both queues and other output lines (notifications) added only to + * the client queue. + * + * When a client becomes writeable, data from blocks on the pane queue are sent + * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written, + * it is removed from both pane and client queues and if this means non-%output + * blocks are now at the head of the client queue, they are written. + * + * This means a %output block holds up any subsequent non-%output blocks until + * it is written which enforces ordering even if the client cannot accept the + * entire block in one go. + */ +struct control_block { + size_t size; + char *line; + uint64_t t; + + TAILQ_ENTRY(control_block) entry; + TAILQ_ENTRY(control_block) all_entry; +}; + +/* Control client pane. */ +struct control_pane { + u_int pane; + + /* + * Offsets into the pane data. The first (offset) is the data we have + * written; the second (queued) the data we have queued (pointed to by + * a block). + */ + struct window_pane_offset offset; + struct window_pane_offset queued; + + int flags; +#define CONTROL_PANE_OFF 0x1 +#define CONTROL_PANE_PAUSED 0x2 + + int pending_flag; + TAILQ_ENTRY(control_pane) pending_entry; + + TAILQ_HEAD(, control_block) blocks; + + RB_ENTRY(control_pane) entry; +}; +RB_HEAD(control_panes, control_pane); + +/* Subscription pane. */ +struct control_sub_pane { + u_int pane; + u_int idx; + char *last; + + RB_ENTRY(control_sub_pane) entry; +}; +RB_HEAD(control_sub_panes, control_sub_pane); + +/* Subscription window. */ +struct control_sub_window { + u_int window; + u_int idx; + char *last; + + RB_ENTRY(control_sub_window) entry; +}; +RB_HEAD(control_sub_windows, control_sub_window); + +/* Control client subscription. */ +struct control_sub { + char *name; + char *format; + + enum control_sub_type type; + u_int id; + + char *last; + struct control_sub_panes panes; + struct control_sub_windows windows; + + RB_ENTRY(control_sub) entry; +}; +RB_HEAD(control_subs, control_sub); + +/* Control client state. */ +struct control_state { + struct control_panes panes; + + TAILQ_HEAD(, control_pane) pending_list; + u_int pending_count; + + TAILQ_HEAD(, control_block) all_blocks; + + struct bufferevent *read_event; + struct bufferevent *write_event; + + struct control_subs subs; + struct event subs_timer; +}; + +/* Low and high watermarks. */ +#define CONTROL_BUFFER_LOW 512 +#define CONTROL_BUFFER_HIGH 8192 + +/* Minimum to write to each client. */ +#define CONTROL_WRITE_MINIMUM 32 + +/* Maximum age for clients that are not using pause mode. */ +#define CONTROL_MAXIMUM_AGE 300000 + +/* Flags to ignore client. */ +#define CONTROL_IGNORE_FLAGS \ + (CLIENT_CONTROL_NOOUTPUT| \ + CLIENT_UNATTACHEDFLAGS) + +/* Compare client panes. */ +static int +control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) +{ + if (cp1->pane < cp2->pane) + return (-1); + if (cp1->pane > cp2->pane) + return (1); + return (0); +} +RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp); + +/* Compare client subs. */ +static int +control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2) +{ + return (strcmp(csub1->name, csub2->name)); +} +RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp); + +/* Compare client subscription panes. */ +static int +control_sub_pane_cmp(struct control_sub_pane *csp1, + struct control_sub_pane *csp2) +{ + if (csp1->pane < csp2->pane) + return (-1); + if (csp1->pane > csp2->pane) + return (1); + if (csp1->idx < csp2->idx) + return (-1); + if (csp1->idx > csp2->idx) + return (1); + return (0); +} +RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry, + control_sub_pane_cmp); + +/* Compare client subscription windows. */ +static int +control_sub_window_cmp(struct control_sub_window *csw1, + struct control_sub_window *csw2) +{ + if (csw1->window < csw2->window) + return (-1); + if (csw1->window > csw2->window) + return (1); + if (csw1->idx < csw2->idx) + return (-1); + if (csw1->idx > csw2->idx) + return (1); + return (0); +} +RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry, + control_sub_window_cmp); + +/* Free a subscription. */ +static void +control_free_sub(struct control_state *cs, struct control_sub *csub) +{ + struct control_sub_pane *csp, *csp1; + struct control_sub_window *csw, *csw1; + + RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) { + RB_REMOVE(control_sub_panes, &csub->panes, csp); + free(csp); + } + RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) { + RB_REMOVE(control_sub_windows, &csub->windows, csw); + free(csw); + } + free(csub->last); + + RB_REMOVE(control_subs, &cs->subs, csub); + free(csub->name); + free(csub->format); + free(csub); +} + +/* Free a block. */ +static void +control_free_block(struct control_state *cs, struct control_block *cb) +{ + free(cb->line); + TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); + free(cb); +} + +/* Get pane offsets for this client. */ +static struct control_pane * +control_get_pane(struct client *c, struct window_pane *wp) +{ + struct control_state *cs = c->control_state; + struct control_pane cp = { .pane = wp->id }; + + return (RB_FIND(control_panes, &cs->panes, &cp)); +} + +/* Add pane offsets for this client. */ +static struct control_pane * +control_add_pane(struct client *c, struct window_pane *wp) +{ + struct control_state *cs = c->control_state; + struct control_pane *cp; + + cp = control_get_pane(c, wp); + if (cp != NULL) + return (cp); + + cp = xcalloc(1, sizeof *cp); + cp->pane = wp->id; + RB_INSERT(control_panes, &cs->panes, cp); + + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); + TAILQ_INIT(&cp->blocks); + + return (cp); +} + +/* Discard output for a pane. */ +static void +control_discard_pane(struct client *c, struct control_pane *cp) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + + TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { + TAILQ_REMOVE(&cp->blocks, cb, entry); + control_free_block(cs, cb); + } +} + +/* Get actual pane for this client. */ +static struct window_pane * +control_window_pane(struct client *c, u_int pane) +{ + struct window_pane *wp; + + if (c->session == NULL) + return (NULL); + if ((wp = window_pane_find_by_id(pane)) == NULL) + return (NULL); + if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) + return (NULL); + return (wp); +} + +/* Reset control offsets. */ +void +control_reset_offsets(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_pane *cp, *cp1; + + RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { + RB_REMOVE(control_panes, &cs->panes, cp); + free(cp); + } + + TAILQ_INIT(&cs->pending_list); + cs->pending_count = 0; +} + +/* Get offsets for client. */ +struct window_pane_offset * +control_pane_offset(struct client *c, struct window_pane *wp, int *off) +{ + struct control_state *cs = c->control_state; + struct control_pane *cp; + + if (c->flags & CLIENT_CONTROL_NOOUTPUT) { + *off = 0; + return (NULL); + } + + cp = control_get_pane(c, wp); + if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) { + *off = 0; + return (NULL); + } + if (cp->flags & CONTROL_PANE_OFF) { + *off = 1; + return (NULL); + } + *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW); + return (&cp->offset); +} + +/* Set pane as on. */ +void +control_set_pane_on(struct client *c, struct window_pane *wp) +{ + struct control_pane *cp; + + cp = control_get_pane(c, wp); + if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) { + cp->flags &= ~CONTROL_PANE_OFF; + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); + } +} + +/* Set pane as off. */ +void +control_set_pane_off(struct client *c, struct window_pane *wp) +{ + struct control_pane *cp; + + cp = control_add_pane(c, wp); + cp->flags |= CONTROL_PANE_OFF; +} + +/* Continue a paused pane. */ +void +control_continue_pane(struct client *c, struct window_pane *wp) +{ + struct control_pane *cp; + + cp = control_get_pane(c, wp); + if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) { + cp->flags &= ~CONTROL_PANE_PAUSED; + memcpy(&cp->offset, &wp->offset, sizeof cp->offset); + memcpy(&cp->queued, &wp->offset, sizeof cp->queued); + control_write(c, "%%continue %%%u", wp->id); + } +} + +/* Pause a pane. */ +void +control_pause_pane(struct client *c, struct window_pane *wp) +{ + struct control_pane *cp; + + cp = control_add_pane(c, wp); + if (~cp->flags & CONTROL_PANE_PAUSED) { + cp->flags |= CONTROL_PANE_PAUSED; + control_discard_pane(c, cp); + control_write(c, "%%pause %%%u", wp->id); + } +} + +/* Write a line. */ +static void printflike(2, 0) +control_vwrite(struct client *c, const char *fmt, va_list ap) +{ + struct control_state *cs = c->control_state; + char *s; + + xvasprintf(&s, fmt, ap); + log_debug("%s: %s: writing line: %s", __func__, c->name, s); + + bufferevent_write(cs->write_event, s, strlen(s)); + bufferevent_write(cs->write_event, "\n", 1); + + bufferevent_enable(cs->write_event, EV_WRITE); + free(s); +} + /* Write a line. */ void control_write(struct client *c, const char *fmt, ...) { - va_list ap; + struct control_state *cs = c->control_state; + struct control_block *cb; + va_list ap; va_start(ap, fmt); - file_vprint(c, fmt, ap); - file_print(c, "\n"); + + if (TAILQ_EMPTY(&cs->all_blocks)) { + control_vwrite(c, fmt, ap); + va_end(ap); + return; + } + + cb = xcalloc(1, sizeof *cb); + xvasprintf(&cb->line, fmt, ap); + TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); + cb->t = get_timer(); + + log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); + bufferevent_enable(cs->write_event, EV_WRITE); + va_end(ap); } -/* Control error callback. */ +/* Check age for this pane. */ +static int +control_check_age(struct client *c, struct window_pane *wp, + struct control_pane *cp) +{ + struct control_block *cb; + uint64_t t, age; + + cb = TAILQ_FIRST(&cp->blocks); + if (cb == NULL) + return (0); + t = get_timer(); + if (cb->t >= t) + return (0); + + age = t - cb->t; + log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id, + (unsigned long long)age); + + if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { + if (age < c->pause_age) + return (0); + cp->flags |= CONTROL_PANE_PAUSED; + control_discard_pane(c, cp); + control_write(c, "%%pause %%%u", wp->id); + } else { + if (age < CONTROL_MAXIMUM_AGE) + return (0); + c->exit_message = xstrdup("too far behind"); + c->flags |= CLIENT_EXIT; + control_discard(c); + } + return (1); +} + +/* Write output from a pane. */ +void +control_write_output(struct client *c, struct window_pane *wp) +{ + struct control_state *cs = c->control_state; + struct control_pane *cp; + struct control_block *cb; + size_t new_size; + + if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) + return; + + if (c->flags & CONTROL_IGNORE_FLAGS) { + cp = control_get_pane(c, wp); + if (cp != NULL) + goto ignore; + return; + } + cp = control_add_pane(c, wp); + if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED)) + goto ignore; + if (control_check_age(c, wp, cp)) + return; + + window_pane_get_new_data(wp, &cp->queued, &new_size); + if (new_size == 0) + return; + window_pane_update_used_data(wp, &cp->queued, new_size); + + cb = xcalloc(1, sizeof *cb); + cb->size = new_size; + TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); + cb->t = get_timer(); + + TAILQ_INSERT_TAIL(&cp->blocks, cb, entry); + log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name, + cb->size, wp->id); + + if (!cp->pending_flag) { + log_debug("%s: %s: %%%u now pending", __func__, c->name, + wp->id); + TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry); + cp->pending_flag = 1; + cs->pending_count++; + } + bufferevent_enable(cs->write_event, EV_WRITE); + return; + +ignore: + log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id); + window_pane_update_used_data(wp, &cp->offset, SIZE_MAX); + window_pane_update_used_data(wp, &cp->queued, SIZE_MAX); +} + +/* Control client error callback. */ static enum cmd_retval control_error(struct cmdq_item *item, void *data) { - struct client *c = item->client; + struct client *c = cmdq_get_client(item); char *error = data; cmdq_guard(item, "begin", 1); @@ -53,54 +532,576 @@ control_error(struct cmdq_item *item, void *data) return (CMD_RETURN_NORMAL); } -/* Control input callback. Read lines and fire commands. */ +/* Control client error callback. */ static void -control_callback(__unused struct client *c, __unused const char *path, - int error, int closed, struct evbuffer *buffer, __unused void *data) +control_error_callback(__unused struct bufferevent *bufev, + __unused short what, void *data) { - char *line; - struct cmdq_item *item; - struct cmd_parse_result *pr; + struct client *c = data; - if (closed || error != 0) - c->flags |= CLIENT_EXIT; + c->flags |= CLIENT_EXIT; +} + +/* Control client input callback. Read lines and fire commands. */ +static void +control_read_callback(__unused struct bufferevent *bufev, void *data) +{ + struct client *c = data; + struct control_state *cs = c->control_state; + struct evbuffer *buffer = cs->read_event->input; + char *line, *error; + struct cmdq_state *state; + enum cmd_parse_status status; for (;;) { line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); if (line == NULL) break; - log_debug("%s: %s", __func__, line); - if (*line == '\0') { /* empty line exit */ + log_debug("%s: %s: %s", __func__, c->name, line); + if (*line == '\0') { /* empty line detach */ free(line); c->flags |= CLIENT_EXIT; break; } - pr = cmd_parse_from_string(line, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - break; - case CMD_PARSE_ERROR: - item = cmdq_get_callback(control_error, pr->error); - cmdq_append(c, item); - break; - case CMD_PARSE_SUCCESS: - item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); - item->shared->flags |= CMDQ_SHARED_CONTROL; - cmdq_append(c, item); - cmd_list_free(pr->cmdlist); - break; - } + state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL); + status = cmd_parse_and_append(line, NULL, c, state, &error); + if (status == CMD_PARSE_ERROR) + cmdq_append(c, cmdq_get_callback(control_error, error)); + cmdq_free_state(state); free(line); } } +/* Does this control client have outstanding data to write? */ +int +control_all_done(struct client *c) +{ + struct control_state *cs = c->control_state; + + if (!TAILQ_EMPTY(&cs->all_blocks)) + return (0); + return (EVBUFFER_LENGTH(cs->write_event->output) == 0); +} + +/* Flush all blocks until output. */ +static void +control_flush_all_blocks(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) { + if (cb->size != 0) + break; + log_debug("%s: %s: flushing line: %s", __func__, c->name, + cb->line); + + bufferevent_write(cs->write_event, cb->line, strlen(cb->line)); + bufferevent_write(cs->write_event, "\n", 1); + control_free_block(cs, cb); + } +} + +/* Append data to buffer. */ +static struct evbuffer * +control_append_data(struct client *c, struct control_pane *cp, uint64_t age, + struct evbuffer *message, struct window_pane *wp, size_t size) +{ + u_char *new_data; + size_t new_size; + u_int i; + + if (message == NULL) { + message = evbuffer_new(); + if (message == NULL) + fatalx("out of memory"); + if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { + evbuffer_add_printf(message, + "%%extended-output %%%u %llu : ", wp->id, + (unsigned long long)age); + } else + evbuffer_add_printf(message, "%%output %%%u ", wp->id); + } + + new_data = window_pane_get_new_data(wp, &cp->offset, &new_size); + if (new_size < size) + fatalx("not enough data: %zu < %zu", new_size, size); + for (i = 0; i < size; i++) { + if (new_data[i] < ' ' || new_data[i] == '\\') + evbuffer_add_printf(message, "\\%03o", new_data[i]); + else + evbuffer_add_printf(message, "%c", new_data[i]); + } + window_pane_update_used_data(wp, &cp->offset, size); + return (message); +} + +/* Write buffer. */ +static void +control_write_data(struct client *c, struct evbuffer *message) +{ + struct control_state *cs = c->control_state; + + log_debug("%s: %s: %.*s", __func__, c->name, + (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message)); + + evbuffer_add(message, "\n", 1); + bufferevent_write_buffer(cs->write_event, message); + evbuffer_free(message); +} + +/* Write output to client. */ +static int +control_write_pending(struct client *c, struct control_pane *cp, size_t limit) +{ + struct control_state *cs = c->control_state; + struct window_pane *wp = NULL; + struct evbuffer *message = NULL; + size_t used = 0, size; + struct control_block *cb, *cb1; + uint64_t age, t = get_timer(); + + wp = control_window_pane(c, cp->pane); + if (wp == NULL || wp->fd == -1) { + TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { + TAILQ_REMOVE(&cp->blocks, cb, entry); + control_free_block(cs, cb); + } + control_flush_all_blocks(c); + return (0); + } + + while (used != limit && !TAILQ_EMPTY(&cp->blocks)) { + if (control_check_age(c, wp, cp)) { + if (message != NULL) + evbuffer_free(message); + message = NULL; + break; + } + + cb = TAILQ_FIRST(&cp->blocks); + if (cb->t < t) + age = t - cb->t; + else + age = 0; + log_debug("%s: %s: output block %zu (age %llu) for %%%u " + "(used %zu/%zu)", __func__, c->name, cb->size, + (unsigned long long)age, cp->pane, used, limit); + + size = cb->size; + if (size > limit - used) + size = limit - used; + used += size; + + message = control_append_data(c, cp, age, message, wp, size); + + cb->size -= size; + if (cb->size == 0) { + TAILQ_REMOVE(&cp->blocks, cb, entry); + control_free_block(cs, cb); + + cb = TAILQ_FIRST(&cs->all_blocks); + if (cb != NULL && cb->size == 0) { + if (wp != NULL && message != NULL) { + control_write_data(c, message); + message = NULL; + } + control_flush_all_blocks(c); + } + } + } + if (message != NULL) + control_write_data(c, message); + return (!TAILQ_EMPTY(&cp->blocks)); +} + +/* Control client write callback. */ +static void +control_write_callback(__unused struct bufferevent *bufev, void *data) +{ + struct client *c = data; + struct control_state *cs = c->control_state; + struct control_pane *cp, *cp1; + struct evbuffer *evb = cs->write_event->output; + size_t space, limit; + + control_flush_all_blocks(c); + + while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) { + if (cs->pending_count == 0) + break; + space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb); + log_debug("%s: %s: %zu bytes available, %u panes", __func__, + c->name, space, cs->pending_count); + + limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */ + if (limit < CONTROL_WRITE_MINIMUM) + limit = CONTROL_WRITE_MINIMUM; + + TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) { + if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH) + break; + if (control_write_pending(c, cp, limit)) + continue; + TAILQ_REMOVE(&cs->pending_list, cp, pending_entry); + cp->pending_flag = 0; + cs->pending_count--; + } + } + if (EVBUFFER_LENGTH(evb) == 0) + bufferevent_disable(cs->write_event, EV_WRITE); +} + +/* Initialize for control mode. */ void control_start(struct client *c) { - file_read(c, "-", control_callback, c); + struct control_state *cs; + + if (c->flags & CLIENT_CONTROLCONTROL) { + close(c->out_fd); + c->out_fd = -1; + } else + setblocking(c->out_fd, 0); + setblocking(c->fd, 0); + + cs = c->control_state = xcalloc(1, sizeof *cs); + RB_INIT(&cs->panes); + TAILQ_INIT(&cs->pending_list); + TAILQ_INIT(&cs->all_blocks); + RB_INIT(&cs->subs); + + cs->read_event = bufferevent_new(c->fd, control_read_callback, + control_write_callback, control_error_callback, c); + bufferevent_enable(cs->read_event, EV_READ); if (c->flags & CLIENT_CONTROLCONTROL) - file_print(c, "\033P1000p"); + cs->write_event = cs->read_event; + else { + cs->write_event = bufferevent_new(c->out_fd, NULL, + control_write_callback, control_error_callback, c); + } + bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, + 0); + + if (c->flags & CLIENT_CONTROLCONTROL) { + bufferevent_write(cs->write_event, "\033P1000p", 7); + bufferevent_enable(cs->write_event, EV_WRITE); + } +} + +/* Discard all output for a client. */ +void +control_discard(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_pane *cp; + + RB_FOREACH(cp, control_panes, &cs->panes) + control_discard_pane(c, cp); + bufferevent_disable(cs->read_event, EV_READ); +} + +/* Stop control mode. */ +void +control_stop(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + struct control_sub *csub, *csub1; + + if (~c->flags & CLIENT_CONTROLCONTROL) + bufferevent_free(cs->write_event); + bufferevent_free(cs->read_event); + + RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) + control_free_sub(cs, csub); + if (evtimer_initialized(&cs->subs_timer)) + evtimer_del(&cs->subs_timer); + + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) + control_free_block(cs, cb); + control_reset_offsets(c); + + free(cs); +} + +/* Check session subscription. */ +static void +control_check_subs_session(struct client *c, struct control_sub *csub) +{ + struct session *s = c->session; + struct format_tree *ft; + char *value; + + ft = format_create_defaults(NULL, c, s, NULL, NULL); + value = format_expand(ft, csub->format); + format_free(ft); + + if (csub->last != NULL && strcmp(value, csub->last) == 0) { + free(value); + return; + } + control_write(c, + "%%subscription-changed %s $%u - - - : %s", + csub->name, s->id, value); + free(csub->last); + csub->last = value; +} + +/* Check pane subscription. */ +static void +control_check_subs_pane(struct client *c, struct control_sub *csub) +{ + struct session *s = c->session; + struct window_pane *wp; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct control_sub_pane *csp, find; + + wp = window_pane_find_by_id(csub->id); + if (wp == NULL || wp->fd == -1) + return; + w = wp->window; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session != s) + continue; + + ft = format_create_defaults(NULL, c, s, wl, wp); + value = format_expand(ft, csub->format); + format_free(ft); + + find.pane = wp->id; + find.idx = wl->idx; + + csp = RB_FIND(control_sub_panes, &csub->panes, &find); + if (csp == NULL) { + csp = xcalloc(1, sizeof *csp); + csp->pane = wp->id; + csp->idx = wl->idx; + RB_INSERT(control_sub_panes, &csub->panes, csp); + } + + if (csp->last != NULL && strcmp(value, csp->last) == 0) { + free(value); + continue; + } + control_write(c, + "%%subscription-changed %s $%u @%u %u %%%u : %s", + csub->name, s->id, w->id, wl->idx, wp->id, value); + free(csp->last); + csp->last = value; + } +} + +/* Check all panes subscription. */ +static void +control_check_subs_all_panes(struct client *c, struct control_sub *csub) +{ + struct session *s = c->session; + struct window_pane *wp; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct control_sub_pane *csp, find; + + RB_FOREACH(wl, winlinks, &s->windows) { + w = wl->window; + TAILQ_FOREACH(wp, &w->panes, entry) { + ft = format_create_defaults(NULL, c, s, wl, wp); + value = format_expand(ft, csub->format); + format_free(ft); + + find.pane = wp->id; + find.idx = wl->idx; + + csp = RB_FIND(control_sub_panes, &csub->panes, &find); + if (csp == NULL) { + csp = xcalloc(1, sizeof *csp); + csp->pane = wp->id; + csp->idx = wl->idx; + RB_INSERT(control_sub_panes, &csub->panes, csp); + } + + if (csp->last != NULL && + strcmp(value, csp->last) == 0) { + free(value); + continue; + } + control_write(c, + "%%subscription-changed %s $%u @%u %u %%%u : %s", + csub->name, s->id, w->id, wl->idx, wp->id, value); + free(csp->last); + csp->last = value; + } + } +} + +/* Check window subscription. */ +static void +control_check_subs_window(struct client *c, struct control_sub *csub) +{ + struct session *s = c->session; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct control_sub_window *csw, find; + + w = window_find_by_id(csub->id); + if (w == NULL) + return; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session != s) + continue; + + ft = format_create_defaults(NULL, c, s, wl, NULL); + value = format_expand(ft, csub->format); + format_free(ft); + + find.window = w->id; + find.idx = wl->idx; + + csw = RB_FIND(control_sub_windows, &csub->windows, &find); + if (csw == NULL) { + csw = xcalloc(1, sizeof *csw); + csw->window = w->id; + csw->idx = wl->idx; + RB_INSERT(control_sub_windows, &csub->windows, csw); + } + + if (csw->last != NULL && strcmp(value, csw->last) == 0) { + free(value); + continue; + } + control_write(c, + "%%subscription-changed %s $%u @%u %u - : %s", + csub->name, s->id, w->id, wl->idx, value); + free(csw->last); + csw->last = value; + } +} + +/* Check all windows subscription. */ +static void +control_check_subs_all_windows(struct client *c, struct control_sub *csub) +{ + struct session *s = c->session; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct control_sub_window *csw, find; + + RB_FOREACH(wl, winlinks, &s->windows) { + w = wl->window; + + ft = format_create_defaults(NULL, c, s, wl, NULL); + value = format_expand(ft, csub->format); + format_free(ft); + + find.window = w->id; + find.idx = wl->idx; + + csw = RB_FIND(control_sub_windows, &csub->windows, &find); + if (csw == NULL) { + csw = xcalloc(1, sizeof *csw); + csw->window = w->id; + csw->idx = wl->idx; + RB_INSERT(control_sub_windows, &csub->windows, csw); + } + + if (csw->last != NULL && strcmp(value, csw->last) == 0) { + free(value); + continue; + } + control_write(c, + "%%subscription-changed %s $%u @%u %u - : %s", + csub->name, s->id, w->id, wl->idx, value); + free(csw->last); + csw->last = value; + } +} + +/* Check subscriptions timer. */ +static void +control_check_subs_timer(__unused int fd, __unused short events, void *data) +{ + struct client *c = data; + struct control_state *cs = c->control_state; + struct control_sub *csub, *csub1; + struct timeval tv = { .tv_sec = 1 }; + + log_debug("%s: timer fired", __func__); + evtimer_add(&cs->subs_timer, &tv); + + RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) { + switch (csub->type) { + case CONTROL_SUB_SESSION: + control_check_subs_session(c, csub); + break; + case CONTROL_SUB_PANE: + control_check_subs_pane(c, csub); + break; + case CONTROL_SUB_ALL_PANES: + control_check_subs_all_panes(c, csub); + break; + case CONTROL_SUB_WINDOW: + control_check_subs_window(c, csub); + break; + case CONTROL_SUB_ALL_WINDOWS: + control_check_subs_all_windows(c, csub); + break; + } + } +} + +/* Add a subscription. */ +void +control_add_sub(struct client *c, const char *name, enum control_sub_type type, + int id, const char *format) +{ + struct control_state *cs = c->control_state; + struct control_sub *csub, find; + struct timeval tv = { .tv_sec = 1 }; + + find.name = (char *)name; + if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) + control_free_sub(cs, csub); + + csub = xcalloc(1, sizeof *csub); + csub->name = xstrdup(name); + csub->type = type; + csub->id = id; + csub->format = xstrdup(format); + RB_INSERT(control_subs, &cs->subs, csub); + + RB_INIT(&csub->panes); + RB_INIT(&csub->windows); + + if (!evtimer_initialized(&cs->subs_timer)) + evtimer_set(&cs->subs_timer, control_check_subs_timer, c); + if (!evtimer_pending(&cs->subs_timer, NULL)) + evtimer_add(&cs->subs_timer, &tv); +} + +/* Remove a subscription. */ +void +control_remove_sub(struct client *c, const char *name) +{ + struct control_state *cs = c->control_state; + struct control_sub *csub, find; + + find.name = (char *)name; + if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) + control_free_sub(cs, csub); + if (RB_EMPTY(&cs->subs)) + evtimer_del(&cs->subs_timer); } diff --git a/environ.c b/environ.c index 25968f7b..74d672e0 100644 --- a/environ.c +++ b/environ.c @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -86,8 +87,10 @@ environ_copy(struct environ *srcenv, struct environ *dstenv) RB_FOREACH(envent, environ, srcenv) { if (envent->value == NULL) environ_clear(dstenv, envent->name); - else - environ_set(dstenv, envent->name, "%s", envent->value); + else { + environ_set(dstenv, envent->name, envent->flags, + "%s", envent->value); + } } } @@ -103,18 +106,21 @@ environ_find(struct environ *env, const char *name) /* Set an environment variable. */ void -environ_set(struct environ *env, const char *name, const char *fmt, ...) +environ_set(struct environ *env, const char *name, int flags, const char *fmt, + ...) { struct environ_entry *envent; va_list ap; va_start(ap, fmt); if ((envent = environ_find(env, name)) != NULL) { + envent->flags = flags; free(envent->value); xvasprintf(&envent->value, fmt, ap); } else { envent = xmalloc(sizeof *envent); envent->name = xstrdup(name); + envent->flags = flags; xvasprintf(&envent->value, fmt, ap); RB_INSERT(environ, env, envent); } @@ -133,6 +139,7 @@ environ_clear(struct environ *env, const char *name) } else { envent = xmalloc(sizeof *envent); envent->name = xstrdup(name); + envent->flags = 0; envent->value = NULL; RB_INSERT(environ, env, envent); } @@ -140,7 +147,7 @@ environ_clear(struct environ *env, const char *name) /* Set an environment variable from a NAME=VALUE string. */ void -environ_put(struct environ *env, const char *var) +environ_put(struct environ *env, const char *var, int flags) { char *name, *value; @@ -152,7 +159,7 @@ environ_put(struct environ *env, const char *var) name = xstrdup(var); name[strcspn(name, "=")] = '\0'; - environ_set(env, name, "%s", value); + environ_set(env, name, flags, "%s", value); free(name); } @@ -170,7 +177,7 @@ environ_unset(struct environ *env, const char *name) free(envent); } -/* Copy variables from a destination into a source * environment. */ +/* Copy variables from a destination into a source environment. */ void environ_update(struct options *oo, struct environ *src, struct environ *dst) { @@ -185,10 +192,14 @@ environ_update(struct options *oo, struct environ *src, struct environ *dst) a = options_array_first(o); while (a != NULL) { ov = options_array_item_value(a); - if ((envent = environ_find(src, ov->string)) == NULL) + RB_FOREACH(envent, environ, src) { + if (fnmatch(ov->string, envent->name, 0) == 0) + break; + } + if (envent == NULL) environ_clear(dst, ov->string); else - environ_set(dst, envent->name, "%s", envent->value); + environ_set(dst, envent->name, 0, "%s", envent->value); a = options_array_next(a); } } @@ -201,7 +212,9 @@ environ_push(struct environ *env) environ = xcalloc(1, sizeof *environ); RB_FOREACH(envent, environ, env) { - if (envent->value != NULL && *envent->name != '\0') + if (envent->value != NULL && + *envent->name != '\0' && + (~envent->flags & ENVIRON_HIDDEN)) setenv(envent->name, envent->value, 1); } } @@ -243,14 +256,17 @@ environ_for_session(struct session *s, int no_TERM) if (!no_TERM) { value = options_get_string(global_options, "default-terminal"); - environ_set(env, "TERM", "%s", value); + environ_set(env, "TERM", 0, "%s", value); + environ_set(env, "TERM_PROGRAM", 0, "%s", "tmux"); + environ_set(env, "TERM_PROGRAM_VERSION", 0, "%s", getversion()); } if (s != NULL) idx = s->id; else idx = -1; - environ_set(env, "TMUX", "%s,%ld,%d", socket_path, (long)getpid(), idx); + environ_set(env, "TMUX", 0, "%s,%ld,%d", socket_path, (long)getpid(), + idx); return (env); } diff --git a/example_tmux.conf b/example_tmux.conf index 11db9ec6..1bd9afdc 100644 --- a/example_tmux.conf +++ b/example_tmux.conf @@ -61,7 +61,7 @@ bind y set synchronize-panes\; display 'synchronize-panes #{?synchronize-panes,o # should be started with "tmux attach" rather than "tmux new" new -d -s0 -nirssi 'exec irssi' set -t0:0 monitor-activity on -set -t0:0 aggressive-resize on +set -t0:0 aggressive-resize on neww -d -ntodo 'exec emacs ~/TODO' setw -t0:1 aggressive-resize on neww -d -nmutt 'exec mutt' diff --git a/file.c b/file.c index 2c06765c..b2f155fe 100644 --- a/file.c +++ b/file.c @@ -17,7 +17,6 @@ */ #include -#include #include #include @@ -28,10 +27,17 @@ #include "tmux.h" +/* + * IPC file handling. Both client and server use the same data structures + * (client_file and client_files) to store list of active files. Most functions + * are for use either in client or server but not both. + */ + static int file_next_stream = 3; RB_GENERATE(client_files, client_file, entry, file_cmp); +/* Get path for file, either as given or from working directory. */ static char * file_get_path(struct client *c, const char *file) { @@ -44,6 +50,7 @@ file_get_path(struct client *c, const char *file) return (path); } +/* Tree comparison function. */ int file_cmp(struct client_file *cf1, struct client_file *cf2) { @@ -54,11 +61,47 @@ file_cmp(struct client_file *cf1, struct client_file *cf2) return (0); } +/* + * Create a file object in the client process - the peer is the server to send + * messages to. Check callback is fired when the file is finished with so the + * process can decide if it needs to exit (if it is waiting for files to + * flush). + */ struct client_file * -file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) +file_create_with_peer(struct tmuxpeer *peer, struct client_files *files, + int stream, client_file_cb cb, void *cbdata) { struct client_file *cf; + cf = xcalloc(1, sizeof *cf); + cf->c = NULL; + cf->references = 1; + cf->stream = stream; + + cf->buffer = evbuffer_new(); + if (cf->buffer == NULL) + fatalx("out of memory"); + + cf->cb = cb; + cf->data = cbdata; + + cf->peer = peer; + cf->tree = files; + RB_INSERT(client_files, files, cf); + + return (cf); +} + +/* Create a file object in the server, communicating with the given client. */ +struct client_file * +file_create_with_client(struct client *c, int stream, client_file_cb cb, + void *cbdata) +{ + struct client_file *cf; + + if (c != NULL && (c->flags & CLIENT_ATTACHED)) + c = NULL; + cf = xcalloc(1, sizeof *cf); cf->c = c; cf->references = 1; @@ -72,6 +115,8 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) cf->data = cbdata; if (cf->c != NULL) { + cf->peer = cf->c->peer; + cf->tree = &cf->c->files; RB_INSERT(client_files, &cf->c->files, cf); cf->c->references++; } @@ -79,6 +124,7 @@ file_create(struct client *c, int stream, client_file_cb cb, void *cbdata) return (cf); } +/* Free a file. */ void file_free(struct client_file *cf) { @@ -88,13 +134,15 @@ file_free(struct client_file *cf) evbuffer_free(cf->buffer); free(cf->path); - if (cf->c != NULL) { - RB_REMOVE(client_files, &cf->c->files, cf); + if (cf->tree != NULL) + RB_REMOVE(client_files, cf->tree, cf); + if (cf->c != NULL) server_client_unref(cf->c); - } + free(cf); } +/* Event to fire the done callback. */ static void file_fire_done_cb(__unused int fd, __unused short events, void *arg) { @@ -106,21 +154,22 @@ file_fire_done_cb(__unused int fd, __unused short events, void *arg) file_free(cf); } +/* Add an event to fire the done callback (used by the server). */ void file_fire_done(struct client_file *cf) { event_once(-1, EV_TIMEOUT, file_fire_done_cb, cf, NULL); } +/* Fire the read callback. */ void file_fire_read(struct client_file *cf) { - struct client *c = cf->c; - if (cf->cb != NULL) - cf->cb(c, cf->path, cf->error, 0, cf->buffer, cf->data); + cf->cb(cf->c, cf->path, cf->error, 0, cf->buffer, cf->data); } +/* Can this file be printed to? */ int file_can_print(struct client *c) { @@ -131,6 +180,7 @@ file_can_print(struct client *c) return (1); } +/* Print a message to a file. */ void file_print(struct client *c, const char *fmt, ...) { @@ -141,6 +191,7 @@ file_print(struct client *c, const char *fmt, ...) va_end(ap); } +/* Print a message to a file. */ void file_vprint(struct client *c, const char *fmt, va_list ap) { @@ -152,7 +203,7 @@ file_vprint(struct client *c, const char *fmt, va_list ap) find.stream = 1; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 1, NULL, NULL); + cf = file_create_with_client(c, 1, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add_vprintf(cf->buffer, fmt, ap); @@ -167,6 +218,7 @@ file_vprint(struct client *c, const char *fmt, va_list ap) } } +/* Print a buffer to a file. */ void file_print_buffer(struct client *c, void *data, size_t size) { @@ -178,7 +230,7 @@ file_print_buffer(struct client *c, void *data, size_t size) find.stream = 1; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 1, NULL, NULL); + cf = file_create_with_client(c, 1, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add(cf->buffer, data, size); @@ -193,6 +245,7 @@ file_print_buffer(struct client *c, void *data, size_t size) } } +/* Report an error to a file. */ void file_error(struct client *c, const char *fmt, ...) { @@ -207,7 +260,7 @@ file_error(struct client *c, const char *fmt, ...) find.stream = 2; if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) { - cf = file_create(c, 2, NULL, NULL); + cf = file_create_with_client(c, 2, NULL, NULL); cf->path = xstrdup("-"); evbuffer_add_vprintf(cf->buffer, fmt, ap); @@ -224,30 +277,34 @@ file_error(struct client *c, const char *fmt, ...) va_end(ap); } +/* Write data to a file. */ void file_write(struct client *c, const char *path, int flags, const void *bdata, size_t bsize, client_file_cb cb, void *cbdata) { struct client_file *cf; - FILE *f; struct msg_write_open *msg; size_t msglen; int fd = -1; + u_int stream = file_next_stream++; + FILE *f; const char *mode; if (strcmp(path, "-") == 0) { - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = xstrdup("-"); fd = STDOUT_FILENO; - if (c == NULL || c->flags & CLIENT_ATTACHED) { + if (c == NULL || + (c->flags & CLIENT_ATTACHED) || + (c->flags & CLIENT_CONTROL)) { cf->error = EBADF; goto done; } goto skip; } - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { @@ -282,7 +339,7 @@ skip: msg->fd = fd; msg->flags = flags; memcpy(msg + 1, cf->path, msglen - sizeof *msg); - if (proc_send(c->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) { + if (proc_send(cf->peer, MSG_WRITE_OPEN, -1, msg, msglen) != 0) { free(msg); cf->error = EINVAL; goto done; @@ -294,29 +351,34 @@ done: file_fire_done(cf); } +/* Read a file. */ void file_read(struct client *c, const char *path, client_file_cb cb, void *cbdata) { struct client_file *cf; - FILE *f; struct msg_read_open *msg; - size_t msglen, size; + size_t msglen; int fd = -1; + u_int stream = file_next_stream++; + FILE *f; + size_t size; char buffer[BUFSIZ]; if (strcmp(path, "-") == 0) { - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = xstrdup("-"); fd = STDIN_FILENO; - if (c == NULL || c->flags & CLIENT_ATTACHED) { + if (c == NULL || + (c->flags & CLIENT_ATTACHED) || + (c->flags & CLIENT_CONTROL)) { cf->error = EBADF; goto done; } goto skip; } - cf = file_create(c, file_next_stream++, cb, cbdata); + cf = file_create_with_client(c, stream, cb, cbdata); cf->path = file_get_path(c, path); if (c == NULL || c->flags & CLIENT_ATTACHED) { @@ -352,7 +414,7 @@ skip: msg->stream = cf->stream; msg->fd = fd; memcpy(msg + 1, cf->path, msglen - sizeof *msg); - if (proc_send(c->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) { + if (proc_send(cf->peer, MSG_READ_OPEN, -1, msg, msglen) != 0) { free(msg); cf->error = EINVAL; goto done; @@ -364,21 +426,21 @@ done: file_fire_done(cf); } +/* Push event, fired if there is more writing to be done. */ static void file_push_cb(__unused int fd, __unused short events, void *arg) { struct client_file *cf = arg; - struct client *c = cf->c; - if (~c->flags & CLIENT_DEAD) + if (cf->c == NULL || ~cf->c->flags & CLIENT_DEAD) file_push(cf); file_free(cf); } +/* Push uwritten data to the client for a file, if it will accept it. */ void file_push(struct client_file *cf) { - struct client *c = cf->c; struct msg_write_data *msg; size_t msglen, sent, left; struct msg_write_close close; @@ -394,21 +456,364 @@ file_push(struct client_file *cf) msg = xrealloc(msg, msglen); msg->stream = cf->stream; memcpy(msg + 1, EVBUFFER_DATA(cf->buffer), sent); - if (proc_send(c->peer, MSG_WRITE, -1, msg, msglen) != 0) + if (proc_send(cf->peer, MSG_WRITE, -1, msg, msglen) != 0) break; evbuffer_drain(cf->buffer, sent); left = EVBUFFER_LENGTH(cf->buffer); - log_debug("%s: file %d sent %zu, left %zu", c->name, cf->stream, - sent, left); + log_debug("file %d sent %zu, left %zu", cf->stream, sent, left); } if (left != 0) { cf->references++; event_once(-1, EV_TIMEOUT, file_push_cb, cf, NULL); } else if (cf->stream > 2) { close.stream = cf->stream; - proc_send(c->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); + proc_send(cf->peer, MSG_WRITE_CLOSE, -1, &close, sizeof close); file_fire_done(cf); } free(msg); } + +/* Check if any files have data left to write. */ +int +file_write_left(struct client_files *files) +{ + struct client_file *cf; + size_t left; + int waiting = 0; + + RB_FOREACH(cf, client_files, files) { + if (cf->event == NULL) + continue; + left = EVBUFFER_LENGTH(cf->event->output); + if (left != 0) { + waiting++; + log_debug("file %u %zu bytes left", cf->stream, left); + } + } + return (waiting != 0); +} + +/* Client file write error callback. */ +static void +file_write_error_callback(__unused struct bufferevent *bev, __unused short what, + void *arg) +{ + struct client_file *cf = arg; + + log_debug("write error file %d", cf->stream); + + bufferevent_free(cf->event); + cf->event = NULL; + + close(cf->fd); + cf->fd = -1; + + if (cf->cb != NULL) + cf->cb(NULL, NULL, 0, -1, NULL, cf->data); +} + +/* Client file write callback. */ +static void +file_write_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + + log_debug("write check file %d", cf->stream); + + if (cf->cb != NULL) + cf->cb(NULL, NULL, 0, -1, NULL, cf->data); + + if (cf->closed && EVBUFFER_LENGTH(cf->event->output) == 0) { + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, cf->tree, cf); + file_free(cf); + } +} + +/* Handle a file write open message (client). */ +void +file_write_open(struct client_files *files, struct tmuxpeer *peer, + struct imsg *imsg, int allow_streams, int close_received, + client_file_cb cb, void *cbdata) +{ + struct msg_write_open *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + const char *path; + struct msg_write_ready reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_WRONLY|O_CREAT; + int error = 0; + + if (msglen < sizeof *msg) + fatalx("bad MSG_WRITE_OPEN size"); + if (msglen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open write file %d %s", msg->stream, path); + + find.stream = msg->stream; + if (RB_FIND(client_files, files, &find) != NULL) { + error = EBADF; + goto reply; + } + cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata); + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(path, msg->flags|flags, 0644); + else if (allow_streams) { + if (msg->fd != STDOUT_FILENO && msg->fd != STDERR_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + if (close_received) + close(msg->fd); /* can only be used once */ + } + } else + errno = EBADF; + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, NULL, file_write_callback, + file_write_error_callback, cf); + bufferevent_enable(cf->event, EV_WRITE); + goto reply; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(peer, MSG_WRITE_READY, -1, &reply, sizeof reply); +} + +/* Handle a file write data message (client). */ +void +file_write_data(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_data *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + size_t size = msglen - sizeof *msg; + + if (msglen < sizeof *msg) + fatalx("bad MSG_WRITE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("write %zu to file %d", size, cf->stream); + + if (cf->event != NULL) + bufferevent_write(cf->event, msg + 1, size); +} + +/* Handle a file write close message (client). */ +void +file_write_close(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_close *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_WRITE_CLOSE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + fatalx("unknown stream number"); + log_debug("close file %d", cf->stream); + + if (cf->event == NULL || EVBUFFER_LENGTH(cf->event->output) == 0) { + if (cf->event != NULL) + bufferevent_free(cf->event); + if (cf->fd != -1) + close(cf->fd); + RB_REMOVE(client_files, files, cf); + file_free(cf); + } +} + +/* Client file read error callback. */ +static void +file_read_error_callback(__unused struct bufferevent *bev, __unused short what, + void *arg) +{ + struct client_file *cf = arg; + struct msg_read_done msg; + + log_debug("read error file %d", cf->stream); + + msg.stream = cf->stream; + msg.error = 0; + proc_send(cf->peer, MSG_READ_DONE, -1, &msg, sizeof msg); + + bufferevent_free(cf->event); + close(cf->fd); + RB_REMOVE(client_files, cf->tree, cf); + file_free(cf); +} + +/* Client file read callback. */ +static void +file_read_callback(__unused struct bufferevent *bev, void *arg) +{ + struct client_file *cf = arg; + void *bdata; + size_t bsize; + struct msg_read_data *msg; + size_t msglen; + + msg = xmalloc(sizeof *msg); + for (;;) { + bdata = EVBUFFER_DATA(cf->event->input); + bsize = EVBUFFER_LENGTH(cf->event->input); + + if (bsize == 0) + break; + if (bsize > MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg) + bsize = MAX_IMSGSIZE - IMSG_HEADER_SIZE - sizeof *msg; + log_debug("read %zu from file %d", bsize, cf->stream); + + msglen = (sizeof *msg) + bsize; + msg = xrealloc(msg, msglen); + msg->stream = cf->stream; + memcpy(msg + 1, bdata, bsize); + proc_send(cf->peer, MSG_READ, -1, msg, msglen); + + evbuffer_drain(cf->event->input, bsize); + } + free(msg); +} + +/* Handle a file read open message (client). */ +void +file_read_open(struct client_files *files, struct tmuxpeer *peer, + struct imsg *imsg, int allow_streams, int close_received, client_file_cb cb, + void *cbdata) +{ + struct msg_read_open *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + const char *path; + struct msg_read_done reply; + struct client_file find, *cf; + const int flags = O_NONBLOCK|O_RDONLY; + int error; + + if (msglen < sizeof *msg) + fatalx("bad MSG_READ_OPEN size"); + if (msglen == sizeof *msg) + path = "-"; + else + path = (const char *)(msg + 1); + log_debug("open read file %d %s", msg->stream, path); + + find.stream = msg->stream; + if (RB_FIND(client_files, files, &find) != NULL) { + error = EBADF; + goto reply; + } + cf = file_create_with_peer(peer, files, msg->stream, cb, cbdata); + if (cf->closed) { + error = EBADF; + goto reply; + } + + cf->fd = -1; + if (msg->fd == -1) + cf->fd = open(path, flags); + else if (allow_streams) { + if (msg->fd != STDIN_FILENO) + errno = EBADF; + else { + cf->fd = dup(msg->fd); + if (close_received) + close(msg->fd); /* can only be used once */ + } + } else + errno = EBADF; + if (cf->fd == -1) { + error = errno; + goto reply; + } + + cf->event = bufferevent_new(cf->fd, file_read_callback, NULL, + file_read_error_callback, cf); + bufferevent_enable(cf->event, EV_READ); + return; + +reply: + reply.stream = msg->stream; + reply.error = error; + proc_send(peer, MSG_READ_DONE, -1, &reply, sizeof reply); +} + +/* Handle a write ready message (server). */ +void +file_write_ready(struct client_files *files, struct imsg *imsg) +{ + struct msg_write_ready *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_WRITE_READY size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + if (msg->error != 0) { + cf->error = msg->error; + file_fire_done(cf); + } else + file_push(cf); +} + +/* Handle read data message (server). */ +void +file_read_data(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_data *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + void *bdata = msg + 1; + size_t bsize = msglen - sizeof *msg; + + if (msglen < sizeof *msg) + fatalx("bad MSG_READ_DATA size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + + log_debug("file %d read %zu bytes", cf->stream, bsize); + if (cf->error == 0) { + if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { + cf->error = ENOMEM; + file_fire_done(cf); + } else + file_fire_read(cf); + } +} + +/* Handle a read done message (server). */ +void +file_read_done(struct client_files *files, struct imsg *imsg) +{ + struct msg_read_done *msg = imsg->data; + size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; + struct client_file find, *cf; + + if (msglen != sizeof *msg) + fatalx("bad MSG_READ_DONE size"); + find.stream = msg->stream; + if ((cf = RB_FIND(client_files, files, &find)) == NULL) + return; + + log_debug("file %d read done", cf->stream); + cf->error = msg->error; + file_fire_done(cf); +} diff --git a/format-draw.c b/format-draw.c index bb16e0dd..6164cc44 100644 --- a/format-draw.c +++ b/format-draw.c @@ -142,7 +142,8 @@ format_draw_put_list(struct screen_write_ctx *octx, width -= list_left->cx; } if (start + width < list->cx && width > list_right->cx) { - screen_write_cursormove(octx, ocx + offset + width - 1, ocy, 0); + screen_write_cursormove(octx, ocx + offset + width - + list_right->cx, ocy, 0); screen_write_fast_copy(octx, list_right, 0, 0, list_right->cx, 1); width -= list_right->cx; @@ -156,13 +157,14 @@ format_draw_put_list(struct screen_write_ctx *octx, static void format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx, u_int ocy, struct screen *left, struct screen *centre, struct screen *right, - struct format_ranges *frs) + struct screen *abs_centre, struct format_ranges *frs) { - u_int width_left, width_centre, width_right; + u_int width_left, width_centre, width_right, width_abs_centre; width_left = left->cx; width_centre = centre->cx; width_right = right->cx; + width_abs_centre = abs_centre->cx; /* * Try to keep as much of the left and right as possible at the expense @@ -198,23 +200,34 @@ format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx, - width_centre / 2, centre->cx / 2 - width_centre / 2, width_centre); + + /* + * Write abs_centre in the perfect centre of all horizontal space. + */ + if (width_abs_centre > available) + width_abs_centre = available; + format_draw_put(octx, ocx, ocy, abs_centre, frs, + (available - width_abs_centre) / 2, + 0, + width_abs_centre); } /* Draw format with list on the left. */ static void format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx, u_int ocy, struct screen *left, struct screen *centre, struct screen *right, - struct screen *list, struct screen *list_left, struct screen *list_right, - struct screen *after, int focus_start, int focus_end, - struct format_ranges *frs) + struct screen *abs_centre, struct screen *list, struct screen *list_left, + struct screen *list_right, struct screen *after, int focus_start, + int focus_end, struct format_ranges *frs) { u_int width_left, width_centre, width_right; - u_int width_list, width_after; + u_int width_list, width_after, width_abs_centre; struct screen_write_ctx ctx; width_left = left->cx; width_centre = centre->cx; width_right = right->cx; + width_abs_centre = abs_centre->cx; width_list = list->cx; width_after = after->cx; @@ -241,12 +254,12 @@ format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, left); + screen_write_start(&ctx, left); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); format_draw_none(octx, available, ocx, ocy, left, centre, - right, frs); + right, abs_centre, frs); return; } @@ -290,23 +303,34 @@ format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx, focus_start = focus_end = 0; format_draw_put_list(octx, ocx, ocy, width_left, width_list, list, list_left, list_right, focus_start, focus_end, frs); + + /* + * Write abs_centre in the perfect centre of all horizontal space. + */ + if (width_abs_centre > available) + width_abs_centre = available; + format_draw_put(octx, ocx, ocy, abs_centre, frs, + (available - width_abs_centre) / 2, + 0, + width_abs_centre); } /* Draw format with list in the centre. */ static void format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx, u_int ocy, struct screen *left, struct screen *centre, struct screen *right, - struct screen *list, struct screen *list_left, struct screen *list_right, - struct screen *after, int focus_start, int focus_end, - struct format_ranges *frs) + struct screen *abs_centre, struct screen *list, struct screen *list_left, + struct screen *list_right, struct screen *after, int focus_start, + int focus_end, struct format_ranges *frs) { - u_int width_left, width_centre, width_right; - u_int width_list, width_after, middle; + u_int width_left, width_centre, width_right, middle; + u_int width_list, width_after, width_abs_centre; struct screen_write_ctx ctx; width_left = left->cx; width_centre = centre->cx; width_right = right->cx; + width_abs_centre = abs_centre->cx; width_list = list->cx; width_after = after->cx; @@ -333,12 +357,12 @@ format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, centre); + screen_write_start(&ctx, centre); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); format_draw_none(octx, available, ocx, ocy, left, centre, - right, frs); + right, abs_centre, frs); return; } @@ -387,23 +411,34 @@ format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx, format_draw_put_list(octx, ocx, ocy, middle - width_list / 2, width_list, list, list_left, list_right, focus_start, focus_end, frs); + + /* + * Write abs_centre in the perfect centre of all horizontal space. + */ + if (width_abs_centre > available) + width_abs_centre = available; + format_draw_put(octx, ocx, ocy, abs_centre, frs, + (available - width_abs_centre) / 2, + 0, + width_abs_centre); } /* Draw format with list on the right. */ static void format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, u_int ocy, struct screen *left, struct screen *centre, struct screen *right, - struct screen *list, struct screen *list_left, struct screen *list_right, - struct screen *after, int focus_start, int focus_end, - struct format_ranges *frs) + struct screen *abs_centre, struct screen *list, + struct screen *list_left, struct screen *list_right, struct screen *after, + int focus_start, int focus_end, struct format_ranges *frs) { u_int width_left, width_centre, width_right; - u_int width_list, width_after; + u_int width_list, width_after, width_abs_centre; struct screen_write_ctx ctx; width_left = left->cx; width_centre = centre->cx; width_right = right->cx; + width_abs_centre = abs_centre->cx; width_list = list->cx; width_after = after->cx; @@ -430,12 +465,12 @@ format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, /* If there is no list left, pass off to the no list function. */ if (width_list == 0) { - screen_write_start(&ctx, NULL, right); + screen_write_start(&ctx, right); screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1); screen_write_stop(&ctx); format_draw_none(octx, available, ocx, ocy, left, centre, - right, frs); + right, abs_centre, frs); return; } @@ -483,6 +518,160 @@ format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx, format_draw_put_list(octx, ocx, ocy, available - width_list - width_after, width_list, list, list_left, list_right, focus_start, focus_end, frs); + + /* + * Write abs_centre in the perfect centre of all horizontal space. + */ + if (width_abs_centre > available) + width_abs_centre = available; + format_draw_put(octx, ocx, ocy, abs_centre, frs, + (available - width_abs_centre) / 2, + 0, + width_abs_centre); +} + +static void +format_draw_absolute_centre(struct screen_write_ctx *octx, u_int available, + u_int ocx, u_int ocy, struct screen *left, struct screen *centre, + struct screen *right, struct screen *abs_centre, struct screen *list, + struct screen *list_left, struct screen *list_right, struct screen *after, + int focus_start, int focus_end, struct format_ranges *frs) +{ + u_int width_left, width_centre, width_right, width_abs_centre; + u_int width_list, width_after, middle, abs_centre_offset; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_abs_centre = abs_centre->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first centre, then the right, then the left. + */ + while (width_left + + width_centre + + width_right > available) { + if (width_centre > 0) + width_centre--; + else if (width_right > 0) + width_right--; + else + width_left--; + } + + /* + * We trim list after and abs_centre independently, as we are drawing + * them over the rest. Trim first the list, then after the list, then + * abs_centre. + */ + while (width_list + width_after + width_abs_centre > available) { + if (width_list > 0) + width_list--; + else if (width_after > 0) + width_after--; + else + width_abs_centre--; + } + + /* Write left at 0. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write right at available - width_right. */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right, + right->cx - width_right, + width_right); + + /* + * Keep writing centre at the relative centre. Only the list is written + * in the absolute centre of the horizontal space. + */ + middle = (width_left + ((available - width_right) - width_left) / 2); + + /* + * Write centre at + * middle - width_centre. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + middle - width_centre, + 0, + width_centre); + + /* + * If there is no focus given, keep the centre in focus. + */ + if (focus_start == -1 || focus_end == -1) + focus_start = focus_end = list->cx / 2; + + /* + * We centre abs_centre and the list together, so their shared centre is + * in the perfect centre of horizontal space. + */ + abs_centre_offset = (available - width_list - width_abs_centre) / 2; + + /* + * Write abs_centre before the list. + */ + format_draw_put(octx, ocx, ocy, abs_centre, frs, abs_centre_offset, + 0, width_abs_centre); + abs_centre_offset += width_abs_centre; + + /* + * Draw the list in the absolute centre + */ + format_draw_put_list(octx, ocx, ocy, abs_centre_offset, width_list, + list, list_left, list_right, focus_start, focus_end, frs); + abs_centre_offset += width_list; + + /* + * Write after at the end of the centre + */ + format_draw_put(octx, ocx, ocy, after, frs, abs_centre_offset, 0, + width_after); +} + +/* Get width and count of any leading #s. */ +static const char * +format_leading_hashes(const char *cp, u_int *n, u_int *width) +{ + for (*n = 0; cp[*n] == '#'; (*n)++) + /* nothing */; + if (*n == 0) { + *width = 0; + return (cp); + } + if (cp[*n] != '[') { + if ((*n % 2) == 0) + *width = (*n / 2); + else + *width = (*n / 2) + 1; + return (cp + *n); + } + *width = (*n / 2); + if ((*n % 2) == 0) { + /* + * An even number of #s means that all #s are escaped, so not a + * style. The caller should not skip this. Return pointing to + * the [. + */ + return (cp + *n); + } + /* This is a style, so return pointing to the #. */ + return (cp + *n - 1); +} + +/* Draw multiple characters. */ +static void +format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch, + u_int n) +{ + u_int i; + + utf8_set(&sy->gc.data, ch); + for (i = 0; i < n; i++) + screen_write_cell(ctx, &sy->gc); } /* Draw a format to a screen. */ @@ -493,6 +682,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, enum { LEFT, CENTRE, RIGHT, + ABSOLUTE_CENTRE, LIST, LIST_LEFT, LIST_RIGHT, @@ -501,6 +691,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, const char *names[] = { "LEFT", "CENTRE", "RIGHT", + "ABSOLUTE_CENTRE", "LIST", "LIST_LEFT", "LIST_RIGHT", @@ -508,10 +699,14 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, size_t size = strlen(expanded); struct screen *os = octx->s, s[TOTAL]; struct screen_write_ctx ctx[TOTAL]; - u_int ocx = os->cx, ocy = os->cy, i, width[TOTAL]; - u_int map[] = { LEFT, LEFT, CENTRE, RIGHT }; + u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL]; + u_int map[] = { LEFT, + LEFT, + CENTRE, + RIGHT, + ABSOLUTE_CENTRE }; int focus_start = -1, focus_end = -1; - int list_state = -1, fill = -1; + int list_state = -1, fill = -1, even; enum style_align list_align = STYLE_ALIGN_DEFAULT; struct grid_cell gc, current_default; struct style sy, saved_sy; @@ -535,7 +730,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ for (i = 0; i < TOTAL; i++) { screen_init(&s[i], size, 1, 0); - screen_write_start(&ctx[i], NULL, &s[i]); + screen_write_start(&ctx[i], &s[i]); screen_write_clearendofline(&ctx[i], current_default.bg); width[i] = 0; } @@ -546,7 +741,39 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ cp = expanded; while (*cp != '\0') { - if (cp[0] != '#' || cp[1] != '[') { + /* Handle sequences of #. */ + if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') { + for (n = 1; cp[n] == '#'; n++) + /* nothing */; + even = ((n % 2) == 0); + if (cp[n] != '[') { + cp += n; + if (even) + n = (n / 2); + else + n = (n / 2) + 1; + width[current] += n; + format_draw_many(&ctx[current], &sy, '#', n); + continue; + } + if (even) + cp += (n + 1); + else + cp += (n - 1); + if (sy.ignore) + continue; + format_draw_many(&ctx[current], &sy, '#', n / 2); + width[current] += (n / 2); + if (even) { + utf8_set(ud, '['); + screen_write_cell(&ctx[current], &sy.gc); + width[current]++; + } + continue; + } + + /* Is this not a style? */ + if (cp[0] != '#' || cp[1] != '[' || sy.ignore) { /* See if this is a UTF-8 character. */ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) @@ -599,7 +826,8 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, /* If this style pushed or popped the default, update it. */ if (sy.default_type == STYLE_DEFAULT_PUSH) { - memcpy(¤t_default, &saved_sy.gc, sizeof current_default); + memcpy(¤t_default, &saved_sy.gc, + sizeof current_default); sy.default_type = STYLE_DEFAULT_BASE; } else if (sy.default_type == STYLE_DEFAULT_POP) { memcpy(¤t_default, base, sizeof current_default); @@ -737,31 +965,41 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, /* * Draw the screens. How they are arranged depends on where the list - * appearsq. + * appears. */ switch (list_align) { case STYLE_ALIGN_DEFAULT: /* No list. */ format_draw_none(octx, available, ocx, ocy, &s[LEFT], - &s[CENTRE], &s[RIGHT], &frs); + &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &frs); break; case STYLE_ALIGN_LEFT: /* List is part of the left. */ format_draw_left(octx, available, ocx, ocy, &s[LEFT], - &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], - &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST], + &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER], + focus_start, focus_end, &frs); break; case STYLE_ALIGN_CENTRE: /* List is part of the centre. */ format_draw_centre(octx, available, ocx, ocy, &s[LEFT], - &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], - &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST], + &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER], + focus_start, focus_end, &frs); break; case STYLE_ALIGN_RIGHT: /* List is part of the right. */ format_draw_right(octx, available, ocx, ocy, &s[LEFT], - &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT], - &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs); + &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST], + &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER], + focus_start, focus_end, &frs); + break; + case STYLE_ALIGN_ABSOLUTE_CENTRE: + /* List is in the centre of the entire horizontal space. */ + format_draw_absolute_centre(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST], + &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER], + focus_start, focus_end, &frs); break; } @@ -794,17 +1032,22 @@ u_int format_width(const char *expanded) { const char *cp, *end; - u_int width = 0; + u_int n, leading_width, width = 0; struct utf8_data ud; enum utf8_state more; cp = expanded; while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { - end = format_skip(cp + 2, "]"); - if (end == NULL) - return 0; - cp = end + 1; + if (*cp == '#') { + end = format_leading_hashes(cp, &n, &leading_width); + width += leading_width; + cp = end; + if (*cp == '#') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + return (0); + cp = end + 1; + } } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) more = utf8_append(&ud, *cp); @@ -821,25 +1064,46 @@ format_width(const char *expanded) return (width); } -/* Trim on the left, taking #[] into account. */ +/* + * Trim on the left, taking #[] into account. Note, we copy the whole set of + * unescaped #s, but only add their escaped size to width. This is because the + * format_draw function will actually do the escaping when it runs + */ char * format_trim_left(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0; + u_int n, width = 0, leading_width; struct utf8_data ud; enum utf8_state more; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { - end = format_skip(cp + 2, "]"); - if (end == NULL) - break; - memcpy(out, cp, end + 1 - cp); - out += (end + 1 - cp); - cp = end + 1; + if (width >= limit) + break; + if (*cp == '#') { + end = format_leading_hashes(cp, &n, &leading_width); + if (leading_width > limit - width) + leading_width = limit - width; + if (leading_width != 0) { + if (n == 1) + *out++ = '#'; + else { + memset(out, '#', 2 * leading_width); + out += 2 * leading_width; + } + width += leading_width; + } + cp = end; + if (*cp == '#') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + break; + memcpy(out, cp, end + 1 - cp); + out += (end + 1 - cp); + cp = end + 1; + } } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) more = utf8_append(&ud, *cp); @@ -871,7 +1135,8 @@ format_trim_right(const char *expanded, u_int limit) { char *copy, *out; const char *cp = expanded, *end; - u_int width = 0, total_width, skip; + u_int width = 0, total_width, skip, n; + u_int leading_width, copy_width; struct utf8_data ud; enum utf8_state more; @@ -880,15 +1145,35 @@ format_trim_right(const char *expanded, u_int limit) return (xstrdup(expanded)); skip = total_width - limit; - out = copy = xmalloc(strlen(expanded) + 1); + out = copy = xcalloc(1, strlen(expanded) + 1); while (*cp != '\0') { - if (cp[0] == '#' && cp[1] == '[') { - end = format_skip(cp + 2, "]"); - if (end == NULL) - break; - memcpy(out, cp, end + 1 - cp); - out += (end + 1 - cp); - cp = end + 1; + if (*cp == '#') { + end = format_leading_hashes(cp, &n, &leading_width); + if (width <= skip) { + if (skip - width >= leading_width) + copy_width = 0; + else + copy_width -= (skip - width); + } else + copy_width = leading_width; + if (copy_width != 0) { + if (n == 1) + *out++ = '#'; + else { + memset(out, '#', 2 * copy_width); + out += 2 * copy_width; + } + } + width += leading_width; + cp = end; + if (*cp == '#') { + end = format_skip(cp + 2, "]"); + if (end == NULL) + break; + memcpy(out, cp, end + 1 - cp); + out += (end + 1 - cp); + cp = end + 1; + } } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) more = utf8_append(&ud, *cp); diff --git a/format.c b/format.c index 79e99b61..9af6e0a0 100644 --- a/format.c +++ b/format.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,23 +38,17 @@ * string. */ -struct format_entry; -typedef void (*format_cb)(struct format_tree *, struct format_entry *); - -static char *format_job_get(struct format_tree *, const char *); -static void format_job_timer(int, short, void *); - -static char *format_find(struct format_tree *, const char *, int); -static void format_add_cb(struct format_tree *, const char *, format_cb); -static void format_add_tv(struct format_tree *, const char *, - struct timeval *); -static int format_replace(struct format_tree *, const char *, size_t, - char **, size_t *, size_t *); +struct format_expand_state; +static char *format_job_get(struct format_expand_state *, const char *); +static char *format_expand1(struct format_expand_state *, const char *); +static int format_replace(struct format_expand_state *, const char *, + size_t, char **, size_t *, size_t *); static void format_defaults_session(struct format_tree *, struct session *); static void format_defaults_client(struct format_tree *, struct client *); -static void format_defaults_winlink(struct format_tree *, struct winlink *); +static void format_defaults_winlink(struct format_tree *, + struct winlink *); /* Entry in format job tree. */ struct format_job { @@ -73,7 +68,6 @@ struct format_job { }; /* Format job tree. */ -static struct event format_job_event; static int format_job_cmp(struct format_job *, struct format_job *); static RB_HEAD(format_job_tree, format_job) format_jobs = RB_INITIALIZER(); RB_GENERATE_STATIC(format_job_tree, format_job, entry, format_job_cmp); @@ -93,40 +87,59 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2) #define FORMAT_TIMESTRING 0x1 #define FORMAT_BASENAME 0x2 #define FORMAT_DIRNAME 0x4 -#define FORMAT_QUOTE 0x8 +#define FORMAT_QUOTE_SHELL 0x8 #define FORMAT_LITERAL 0x10 #define FORMAT_EXPAND 0x20 #define FORMAT_EXPANDTIME 0x40 #define FORMAT_SESSIONS 0x80 #define FORMAT_WINDOWS 0x100 #define FORMAT_PANES 0x200 +#define FORMAT_PRETTY 0x400 +#define FORMAT_LENGTH 0x800 +#define FORMAT_WIDTH 0x1000 +#define FORMAT_QUOTE_STYLE 0x2000 +#define FORMAT_WINDOW_NAME 0x4000 +#define FORMAT_SESSION_NAME 0x8000 +#define FORMAT_CHARACTER 0x10000 /* Limit on recursion. */ -#define FORMAT_LOOP_LIMIT 10 +#define FORMAT_LOOP_LIMIT 100 + +/* Format expand flags. */ +#define FORMAT_EXPAND_TIME 0x1 +#define FORMAT_EXPAND_NOJOBS 0x2 /* Entry in format tree. */ struct format_entry { char *key; char *value; - time_t t; + time_t time; format_cb cb; RB_ENTRY(format_entry) entry; }; -/* Format entry tree. */ +/* Format type. */ +enum format_type { + FORMAT_TYPE_UNKNOWN, + FORMAT_TYPE_SESSION, + FORMAT_TYPE_WINDOW, + FORMAT_TYPE_PANE +}; + struct format_tree { + enum format_type type; + struct client *c; struct session *s; struct winlink *wl; struct window *w; struct window_pane *wp; + struct paste_buffer *pb; struct cmdq_item *item; struct client *client; - u_int tag; int flags; - time_t time; - u_int loop; + u_int tag; struct mouse_event m; @@ -135,6 +148,15 @@ struct format_tree { static int format_entry_cmp(struct format_entry *, struct format_entry *); RB_GENERATE_STATIC(format_entry_tree, format_entry, entry, format_entry_cmp); +/* Format expand state. */ +struct format_expand_state { + struct format_tree *ft; + u_int loop; + time_t time; + struct tm tm; + int flags; +}; + /* Format modifier. */ struct format_modifier { char modifier[3]; @@ -220,8 +242,10 @@ format_logging(struct format_tree *ft) /* Log a message if verbose. */ static void printflike(3, 4) -format_log1(struct format_tree *ft, const char *from, const char *fmt, ...) +format_log1(struct format_expand_state *es, const char *from, const char *fmt, + ...) { + struct format_tree *ft = es->ft; va_list ap; char *s; static const char spaces[] = " "; @@ -230,16 +254,28 @@ format_log1(struct format_tree *ft, const char *from, const char *fmt, ...) return; va_start(ap, fmt); - vasprintf(&s, fmt, ap); + xvasprintf(&s, fmt, ap); va_end(ap); log_debug("%s: %s", from, s); if (ft->item != NULL && (ft->flags & FORMAT_VERBOSE)) - cmdq_print(ft->item, "#%.*s%s", ft->loop, spaces, s); + cmdq_print(ft->item, "#%.*s%s", es->loop, spaces, s); free(s); } -#define format_log(ft, fmt, ...) format_log1(ft, __func__, fmt, ##__VA_ARGS__) +#define format_log(es, fmt, ...) format_log1(es, __func__, fmt, ##__VA_ARGS__) + +/* Copy expand state. */ +static void +format_copy_state(struct format_expand_state *to, + struct format_expand_state *from, int flags) +{ + to->ft = from->ft; + to->loop = from->loop; + to->time = from->time; + memcpy(&to->tm, &from->tm, sizeof to->tm); + to->flags = from->flags|flags; +} /* Format job update callback. */ static void @@ -309,13 +345,15 @@ format_job_complete(struct job *job) /* Find a job. */ static char * -format_job_get(struct format_tree *ft, const char *cmd) +format_job_get(struct format_expand_state *es, const char *cmd) { - struct format_job_tree *jobs; - struct format_job fj0, *fj; - time_t t; - char *expanded; - int force; + struct format_tree *ft = es->ft; + struct format_job_tree *jobs; + struct format_job fj0, *fj; + time_t t; + char *expanded; + int force; + struct format_expand_state next; if (ft->client == NULL) jobs = &format_jobs; @@ -333,14 +371,14 @@ format_job_get(struct format_tree *ft, const char *cmd) fj->client = ft->client; fj->tag = ft->tag; fj->cmd = xstrdup(cmd); - fj->expanded = NULL; - - xasprintf(&fj->out, "<'%s' not ready>", fj->cmd); RB_INSERT(format_job_tree, jobs, fj); } - expanded = format_expand(ft, cmd); + format_copy_state(&next, es, FORMAT_EXPAND_NOJOBS); + next.flags &= ~FORMAT_EXPAND_TIME; + + expanded = format_expand1(&next, cmd); if (fj->expanded == NULL || strcmp(expanded, fj->expanded) != 0) { free((void *)fj->expanded); fj->expanded = xstrdup(expanded); @@ -352,22 +390,24 @@ format_job_get(struct format_tree *ft, const char *cmd) if (force && fj->job != NULL) job_free(fj->job); if (force || (fj->job == NULL && fj->last != t)) { - fj->job = job_run(expanded, NULL, + fj->job = job_run(expanded, 0, NULL, NULL, server_client_get_cwd(ft->client, NULL), format_job_update, - format_job_complete, NULL, fj, JOB_NOWAIT); + format_job_complete, NULL, fj, JOB_NOWAIT, -1, -1); if (fj->job == NULL) { free(fj->out); xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); } fj->last = t; fj->updated = 0; - } + } else if (fj->job != NULL && (t - fj->last) > 1 && fj->out == NULL) + xasprintf(&fj->out, "<'%s' not ready>", fj->cmd); + free(expanded); if (ft->flags & FORMAT_STATUS) fj->status = 1; - - free(expanded); - return (format_expand(ft, fj->out)); + if (fj->out == NULL) + return (xstrdup("")); + return (format_expand1(&next, fj->out)); } /* Remove old jobs. */ @@ -396,6 +436,19 @@ format_job_tidy(struct format_job_tree *jobs, int force) } } +/* Tidy old jobs for all clients. */ +void +format_tidy_jobs(void) +{ + struct client *c; + + format_job_tidy(&format_jobs, 0); + TAILQ_FOREACH(c, &clients, entry) { + if (c->jobs != NULL) + format_job_tidy(c->jobs, 0); + } +} + /* Remove old jobs for client. */ void format_lost_client(struct client *c) @@ -405,68 +458,65 @@ format_lost_client(struct client *c) free(c->jobs); } -/* Remove old jobs periodically. */ -static void -format_job_timer(__unused int fd, __unused short events, __unused void *arg) +/* Wrapper for asprintf. */ +static char * printflike(1, 2) +format_printf(const char *fmt, ...) { - struct client *c; - struct timeval tv = { .tv_sec = 60 }; + va_list ap; + char *s; - format_job_tidy(&format_jobs, 0); - TAILQ_FOREACH(c, &clients, entry) { - if (c->jobs != NULL) - format_job_tidy(c->jobs, 0); - } - - evtimer_del(&format_job_event); - evtimer_add(&format_job_event, &tv); + va_start(ap, fmt); + xvasprintf(&s, fmt, ap); + va_end(ap); + return (s); } /* Callback for host. */ -static void -format_cb_host(__unused struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_host(__unused struct format_tree *ft) { char host[HOST_NAME_MAX + 1]; if (gethostname(host, sizeof host) != 0) - fe->value = xstrdup(""); - else - fe->value = xstrdup(host); + return (xstrdup("")); + return (xstrdup(host)); } /* Callback for host_short. */ -static void -format_cb_host_short(__unused struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_host_short(__unused struct format_tree *ft) { char host[HOST_NAME_MAX + 1], *cp; if (gethostname(host, sizeof host) != 0) - fe->value = xstrdup(""); - else { - if ((cp = strchr(host, '.')) != NULL) - *cp = '\0'; - fe->value = xstrdup(host); - } + return (xstrdup("")); + if ((cp = strchr(host, '.')) != NULL) + *cp = '\0'; + return (xstrdup(host)); } /* Callback for pid. */ -static void -format_cb_pid(__unused struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_pid(__unused struct format_tree *ft) { - xasprintf(&fe->value, "%ld", (long)getpid()); + char *value; + + xasprintf(&value, "%ld", (long)getpid()); + return (value); } /* Callback for session_attached_list. */ -static void -format_cb_session_attached_list(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_session_attached_list(struct format_tree *ft) { struct session *s = ft->s; struct client *loop; struct evbuffer *buffer; int size; + char *value = NULL; if (s == NULL) - return; + return (NULL); buffer = evbuffer_new(); if (buffer == NULL) @@ -481,20 +531,21 @@ format_cb_session_attached_list(struct format_tree *ft, struct format_entry *fe) } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for session_alerts. */ -static void -format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_session_alerts(struct format_tree *ft) { struct session *s = ft->s; struct winlink *wl; char alerts[1024], tmp[16]; if (s == NULL) - return; + return (NULL); *alerts = '\0'; RB_FOREACH(wl, winlinks, &s->windows) { @@ -512,19 +563,19 @@ format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe) if (wl->flags & WINLINK_SILENCE) strlcat(alerts, "~", sizeof alerts); } - fe->value = xstrdup(alerts); + return (xstrdup(alerts)); } /* Callback for session_stack. */ -static void -format_cb_session_stack(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_session_stack(struct format_tree *ft) { struct session *s = ft->s; struct winlink *wl; char result[1024], tmp[16]; if (s == NULL) - return; + return (NULL); xsnprintf(result, sizeof result, "%u", s->curw->idx); TAILQ_FOREACH(wl, &s->lastw, sentry) { @@ -534,16 +585,21 @@ format_cb_session_stack(struct format_tree *ft, struct format_entry *fe) strlcat(result, ",", sizeof result); strlcat(result, tmp, sizeof result); } - fe->value = xstrdup(result); + return (xstrdup(result)); } /* Callback for window_stack_index. */ -static void -format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_window_stack_index(struct format_tree *ft) { - struct session *s = ft->wl->session; + struct session *s; struct winlink *wl; u_int idx; + char *value = NULL; + + if (ft->wl == NULL) + return (NULL); + s = ft->wl->session; idx = 0; TAILQ_FOREACH(wl, &s->lastw, sentry) { @@ -551,21 +607,25 @@ format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe) if (wl == ft->wl) break; } - if (wl != NULL) - xasprintf(&fe->value, "%u", idx); - else - fe->value = xstrdup("0"); + if (wl == NULL) + return (xstrdup("0")); + xasprintf(&value, "%u", idx); + return (value); } /* Callback for window_linked_sessions_list. */ -static void -format_cb_window_linked_sessions_list(struct format_tree *ft, - struct format_entry *fe) +static void * +format_cb_window_linked_sessions_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; struct evbuffer *buffer; int size; + char *value = NULL; + + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; buffer = evbuffer_new(); if (buffer == NULL) @@ -578,36 +638,46 @@ format_cb_window_linked_sessions_list(struct format_tree *ft, } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for window_active_sessions. */ -static void -format_cb_window_active_sessions(struct format_tree *ft, - struct format_entry *fe) +static void * +format_cb_window_active_sessions(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; u_int n = 0; + char *value; + + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; TAILQ_FOREACH(wl, &w->winlinks, wentry) { if (wl->session->curw == wl) n++; } - xasprintf(&fe->value, "%u", n); + xasprintf(&value, "%u", n); + return (value); } /* Callback for window_active_sessions_list. */ -static void -format_cb_window_active_sessions_list(struct format_tree *ft, - struct format_entry *fe) +static void * +format_cb_window_active_sessions_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct winlink *wl; struct evbuffer *buffer; int size; + char *value = NULL; + + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; buffer = evbuffer_new(); if (buffer == NULL) @@ -622,18 +692,24 @@ format_cb_window_active_sessions_list(struct format_tree *ft, } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for window_active_clients. */ -static void -format_cb_window_active_clients(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_window_active_clients(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct client *loop; struct session *client_session; u_int n = 0; + char *value; + + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; TAILQ_FOREACH(loop, &clients, entry) { client_session = loop->session; @@ -644,19 +720,24 @@ format_cb_window_active_clients(struct format_tree *ft, struct format_entry *fe) n++; } - xasprintf(&fe->value, "%u", n); + xasprintf(&value, "%u", n); + return (value); } /* Callback for window_active_clients_list. */ -static void -format_cb_window_active_clients_list(struct format_tree *ft, - struct format_entry *fe) +static void * +format_cb_window_active_clients_list(struct format_tree *ft) { - struct window *w = ft->wl->window; + struct window *w; struct client *loop; struct session *client_session; struct evbuffer *buffer; int size; + char *value = NULL; + + if (ft->wl == NULL) + return (NULL); + w = ft->wl->window; buffer = evbuffer_new(); if (buffer == NULL) @@ -675,58 +756,58 @@ format_cb_window_active_clients_list(struct format_tree *ft, } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for window_layout. */ -static void -format_cb_window_layout(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_window_layout(struct format_tree *ft) { struct window *w = ft->w; if (w == NULL) - return; + return (NULL); if (w->saved_layout_root != NULL) - fe->value = layout_dump(w->saved_layout_root); - else - fe->value = layout_dump(w->layout_root); + return (layout_dump(w->saved_layout_root)); + return (layout_dump(w->layout_root)); } /* Callback for window_visible_layout. */ -static void -format_cb_window_visible_layout(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_window_visible_layout(struct format_tree *ft) { struct window *w = ft->w; if (w == NULL) - return; + return (NULL); - fe->value = layout_dump(w->layout_root); + return (layout_dump(w->layout_root)); } /* Callback for pane_start_command. */ -static void -format_cb_start_command(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_start_command(struct format_tree *ft) { struct window_pane *wp = ft->wp; if (wp == NULL) - return; + return (NULL); - fe->value = cmd_stringify_argv(wp->argc, wp->argv); + return (cmd_stringify_argv(wp->argc, wp->argv)); } /* Callback for pane_current_command. */ -static void -format_cb_current_command(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_current_command(struct format_tree *ft) { struct window_pane *wp = ft->wp; - char *cmd; + char *cmd, *value; if (wp == NULL || wp->shell == NULL) - return; + return (NULL); cmd = osdep_get_name(wp->fd, wp->tty); if (cmd == NULL || *cmd == '\0') { @@ -737,61 +818,92 @@ format_cb_current_command(struct format_tree *ft, struct format_entry *fe) cmd = xstrdup(wp->shell); } } - fe->value = parse_window_name(cmd); + value = parse_window_name(cmd); free(cmd); + return (value); } /* Callback for pane_current_path. */ -static void -format_cb_current_path(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_current_path(struct format_tree *ft) { struct window_pane *wp = ft->wp; char *cwd; if (wp == NULL) - return; + return (NULL); cwd = osdep_get_cwd(wp->fd); - if (cwd != NULL) - fe->value = xstrdup(cwd); + if (cwd == NULL) + return (NULL); + return (xstrdup(cwd)); } /* Callback for history_bytes. */ -static void -format_cb_history_bytes(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_history_bytes(struct format_tree *ft) { struct window_pane *wp = ft->wp; struct grid *gd; struct grid_line *gl; - unsigned long long size; + size_t size = 0; u_int i; + char *value; if (wp == NULL) - return; + return (NULL); gd = wp->base.grid; - size = 0; - for (i = 0; i < gd->hsize; i++) { + for (i = 0; i < gd->hsize + gd->sy; i++) { gl = grid_get_line(gd, i); size += gl->cellsize * sizeof *gl->celldata; size += gl->extdsize * sizeof *gl->extddata; } - size += gd->hsize * sizeof *gl; + size += (gd->hsize + gd->sy) * sizeof *gl; - xasprintf(&fe->value, "%llu", size); + xasprintf(&value, "%zu", size); + return (value); +} + +/* Callback for history_all_bytes. */ +static void * +format_cb_history_all_bytes(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + struct grid *gd; + struct grid_line *gl; + u_int i, lines, cells = 0, extended_cells = 0; + char *value; + + if (wp == NULL) + return (NULL); + gd = wp->base.grid; + + lines = gd->hsize + gd->sy; + for (i = 0; i < lines; i++) { + gl = grid_get_line(gd, i); + cells += gl->cellsize; + extended_cells += gl->extdsize; + } + + xasprintf(&value, "%u,%zu,%u,%zu,%u,%zu", lines, + lines * sizeof *gl, cells, cells * sizeof *gl->celldata, + extended_cells, extended_cells * sizeof *gl->extddata); + return (value); } /* Callback for pane_tabs. */ -static void -format_cb_pane_tabs(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_pane_tabs(struct format_tree *ft) { struct window_pane *wp = ft->wp; struct evbuffer *buffer; u_int i; int size; + char *value = NULL; if (wp == NULL) - return; + return (NULL); buffer = evbuffer_new(); if (buffer == NULL) @@ -805,25 +917,55 @@ format_cb_pane_tabs(struct format_tree *ft, struct format_entry *fe) evbuffer_add_printf(buffer, "%u", i); } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); +} + +/* Callback for pane_fg. */ +static void * +format_cb_pane_fg(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + struct grid_cell gc; + + if (wp == NULL) + return (NULL); + + tty_default_colours(&gc, wp); + return (xstrdup(colour_tostring(gc.fg))); +} + +/* Callback for pane_bg. */ +static void * +format_cb_pane_bg(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + struct grid_cell gc; + + if (wp == NULL) + return (NULL); + + tty_default_colours(&gc, wp); + return (xstrdup(colour_tostring(gc.bg))); } /* Callback for session_group_list. */ -static void -format_cb_session_group_list(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_session_group_list(struct format_tree *ft) { struct session *s = ft->s; struct session_group *sg; struct session *loop; struct evbuffer *buffer; int size; + char *value = NULL; if (s == NULL) - return; + return (NULL); sg = session_group_contains(s); if (sg == NULL) - return; + return (NULL); buffer = evbuffer_new(); if (buffer == NULL) @@ -836,26 +978,27 @@ format_cb_session_group_list(struct format_tree *ft, struct format_entry *fe) } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for session_group_attached_list. */ -static void -format_cb_session_group_attached_list(struct format_tree *ft, - struct format_entry *fe) +static void * +format_cb_session_group_attached_list(struct format_tree *ft) { struct session *s = ft->s, *client_session, *session_loop; struct session_group *sg; struct client *loop; struct evbuffer *buffer; int size; + char *value = NULL; if (s == NULL) - return; + return (NULL); sg = session_group_contains(s); if (sg == NULL) - return; + return (NULL); buffer = evbuffer_new(); if (buffer == NULL) @@ -875,225 +1018,2013 @@ format_cb_session_group_attached_list(struct format_tree *ft, } if ((size = EVBUFFER_LENGTH(buffer)) != 0) - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); + return (value); } /* Callback for pane_in_mode. */ -static void -format_cb_pane_in_mode(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_pane_in_mode(struct format_tree *ft) { struct window_pane *wp = ft->wp; u_int n = 0; struct window_mode_entry *wme; + char *value; if (wp == NULL) - return; + return (NULL); TAILQ_FOREACH(wme, &wp->modes, entry) - n++; - xasprintf(&fe->value, "%u", n); + n++; + xasprintf(&value, "%u", n); + return (value); } /* Callback for pane_at_top. */ -static void -format_cb_pane_at_top(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_pane_at_top(struct format_tree *ft) { struct window_pane *wp = ft->wp; - struct window *w = wp->window; + struct window *w; int status, flag; + char *value; if (wp == NULL) - return; + return (NULL); + w = wp->window; status = options_get_number(w->options, "pane-border-status"); if (status == PANE_STATUS_TOP) flag = (wp->yoff == 1); else flag = (wp->yoff == 0); - xasprintf(&fe->value, "%d", flag); + xasprintf(&value, "%d", flag); + return (value); } /* Callback for pane_at_bottom. */ -static void -format_cb_pane_at_bottom(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_pane_at_bottom(struct format_tree *ft) { struct window_pane *wp = ft->wp; - struct window *w = wp->window; + struct window *w; int status, flag; + char *value; if (wp == NULL) - return; + return (NULL); + w = wp->window; status = options_get_number(w->options, "pane-border-status"); if (status == PANE_STATUS_BOTTOM) flag = (wp->yoff + wp->sy == w->sy - 1); else flag = (wp->yoff + wp->sy == w->sy); - xasprintf(&fe->value, "%d", flag); + xasprintf(&value, "%d", flag); + return (value); } /* Callback for cursor_character. */ -static void -format_cb_cursor_character(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_cursor_character(struct format_tree *ft) { struct window_pane *wp = ft->wp; struct grid_cell gc; + char *value = NULL; if (wp == NULL) - return; + return (NULL); grid_view_get_cell(wp->base.grid, wp->base.cx, wp->base.cy, &gc); if (~gc.flags & GRID_FLAG_PADDING) - xasprintf(&fe->value, "%.*s", (int)gc.data.size, gc.data.data); -} - -/* Return word at given coordinates. Caller frees. */ -char * -format_grid_word(struct grid *gd, u_int x, u_int y) -{ - struct grid_line *gl; - struct grid_cell gc; - const char *ws; - struct utf8_data *ud = NULL; - u_int end; - size_t size = 0; - int found = 0; - char *s = NULL; - - ws = options_get_string(global_s_options, "word-separators"); - - y = gd->hsize + y; - for (;;) { - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - if (utf8_cstrhas(ws, &gc.data)) { - found = 1; - break; - } - - if (x == 0) { - if (y == 0) - break; - gl = &gd->linedata[y - 1]; - if (~gl->flags & GRID_LINE_WRAPPED) - break; - y--; - x = grid_line_length(gd, y); - if (x == 0) - break; - } - x--; - } - for (;;) { - if (found) { - end = grid_line_length(gd, y); - if (end == 0 || x == end - 1) { - if (y == gd->hsize + gd->sy - 1) - break; - gl = &gd->linedata[y]; - if (~gl->flags & GRID_LINE_WRAPPED) - break; - y++; - x = 0; - } else - x++; - } - found = 1; - - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - if (utf8_cstrhas(ws, &gc.data)) - break; - - ud = xreallocarray(ud, size + 2, sizeof *ud); - memcpy(&ud[size++], &gc.data, sizeof *ud); - } - if (size != 0) { - ud[size].size = 0; - s = utf8_tocstr(ud); - free(ud); - } - return (s); + xasprintf(&value, "%.*s", (int)gc.data.size, gc.data.data); + return (value); } /* Callback for mouse_word. */ -static void -format_cb_mouse_word(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_mouse_word(struct format_tree *ft) { struct window_pane *wp; + struct grid *gd; u_int x, y; char *s; if (!ft->m.valid) - return; + return (NULL); wp = cmd_mouse_pane(&ft->m, NULL, NULL); if (wp == NULL) - return; - if (!TAILQ_EMPTY (&wp->modes)) - return; + return (NULL); if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) - return; + return (NULL); - s = format_grid_word(wp->base.grid, x, y); - if (s != NULL) - fe->value = s; -} - -/* Return line at given coordinates. Caller frees. */ -char * -format_grid_line(struct grid *gd, u_int y) -{ - struct grid_cell gc; - struct utf8_data *ud = NULL; - u_int x; - size_t size = 0; - char *s = NULL; - - y = gd->hsize + y; - for (x = 0; x < grid_line_length(gd, y); x++) { - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - break; - - ud = xreallocarray(ud, size + 2, sizeof *ud); - memcpy(&ud[size++], &gc.data, sizeof *ud); + if (!TAILQ_EMPTY(&wp->modes)) { + if (TAILQ_FIRST(&wp->modes)->mode == &window_copy_mode || + TAILQ_FIRST(&wp->modes)->mode == &window_view_mode) + return (s = window_copy_get_word(wp, x, y)); + return (NULL); } - if (size != 0) { - ud[size].size = 0; - s = utf8_tocstr(ud); - free(ud); - } - return (s); + gd = wp->base.grid; + return (format_grid_word(gd, x, gd->hsize + y)); } /* Callback for mouse_line. */ -static void -format_cb_mouse_line(struct format_tree *ft, struct format_entry *fe) +static void * +format_cb_mouse_line(struct format_tree *ft) +{ + struct window_pane *wp; + struct grid *gd; + u_int x, y; + + if (!ft->m.valid) + return (NULL); + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp == NULL) + return (NULL); + if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) + return (NULL); + + if (!TAILQ_EMPTY(&wp->modes)) { + if (TAILQ_FIRST(&wp->modes)->mode == &window_copy_mode || + TAILQ_FIRST(&wp->modes)->mode == &window_view_mode) + return (window_copy_get_line(wp, y)); + return (NULL); + } + gd = wp->base.grid; + return (format_grid_line(gd, gd->hsize + y)); +} + +/* Callback for alternate_on. */ +static void * +format_cb_alternate_on(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.saved_grid != NULL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for alternate_saved_x. */ +static void * +format_cb_alternate_saved_x(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.saved_cx)); + return (NULL); +} + +/* Callback for alternate_saved_y. */ +static void * +format_cb_alternate_saved_y(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.saved_cy)); + return (NULL); +} + +/* Callback for buffer_name. */ +static void * +format_cb_buffer_name(struct format_tree *ft) +{ + if (ft->pb != NULL) + return (xstrdup(paste_buffer_name(ft->pb))); + return (NULL); +} + +/* Callback for buffer_sample. */ +static void * +format_cb_buffer_sample(struct format_tree *ft) +{ + if (ft->pb != NULL) + return (paste_make_sample(ft->pb)); + return (NULL); +} + +/* Callback for buffer_size. */ +static void * +format_cb_buffer_size(struct format_tree *ft) +{ + size_t size; + + if (ft->pb != NULL) { + paste_buffer_data(ft->pb, &size); + return (format_printf("%zu", size)); + } + return (NULL); +} + +/* Callback for client_cell_height. */ +static void * +format_cb_client_cell_height(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.ypixel)); + return (NULL); +} + +/* Callback for client_cell_width. */ +static void * +format_cb_client_cell_width(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.xpixel)); + return (NULL); +} + +/* Callback for client_control_mode. */ +static void * +format_cb_client_control_mode(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_CONTROL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_discarded. */ +static void * +format_cb_client_discarded(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%zu", ft->c->discarded)); + return (NULL); +} + +/* Callback for client_flags. */ +static void * +format_cb_client_flags(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(server_client_get_flags(ft->c))); + return (NULL); +} + +/* Callback for client_height. */ +static void * +format_cb_client_height(struct format_tree *ft) +{ + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) + return (format_printf("%u", ft->c->tty.sy)); + return (NULL); +} + +/* Callback for client_key_table. */ +static void * +format_cb_client_key_table(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->keytable->name)); + return (NULL); +} + +/* Callback for client_last_session. */ +static void * +format_cb_client_last_session(struct format_tree *ft) +{ + if (ft->c != NULL && + ft->c->last_session != NULL && + session_alive(ft->c->last_session)) + return (xstrdup(ft->c->last_session->name)); + return (NULL); +} + +/* Callback for client_name. */ +static void * +format_cb_client_name(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->name)); + return (NULL); +} + +/* Callback for client_pid. */ +static void * +format_cb_client_pid(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%ld", (long)ft->c->pid)); + return (NULL); +} + +/* Callback for client_prefix. */ +static void * +format_cb_client_prefix(struct format_tree *ft) +{ + const char *name; + + if (ft->c != NULL) { + name = server_client_get_key_table(ft->c); + if (strcmp(ft->c->keytable->name, name) == 0) + return (xstrdup("0")); + return (xstrdup("1")); + } + return (NULL); +} + +/* Callback for client_readonly. */ +static void * +format_cb_client_readonly(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_READONLY) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_session. */ +static void * +format_cb_client_session(struct format_tree *ft) +{ + if (ft->c != NULL && ft->c->session != NULL) + return (xstrdup(ft->c->session->name)); + return (NULL); +} + +/* Callback for client_termfeatures. */ +static void * +format_cb_client_termfeatures(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(tty_get_features(ft->c->term_features))); + return (NULL); +} + +/* Callback for client_termname. */ +static void * +format_cb_client_termname(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->term_name)); + return (NULL); +} + +/* Callback for client_termtype. */ +static void * +format_cb_client_termtype(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->term_type == NULL) + return (xstrdup("")); + return (xstrdup(ft->c->term_type)); + } + return (NULL); +} + +/* Callback for client_tty. */ +static void * +format_cb_client_tty(struct format_tree *ft) +{ + if (ft->c != NULL) + return (xstrdup(ft->c->ttyname)); + return (NULL); +} + +/* Callback for client_utf8. */ +static void * +format_cb_client_utf8(struct format_tree *ft) +{ + if (ft->c != NULL) { + if (ft->c->flags & CLIENT_UTF8) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for client_width. */ +static void * +format_cb_client_width(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%u", ft->c->tty.sx)); + return (NULL); +} + +/* Callback for client_written. */ +static void * +format_cb_client_written(struct format_tree *ft) +{ + if (ft->c != NULL) + return (format_printf("%zu", ft->c->written)); + return (NULL); +} + +/* Callback for config_files. */ +static void * +format_cb_config_files(__unused struct format_tree *ft) +{ + char *s = NULL; + size_t slen = 0; + u_int i; + size_t n; + + for (i = 0; i < cfg_nfiles; i++) { + n = strlen(cfg_files[i]) + 1; + s = xrealloc(s, slen + n + 1); + slen += xsnprintf(s + slen, n + 1, "%s,", cfg_files[i]); + } + if (s == NULL) + return (xstrdup("")); + s[slen - 1] = '\0'; + return (s); +} + +/* Callback for cursor_flag. */ +static void * +format_cb_cursor_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_CURSOR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for cursor_x. */ +static void * +format_cb_cursor_x(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.cx)); + return (NULL); +} + +/* Callback for cursor_y. */ +static void * +format_cb_cursor_y(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.cy)); + return (NULL); +} + +/* Callback for history_limit. */ +static void * +format_cb_history_limit(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.grid->hlimit)); + return (NULL); +} + +/* Callback for history_size. */ +static void * +format_cb_history_size(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.grid->hsize)); + return (NULL); +} + +/* Callback for insert_flag. */ +static void * +format_cb_insert_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_INSERT) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for keypad_cursor_flag. */ +static void * +format_cb_keypad_cursor_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_KCURSOR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for keypad_flag. */ +static void * +format_cb_keypad_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_KKEYPAD) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_all_flag. */ +static void * +format_cb_mouse_all_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_ALL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_any_flag. */ +static void * +format_cb_mouse_any_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & ALL_MOUSE_MODES) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_button_flag. */ +static void * +format_cb_mouse_button_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_BUTTON) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_pane. */ +static void * +format_cb_mouse_pane(struct format_tree *ft) +{ + struct window_pane *wp; + + if (ft->m.valid) { + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp != NULL) + return (format_printf("%%%u", wp->id)); + return (NULL); + } + return (NULL); +} + +/* Callback for mouse_sgr_flag. */ +static void * +format_cb_mouse_sgr_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_SGR) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_standard_flag. */ +static void * +format_cb_mouse_standard_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_STANDARD) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_utf8_flag. */ +static void * +format_cb_mouse_utf8_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_MOUSE_UTF8) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for mouse_x. */ +static void * +format_cb_mouse_x(struct format_tree *ft) { struct window_pane *wp; u_int x, y; - char *s; if (!ft->m.valid) - return; + return (NULL); wp = cmd_mouse_pane(&ft->m, NULL, NULL); - if (wp == NULL) - return; - if (!TAILQ_EMPTY (&wp->modes)) - return; - if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) - return; - - s = format_grid_line(wp->base.grid, y); - if (s != NULL) - fe->value = s; + if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0) + return (format_printf("%u", x)); + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) { + if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) + return (format_printf("%u", ft->m.x)); + if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) + return (format_printf("%u", ft->m.x)); + } + return (NULL); } -/* Merge a format tree. */ -static void +/* Callback for mouse_y. */ +static void * +format_cb_mouse_y(struct format_tree *ft) +{ + struct window_pane *wp; + u_int x, y; + + if (!ft->m.valid) + return (NULL); + wp = cmd_mouse_pane(&ft->m, NULL, NULL); + if (wp != NULL && cmd_mouse_at(wp, &ft->m, &x, &y, 0) == 0) + return (format_printf("%u", y)); + if (ft->c != NULL && (ft->c->tty.flags & TTY_STARTED)) { + if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) + return (format_printf("%u", ft->m.y)); + if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) + return (format_printf("%u", ft->m.y - ft->m.statusat)); + } + return (NULL); +} + +/* Callback for origin_flag. */ +static void * +format_cb_origin_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_ORIGIN) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_active. */ +static void * +format_cb_pane_active(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp == ft->wp->window->active) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_at_left. */ +static void * +format_cb_pane_at_left(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->xoff == 0) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_at_right. */ +static void * +format_cb_pane_at_right(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->xoff + ft->wp->sx == ft->wp->window->sx) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_bottom. */ +static void * +format_cb_pane_bottom(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->yoff + ft->wp->sy - 1)); + return (NULL); +} + +/* Callback for pane_dead. */ +static void * +format_cb_pane_dead(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->fd == -1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_dead_status. */ +static void * +format_cb_pane_dead_status(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) { + if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(wp->status)) + return (format_printf("%d", WEXITSTATUS(wp->status))); + return (NULL); + } + return (NULL); +} + +/* Callback for pane_format. */ +static void * +format_cb_pane_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_PANE) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for pane_height. */ +static void * +format_cb_pane_height(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->sy)); + return (NULL); +} + +/* Callback for pane_id. */ +static void * +format_cb_pane_id(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%%%u", ft->wp->id)); + return (NULL); +} + +/* Callback for pane_index. */ +static void * +format_cb_pane_index(struct format_tree *ft) +{ + u_int idx; + + if (ft->wp != NULL && window_pane_index(ft->wp, &idx) == 0) + return (format_printf("%u", idx)); + return (NULL); +} + +/* Callback for pane_input_off. */ +static void * +format_cb_pane_input_off(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->flags & PANE_INPUTOFF) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_last. */ +static void * +format_cb_pane_last(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp == ft->wp->window->last) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_left. */ +static void * +format_cb_pane_left(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->xoff)); + return (NULL); +} + +/* Callback for pane_marked. */ +static void * +format_cb_pane_marked(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (server_check_marked() && marked_pane.wp == ft->wp) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_marked_set. */ +static void * +format_cb_pane_marked_set(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (server_check_marked()) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_mode. */ +static void * +format_cb_pane_mode(struct format_tree *ft) +{ + struct window_mode_entry *wme; + + if (ft->wp != NULL) { + wme = TAILQ_FIRST(&ft->wp->modes); + if (wme != NULL) + return (xstrdup(wme->mode->name)); + return (NULL); + } + return (NULL); +} + +/* Callback for pane_path. */ +static void * +format_cb_pane_path(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.path == NULL) + return (xstrdup("")); + return (xstrdup(ft->wp->base.path)); + } + return (NULL); +} + +/* Callback for pane_pid. */ +static void * +format_cb_pane_pid(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%ld", (long)ft->wp->pid)); + return (NULL); +} + +/* Callback for pane_pipe. */ +static void * +format_cb_pane_pipe(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->pipe_fd != -1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_right. */ +static void * +format_cb_pane_right(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->xoff + ft->wp->sx - 1)); + return (NULL); +} + +/* Callback for pane_search_string. */ +static void * +format_cb_pane_search_string(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->searchstr == NULL) + return (xstrdup("")); + return (xstrdup(ft->wp->searchstr)); + } + return (NULL); +} + +/* Callback for pane_synchronized. */ +static void * +format_cb_pane_synchronized(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (options_get_number(ft->wp->options, "synchronize-panes")) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for pane_title. */ +static void * +format_cb_pane_title(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (xstrdup(ft->wp->base.title)); + return (NULL); +} + +/* Callback for pane_top. */ +static void * +format_cb_pane_top(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->yoff)); + return (NULL); +} + +/* Callback for pane_tty. */ +static void * +format_cb_pane_tty(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (xstrdup(ft->wp->tty)); + return (NULL); +} + +/* Callback for pane_width. */ +static void * +format_cb_pane_width(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->sx)); + return (NULL); +} + +/* Callback for scroll_region_lower. */ +static void * +format_cb_scroll_region_lower(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.rlower)); + return (NULL); +} + +/* Callback for scroll_region_upper. */ +static void * +format_cb_scroll_region_upper(struct format_tree *ft) +{ + if (ft->wp != NULL) + return (format_printf("%u", ft->wp->base.rupper)); + return (NULL); +} + +/* Callback for session_attached. */ +static void * +format_cb_session_attached(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("%u", ft->s->attached)); + return (NULL); +} + +/* Callback for session_format. */ +static void * +format_cb_session_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_SESSION) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for session_group. */ +static void * +format_cb_session_group(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (xstrdup(sg->name)); + return (NULL); +} + +/* Callback for session_group_attached. */ +static void * +format_cb_session_group_attached(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (format_printf("%u", session_group_attached_count (sg))); + return (NULL); +} + +/* Callback for session_group_many_attached. */ +static void * +format_cb_session_group_many_attached(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) { + if (session_group_attached_count (sg) > 1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_group_size. */ +static void * +format_cb_session_group_size(struct format_tree *ft) +{ + struct session_group *sg; + + if (ft->s != NULL && (sg = session_group_contains(ft->s)) != NULL) + return (format_printf("%u", session_group_count (sg))); + return (NULL); +} + +/* Callback for session_grouped. */ +static void * +format_cb_session_grouped(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (session_group_contains(ft->s) != NULL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_id. */ +static void * +format_cb_session_id(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("$%u", ft->s->id)); + return (NULL); +} + +/* Callback for session_many_attached. */ +static void * +format_cb_session_many_attached(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (ft->s->attached > 1) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_marked. */ +static void * +format_cb_session_marked(struct format_tree *ft) +{ + if (ft->s != NULL) { + if (server_check_marked() && marked_pane.s == ft->s) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for session_name. */ +static void * +format_cb_session_name(struct format_tree *ft) +{ + if (ft->s != NULL) + return (xstrdup(ft->s->name)); + return (NULL); +} + +/* Callback for session_path. */ +static void * +format_cb_session_path(struct format_tree *ft) +{ + if (ft->s != NULL) + return (xstrdup(ft->s->cwd)); + return (NULL); +} + +/* Callback for session_windows. */ +static void * +format_cb_session_windows(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("%u", winlink_count(&ft->s->windows))); + return (NULL); +} + +/* Callback for socket_path. */ +static void * +format_cb_socket_path(__unused struct format_tree *ft) +{ + return (xstrdup(socket_path)); +} + +/* Callback for version. */ +static void * +format_cb_version(__unused struct format_tree *ft) +{ + return (xstrdup(getversion())); +} + +/* Callback for active_window_index. */ +static void * +format_cb_active_window_index(struct format_tree *ft) +{ + if (ft->s != NULL) + return (format_printf("%u", ft->s->curw->idx)); + return (NULL); +} + +/* Callback for last_window_index. */ +static void * +format_cb_last_window_index(struct format_tree *ft) +{ + struct winlink *wl; + + if (ft->s != NULL) { + wl = RB_MAX(winlinks, &ft->s->windows); + return (format_printf("%u", wl->idx)); + } + return (NULL); +} + +/* Callback for window_active. */ +static void * +format_cb_window_active(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == ft->wl->session->curw) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_activity_flag. */ +static void * +format_cb_window_activity_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_ACTIVITY) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_bell_flag. */ +static void * +format_cb_window_bell_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_BELL) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_bigger. */ +static void * +format_cb_window_bigger(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_cell_height. */ +static void * +format_cb_window_cell_height(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->ypixel)); + return (NULL); +} + +/* Callback for window_cell_width. */ +static void * +format_cb_window_cell_width(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->xpixel)); + return (NULL); +} + +/* Callback for window_end_flag. */ +static void * +format_cb_window_end_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == RB_MAX(winlinks, &ft->wl->session->windows)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_flags. */ +static void * +format_cb_window_flags(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (xstrdup(window_printable_flags(ft->wl, 1))); + return (NULL); +} + +/* Callback for window_format. */ +static void * +format_cb_window_format(struct format_tree *ft) +{ + if (ft->type == FORMAT_TYPE_WINDOW) + return (xstrdup("1")); + return (xstrdup("0")); +} + +/* Callback for window_height. */ +static void * +format_cb_window_height(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->sy)); + return (NULL); +} + +/* Callback for window_id. */ +static void * +format_cb_window_id(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("@%u", ft->w->id)); + return (NULL); +} + +/* Callback for window_index. */ +static void * +format_cb_window_index(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (format_printf("%d", ft->wl->idx)); + return (NULL); +} + +/* Callback for window_last_flag. */ +static void * +format_cb_window_last_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == TAILQ_FIRST(&ft->wl->session->lastw)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_linked. */ +static void * +format_cb_window_linked(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (session_is_linked(ft->wl->session, ft->wl->window)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_linked_sessions. */ +static void * +format_cb_window_linked_sessions(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (format_printf("%u", ft->wl->window->references)); + return (NULL); +} + +/* Callback for window_marked_flag. */ +static void * +format_cb_window_marked_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (server_check_marked() && marked_pane.wl == ft->wl) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_name. */ +static void * +format_cb_window_name(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%s", ft->w->name)); + return (NULL); +} + +/* Callback for window_offset_x. */ +static void * +format_cb_window_offset_x(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (format_printf("%u", ox)); + return (NULL); + } + return (NULL); +} + +/* Callback for window_offset_y. */ +static void * +format_cb_window_offset_y(struct format_tree *ft) +{ + u_int ox, oy, sx, sy; + + if (ft->c != NULL) { + if (tty_window_offset(&ft->c->tty, &ox, &oy, &sx, &sy)) + return (format_printf("%u", oy)); + return (NULL); + } + return (NULL); +} + +/* Callback for window_panes. */ +static void * +format_cb_window_panes(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", window_count_panes(ft->w))); + return (NULL); +} + +/* Callback for window_raw_flags. */ +static void * +format_cb_window_raw_flags(struct format_tree *ft) +{ + if (ft->wl != NULL) + return (xstrdup(window_printable_flags(ft->wl, 0))); + return (NULL); +} + +/* Callback for window_silence_flag. */ +static void * +format_cb_window_silence_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl->flags & WINLINK_SILENCE) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_start_flag. */ +static void * +format_cb_window_start_flag(struct format_tree *ft) +{ + if (ft->wl != NULL) { + if (ft->wl == RB_MIN(winlinks, &ft->wl->session->windows)) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for window_width. */ +static void * +format_cb_window_width(struct format_tree *ft) +{ + if (ft->w != NULL) + return (format_printf("%u", ft->w->sx)); + return (NULL); +} + +/* Callback for window_zoomed_flag. */ +static void * +format_cb_window_zoomed_flag(struct format_tree *ft) +{ + if (ft->w != NULL) { + if (ft->w->flags & WINDOW_ZOOMED) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for wrap_flag. */ +static void * +format_cb_wrap_flag(struct format_tree *ft) +{ + if (ft->wp != NULL) { + if (ft->wp->base.mode & MODE_WRAP) + return (xstrdup("1")); + return (xstrdup("0")); + } + return (NULL); +} + +/* Callback for buffer_created. */ +static void * +format_cb_buffer_created(struct format_tree *ft) +{ + static struct timeval tv; + + if (ft->pb != NULL) { + timerclear(&tv); + tv.tv_sec = paste_buffer_created(ft->pb); + return (&tv); + } + return (NULL); +} + +/* Callback for client_activity. */ +static void * +format_cb_client_activity(struct format_tree *ft) +{ + if (ft->c != NULL) + return (&ft->c->activity_time); + return (NULL); +} + +/* Callback for client_created. */ +static void * +format_cb_client_created(struct format_tree *ft) +{ + if (ft->c != NULL) + return (&ft->c->creation_time); + return (NULL); +} + +/* Callback for session_activity. */ +static void * +format_cb_session_activity(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->activity_time); + return (NULL); +} + +/* Callback for session_created. */ +static void * +format_cb_session_created(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->creation_time); + return (NULL); +} + +/* Callback for session_last_attached. */ +static void * +format_cb_session_last_attached(struct format_tree *ft) +{ + if (ft->s != NULL) + return (&ft->s->last_attached_time); + return (NULL); +} + +/* Callback for start_time. */ +static void * +format_cb_start_time(__unused struct format_tree *ft) +{ + return (&start_time); +} + +/* Callback for window_activity. */ +static void * +format_cb_window_activity(struct format_tree *ft) +{ + if (ft->w != NULL) + return (&ft->w->activity_time); + return (NULL); +} + +/* Callback for buffer_mode_format, */ +static void * +format_cb_buffer_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_buffer_mode.default_format)); +} + +/* Callback for client_mode_format, */ +static void * +format_cb_client_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_client_mode.default_format)); +} + +/* Callback for tree_mode_format, */ +static void * +format_cb_tree_mode_format(__unused struct format_tree *ft) +{ + return (xstrdup(window_tree_mode.default_format)); +} + +/* Format table type. */ +enum format_table_type { + FORMAT_TABLE_STRING, + FORMAT_TABLE_TIME +}; + +/* Format table entry. */ +struct format_table_entry { + const char *key; + enum format_table_type type; + format_cb cb; +}; + +/* + * Format table. Default format variables (that are almost always in the tree + * and where the value is expanded by a callback in this file) are listed here. + * Only variables which are added by the caller go into the tree. + */ +static const struct format_table_entry format_table[] = { + { "active_window_index", FORMAT_TABLE_STRING, + format_cb_active_window_index + }, + { "alternate_on", FORMAT_TABLE_STRING, + format_cb_alternate_on + }, + { "alternate_saved_x", FORMAT_TABLE_STRING, + format_cb_alternate_saved_x + }, + { "alternate_saved_y", FORMAT_TABLE_STRING, + format_cb_alternate_saved_y + }, + { "buffer_created", FORMAT_TABLE_TIME, + format_cb_buffer_created + }, + { "buffer_mode_format", FORMAT_TABLE_STRING, + format_cb_buffer_mode_format + }, + { "buffer_name", FORMAT_TABLE_STRING, + format_cb_buffer_name + }, + { "buffer_sample", FORMAT_TABLE_STRING, + format_cb_buffer_sample + }, + { "buffer_size", FORMAT_TABLE_STRING, + format_cb_buffer_size + }, + { "client_activity", FORMAT_TABLE_TIME, + format_cb_client_activity + }, + { "client_cell_height", FORMAT_TABLE_STRING, + format_cb_client_cell_height + }, + { "client_cell_width", FORMAT_TABLE_STRING, + format_cb_client_cell_width + }, + { "client_control_mode", FORMAT_TABLE_STRING, + format_cb_client_control_mode + }, + { "client_created", FORMAT_TABLE_TIME, + format_cb_client_created + }, + { "client_discarded", FORMAT_TABLE_STRING, + format_cb_client_discarded + }, + { "client_flags", FORMAT_TABLE_STRING, + format_cb_client_flags + }, + { "client_height", FORMAT_TABLE_STRING, + format_cb_client_height + }, + { "client_key_table", FORMAT_TABLE_STRING, + format_cb_client_key_table + }, + { "client_last_session", FORMAT_TABLE_STRING, + format_cb_client_last_session + }, + { "client_mode_format", FORMAT_TABLE_STRING, + format_cb_client_mode_format + }, + { "client_name", FORMAT_TABLE_STRING, + format_cb_client_name + }, + { "client_pid", FORMAT_TABLE_STRING, + format_cb_client_pid + }, + { "client_prefix", FORMAT_TABLE_STRING, + format_cb_client_prefix + }, + { "client_readonly", FORMAT_TABLE_STRING, + format_cb_client_readonly + }, + { "client_session", FORMAT_TABLE_STRING, + format_cb_client_session + }, + { "client_termfeatures", FORMAT_TABLE_STRING, + format_cb_client_termfeatures + }, + { "client_termname", FORMAT_TABLE_STRING, + format_cb_client_termname + }, + { "client_termtype", FORMAT_TABLE_STRING, + format_cb_client_termtype + }, + { "client_tty", FORMAT_TABLE_STRING, + format_cb_client_tty + }, + { "client_utf8", FORMAT_TABLE_STRING, + format_cb_client_utf8 + }, + { "client_width", FORMAT_TABLE_STRING, + format_cb_client_width + }, + { "client_written", FORMAT_TABLE_STRING, + format_cb_client_written + }, + { "config_files", FORMAT_TABLE_STRING, + format_cb_config_files + }, + { "cursor_character", FORMAT_TABLE_STRING, + format_cb_cursor_character + }, + { "cursor_flag", FORMAT_TABLE_STRING, + format_cb_cursor_flag + }, + { "cursor_x", FORMAT_TABLE_STRING, + format_cb_cursor_x + }, + { "cursor_y", FORMAT_TABLE_STRING, + format_cb_cursor_y + }, + { "history_all_bytes", FORMAT_TABLE_STRING, + format_cb_history_all_bytes + }, + { "history_bytes", FORMAT_TABLE_STRING, + format_cb_history_bytes + }, + { "history_limit", FORMAT_TABLE_STRING, + format_cb_history_limit + }, + { "history_size", FORMAT_TABLE_STRING, + format_cb_history_size + }, + { "host", FORMAT_TABLE_STRING, + format_cb_host + }, + { "host_short", FORMAT_TABLE_STRING, + format_cb_host_short + }, + { "insert_flag", FORMAT_TABLE_STRING, + format_cb_insert_flag + }, + { "keypad_cursor_flag", FORMAT_TABLE_STRING, + format_cb_keypad_cursor_flag + }, + { "keypad_flag", FORMAT_TABLE_STRING, + format_cb_keypad_flag + }, + { "last_window_index", FORMAT_TABLE_STRING, + format_cb_last_window_index + }, + { "mouse_all_flag", FORMAT_TABLE_STRING, + format_cb_mouse_all_flag + }, + { "mouse_any_flag", FORMAT_TABLE_STRING, + format_cb_mouse_any_flag + }, + { "mouse_button_flag", FORMAT_TABLE_STRING, + format_cb_mouse_button_flag + }, + { "mouse_line", FORMAT_TABLE_STRING, + format_cb_mouse_line + }, + { "mouse_pane", FORMAT_TABLE_STRING, + format_cb_mouse_pane + }, + { "mouse_sgr_flag", FORMAT_TABLE_STRING, + format_cb_mouse_sgr_flag + }, + { "mouse_standard_flag", FORMAT_TABLE_STRING, + format_cb_mouse_standard_flag + }, + { "mouse_utf8_flag", FORMAT_TABLE_STRING, + format_cb_mouse_utf8_flag + }, + { "mouse_word", FORMAT_TABLE_STRING, + format_cb_mouse_word + }, + { "mouse_x", FORMAT_TABLE_STRING, + format_cb_mouse_x + }, + { "mouse_y", FORMAT_TABLE_STRING, + format_cb_mouse_y + }, + { "origin_flag", FORMAT_TABLE_STRING, + format_cb_origin_flag + }, + { "pane_active", FORMAT_TABLE_STRING, + format_cb_pane_active + }, + { "pane_at_bottom", FORMAT_TABLE_STRING, + format_cb_pane_at_bottom + }, + { "pane_at_left", FORMAT_TABLE_STRING, + format_cb_pane_at_left + }, + { "pane_at_right", FORMAT_TABLE_STRING, + format_cb_pane_at_right + }, + { "pane_at_top", FORMAT_TABLE_STRING, + format_cb_pane_at_top + }, + { "pane_bg", FORMAT_TABLE_STRING, + format_cb_pane_bg + }, + { "pane_bottom", FORMAT_TABLE_STRING, + format_cb_pane_bottom + }, + { "pane_current_command", FORMAT_TABLE_STRING, + format_cb_current_command + }, + { "pane_current_path", FORMAT_TABLE_STRING, + format_cb_current_path + }, + { "pane_dead", FORMAT_TABLE_STRING, + format_cb_pane_dead + }, + { "pane_dead_status", FORMAT_TABLE_STRING, + format_cb_pane_dead_status + }, + { "pane_fg", FORMAT_TABLE_STRING, + format_cb_pane_fg + }, + { "pane_format", FORMAT_TABLE_STRING, + format_cb_pane_format + }, + { "pane_height", FORMAT_TABLE_STRING, + format_cb_pane_height + }, + { "pane_id", FORMAT_TABLE_STRING, + format_cb_pane_id + }, + { "pane_in_mode", FORMAT_TABLE_STRING, + format_cb_pane_in_mode + }, + { "pane_index", FORMAT_TABLE_STRING, + format_cb_pane_index + }, + { "pane_input_off", FORMAT_TABLE_STRING, + format_cb_pane_input_off + }, + { "pane_last", FORMAT_TABLE_STRING, + format_cb_pane_last + }, + { "pane_left", FORMAT_TABLE_STRING, + format_cb_pane_left + }, + { "pane_marked", FORMAT_TABLE_STRING, + format_cb_pane_marked + }, + { "pane_marked_set", FORMAT_TABLE_STRING, + format_cb_pane_marked_set + }, + { "pane_mode", FORMAT_TABLE_STRING, + format_cb_pane_mode + }, + { "pane_path", FORMAT_TABLE_STRING, + format_cb_pane_path + }, + { "pane_pid", FORMAT_TABLE_STRING, + format_cb_pane_pid + }, + { "pane_pipe", FORMAT_TABLE_STRING, + format_cb_pane_pipe + }, + { "pane_right", FORMAT_TABLE_STRING, + format_cb_pane_right + }, + { "pane_search_string", FORMAT_TABLE_STRING, + format_cb_pane_search_string + }, + { "pane_start_command", FORMAT_TABLE_STRING, + format_cb_start_command + }, + { "pane_synchronized", FORMAT_TABLE_STRING, + format_cb_pane_synchronized + }, + { "pane_tabs", FORMAT_TABLE_STRING, + format_cb_pane_tabs + }, + { "pane_title", FORMAT_TABLE_STRING, + format_cb_pane_title + }, + { "pane_top", FORMAT_TABLE_STRING, + format_cb_pane_top + }, + { "pane_tty", FORMAT_TABLE_STRING, + format_cb_pane_tty + }, + { "pane_width", FORMAT_TABLE_STRING, + format_cb_pane_width + }, + { "pid", FORMAT_TABLE_STRING, + format_cb_pid + }, + { "scroll_region_lower", FORMAT_TABLE_STRING, + format_cb_scroll_region_lower + }, + { "scroll_region_upper", FORMAT_TABLE_STRING, + format_cb_scroll_region_upper + }, + { "session_activity", FORMAT_TABLE_TIME, + format_cb_session_activity + }, + { "session_alerts", FORMAT_TABLE_STRING, + format_cb_session_alerts + }, + { "session_attached", FORMAT_TABLE_STRING, + format_cb_session_attached + }, + { "session_attached_list", FORMAT_TABLE_STRING, + format_cb_session_attached_list + }, + { "session_created", FORMAT_TABLE_TIME, + format_cb_session_created + }, + { "session_format", FORMAT_TABLE_STRING, + format_cb_session_format + }, + { "session_group", FORMAT_TABLE_STRING, + format_cb_session_group + }, + { "session_group_attached", FORMAT_TABLE_STRING, + format_cb_session_group_attached + }, + { "session_group_attached_list", FORMAT_TABLE_STRING, + format_cb_session_group_attached_list + }, + { "session_group_list", FORMAT_TABLE_STRING, + format_cb_session_group_list + }, + { "session_group_many_attached", FORMAT_TABLE_STRING, + format_cb_session_group_many_attached + }, + { "session_group_size", FORMAT_TABLE_STRING, + format_cb_session_group_size + }, + { "session_grouped", FORMAT_TABLE_STRING, + format_cb_session_grouped + }, + { "session_id", FORMAT_TABLE_STRING, + format_cb_session_id + }, + { "session_last_attached", FORMAT_TABLE_TIME, + format_cb_session_last_attached + }, + { "session_many_attached", FORMAT_TABLE_STRING, + format_cb_session_many_attached + }, + { "session_marked", FORMAT_TABLE_STRING, + format_cb_session_marked, + }, + { "session_name", FORMAT_TABLE_STRING, + format_cb_session_name + }, + { "session_path", FORMAT_TABLE_STRING, + format_cb_session_path + }, + { "session_stack", FORMAT_TABLE_STRING, + format_cb_session_stack + }, + { "session_windows", FORMAT_TABLE_STRING, + format_cb_session_windows + }, + { "socket_path", FORMAT_TABLE_STRING, + format_cb_socket_path + }, + { "start_time", FORMAT_TABLE_TIME, + format_cb_start_time + }, + { "tree_mode_format", FORMAT_TABLE_STRING, + format_cb_tree_mode_format + }, + { "version", FORMAT_TABLE_STRING, + format_cb_version + }, + { "window_active", FORMAT_TABLE_STRING, + format_cb_window_active + }, + { "window_active_clients", FORMAT_TABLE_STRING, + format_cb_window_active_clients + }, + { "window_active_clients_list", FORMAT_TABLE_STRING, + format_cb_window_active_clients_list + }, + { "window_active_sessions", FORMAT_TABLE_STRING, + format_cb_window_active_sessions + }, + { "window_active_sessions_list", FORMAT_TABLE_STRING, + format_cb_window_active_sessions_list + }, + { "window_activity", FORMAT_TABLE_TIME, + format_cb_window_activity + }, + { "window_activity_flag", FORMAT_TABLE_STRING, + format_cb_window_activity_flag + }, + { "window_bell_flag", FORMAT_TABLE_STRING, + format_cb_window_bell_flag + }, + { "window_bigger", FORMAT_TABLE_STRING, + format_cb_window_bigger + }, + { "window_cell_height", FORMAT_TABLE_STRING, + format_cb_window_cell_height + }, + { "window_cell_width", FORMAT_TABLE_STRING, + format_cb_window_cell_width + }, + { "window_end_flag", FORMAT_TABLE_STRING, + format_cb_window_end_flag + }, + { "window_flags", FORMAT_TABLE_STRING, + format_cb_window_flags + }, + { "window_format", FORMAT_TABLE_STRING, + format_cb_window_format + }, + { "window_height", FORMAT_TABLE_STRING, + format_cb_window_height + }, + { "window_id", FORMAT_TABLE_STRING, + format_cb_window_id + }, + { "window_index", FORMAT_TABLE_STRING, + format_cb_window_index + }, + { "window_last_flag", FORMAT_TABLE_STRING, + format_cb_window_last_flag + }, + { "window_layout", FORMAT_TABLE_STRING, + format_cb_window_layout + }, + { "window_linked", FORMAT_TABLE_STRING, + format_cb_window_linked + }, + { "window_linked_sessions", FORMAT_TABLE_STRING, + format_cb_window_linked_sessions + }, + { "window_linked_sessions_list", FORMAT_TABLE_STRING, + format_cb_window_linked_sessions_list + }, + { "window_marked_flag", FORMAT_TABLE_STRING, + format_cb_window_marked_flag + }, + { "window_name", FORMAT_TABLE_STRING, + format_cb_window_name + }, + { "window_offset_x", FORMAT_TABLE_STRING, + format_cb_window_offset_x + }, + { "window_offset_y", FORMAT_TABLE_STRING, + format_cb_window_offset_y + }, + { "window_panes", FORMAT_TABLE_STRING, + format_cb_window_panes + }, + { "window_raw_flags", FORMAT_TABLE_STRING, + format_cb_window_raw_flags + }, + { "window_silence_flag", FORMAT_TABLE_STRING, + format_cb_window_silence_flag + }, + { "window_stack_index", FORMAT_TABLE_STRING, + format_cb_window_stack_index + }, + { "window_start_flag", FORMAT_TABLE_STRING, + format_cb_window_start_flag + }, + { "window_visible_layout", FORMAT_TABLE_STRING, + format_cb_window_visible_layout + }, + { "window_width", FORMAT_TABLE_STRING, + format_cb_window_width + }, + { "window_zoomed_flag", FORMAT_TABLE_STRING, + format_cb_window_zoomed_flag + }, + { "wrap_flag", FORMAT_TABLE_STRING, + format_cb_wrap_flag + } +}; + +/* Compare format table entries. */ +static int +format_table_compare(const void *key0, const void *entry0) +{ + const char *key = key0; + const struct format_table_entry *entry = entry0; + + return (strcmp(key, entry->key)); +} + +/* Get a format callback. */ +static struct format_table_entry * +format_table_get(const char *key) +{ + return (bsearch(key, format_table, nitems(format_table), + sizeof *format_table, format_table_compare)); +} + +/* Merge one format tree into another. */ +void format_merge(struct format_tree *ft, struct format_tree *from) { struct format_entry *fe; @@ -1104,32 +3035,21 @@ format_merge(struct format_tree *ft, struct format_tree *from) } } +/* Get format pane. */ +struct window_pane * +format_get_pane(struct format_tree *ft) +{ + return (ft->wp); +} + /* Add item bits to tree. */ static void format_create_add_item(struct format_tree *ft, struct cmdq_item *item) { - struct mouse_event *m; - struct window_pane *wp; - u_int x, y; + struct key_event *event = cmdq_get_event(item); + struct mouse_event *m = &event->m; - if (item->cmd != NULL) - format_add(ft, "command", "%s", item->cmd->entry->name); - - if (item->shared == NULL) - return; - if (item->shared->formats != NULL) - format_merge(ft, item->shared->formats); - - m = &item->shared->mouse; - if (m->valid && ((wp = cmd_mouse_pane(m, NULL, NULL)) != NULL)) { - format_add(ft, "mouse_pane", "%%%u", wp->id); - if (cmd_mouse_at(wp, m, &x, &y, 0) == 0) { - format_add(ft, "mouse_x", "%u", x); - format_add(ft, "mouse_y", "%u", y); - format_add_cb(ft, "mouse_word", format_cb_mouse_word); - format_add_cb(ft, "mouse_line", format_cb_mouse_line); - } - } + cmdq_merge_formats(item, ft); memcpy(&ft->m, m, sizeof ft->m); } @@ -1137,14 +3057,7 @@ format_create_add_item(struct format_tree *ft, struct cmdq_item *item) struct format_tree * format_create(struct client *c, struct cmdq_item *item, int tag, int flags) { - struct format_tree *ft; - const struct window_mode **wm; - char tmp[64]; - - if (!event_initialized(&format_job_event)) { - evtimer_set(&format_job_event, format_job_timer, NULL); - format_job_timer(-1, 0, NULL); - } + struct format_tree *ft; ft = xcalloc(1, sizeof *ft); RB_INIT(&ft->tree); @@ -1157,22 +3070,6 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) ft->tag = tag; ft->flags = flags; - ft->time = time(NULL); - - format_add(ft, "version", "%s", getversion()); - format_add_cb(ft, "host", format_cb_host); - format_add_cb(ft, "host_short", format_cb_host_short); - format_add_cb(ft, "pid", format_cb_pid); - format_add(ft, "socket_path", "%s", socket_path); - format_add_tv(ft, "start_time", &start_time); - - for (wm = all_window_modes; *wm != NULL; wm++) { - if ((*wm)->default_format != NULL) { - xsnprintf(tmp, sizeof tmp, "%s_format", (*wm)->name); - tmp[strcspn(tmp, "-")] = '_'; - format_add(ft, tmp, "%s", (*wm)->default_format); - } - } if (item != NULL) format_create_add_item(ft, item); @@ -1198,21 +3095,56 @@ format_free(struct format_tree *ft) free(ft); } +/* Log each format. */ +static void +format_log_debug_cb(const char *key, const char *value, void *arg) +{ + const char *prefix = arg; + + log_debug("%s: %s=%s", prefix, key, value); +} + +/* Log a format tree. */ +void +format_log_debug(struct format_tree *ft, const char *prefix) +{ + format_each(ft, format_log_debug_cb, (void *)prefix); +} + /* Walk each format. */ void format_each(struct format_tree *ft, void (*cb)(const char *, const char *, void *), void *arg) { - struct format_entry *fe; - char s[64]; + const struct format_table_entry *fte; + struct format_entry *fe; + u_int i; + char s[64]; + void *value; + struct timeval *tv; + for (i = 0; i < nitems(format_table); i++) { + fte = &format_table[i]; + + value = fte->cb(ft); + if (value == NULL) + continue; + if (fte->type == FORMAT_TABLE_TIME) { + tv = value; + xsnprintf(s, sizeof s, "%lld", (long long)tv->tv_sec); + cb(fte->key, s, arg); + } else { + cb(fte->key, value, arg); + free(value); + } + } RB_FOREACH(fe, format_entry_tree, &ft->tree) { - if (fe->t != 0) { - xsnprintf(s, sizeof s, "%lld", (long long)fe->t); + if (fe->time != 0) { + xsnprintf(s, sizeof s, "%lld", (long long)fe->time); cb(fe->key, s, arg); } else { if (fe->value == NULL && fe->cb != NULL) { - fe->cb(ft, fe); + fe->value = fe->cb(ft); if (fe->value == NULL) fe->value = xstrdup(""); } @@ -1241,7 +3173,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) } fe->cb = NULL; - fe->t = 0; + fe->time = 0; va_start(ap, fmt); xvasprintf(&fe->value, fmt, ap); @@ -1249,7 +3181,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) } /* Add a key and time. */ -static void +void format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) { struct format_entry *fe, *fe_now; @@ -1266,13 +3198,13 @@ format_add_tv(struct format_tree *ft, const char *key, struct timeval *tv) } fe->cb = NULL; - fe->t = tv->tv_sec; + fe->time = tv->tv_sec; fe->value = NULL; } /* Add a key and function. */ -static void +void format_add_cb(struct format_tree *ft, const char *key, format_cb cb) { struct format_entry *fe; @@ -1290,14 +3222,14 @@ format_add_cb(struct format_tree *ft, const char *key, format_cb cb) } fe->cb = cb; - fe->t = 0; + fe->time = 0; fe->value = NULL; } -/* Quote special characters in string. */ +/* Quote shell special characters in string. */ static char * -format_quote(const char *s) +format_quote_shell(const char *s) { const char *cp; char *out, *at; @@ -1312,55 +3244,117 @@ format_quote(const char *s) return (out); } +/* Quote #s in string. */ +static char * +format_quote_style(const char *s) +{ + const char *cp; + char *out, *at; + + at = out = xmalloc(strlen(s) * 2 + 1); + for (cp = s; *cp != '\0'; cp++) { + if (*cp == '#') + *at++ = '#'; + *at++ = *cp; + } + *at = '\0'; + return (out); +} + +/* Make a prettier time. */ +static char * +format_pretty_time(time_t t) +{ + struct tm now_tm, tm; + time_t now, age; + char s[6]; + + time(&now); + if (now < t) + now = t; + age = now - t; + + localtime_r(&now, &now_tm); + localtime_r(&t, &tm); + + /* Last 24 hours. */ + if (age < 24 * 3600) { + strftime(s, sizeof s, "%H:%M", &tm); + return (xstrdup(s)); + } + + /* This month or last 28 days. */ + if ((tm.tm_year == now_tm.tm_year && tm.tm_mon == now_tm.tm_mon) || + age < 28 * 24 * 3600) { + strftime(s, sizeof s, "%a%d", &tm); + return (xstrdup(s)); + } + + /* Last 12 months. */ + if ((tm.tm_year == now_tm.tm_year && tm.tm_mon < now_tm.tm_mon) || + (tm.tm_year == now_tm.tm_year - 1 && tm.tm_mon > now_tm.tm_mon)) { + strftime(s, sizeof s, "%d%b", &tm); + return (xstrdup(s)); + } + + /* Older than that. */ + strftime(s, sizeof s, "%h%y", &tm); + return (xstrdup(s)); +} + /* Find a format entry. */ static char * -format_find(struct format_tree *ft, const char *key, int modifiers) +format_find(struct format_tree *ft, const char *key, int modifiers, + const char *time_format) { - struct format_entry *fe, fe_find; - struct environ_entry *envent; - static char s[64]; - struct options_entry *o; - int idx; - char *found, *saved; + struct format_table_entry *fte; + void *value; + struct format_entry *fe, fe_find; + struct environ_entry *envent; + struct options_entry *o; + int idx; + char *found = NULL, *saved, s[512]; + const char *errstr; + time_t t = 0; + struct tm tm; - if (~modifiers & FORMAT_TIMESTRING) { - o = options_parse_get(global_options, key, &idx, 0); - if (o == NULL && ft->wp != NULL) - o = options_parse_get(ft->wp->options, key, &idx, 0); - if (o == NULL && ft->w != NULL) - o = options_parse_get(ft->w->options, key, &idx, 0); - if (o == NULL) - o = options_parse_get(global_w_options, key, &idx, 0); - if (o == NULL && ft->s != NULL) - o = options_parse_get(ft->s->options, key, &idx, 0); - if (o == NULL) - o = options_parse_get(global_s_options, key, &idx, 0); - if (o != NULL) { - found = options_tostring(o, idx, 1); - goto found; - } + o = options_parse_get(global_options, key, &idx, 0); + if (o == NULL && ft->wp != NULL) + o = options_parse_get(ft->wp->options, key, &idx, 0); + if (o == NULL && ft->w != NULL) + o = options_parse_get(ft->w->options, key, &idx, 0); + if (o == NULL) + o = options_parse_get(global_w_options, key, &idx, 0); + if (o == NULL && ft->s != NULL) + o = options_parse_get(ft->s->options, key, &idx, 0); + if (o == NULL) + o = options_parse_get(global_s_options, key, &idx, 0); + if (o != NULL) { + found = options_to_string(o, idx, 1); + goto found; } - found = NULL; - fe_find.key = (char *) key; + fte = format_table_get(key); + if (fte != NULL) { + value = fte->cb(ft); + if (fte->type == FORMAT_TABLE_TIME && value != NULL) + t = ((struct timeval *)value)->tv_sec; + else + found = value; + goto found; + } + fe_find.key = (char *)key; fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find); if (fe != NULL) { - if (modifiers & FORMAT_TIMESTRING) { - if (fe->t == 0) - return (NULL); - ctime_r(&fe->t, s); - s[strcspn(s, "\n")] = '\0'; - found = xstrdup(s); + if (fe->time != 0) { + t = fe->time; goto found; } - if (fe->t != 0) { - xasprintf(&found, "%lld", (long long)fe->t); - goto found; + if (fe->value == NULL && fe->cb != NULL) { + fe->value = fe->cb(ft); + if (fe->value == NULL) + fe->value = xstrdup(""); } - if (fe->value == NULL && fe->cb != NULL) - fe->cb(ft, fe); - if (fe->value == NULL) - fe->value = xstrdup(""); found = xstrdup(fe->value); goto found; } @@ -1380,7 +3374,33 @@ format_find(struct format_tree *ft, const char *key, int modifiers) return (NULL); found: - if (found == NULL) + if (modifiers & FORMAT_TIMESTRING) { + if (t == 0 && found != NULL) { + t = strtonum(found, 0, INT64_MAX, &errstr); + if (errstr != NULL) + t = 0; + free(found); + } + if (t == 0) + return (NULL); + if (modifiers & FORMAT_PRETTY) + found = format_pretty_time(t); + else { + if (time_format != NULL) { + localtime_r(&t, &tm); + strftime(s, sizeof s, time_format, &tm); + } else { + ctime_r(&t, s); + s[strcspn(s, "\n")] = '\0'; + } + found = xstrdup(s); + } + return (found); + } + + if (t != 0) + xasprintf(&found, "%lld", (long long)t); + else if (found == NULL) return (NULL); if (modifiers & FORMAT_BASENAME) { saved = found; @@ -1392,14 +3412,43 @@ found: found = xstrdup(dirname(saved)); free(saved); } - if (modifiers & FORMAT_QUOTE) { + if (modifiers & FORMAT_QUOTE_SHELL) { saved = found; - found = xstrdup(format_quote(saved)); + found = xstrdup(format_quote_shell(saved)); + free(saved); + } + if (modifiers & FORMAT_QUOTE_STYLE) { + saved = found; + found = xstrdup(format_quote_style(saved)); free(saved); } return (found); } +/* Remove escaped characters from string. */ +static char * +format_strip(const char *s) +{ + char *out, *cp; + int brackets = 0; + + cp = out = xmalloc(strlen(s) + 1); + for (; *s != '\0'; s++) { + if (*s == '#' && s[1] == '{') + brackets++; + if (*s == '#' && strchr(",#{}:", s[1]) != NULL) { + if (brackets != 0) + *cp++ = *s; + continue; + } + if (*s == '}') + brackets--; + *cp++ = *s; + } + *cp = '\0'; + return (out); +} + /* Skip until end. */ const char * format_skip(const char *s, const char *end) @@ -1409,7 +3458,7 @@ format_skip(const char *s, const char *end) for (; *s != '\0'; s++) { if (*s == '#' && s[1] == '{') brackets++; - if (*s == '#' && strchr(",#{}", s[1]) != NULL) { + if (*s == '#' && strchr(",#{}:", s[1]) != NULL) { s++; continue; } @@ -1425,8 +3474,8 @@ format_skip(const char *s, const char *end) /* Return left and right alternatives separated by commas. */ static int -format_choose(struct format_tree *ft, const char *s, char **left, char **right, - int expand) +format_choose(struct format_expand_state *es, const char *s, char **left, + char **right, int expand) { const char *cp; char *left0, *right0; @@ -1438,9 +3487,9 @@ format_choose(struct format_tree *ft, const char *s, char **left, char **right, right0 = xstrdup(cp + 1); if (expand) { - *left = format_expand(ft, left0); + *left = format_expand1(es, left0); free(left0); - *right = format_expand(ft, right0); + *right = format_expand1(es, right0); free(right0); } else { *left = left0; @@ -1496,7 +3545,8 @@ format_free_modifiers(struct format_modifier *list, u_int count) /* Build modifier list. */ static struct format_modifier * -format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) +format_build_modifiers(struct format_expand_state *es, const char **s, + u_int *count) { const char *cp = *s, *end; struct format_modifier *list = NULL; @@ -1505,7 +3555,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) /* * Modifiers are a ; separated list of the forms: - * l,m,C,b,d,t,q,E,T,S,W,P,<,> + * l,m,C,a,b,d,n,t,w,q,E,T,S,W,P,<,> * =a * =/a * =/a/ @@ -1517,12 +3567,12 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) *count = 0; while (*cp != '\0' && *cp != ':') { - /* Skip and separator character. */ + /* Skip any separator character. */ if (*cp == ';') cp++; /* Check single character modifiers with no arguments. */ - if (strchr("lbdtqETSWP<>", cp[0]) != NULL && + if (strchr("labdnwETSWP<>", cp[0]) != NULL && format_is_end(cp[1])) { format_add_modifier(&list, count, cp, 1, NULL, 0); cp++; @@ -1543,7 +3593,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) } /* Now try single character with arguments. */ - if (strchr("mCs=p", cp[0]) == NULL) + if (strchr("mCNst=peq", cp[0]) == NULL) break; c = cp[0]; @@ -1564,7 +3614,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) argv = xcalloc(1, sizeof *argv); value = xstrndup(cp + 1, end - (cp + 1)); - argv[0] = format_expand(ft, value); + argv[0] = format_expand1(es, value); free(value); argc = 1; @@ -1586,9 +3636,9 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) break; cp++; - argv = xreallocarray (argv, argc + 1, sizeof *argv); + argv = xreallocarray(argv, argc + 1, sizeof *argv); value = xstrndup(cp, end - cp); - argv[argc++] = format_expand(ft, value); + argv[argc++] = format_expand1(es, value); free(value); cp = end; @@ -1601,7 +3651,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) return (NULL); } *s = cp + 1; - return list; + return (list); } /* Match against an fnmatch(3) pattern or regular expression. */ @@ -1667,27 +3717,48 @@ format_search(struct format_modifier *fm, struct window_pane *wp, const char *s) return (value); } +/* Does session name exist? */ +static char * +format_session_name(struct format_expand_state *es, const char *fmt) +{ + char *name; + struct session *s; + + name = format_expand1(es, fmt); + RB_FOREACH(s, sessions, &sessions) { + if (strcmp(s->name, name) == 0) { + free(name); + return (xstrdup("1")); + } + } + free(name); + return (xstrdup("0")); +} + /* Loop over sessions. */ static char * -format_loop_sessions(struct format_tree *ft, const char *fmt) +format_loop_sessions(struct format_expand_state *es, const char *fmt) { - struct client *c = ft->client; - struct cmdq_item *item = ft->item; - struct format_tree *nft; - char *expanded, *value; - size_t valuelen; - struct session *s; + struct format_tree *ft = es->ft; + struct client *c = ft->client; + struct cmdq_item *item = ft->item; + struct format_tree *nft; + struct format_expand_state next; + char *expanded, *value; + size_t valuelen; + struct session *s; value = xcalloc(1, 1); valuelen = 1; RB_FOREACH(s, sessions, &sessions) { - format_log(ft, "session loop: $%u", s->id); + format_log(es, "session loop: $%u", s->id); nft = format_create(c, item, FORMAT_NONE, ft->flags); - nft->loop = ft->loop; format_defaults(nft, ft->c, s, NULL, NULL); - expanded = format_expand(nft, fmt); - format_free(nft); + format_copy_state(&next, es, 0); + next.ft = nft; + expanded = format_expand1(&next, fmt); + format_free(next.ft); valuelen += strlen(expanded); value = xrealloc(value, valuelen); @@ -1699,24 +3770,50 @@ format_loop_sessions(struct format_tree *ft, const char *fmt) return (value); } -/* Loop over windows. */ +/* Does window name exist? */ static char * -format_loop_windows(struct format_tree *ft, const char *fmt) +format_window_name(struct format_expand_state *es, const char *fmt) { - struct client *c = ft->client; - struct cmdq_item *item = ft->item; - struct format_tree *nft; - char *all, *active, *use, *expanded, *value; - size_t valuelen; + struct format_tree *ft = es->ft; + char *name; struct winlink *wl; - struct window *w; if (ft->s == NULL) { - format_log(ft, "window loop but no session"); + format_log(es, "window name but no session"); return (NULL); } - if (format_choose(ft, fmt, &all, &active, 0) != 0) { + name = format_expand1(es, fmt); + RB_FOREACH(wl, winlinks, &ft->s->windows) { + if (strcmp(wl->window->name, name) == 0) { + free(name); + return (xstrdup("1")); + } + } + free(name); + return (xstrdup("0")); +} + +/* Loop over windows. */ +static char * +format_loop_windows(struct format_expand_state *es, const char *fmt) +{ + struct format_tree *ft = es->ft; + struct client *c = ft->client; + struct cmdq_item *item = ft->item; + struct format_tree *nft; + struct format_expand_state next; + char *all, *active, *use, *expanded, *value; + size_t valuelen; + struct winlink *wl; + struct window *w; + + if (ft->s == NULL) { + format_log(es, "window loop but no session"); + return (NULL); + } + + if (format_choose(es, fmt, &all, &active, 0) != 0) { all = xstrdup(fmt); active = NULL; } @@ -1726,15 +3823,16 @@ format_loop_windows(struct format_tree *ft, const char *fmt) RB_FOREACH(wl, winlinks, &ft->s->windows) { w = wl->window; - format_log(ft, "window loop: %u @%u", wl->idx, w->id); + format_log(es, "window loop: %u @%u", wl->idx, w->id); if (active != NULL && wl == ft->s->curw) use = active; else use = all; nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags); - nft->loop = ft->loop; format_defaults(nft, ft->c, ft->s, wl, NULL); - expanded = format_expand(nft, use); + format_copy_state(&next, es, 0); + next.ft = nft; + expanded = format_expand1(&next, use); format_free(nft); valuelen += strlen(expanded); @@ -1752,21 +3850,23 @@ format_loop_windows(struct format_tree *ft, const char *fmt) /* Loop over panes. */ static char * -format_loop_panes(struct format_tree *ft, const char *fmt) +format_loop_panes(struct format_expand_state *es, const char *fmt) { - struct client *c = ft->client; - struct cmdq_item *item = ft->item; - struct format_tree *nft; - char *all, *active, *use, *expanded, *value; - size_t valuelen; - struct window_pane *wp; + struct format_tree *ft = es->ft; + struct client *c = ft->client; + struct cmdq_item *item = ft->item; + struct format_tree *nft; + struct format_expand_state next; + char *all, *active, *use, *expanded, *value; + size_t valuelen; + struct window_pane *wp; if (ft->w == NULL) { - format_log(ft, "pane loop but no window"); + format_log(es, "pane loop but no window"); return (NULL); } - if (format_choose(ft, fmt, &all, &active, 0) != 0) { + if (format_choose(es, fmt, &all, &active, 0) != 0) { all = xstrdup(fmt); active = NULL; } @@ -1775,15 +3875,16 @@ format_loop_panes(struct format_tree *ft, const char *fmt) valuelen = 1; TAILQ_FOREACH(wp, &ft->w->panes, entry) { - format_log(ft, "pane loop: %%%u", wp->id); + format_log(es, "pane loop: %%%u", wp->id); if (active != NULL && wp == ft->w->active) use = active; else use = all; nft = format_create(c, item, FORMAT_PANE|wp->id, ft->flags); - nft->loop = ft->loop; format_defaults(nft, ft->c, ft->s, ft->wl, wp); - expanded = format_expand(nft, use); + format_copy_state(&next, es, 0); + next.ft = nft; + expanded = format_expand1(&next, use); format_free(nft); valuelen += strlen(expanded); @@ -1799,32 +3900,178 @@ format_loop_panes(struct format_tree *ft, const char *fmt) return (value); } +static char * +format_replace_expression(struct format_modifier *mexp, + struct format_expand_state *es, const char *copy) +{ + int argc = mexp->argc; + const char *errstr; + char *endch, *value, *left = NULL, *right = NULL; + int use_fp = 0; + u_int prec = 0; + double mleft, mright, result; + enum { ADD, + SUBTRACT, + MULTIPLY, + DIVIDE, + MODULUS, + EQUAL, + NOT_EQUAL, + GREATER_THAN, + GREATER_THAN_EQUAL, + LESS_THAN, + LESS_THAN_EQUAL } operator; + + if (strcmp(mexp->argv[0], "+") == 0) + operator = ADD; + else if (strcmp(mexp->argv[0], "-") == 0) + operator = SUBTRACT; + else if (strcmp(mexp->argv[0], "*") == 0) + operator = MULTIPLY; + else if (strcmp(mexp->argv[0], "/") == 0) + operator = DIVIDE; + else if (strcmp(mexp->argv[0], "%") == 0 || + strcmp(mexp->argv[0], "m") == 0) + operator = MODULUS; + else if (strcmp(mexp->argv[0], "==") == 0) + operator = EQUAL; + else if (strcmp(mexp->argv[0], "!=") == 0) + operator = NOT_EQUAL; + else if (strcmp(mexp->argv[0], ">") == 0) + operator = GREATER_THAN; + else if (strcmp(mexp->argv[0], "<") == 0) + operator = LESS_THAN; + else if (strcmp(mexp->argv[0], ">=") == 0) + operator = GREATER_THAN_EQUAL; + else if (strcmp(mexp->argv[0], "<=") == 0) + operator = LESS_THAN_EQUAL; + else { + format_log(es, "expression has no valid operator: '%s'", + mexp->argv[0]); + goto fail; + } + + /* The second argument may be flags. */ + if (argc >= 2 && strchr(mexp->argv[1], 'f') != NULL) { + use_fp = 1; + prec = 2; + } + + /* The third argument may be precision. */ + if (argc >= 3) { + prec = strtonum(mexp->argv[2], INT_MIN, INT_MAX, &errstr); + if (errstr != NULL) { + format_log(es, "expression precision %s: %s", errstr, + mexp->argv[2]); + goto fail; + } + } + + if (format_choose(es, copy, &left, &right, 1) != 0) { + format_log(es, "expression syntax error"); + goto fail; + } + + mleft = strtod(left, &endch); + if (*endch != '\0') { + format_log(es, "expression left side is invalid: %s", left); + goto fail; + } + + mright = strtod(right, &endch); + if (*endch != '\0') { + format_log(es, "expression right side is invalid: %s", right); + goto fail; + } + + if (!use_fp) { + mleft = (long long)mleft; + mright = (long long)mright; + } + format_log(es, "expression left side is: %.*f", prec, mleft); + format_log(es, "expression right side is: %.*f", prec, mright); + + switch (operator) { + case ADD: + result = mleft + mright; + break; + case SUBTRACT: + result = mleft - mright; + break; + case MULTIPLY: + result = mleft * mright; + break; + case DIVIDE: + result = mleft / mright; + break; + case MODULUS: + result = fmod(mleft, mright); + break; + case EQUAL: + result = fabs(mleft - mright) < 1e-9; + break; + case NOT_EQUAL: + result = fabs(mleft - mright) > 1e-9; + break; + case GREATER_THAN: + result = (mleft > mright); + break; + case GREATER_THAN_EQUAL: + result = (mleft >= mright); + break; + case LESS_THAN: + result = (mleft < mright); + break; + case LESS_THAN_EQUAL: + result = (mleft <= mright); + break; + } + if (use_fp) + xasprintf(&value, "%.*f", prec, result); + else + xasprintf(&value, "%.*f", prec, (double)(long long)result); + format_log(es, "expression result is %s", value); + + free(right); + free(left); + return (value); + +fail: + free(right); + free(left); + return (NULL); +} + /* Replace a key. */ static int -format_replace(struct format_tree *ft, const char *key, size_t keylen, +format_replace(struct format_expand_state *es, const char *key, size_t keylen, char **buf, size_t *len, size_t *off) { - struct window_pane *wp = ft->wp; - const char *errptr, *copy, *cp, *marker = NULL; - char *copy0, *condition, *found, *new; - char *value, *left, *right; - size_t valuelen; - int modifiers = 0, limit = 0, width = 0, j; - struct format_modifier *list, *fm, *cmp = NULL, *search = NULL; - struct format_modifier **sub = NULL; - u_int i, count, nsub = 0; + struct format_tree *ft = es->ft; + struct window_pane *wp = ft->wp; + const char *errstr, *copy, *cp, *marker = NULL; + const char *time_format = NULL; + char *copy0, *condition, *found, *new; + char *value, *left, *right, c; + size_t valuelen; + int modifiers = 0, limit = 0, width = 0; + int j; + struct format_modifier *list, *cmp = NULL, *search = NULL; + struct format_modifier **sub = NULL, *mexp = NULL, *fm; + u_int i, count, nsub = 0; + struct format_expand_state next; /* Make a copy of the key. */ copy = copy0 = xstrndup(key, keylen); /* Process modifier list. */ - list = format_build_modifiers(ft, ©, &count); + list = format_build_modifiers(es, ©, &count); for (i = 0; i < count; i++) { fm = &list[i]; if (format_logging(ft)) { - format_log(ft, "modifier %u is %s", i, fm->modifier); + format_log(es, "modifier %u is %s", i, fm->modifier); for (j = 0; j < fm->argc; j++) { - format_log(ft, "modifier %u argument %d: %s", i, + format_log(es, "modifier %u argument %d: %s", i, j, fm->argv[j]); } } @@ -1841,16 +4088,15 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, case 's': if (fm->argc < 2) break; - sub = xreallocarray (sub, nsub + 1, - sizeof *sub); + sub = xreallocarray(sub, nsub + 1, sizeof *sub); sub[nsub++] = fm; break; case '=': if (fm->argc < 1) break; limit = strtonum(fm->argv[0], INT_MIN, INT_MAX, - &errptr); - if (errptr != NULL) + &errstr); + if (errstr != NULL) limit = 0; if (fm->argc >= 2 && fm->argv[1] != NULL) marker = fm->argv[1]; @@ -1859,24 +4105,49 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, if (fm->argc < 1) break; width = strtonum(fm->argv[0], INT_MIN, INT_MAX, - &errptr); - if (errptr != NULL) + &errstr); + if (errstr != NULL) width = 0; break; + case 'w': + modifiers |= FORMAT_WIDTH; + break; + case 'e': + if (fm->argc < 1 || fm->argc > 3) + break; + mexp = fm; + break; case 'l': modifiers |= FORMAT_LITERAL; break; + case 'a': + modifiers |= FORMAT_CHARACTER; + break; case 'b': modifiers |= FORMAT_BASENAME; break; case 'd': modifiers |= FORMAT_DIRNAME; break; + case 'n': + modifiers |= FORMAT_LENGTH; + break; case 't': modifiers |= FORMAT_TIMESTRING; + if (fm->argc < 1) + break; + if (strchr(fm->argv[0], 'p') != NULL) + modifiers |= FORMAT_PRETTY; + else if (fm->argc >= 2 && + strchr(fm->argv[0], 'f') != NULL) + time_format = format_strip(fm->argv[1]); break; case 'q': - modifiers |= FORMAT_QUOTE; + if (fm->argc < 1) + modifiers |= FORMAT_QUOTE_SHELL; + else if (strchr(fm->argv[0], 'e') != NULL || + strchr(fm->argv[0], 'h') != NULL) + modifiers |= FORMAT_QUOTE_STYLE; break; case 'E': modifiers |= FORMAT_EXPAND; @@ -1884,6 +4155,13 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, case 'T': modifiers |= FORMAT_EXPANDTIME; break; + case 'N': + if (fm->argc < 1 || + strchr(fm->argv[0], 'w') != NULL) + modifiers |= FORMAT_WINDOW_NAME; + else if (strchr(fm->argv[0], 's') != NULL) + modifiers |= FORMAT_SESSION_NAME; + break; case 'S': modifiers |= FORMAT_SESSIONS; break; @@ -1911,39 +4189,59 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, goto done; } + /* Is this a character? */ + if (modifiers & FORMAT_CHARACTER) { + new = format_expand1(es, copy); + c = strtonum(new, 32, 126, &errstr); + if (errstr != NULL) + value = xstrdup(""); + else + xasprintf(&value, "%c", c); + free(new); + goto done; + } + /* Is this a loop, comparison or condition? */ if (modifiers & FORMAT_SESSIONS) { - value = format_loop_sessions(ft, copy); + value = format_loop_sessions(es, copy); if (value == NULL) goto fail; } else if (modifiers & FORMAT_WINDOWS) { - value = format_loop_windows(ft, copy); + value = format_loop_windows(es, copy); if (value == NULL) goto fail; } else if (modifiers & FORMAT_PANES) { - value = format_loop_panes(ft, copy); + value = format_loop_panes(es, copy); + if (value == NULL) + goto fail; + } else if (modifiers & FORMAT_WINDOW_NAME) { + value = format_window_name(es, copy); + if (value == NULL) + goto fail; + } else if (modifiers & FORMAT_SESSION_NAME) { + value = format_session_name(es, copy); if (value == NULL) goto fail; } else if (search != NULL) { /* Search in pane. */ - new = format_expand(ft, copy); + new = format_expand1(es, copy); if (wp == NULL) { - format_log(ft, "search '%s' but no pane", new); + format_log(es, "search '%s' but no pane", new); value = xstrdup("0"); } else { - format_log(ft, "search '%s' pane %%%u", new, wp->id); - value = format_search(fm, wp, new); + format_log(es, "search '%s' pane %%%u", new, wp->id); + value = format_search(search, wp, new); } free(new); } else if (cmp != NULL) { /* Comparison of left and right. */ - if (format_choose(ft, copy, &left, &right, 1) != 0) { - format_log(ft, "compare %s syntax error: %s", + if (format_choose(es, copy, &left, &right, 1) != 0) { + format_log(es, "compare %s syntax error: %s", cmp->modifier, copy); goto fail; } - format_log(ft, "compare %s left is: %s", cmp->modifier, left); - format_log(ft, "compare %s right is: %s", cmp->modifier, right); + format_log(es, "compare %s left is: %s", cmp->modifier, left); + format_log(es, "compare %s right is: %s", cmp->modifier, right); if (strcmp(cmp->modifier, "||") == 0) { if (format_true(left) || format_true(right)) @@ -1994,80 +4292,89 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, /* Conditional: check first and choose second or third. */ cp = format_skip(copy + 1, ","); if (cp == NULL) { - format_log(ft, "condition syntax error: %s", copy + 1); + format_log(es, "condition syntax error: %s", copy + 1); goto fail; } condition = xstrndup(copy + 1, cp - (copy + 1)); - format_log(ft, "condition is: %s", condition); + format_log(es, "condition is: %s", condition); - found = format_find(ft, condition, modifiers); + found = format_find(ft, condition, modifiers, time_format); if (found == NULL) { /* * If the condition not found, try to expand it. If * the expansion doesn't have any effect, then assume * false. */ - found = format_expand(ft, condition); + found = format_expand1(es, condition); if (strcmp(found, condition) == 0) { free(found); found = xstrdup(""); - format_log(ft, "condition '%s' found: %s", - condition, found); - } else { - format_log(ft, + format_log(es, "condition '%s' not found; assuming false", condition); } - } else - format_log(ft, "condition '%s' found", condition); + } else { + format_log(es, "condition '%s' found: %s", condition, + found); + } - if (format_choose(ft, cp + 1, &left, &right, 0) != 0) { - format_log(ft, "condition '%s' syntax error: %s", + if (format_choose(es, cp + 1, &left, &right, 0) != 0) { + format_log(es, "condition '%s' syntax error: %s", condition, cp + 1); free(found); goto fail; } if (format_true(found)) { - format_log(ft, "condition '%s' is true", condition); - value = format_expand(ft, left); + format_log(es, "condition '%s' is true", condition); + value = format_expand1(es, left); } else { - format_log(ft, "condition '%s' is false", condition); - value = format_expand(ft, right); + format_log(es, "condition '%s' is false", condition); + value = format_expand1(es, right); } free(right); free(left); free(condition); free(found); - } else { - /* Neither: look up directly. */ - value = format_find(ft, copy, modifiers); - if (value == NULL) { - format_log(ft, "format '%s' not found", copy); + } else if (mexp != NULL) { + value = format_replace_expression(mexp, es, copy); + if (value == NULL) value = xstrdup(""); - } else - format_log(ft, "format '%s' found: %s", copy, value); + } else { + if (strstr(copy, "#{") != 0) { + format_log(es, "expanding inner format '%s'", copy); + value = format_expand1(es, copy); + } else { + value = format_find(ft, copy, modifiers, time_format); + if (value == NULL) { + format_log(es, "format '%s' not found", copy); + value = xstrdup(""); + } else { + format_log(es, "format '%s' found: %s", copy, + value); + } + } } done: /* Expand again if required. */ if (modifiers & FORMAT_EXPAND) { - new = format_expand(ft, value); + new = format_expand1(es, value); free(value); value = new; - } - else if (modifiers & FORMAT_EXPANDTIME) { - new = format_expand_time(ft, value); + } else if (modifiers & FORMAT_EXPANDTIME) { + format_copy_state(&next, es, FORMAT_EXPAND_TIME); + new = format_expand1(&next, value); free(value); value = new; } /* Perform substitution if any. */ for (i = 0; i < nsub; i++) { - left = format_expand(ft, sub[i]->argv[0]); - right = format_expand(ft, sub[i]->argv[1]); + left = format_expand1(es, sub[i]->argv[0]); + right = format_expand1(es, sub[i]->argv[1]); new = format_sub(sub[i], value, left, right); - format_log(ft, "substitute '%s' to '%s': %s", left, right, new); + format_log(es, "substitute '%s' to '%s': %s", left, right, new); free(value); value = new; free(right); @@ -2084,7 +4391,7 @@ done: free(value); value = new; } - format_log(ft, "applied length limit %d: %s", limit, value); + format_log(es, "applied length limit %d: %s", limit, value); } else if (limit < 0) { new = format_trim_right(value, -limit); if (marker != NULL && strcmp(new, value) != 0) { @@ -2094,7 +4401,7 @@ done: free(value); value = new; } - format_log(ft, "applied length limit %d: %s", limit, value); + format_log(es, "applied length limit %d: %s", limit, value); } /* Pad the value if needed. */ @@ -2102,12 +4409,26 @@ done: new = utf8_padcstr(value, width); free(value); value = new; - format_log(ft, "applied padding width %d: %s", width, value); + format_log(es, "applied padding width %d: %s", width, value); } else if (width < 0) { new = utf8_rpadcstr(value, -width); free(value); value = new; - format_log(ft, "applied padding width %d: %s", width, value); + format_log(es, "applied padding width %d: %s", width, value); + } + + /* Replace with the length or width if needed. */ + if (modifiers & FORMAT_LENGTH) { + xasprintf(&new, "%zu", strlen(value)); + free(value); + value = new; + format_log(es, "replacing with length: %s", new); + } + if (modifiers & FORMAT_WIDTH) { + xasprintf(&new, "%u", format_width(value)); + free(value); + value = new; + format_log(es, "replacing with width: %s", new); } /* Expand the buffer and copy in the value. */ @@ -2119,7 +4440,7 @@ done: memcpy(*buf + *off, value, valuelen); *off += valuelen; - format_log(ft, "replaced '%s' with '%s'", copy0, value); + format_log(es, "replaced '%s' with '%s'", copy0, value); free(value); free(sub); @@ -2128,7 +4449,7 @@ done: return (0); fail: - format_log(ft, "failed %s", copy0); + format_log(es, "failed %s", copy0); free(sub); format_free_modifiers(list, count); @@ -2138,32 +4459,37 @@ fail: /* Expand keys in a template. */ static char * -format_expand1(struct format_tree *ft, const char *fmt, int time) +format_expand1(struct format_expand_state *es, const char *fmt) { - char *buf, *out, *name; - const char *ptr, *s; - size_t off, len, n, outlen; - int ch, brackets; - struct tm *tm; - char expanded[8192]; + struct format_tree *ft = es->ft; + char *buf, *out, *name; + const char *ptr, *s; + size_t off, len, n, outlen; + int ch, brackets; + char expanded[8192]; if (fmt == NULL || *fmt == '\0') return (xstrdup("")); - if (ft->loop == FORMAT_LOOP_LIMIT) + if (es->loop == FORMAT_LOOP_LIMIT) { + format_log(es, "reached loop limit (%u)", FORMAT_LOOP_LIMIT); return (xstrdup("")); - ft->loop++; + } + es->loop++; - format_log(ft, "expanding format: %s", fmt); + format_log(es, "expanding format: %s", fmt); - if (time) { - tm = localtime(&ft->time); - if (strftime(expanded, sizeof expanded, fmt, tm) == 0) { - format_log(ft, "format is too long"); + if ((es->flags & FORMAT_EXPAND_TIME) && strchr(fmt, '%') != NULL) { + if (es->time == 0) { + es->time = time(NULL); + localtime_r(&es->time, &es->tm); + } + if (strftime(expanded, sizeof expanded, fmt, &es->tm) == 0) { + format_log(es, "format is too long"); return (xstrdup("")); } if (format_logging(ft) && strcmp(expanded, fmt) != 0) - format_log(ft, "after time expanded: %s", expanded); + format_log(es, "after time expanded: %s", expanded); fmt = expanded; } @@ -2197,14 +4523,15 @@ format_expand1(struct format_tree *ft, const char *fmt, int time) n = ptr - fmt; name = xstrndup(fmt, n); - format_log(ft, "found #(): %s", name); + format_log(es, "found #(): %s", name); - if (ft->flags & FORMAT_NOJOBS) { + if ((ft->flags & FORMAT_NOJOBS) || + (es->flags & FORMAT_EXPAND_NOJOBS)) { out = xstrdup(""); - format_log(ft, "#() is disabled"); + format_log(es, "#() is disabled"); } else { - out = format_job_get(ft, name); - format_log(ft, "#() result: %s", out); + out = format_job_get(es, name); + format_log(es, "#() result: %s", out); } free(name); @@ -2226,15 +4553,37 @@ format_expand1(struct format_tree *ft, const char *fmt, int time) break; n = ptr - fmt; - format_log(ft, "found #{}: %.*s", (int)n, fmt); - if (format_replace(ft, fmt, n, &buf, &len, &off) != 0) + format_log(es, "found #{}: %.*s", (int)n, fmt); + if (format_replace(es, fmt, n, &buf, &len, &off) != 0) break; fmt += n + 1; continue; - case '}': case '#': + /* + * If ##[ (with two or more #s), then it is a style and + * can be left for format_draw to handle. + */ + ptr = fmt; + n = 2; + while (*ptr == '#') { + ptr++; + n++; + } + if (*ptr == '[') { + format_log(es, "found #*%zu[", n); + while (len - off < n + 2) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, fmt - 2, n + 1); + off += n + 1; + fmt = ptr + 1; + continue; + } + /* FALLTHROUGH */ + case '}': case ',': - format_log(ft, "found #%c", ch); + format_log(es, "found #%c", ch); while (len - off < 2) { buf = xreallocarray(buf, 2, len); len *= 2; @@ -2257,8 +4606,8 @@ format_expand1(struct format_tree *ft, const char *fmt, int time) continue; } n = strlen(s); - format_log(ft, "found #%c: %s", ch, s); - if (format_replace(ft, s, n, &buf, &len, &off) != 0) + format_log(es, "found #%c: %s", ch, s); + if (format_replace(es, s, n, &buf, &len, &off) != 0) break; continue; } @@ -2267,8 +4616,8 @@ format_expand1(struct format_tree *ft, const char *fmt, int time) } buf[off] = '\0'; - format_log(ft, "result is: %s", buf); - ft->loop--; + format_log(es, "result is: %s", buf); + es->loop--; return (buf); } @@ -2277,14 +4626,24 @@ format_expand1(struct format_tree *ft, const char *fmt, int time) char * format_expand_time(struct format_tree *ft, const char *fmt) { - return (format_expand1(ft, fmt, 1)); + struct format_expand_state es; + + memset(&es, 0, sizeof es); + es.ft = ft; + es.flags = FORMAT_EXPAND_TIME; + return (format_expand1(&es, fmt)); } /* Expand keys in a template. */ char * format_expand(struct format_tree *ft, const char *fmt) { - return (format_expand1(ft, fmt, 0)); + struct format_expand_state es; + + memset(&es, 0, sizeof es); + es.ft = ft; + es.flags = 0; + return (format_expand1(&es, fmt)); } /* Expand a single string. */ @@ -2295,22 +4654,68 @@ format_single(struct cmdq_item *item, const char *fmt, struct client *c, struct format_tree *ft; char *expanded; - if (item != NULL) - ft = format_create(item->client, item, FORMAT_NONE, 0); - else - ft = format_create(NULL, item, FORMAT_NONE, 0); - format_defaults(ft, c, s, wl, wp); - + ft = format_create_defaults(item, c, s, wl, wp); expanded = format_expand(ft, fmt); format_free(ft); return (expanded); } +/* Expand a single string using state. */ +char * +format_single_from_state(struct cmdq_item *item, const char *fmt, + struct client *c, struct cmd_find_state *fs) +{ + return (format_single(item, fmt, c, fs->s, fs->wl, fs->wp)); +} + +/* Expand a single string using target. */ +char * +format_single_from_target(struct cmdq_item *item, const char *fmt) +{ + struct client *tc = cmdq_get_target_client(item); + + return (format_single_from_state(item, fmt, tc, cmdq_get_target(item))); +} + +/* Create and add defaults. */ +struct format_tree * +format_create_defaults(struct cmdq_item *item, struct client *c, + struct session *s, struct winlink *wl, struct window_pane *wp) +{ + struct format_tree *ft; + + if (item != NULL) + ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); + else + ft = format_create(NULL, item, FORMAT_NONE, 0); + format_defaults(ft, c, s, wl, wp); + return (ft); +} + +/* Create and add defaults using state. */ +struct format_tree * +format_create_from_state(struct cmdq_item *item, struct client *c, + struct cmd_find_state *fs) +{ + return (format_create_defaults(item, c, fs->s, fs->wl, fs->wp)); +} + +/* Create and add defaults using target. */ +struct format_tree * +format_create_from_target(struct cmdq_item *item) +{ + struct client *tc = cmdq_get_target_client(item); + + return (format_create_from_state(item, tc, cmdq_get_target(item))); +} + /* Set defaults for any of arguments that are not NULL. */ void format_defaults(struct format_tree *ft, struct client *c, struct session *s, struct winlink *wl, struct window_pane *wp) { + struct paste_buffer *pb; + if (c != NULL && c->name != NULL) log_debug("%s: c=%s", __func__, c->name); else @@ -2320,7 +4725,7 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, else log_debug("%s: s=none", __func__); if (wl != NULL) - log_debug("%s: wl=%u w=@%u", __func__, wl->idx, wl->window->id); + log_debug("%s: wl=%u", __func__, wl->idx); else log_debug("%s: wl=none", __func__); if (wp != NULL) @@ -2331,9 +4736,14 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, if (c != NULL && s != NULL && c->session != s) log_debug("%s: session does not match", __func__); - format_add(ft, "session_format", "%d", s != NULL); - format_add(ft, "window_format", "%d", wl != NULL); - format_add(ft, "pane_format", "%d", wp != NULL); + if (wp != NULL) + ft->type = FORMAT_TYPE_PANE; + else if (wl != NULL) + ft->type = FORMAT_TYPE_WINDOW; + else if (s != NULL) + ft->type = FORMAT_TYPE_SESSION; + else + ft->type = FORMAT_TYPE_UNKNOWN; if (s == NULL && c != NULL) s = c->session; @@ -2350,103 +4760,26 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, format_defaults_winlink(ft, wl); if (wp != NULL) format_defaults_pane(ft, wp); + + pb = paste_get_top(NULL); + if (pb != NULL) + format_defaults_paste_buffer(ft, pb); } /* Set default format keys for a session. */ static void format_defaults_session(struct format_tree *ft, struct session *s) { - struct session_group *sg; - ft->s = s; - - format_add(ft, "session_name", "%s", s->name); - format_add(ft, "session_windows", "%u", winlink_count(&s->windows)); - format_add(ft, "session_id", "$%u", s->id); - - sg = session_group_contains(s); - format_add(ft, "session_grouped", "%d", sg != NULL); - if (sg != NULL) { - format_add(ft, "session_group", "%s", sg->name); - format_add(ft, "session_group_size", "%u", - session_group_count (sg)); - format_add(ft, "session_group_attached", "%u", - session_group_attached_count (sg)); - format_add(ft, "session_group_many_attached", "%u", - session_group_attached_count (sg) > 1); - format_add_cb(ft, "session_group_list", - format_cb_session_group_list); - format_add_cb(ft, "session_group_attached_list", - format_cb_session_group_attached_list); - } - - format_add_tv(ft, "session_created", &s->creation_time); - format_add_tv(ft, "session_last_attached", &s->last_attached_time); - format_add_tv(ft, "session_activity", &s->activity_time); - - format_add(ft, "session_attached", "%u", s->attached); - format_add(ft, "session_many_attached", "%d", s->attached > 1); - format_add_cb(ft, "session_attached_list", - format_cb_session_attached_list); - - format_add_cb(ft, "session_alerts", format_cb_session_alerts); - format_add_cb(ft, "session_stack", format_cb_session_stack); } /* Set default format keys for a client. */ static void format_defaults_client(struct format_tree *ft, struct client *c) { - struct session *s; - const char *name; - struct tty *tty = &c->tty; - if (ft->s == NULL) ft->s = c->session; ft->c = c; - - format_add(ft, "client_name", "%s", c->name); - format_add(ft, "client_pid", "%ld", (long) c->pid); - format_add(ft, "client_height", "%u", tty->sy); - format_add(ft, "client_width", "%u", tty->sx); - format_add(ft, "client_cell_width", "%u", tty->xpixel); - format_add(ft, "client_cell_height", "%u", tty->ypixel); - format_add(ft, "client_tty", "%s", c->ttyname); - format_add(ft, "client_control_mode", "%d", - !!(c->flags & CLIENT_CONTROL)); - - if (tty->term_name != NULL) - format_add(ft, "client_termname", "%s", tty->term_name); - - format_add_tv(ft, "client_created", &c->creation_time); - format_add_tv(ft, "client_activity", &c->activity_time); - - format_add(ft, "client_written", "%zu", c->written); - format_add(ft, "client_discarded", "%zu", c->discarded); - - name = server_client_get_key_table(c); - if (strcmp(c->keytable->name, name) == 0) - format_add(ft, "client_prefix", "%d", 0); - else - format_add(ft, "client_prefix", "%d", 1); - format_add(ft, "client_key_table", "%s", c->keytable->name); - - if (tty->flags & TTY_UTF8) - format_add(ft, "client_utf8", "%d", 1); - else - format_add(ft, "client_utf8", "%d", 0); - - if (c->flags & CLIENT_READONLY) - format_add(ft, "client_readonly", "%d", 1); - else - format_add(ft, "client_readonly", "%d", 0); - - s = c->session; - if (s != NULL) - format_add(ft, "client_session", "%s", s->name); - s = c->last_session; - if (s != NULL && session_alive(s)) - format_add(ft, "client_last_session", "%s", s->name); } /* Set default format keys for a window. */ @@ -2454,216 +4787,133 @@ void format_defaults_window(struct format_tree *ft, struct window *w) { ft->w = w; - - format_add_tv(ft, "window_activity", &w->activity_time); - format_add(ft, "window_id", "@%u", w->id); - format_add(ft, "window_name", "%s", w->name); - format_add(ft, "window_width", "%u", w->sx); - format_add(ft, "window_height", "%u", w->sy); - format_add(ft, "window_cell_width", "%u", w->xpixel); - format_add(ft, "window_cell_height", "%u", w->ypixel); - format_add_cb(ft, "window_layout", format_cb_window_layout); - format_add_cb(ft, "window_visible_layout", - format_cb_window_visible_layout); - format_add(ft, "window_panes", "%u", window_count_panes(w)); - format_add(ft, "window_zoomed_flag", "%d", - !!(w->flags & WINDOW_ZOOMED)); } /* Set default format keys for a winlink. */ static void format_defaults_winlink(struct format_tree *ft, struct winlink *wl) { - struct client *c = ft->c; - struct session *s = wl->session; - struct window *w = wl->window; - int flag; - u_int ox, oy, sx, sy; - if (ft->w == NULL) - ft->w = wl->window; + format_defaults_window(ft, wl->window); ft->wl = wl; - - format_defaults_window(ft, w); - - if (c != NULL) { - flag = tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); - format_add(ft, "window_bigger", "%d", flag); - if (flag) { - format_add(ft, "window_offset_x", "%u", ox); - format_add(ft, "window_offset_y", "%u", oy); - } - } - - format_add(ft, "window_index", "%d", wl->idx); - format_add_cb(ft, "window_stack_index", format_cb_window_stack_index); - format_add(ft, "window_flags", "%s", window_printable_flags(wl)); - format_add(ft, "window_active", "%d", wl == s->curw); - format_add_cb(ft, "window_active_sessions", - format_cb_window_active_sessions); - format_add_cb(ft, "window_active_sessions_list", - format_cb_window_active_sessions_list); - format_add_cb(ft, "window_active_clients", - format_cb_window_active_clients); - format_add_cb(ft, "window_active_clients_list", - format_cb_window_active_clients_list); - - format_add(ft, "window_start_flag", "%d", - !!(wl == RB_MIN(winlinks, &s->windows))); - format_add(ft, "window_end_flag", "%d", - !!(wl == RB_MAX(winlinks, &s->windows))); - - if (server_check_marked() && marked_pane.wl == wl) - format_add(ft, "window_marked_flag", "1"); - else - format_add(ft, "window_marked_flag", "0"); - - format_add(ft, "window_bell_flag", "%d", - !!(wl->flags & WINLINK_BELL)); - format_add(ft, "window_activity_flag", "%d", - !!(wl->flags & WINLINK_ACTIVITY)); - format_add(ft, "window_silence_flag", "%d", - !!(wl->flags & WINLINK_SILENCE)); - format_add(ft, "window_last_flag", "%d", - !!(wl == TAILQ_FIRST(&s->lastw))); - format_add(ft, "window_linked", "%d", session_is_linked(s, wl->window)); - - format_add_cb(ft, "window_linked_sessions_list", - format_cb_window_linked_sessions_list); - format_add(ft, "window_linked_sessions", "%u", - wl->window->references); } /* Set default format keys for a window pane. */ void format_defaults_pane(struct format_tree *ft, struct window_pane *wp) { - struct window *w = wp->window; - struct grid *gd = wp->base.grid; - int status = wp->status; - u_int idx; struct window_mode_entry *wme; if (ft->w == NULL) - ft->w = w; + format_defaults_window(ft, wp->window); ft->wp = wp; - format_add(ft, "history_size", "%u", gd->hsize); - format_add(ft, "history_limit", "%u", gd->hlimit); - format_add_cb(ft, "history_bytes", format_cb_history_bytes); - - if (window_pane_index(wp, &idx) != 0) - fatalx("index not found"); - format_add(ft, "pane_index", "%u", idx); - - format_add(ft, "pane_width", "%u", wp->sx); - format_add(ft, "pane_height", "%u", wp->sy); - format_add(ft, "pane_title", "%s", wp->base.title); - if (wp->base.path != NULL) - format_add(ft, "pane_path", "%s", wp->base.path); - format_add(ft, "pane_id", "%%%u", wp->id); - format_add(ft, "pane_active", "%d", wp == w->active); - format_add(ft, "pane_input_off", "%d", !!(wp->flags & PANE_INPUTOFF)); - format_add(ft, "pane_pipe", "%d", wp->pipe_fd != -1); - - if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status)) - format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status)); - if (~wp->flags & PANE_EMPTY) - format_add(ft, "pane_dead", "%d", wp->fd == -1); - else - format_add(ft, "pane_dead", "0"); - - if (server_check_marked() && marked_pane.wp == wp) - format_add(ft, "pane_marked", "1"); - else - format_add(ft, "pane_marked", "0"); - format_add(ft, "pane_marked_set", "%d", server_check_marked()); - - format_add(ft, "pane_left", "%u", wp->xoff); - format_add(ft, "pane_top", "%u", wp->yoff); - format_add(ft, "pane_right", "%u", wp->xoff + wp->sx - 1); - format_add(ft, "pane_bottom", "%u", wp->yoff + wp->sy - 1); - format_add(ft, "pane_at_left", "%d", wp->xoff == 0); - format_add_cb(ft, "pane_at_top", format_cb_pane_at_top); - format_add(ft, "pane_at_right", "%d", wp->xoff + wp->sx == w->sx); - format_add_cb(ft, "pane_at_bottom", format_cb_pane_at_bottom); - wme = TAILQ_FIRST(&wp->modes); - if (wme != NULL) { - format_add(ft, "pane_mode", "%s", wme->mode->name); - if (wme->mode->formats != NULL) - wme->mode->formats(wme, ft); - } - format_add_cb(ft, "pane_in_mode", format_cb_pane_in_mode); - - format_add(ft, "pane_synchronized", "%d", - !!options_get_number(w->options, "synchronize-panes")); - if (wp->searchstr != NULL) - format_add(ft, "pane_search_string", "%s", wp->searchstr); - - format_add(ft, "pane_tty", "%s", wp->tty); - format_add(ft, "pane_pid", "%ld", (long) wp->pid); - format_add_cb(ft, "pane_start_command", format_cb_start_command); - format_add_cb(ft, "pane_current_command", format_cb_current_command); - format_add_cb(ft, "pane_current_path", format_cb_current_path); - - format_add(ft, "cursor_x", "%u", wp->base.cx); - format_add(ft, "cursor_y", "%u", wp->base.cy); - format_add_cb(ft, "cursor_character", format_cb_cursor_character); - - format_add(ft, "scroll_region_upper", "%u", wp->base.rupper); - format_add(ft, "scroll_region_lower", "%u", wp->base.rlower); - - format_add(ft, "alternate_on", "%d", wp->saved_grid ? 1 : 0); - format_add(ft, "alternate_saved_x", "%u", wp->saved_cx); - format_add(ft, "alternate_saved_y", "%u", wp->saved_cy); - - format_add(ft, "cursor_flag", "%d", - !!(wp->base.mode & MODE_CURSOR)); - format_add(ft, "insert_flag", "%d", - !!(wp->base.mode & MODE_INSERT)); - format_add(ft, "keypad_cursor_flag", "%d", - !!(wp->base.mode & MODE_KCURSOR)); - format_add(ft, "keypad_flag", "%d", - !!(wp->base.mode & MODE_KKEYPAD)); - format_add(ft, "wrap_flag", "%d", - !!(wp->base.mode & MODE_WRAP)); - format_add(ft, "origin_flag", "%d", - !!(wp->base.mode & MODE_ORIGIN)); - - format_add(ft, "mouse_any_flag", "%d", - !!(wp->base.mode & ALL_MOUSE_MODES)); - format_add(ft, "mouse_standard_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_STANDARD)); - format_add(ft, "mouse_button_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_BUTTON)); - format_add(ft, "mouse_all_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_ALL)); - format_add(ft, "mouse_utf8_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_UTF8)); - format_add(ft, "mouse_sgr_flag", "%d", - !!(wp->base.mode & MODE_MOUSE_SGR)); - - format_add_cb(ft, "pane_tabs", format_cb_pane_tabs); + if (wme != NULL && wme->mode->formats != NULL) + wme->mode->formats(wme, ft); } /* Set default format keys for paste buffer. */ void format_defaults_paste_buffer(struct format_tree *ft, struct paste_buffer *pb) { - struct timeval tv; - size_t size; - char *s; - - timerclear(&tv); - tv.tv_sec = paste_buffer_created(pb); - paste_buffer_data(pb, &size); - - format_add(ft, "buffer_size", "%zu", size); - format_add(ft, "buffer_name", "%s", paste_buffer_name(pb)); - format_add_tv(ft, "buffer_created", &tv); - - s = paste_make_sample(pb); - format_add(ft, "buffer_sample", "%s", s); - free(s); + ft->pb = pb; +} + +/* Return word at given coordinates. Caller frees. */ +char * +format_grid_word(struct grid *gd, u_int x, u_int y) +{ + const struct grid_line *gl; + struct grid_cell gc; + const char *ws; + struct utf8_data *ud = NULL; + u_int end; + size_t size = 0; + int found = 0; + char *s = NULL; + + ws = options_get_string(global_s_options, "word-separators"); + + for (;;) { + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + if (utf8_cstrhas(ws, &gc.data) || + (gc.data.size == 1 && *gc.data.data == ' ')) { + found = 1; + break; + } + + if (x == 0) { + if (y == 0) + break; + gl = grid_peek_line(gd, y - 1); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + y--; + x = grid_line_length(gd, y); + if (x == 0) + break; + } + x--; + } + for (;;) { + if (found) { + end = grid_line_length(gd, y); + if (end == 0 || x == end - 1) { + if (y == gd->hsize + gd->sy - 1) + break; + gl = grid_peek_line(gd, y); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + y++; + x = 0; + } else + x++; + } + found = 1; + + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + if (utf8_cstrhas(ws, &gc.data) || + (gc.data.size == 1 && *gc.data.data == ' ')) + break; + + ud = xreallocarray(ud, size + 2, sizeof *ud); + memcpy(&ud[size++], &gc.data, sizeof *ud); + } + if (size != 0) { + ud[size].size = 0; + s = utf8_tocstr(ud); + free(ud); + } + return (s); +} + +/* Return line at given coordinates. Caller frees. */ +char * +format_grid_line(struct grid *gd, u_int y) +{ + struct grid_cell gc; + struct utf8_data *ud = NULL; + u_int x; + size_t size = 0; + char *s = NULL; + + for (x = 0; x < grid_line_length(gd, y); x++) { + grid_get_cell(gd, x, y, &gc); + if (gc.flags & GRID_FLAG_PADDING) + break; + + ud = xreallocarray(ud, size + 2, sizeof *ud); + memcpy(&ud[size++], &gc.data, sizeof *ud); + } + if (size != 0) { + ud[size].size = 0; + s = utf8_tocstr(ud); + free(ud); + } + return (s); } diff --git a/fuzz/input-fuzzer.c b/fuzz/input-fuzzer.c new file mode 100644 index 00000000..049762b8 --- /dev/null +++ b/fuzz/input-fuzzer.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020 Sergey Nizovtsev + * + * 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" + +#define FUZZER_MAXLEN 512 +#define PANE_WIDTH 80 +#define PANE_HEIGHT 25 + +struct event_base *libevent; + +int +LLVMFuzzerTestOneInput(const u_char *data, size_t size) +{ + struct bufferevent *vpty[2]; + struct window *w; + struct window_pane *wp; + int error; + + /* + * Since AFL doesn't support -max_len paramenter we have to + * discard long inputs manually. + */ + if (size > FUZZER_MAXLEN) + return 0; + + w = window_create(PANE_WIDTH, PANE_HEIGHT, 0, 0); + wp = window_add_pane(w, NULL, 0, 0); + bufferevent_pair_new(libevent, BEV_OPT_CLOSE_ON_FREE, vpty); + wp->ictx = input_init(wp, vpty[0], NULL); + window_add_ref(w, __func__); + + wp->fd = open("/dev/null", O_WRONLY); + if (wp->fd == -1) + errx(1, "open(\"/dev/null\") failed"); + wp->event = bufferevent_new(wp->fd, NULL, NULL, NULL, NULL); + + input_parse_buffer(wp, (u_char *)data, size); + while (cmdq_next(NULL) != 0) + ; + error = event_base_loop(libevent, EVLOOP_NONBLOCK); + if (error == -1) + errx(1, "event_base_loop failed"); + + assert(w->references == 1); + window_remove_ref(w, __func__); + + bufferevent_free(vpty[0]); + bufferevent_free(vpty[1]); + + return 0; +} + +int +LLVMFuzzerInitialize(__unused int *argc, __unused char ***argv) +{ + const struct options_table_entry *oe; + + global_environ = environ_create(); + global_options = options_create(NULL); + global_s_options = options_create(NULL); + global_w_options = options_create(NULL); + for (oe = options_table; oe->name != NULL; oe++) { + if (oe->scope & OPTIONS_TABLE_SERVER) + options_default(global_options, oe); + if (oe->scope & OPTIONS_TABLE_SESSION) + options_default(global_s_options, oe); + if (oe->scope & OPTIONS_TABLE_WINDOW) + options_default(global_w_options, oe); + } + libevent = osdep_event_init(); + + options_set_number(global_w_options, "monitor-bell", 0); + options_set_number(global_w_options, "allow-rename", 1); + options_set_number(global_options, "set-clipboard", 2); + socket_path = xstrdup("dummy"); + + return 0; +} diff --git a/fuzz/input-fuzzer.dict b/fuzz/input-fuzzer.dict new file mode 100644 index 00000000..2091b970 --- /dev/null +++ b/fuzz/input-fuzzer.dict @@ -0,0 +1,8 @@ +"\x1b[" +"1000" +"2004" +"1049" +"38;2" +"100;" +"tmux;" +"rgb:00/00/00" diff --git a/fuzz/input-fuzzer.options b/fuzz/input-fuzzer.options new file mode 100644 index 00000000..5d468bc6 --- /dev/null +++ b/fuzz/input-fuzzer.options @@ -0,0 +1,2 @@ +[libfuzzer] +max_len = 512 diff --git a/grid-reader.c b/grid-reader.c new file mode 100644 index 00000000..c14e3d33 --- /dev/null +++ b/grid-reader.c @@ -0,0 +1,429 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 Anindya Mukherjee + * + * 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 "tmux.h" +#include + +/* Initialise virtual cursor. */ +void +grid_reader_start(struct grid_reader *gr, struct grid *gd, u_int cx, u_int cy) +{ + gr->gd = gd; + gr->cx = cx; + gr->cy = cy; +} + +/* Get cursor position from reader. */ +void +grid_reader_get_cursor(struct grid_reader *gr, u_int *cx, u_int *cy) +{ + *cx = gr->cx; + *cy = gr->cy; +} + +/* Get length of line containing the cursor. */ +u_int +grid_reader_line_length(struct grid_reader *gr) +{ + return (grid_line_length(gr->gd, gr->cy)); +} + +/* Move cursor forward one position. */ +void +grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all) +{ + u_int px; + struct grid_cell gc; + + if (all) + px = gr->gd->sx; + else + px = grid_reader_line_length(gr); + + if (wrap && gr->cx >= px && gr->cy < gr->gd->hsize + gr->gd->sy - 1) { + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + } else if (gr->cx < px) { + gr->cx++; + while (gr->cx < px) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx++; + } + } +} + +/* Move cursor back one position. */ +void +grid_reader_cursor_left(struct grid_reader *gr, int wrap) +{ + struct grid_cell gc; + + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } + if (gr->cx == 0 && gr->cy > 0 && + (wrap || + grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) { + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 0); + } else if (gr->cx > 0) + gr->cx--; +} + +/* Move cursor down one line. */ +void +grid_reader_cursor_down(struct grid_reader *gr) +{ + struct grid_cell gc; + + if (gr->cy < gr->gd->hsize + gr->gd->sy - 1) + gr->cy++; + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } +} + +/* Move cursor up one line. */ +void +grid_reader_cursor_up(struct grid_reader *gr) +{ + struct grid_cell gc; + + if (gr->cy > 0) + gr->cy--; + while (gr->cx > 0) { + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + gr->cx--; + } +} + +/* Move cursor to the start of the line. */ +void +grid_reader_cursor_start_of_line(struct grid_reader *gr, int wrap) +{ + if (wrap) { + while (gr->cy > 0 && + grid_get_line(gr->gd, gr->cy - 1)->flags & + GRID_LINE_WRAPPED) + gr->cy--; + } + gr->cx = 0; +} + +/* Move cursor to the end of the line. */ +void +grid_reader_cursor_end_of_line(struct grid_reader *gr, int wrap, int all) +{ + u_int yy; + + if (wrap) { + yy = gr->gd->hsize + gr->gd->sy - 1; + while (gr->cy < yy && grid_get_line(gr->gd, gr->cy)->flags & + GRID_LINE_WRAPPED) + gr->cy++; + } + if (all) + gr->cx = gr->gd->sx; + else + gr->cx = grid_reader_line_length(gr); +} + +/* Handle line wrapping while moving the cursor. */ +static int +grid_reader_handle_wrap(struct grid_reader *gr, u_int *xx, u_int *yy) +{ + /* + * Make sure the cursor lies within the grid reader's bounding area, + * wrapping to the next line as necessary. Return zero if the cursor + * would wrap past the bottom of the grid. + */ + while (gr->cx > *xx) { + if (gr->cy == *yy) + return (0); + grid_reader_cursor_start_of_line(gr, 0); + grid_reader_cursor_down(gr); + + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + *xx = gr->gd->sx - 1; + else + *xx = grid_reader_line_length(gr); + } + return (1); +} + +/* Check if character under cursor is in set. */ +int +grid_reader_in_set(struct grid_reader *gr, const char *set) +{ + struct grid_cell gc; + + grid_get_cell(gr->gd, gr->cx, gr->cy, &gc); + if (gc.flags & GRID_FLAG_PADDING) + return (0); + return (utf8_cstrhas(set, &gc.data)); +} + +/* Move cursor to the start of the next word. */ +void +grid_reader_cursor_next_word(struct grid_reader *gr, const char *separators) +{ + u_int xx, yy; + + /* Do not break up wrapped words. */ + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + xx = gr->gd->sx - 1; + else + xx = grid_reader_line_length(gr); + yy = gr->gd->hsize + gr->gd->sy - 1; + + /* + * When navigating via spaces (for example with next-space) separators + * should be empty. + * + * If we started on a separator that is not whitespace, skip over + * subsequent separators that are not whitespace. Otherwise, if we + * started on a non-whitespace character, skip over subsequent + * characters that are neither whitespace nor separators. Then, skip + * over whitespace (if any) until the next non-whitespace character. + */ + if (!grid_reader_handle_wrap(gr, &xx, &yy)) + return; + if (!grid_reader_in_set(gr, WHITESPACE)) { + if (grid_reader_in_set(gr, separators)) { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, separators) && + !grid_reader_in_set(gr, WHITESPACE)); + } else { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + !(grid_reader_in_set(gr, separators) || + grid_reader_in_set(gr, WHITESPACE))); + } + } + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, WHITESPACE)) + gr->cx++; +} + +/* Move cursor to the end of the next word. */ +void +grid_reader_cursor_next_word_end(struct grid_reader *gr, const char *separators) +{ + u_int xx, yy; + + /* Do not break up wrapped words. */ + if (grid_get_line(gr->gd, gr->cy)->flags & GRID_LINE_WRAPPED) + xx = gr->gd->sx - 1; + else + xx = grid_reader_line_length(gr); + yy = gr->gd->hsize + gr->gd->sy - 1; + + /* + * When navigating via spaces (for example with next-space), separators + * should be empty in both modes. + * + * If we started on a whitespace, move until reaching the first + * non-whitespace character. If that character is a separator, treat + * subsequent separators as a word, and continue moving until the first + * non-separator. Otherwise, continue moving until the first separator + * or whitespace. + */ + + while (grid_reader_handle_wrap(gr, &xx, &yy)) { + if (grid_reader_in_set(gr, WHITESPACE)) + gr->cx++; + else if (grid_reader_in_set(gr, separators)) { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + grid_reader_in_set(gr, separators) && + !grid_reader_in_set(gr, WHITESPACE)); + return; + } else { + do + gr->cx++; + while (grid_reader_handle_wrap(gr, &xx, &yy) && + !(grid_reader_in_set(gr, WHITESPACE) || + grid_reader_in_set(gr, separators))); + return; + } + } +} + +/* Move to the previous place where a word begins. */ +void +grid_reader_cursor_previous_word(struct grid_reader *gr, const char *separators, + int already, int stop_at_eol) +{ + int oldx, oldy, at_eol, word_is_letters; + + /* Move back to the previous word character. */ + if (already || grid_reader_in_set(gr, WHITESPACE)) { + for (;;) { + if (gr->cx > 0) { + gr->cx--; + if (!grid_reader_in_set(gr, WHITESPACE)) { + word_is_letters = + !grid_reader_in_set(gr, separators); + break; + } + } else { + if (gr->cy == 0) + return; + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 0); + + /* Stop if separator at EOL. */ + if (stop_at_eol && gr->cx > 0) { + oldx = gr->cx; + gr->cx--; + at_eol = grid_reader_in_set(gr, + WHITESPACE); + gr->cx = oldx; + if (at_eol) { + word_is_letters = 0; + break; + } + } + } + } + } else + word_is_letters = !grid_reader_in_set(gr, separators); + + /* Move back to the beginning of this word. */ + do { + oldx = gr->cx; + oldy = gr->cy; + if (gr->cx == 0) { + if (gr->cy == 0 || + (~grid_get_line(gr->gd, gr->cy - 1)->flags & + GRID_LINE_WRAPPED)) + break; + grid_reader_cursor_up(gr); + grid_reader_cursor_end_of_line(gr, 0, 1); + } + if (gr->cx > 0) + gr->cx--; + } while (!grid_reader_in_set(gr, WHITESPACE) && + word_is_letters != grid_reader_in_set(gr, separators)); + gr->cx = oldx; + gr->cy = oldy; +} + +/* Jump forward to character. */ +int +grid_reader_cursor_jump(struct grid_reader *gr, const struct utf8_data *jc) +{ + struct grid_cell gc; + u_int px, py, xx, yy; + + px = gr->cx; + yy = gr->gd->hsize + gr->gd->sy - 1; + + for (py = gr->cy; py <= yy; py++) { + xx = grid_line_length(gr->gd, py); + while (px < xx) { + grid_get_cell(gr->gd, px, py, &gc); + if (!(gc.flags & GRID_FLAG_PADDING) && + gc.data.size == jc->size && + memcmp(gc.data.data, jc->data, gc.data.size) == 0) { + gr->cx = px; + gr->cy = py; + return (1); + } + px++; + } + + if (py == yy || + !(grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)) + return (0); + px = 0; + } + return (0); +} + +/* Jump back to character. */ +int +grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc) +{ + struct grid_cell gc; + u_int px, py, xx; + + xx = gr->cx + 1; + + for (py = gr->cy + 1; py > 0; py--) { + for (px = xx; px > 0; px--) { + grid_get_cell(gr->gd, px - 1, py - 1, &gc); + if (!(gc.flags & GRID_FLAG_PADDING) && + gc.data.size == jc->size && + memcmp(gc.data.data, jc->data, gc.data.size) == 0) { + gr->cx = px - 1; + gr->cy = py - 1; + return (1); + } + } + + if (py == 1 || + !(grid_get_line(gr->gd, py - 2)->flags & GRID_LINE_WRAPPED)) + return (0); + xx = grid_line_length(gr->gd, py - 2); + } + return (0); +} + +/* Jump back to the first non-blank character of the line. */ +void +grid_reader_cursor_back_to_indentation(struct grid_reader *gr) +{ + struct grid_cell gc; + u_int px, py, xx, yy, oldx, oldy; + + yy = gr->gd->hsize + gr->gd->sy - 1; + oldx = gr->cx; + oldy = gr->cy; + grid_reader_cursor_start_of_line(gr, 1); + + for (py = gr->cy; py <= yy; py++) { + xx = grid_line_length(gr->gd, py); + for (px = 0; px < xx; px++) { + grid_get_cell(gr->gd, px, py, &gc); + if (gc.data.size != 1 || *gc.data.data != ' ') { + gr->cx = px; + gr->cy = py; + return; + } + } + if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED) + break; + } + gr->cx = oldx; + gr->cy = oldy; +} diff --git a/grid-view.c b/grid-view.c index a4bd5ba2..f230d3c8 100644 --- a/grid-view.c +++ b/grid-view.c @@ -45,6 +45,13 @@ grid_view_set_cell(struct grid *gd, u_int px, u_int py, grid_set_cell(gd, grid_view_x(gd, px), grid_view_y(gd, py), gc); } +/* Set padding. */ +void +grid_view_set_padding(struct grid *gd, u_int px, u_int py) +{ + grid_set_padding(gd, grid_view_x(gd, px), grid_view_y(gd, py)); +} + /* Set cells. */ void grid_view_set_cells(struct grid *gd, u_int px, u_int py, diff --git a/grid.c b/grid.c index b2031045..7744587a 100644 --- a/grid.c +++ b/grid.c @@ -40,16 +40,22 @@ const struct grid_cell grid_default_cell = { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }; +/* + * Padding grid cell data. Padding cells are the only zero width cell that + * appears in the grid - because of this, they are always extended cells. + */ +static const struct grid_cell grid_padding_cell = { + { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 0 +}; + /* Cleared grid cell data. */ -const struct grid_cell grid_cleared_cell = { +static const struct grid_cell grid_cleared_cell = { { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 0 }; static const struct grid_cell_entry grid_cleared_entry = { GRID_FLAG_CLEARED, { .data = { 0, 8, 8, ' ' } } }; -static void grid_empty_line(struct grid *, u_int, u_int); - /* Store cell in entry. */ static void grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, @@ -102,12 +108,13 @@ grid_get_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, } /* Set cell as extended. */ -static struct grid_cell * +static struct grid_extd_entry * grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, const struct grid_cell *gc) { - struct grid_cell *gcp; + struct grid_extd_entry *gee; int flags = (gc->flags & ~GRID_FLAG_CLEARED); + utf8_char uc; if (~gce->flags & GRID_FLAG_EXTENDED) grid_get_extended_cell(gl, gce, flags); @@ -115,10 +122,16 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, fatalx("offset too big"); gl->flags |= GRID_LINE_EXTENDED; - gcp = &gl->extddata[gce->offset]; - memcpy(gcp, gc, sizeof *gcp); - gcp->flags = flags; - return (gcp); + utf8_from_data(&gc->data, &uc); + + gee = &gl->extddata[gce->offset]; + gee->data = uc; + gee->attr = gc->attr; + gee->flags = flags; + gee->fg = gc->fg; + gee->bg = gc->bg; + gee->us = gc->us; + return (gee); } /* Free up unused extended cells. */ @@ -126,9 +139,9 @@ static void grid_compact_line(struct grid_line *gl) { int new_extdsize = 0; - struct grid_cell *new_extddata; + struct grid_extd_entry *new_extddata; struct grid_cell_entry *gce; - struct grid_cell *gc; + struct grid_extd_entry *gee; u_int px, idx; if (gl->extdsize == 0) @@ -152,8 +165,8 @@ grid_compact_line(struct grid_line *gl) for (px = 0; px < gl->cellsize; px++) { gce = &gl->celldata[px]; if (gce->flags & GRID_FLAG_EXTENDED) { - gc = &gl->extddata[gce->offset]; - memcpy(&new_extddata[idx], gc, sizeof *gc); + gee = &gl->extddata[gce->offset]; + memcpy(&new_extddata[idx], gee, sizeof *gee); gce->offset = idx++; } } @@ -183,17 +196,14 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg) { struct grid_line *gl = &gd->linedata[py]; struct grid_cell_entry *gce = &gl->celldata[px]; - struct grid_cell *gc; + struct grid_extd_entry *gee; memcpy(gce, &grid_cleared_entry, sizeof *gce); if (bg != 8) { if (bg & COLOUR_FLAG_RGB) { grid_get_extended_cell(gl, gce, gce->flags); - gl->flags |= GRID_LINE_EXTENDED; - - gc = &gl->extddata[gce->offset]; - memcpy(gc, &grid_cleared_cell, sizeof *gc); - gc->bg = bg; + gee = grid_extended_cell(gl, gce, &grid_cleared_cell); + gee->bg = bg; } else { if (bg & COLOUR_FLAG_256) gce->flags |= GRID_FLAG_BG256; @@ -213,19 +223,28 @@ grid_check_y(struct grid *gd, const char *from, u_int py) return (0); } +/* Check if two styles are (visibly) the same. */ +int +grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) +{ + if (gc1->fg != gc2->fg || gc1->bg != gc2->bg) + return (0); + if (gc1->attr != gc2->attr || gc1->flags != gc2->flags) + return (0); + return (1); +} + /* Compare grid cells. Return 1 if equal, 0 if not. */ int -grid_cells_equal(const struct grid_cell *gca, const struct grid_cell *gcb) +grid_cells_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) { - if (gca->fg != gcb->fg || gca->bg != gcb->bg) + if (!grid_cells_look_equal(gc1, gc2)) return (0); - if (gca->attr != gcb->attr || gca->flags != gcb->flags) + if (gc1->data.width != gc2->data.width) return (0); - if (gca->data.width != gcb->data.width) + if (gc1->data.size != gc2->data.size) return (0); - if (gca->data.size != gcb->data.size) - return (0); - return (memcmp(gca->data.data, gcb->data.data, gca->data.size) == 0); + return (memcmp(gc1->data.data, gc2->data.data, gc1->data.size) == 0); } /* Free one line. */ @@ -258,7 +277,10 @@ grid_create(u_int sx, u_int sy, u_int hlimit) gd->sx = sx; gd->sy = sy; - gd->flags = GRID_HISTORY; + if (hlimit != 0) + gd->flags = GRID_HISTORY; + else + gd->flags = 0; gd->hscrolled = 0; gd->hsize = 0; @@ -348,6 +370,19 @@ grid_collect_history(struct grid *gd) gd->hscrolled = gd->hsize; } +/* Remove lines from the bottom of the history. */ +void +grid_remove_history(struct grid *gd, u_int ny) +{ + u_int yy; + + if (ny > gd->hsize) + return; + for (yy = 0; yy < ny; yy++) + grid_free_line(gd, gd->hsize + gd->sy - 1 - yy); + gd->hsize -= ny; +} + /* * Scroll the entire visible screen, moving one line into the history. Just * allocate a new line at the bottom and move the history size indicator. @@ -428,7 +463,7 @@ grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg) sx = gd->sx / 4; else if (sx < gd->sx / 2) sx = gd->sx / 2; - else + else if (gd->sx > sx) sx = gd->sx; gl->celldata = xreallocarray(gl->celldata, sx, sizeof *gl->celldata); @@ -438,7 +473,7 @@ grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg) } /* Empty a line and set background colour if needed. */ -static void +void grid_empty_line(struct grid *gd, u_int py, u_int bg) { memset(&gd->linedata[py], 0, sizeof gd->linedata[py]); @@ -460,12 +495,20 @@ static void grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) { struct grid_cell_entry *gce = &gl->celldata[px]; + struct grid_extd_entry *gee; if (gce->flags & GRID_FLAG_EXTENDED) { if (gce->offset >= gl->extdsize) memcpy(gc, &grid_default_cell, sizeof *gc); - else - memcpy(gc, &gl->extddata[gce->offset], sizeof *gc); + else { + gee = &gl->extddata[gce->offset]; + gc->flags = gee->flags; + gc->attr = gee->attr; + gc->fg = gee->fg; + gc->bg = gee->bg; + gc->us = gee->us; + utf8_to_data(gee->data, &gc->data); + } return; } @@ -492,7 +535,7 @@ grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) grid_get_cell1(&gd->linedata[py], px, gc); } -/* Set cell at relative position. */ +/* Set cell at position. */ void grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) { @@ -515,14 +558,21 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) grid_store_cell(gce, gc, gc->data.data[0]); } -/* Set cells at relative position. */ +/* Set padding at position. */ +void +grid_set_padding(struct grid *gd, u_int px, u_int py) +{ + grid_set_cell(gd, px, py, &grid_padding_cell); +} + +/* Set cells at position. */ void grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, const char *s, size_t slen) { struct grid_line *gl; struct grid_cell_entry *gce; - struct grid_cell *gcp; + struct grid_extd_entry *gee; u_int i; if (grid_check_y(gd, __func__, py) != 0) @@ -537,8 +587,8 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, for (i = 0; i < slen; i++) { gce = &gl->celldata[px + i]; if (grid_need_extended_cell(gce, gc)) { - gcp = grid_extended_cell(gl, gce, gc); - utf8_set(&gcp->data, s[i]); + gee = grid_extended_cell(gl, gce, gc); + gee->data = utf8_build_one(s[i]); } else grid_store_cell(gce, gc, s[i]); } @@ -602,6 +652,8 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg) grid_free_line(gd, yy); grid_empty_line(gd, yy, bg); } + if (py != 0) + gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED; } /* Move a group of lines. */ @@ -628,6 +680,8 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg) continue; grid_free_line(gd, yy); } + if (dy != 0) + gd->linedata[dy - 1].flags &= ~GRID_LINE_WRAPPED; memmove(&gd->linedata[dy], &gd->linedata[py], ny * (sizeof *gd->linedata)); @@ -640,6 +694,8 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg) if (yy < dy || yy >= dy + ny) grid_empty_line(gd, yy, bg); } + if (py != 0 && (py < dy || py >= dy + ny)) + gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED; } /* Move a group of cells. */ @@ -755,15 +811,15 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values) case 8: values[n++] = 49; break; - case 100: - case 101: - case 102: - case 103: - case 104: - case 105: - case 106: - case 107: - values[n++] = gc->bg - 10; + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: + values[n++] = gc->bg + 10; break; } } @@ -989,14 +1045,14 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, srcl->cellsize * sizeof *dstl->celldata); } else dstl->celldata = NULL; - if (srcl->extdsize != 0) { dstl->extdsize = srcl->extdsize; dstl->extddata = xreallocarray(NULL, dstl->extdsize, sizeof *dstl->extddata); memcpy(dstl->extddata, srcl->extddata, dstl->extdsize * sizeof *dstl->extddata); - } + } else + dstl->extddata = NULL; sy++; dy++; @@ -1220,7 +1276,7 @@ grid_reflow(struct grid *gd, u_int sx) struct grid *target; struct grid_line *gl; struct grid_cell gc; - u_int yy, width, i, at, first; + u_int yy, width, i, at; /* * Create a destination grid. This is just used as a container for the @@ -1237,13 +1293,12 @@ grid_reflow(struct grid *gd, u_int sx) continue; /* - * Work out the width of this line. first is the width of the - * first character, at is the point at which the available - * width is hit, and width is the full line width. + * Work out the width of this line. at is the point at which + * the available width is hit, and width is the full line + * width. */ - first = at = width = 0; + at = width = 0; if (~gl->flags & GRID_LINE_EXTENDED) { - first = 1; width = gl->cellused; if (width > sx) at = sx; @@ -1252,8 +1307,6 @@ grid_reflow(struct grid *gd, u_int sx) } else { for (i = 0; i < gl->cellused; i++) { grid_get_cell1(gl, i, &gc); - if (i == 0) - first = gc.data.width; if (at == 0 && width + gc.data.width > sx) at = i; width += gc.data.width; @@ -1261,10 +1314,10 @@ grid_reflow(struct grid *gd, u_int sx) } /* - * If the line is exactly right or the first character is wider - * than the targe width, just move it across unchanged. + * If the line is exactly right, just move it across + * unchanged. */ - if (width == sx || first > sx) { + if (width == sx) { grid_reflow_move(target, gl); continue; } @@ -1327,17 +1380,13 @@ grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy) void grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy) { - u_int yy, ax = 0, ay = 0; + u_int yy, ay = 0; for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) { if (ay == wy) break; - if (gd->linedata[yy].flags & GRID_LINE_WRAPPED) - ax += gd->linedata[yy].cellused; - else { - ax = 0; + if (~gd->linedata[yy].flags & GRID_LINE_WRAPPED) ay++; - } } /* @@ -1372,7 +1421,9 @@ grid_line_length(struct grid *gd, u_int py) px = gd->sx; while (px > 0) { grid_get_cell(gd, px - 1, py, &gc); - if (gc.data.size != 1 || *gc.data.data != ' ') + if ((gc.flags & GRID_FLAG_PADDING) || + gc.data.size != 1 || + *gc.data.data != ' ') break; px--; } diff --git a/input-keys.c b/input-keys.c index 69c5199e..b4770808 100644 --- a/input-keys.c +++ b/input-keys.c @@ -32,112 +32,317 @@ static void input_key_mouse(struct window_pane *, struct mouse_event *); -struct input_key_ent { - key_code key; - const char *data; +/* Entry in the key tree. */ +struct input_key_entry { + key_code key; + const char *data; - int flags; -#define INPUTKEY_KEYPAD 0x1 /* keypad key */ -#define INPUTKEY_CURSOR 0x2 /* cursor key */ + RB_ENTRY(input_key_entry) entry; }; +RB_HEAD(input_key_tree, input_key_entry); -static const struct input_key_ent input_keys[] = { +/* Tree of input keys. */ +static int input_key_cmp(struct input_key_entry *, + struct input_key_entry *); +RB_GENERATE_STATIC(input_key_tree, input_key_entry, entry, input_key_cmp); +struct input_key_tree input_key_tree = RB_INITIALIZER(&input_key_tree); + +/* List of default keys, the tree is built from this. */ +static struct input_key_entry input_key_defaults[] = { /* Paste keys. */ - { KEYC_PASTE_START, "\033[200~", 0 }, - { KEYC_PASTE_END, "\033[201~", 0 }, + { .key = KEYC_PASTE_START, + .data = "\033[200~" + }, + { .key = KEYC_PASTE_END, + .data = "\033[201~" + }, /* Function keys. */ - { KEYC_F1, "\033OP", 0 }, - { KEYC_F2, "\033OQ", 0 }, - { KEYC_F3, "\033OR", 0 }, - { KEYC_F4, "\033OS", 0 }, - { KEYC_F5, "\033[15~", 0 }, - { KEYC_F6, "\033[17~", 0 }, - { KEYC_F7, "\033[18~", 0 }, - { KEYC_F8, "\033[19~", 0 }, - { KEYC_F9, "\033[20~", 0 }, - { KEYC_F10, "\033[21~", 0 }, - { KEYC_F11, "\033[23~", 0 }, - { KEYC_F12, "\033[24~", 0 }, - { KEYC_F1|KEYC_SHIFT, "\033[25~", 0 }, - { KEYC_F2|KEYC_SHIFT, "\033[26~", 0 }, - { KEYC_F3|KEYC_SHIFT, "\033[28~", 0 }, - { KEYC_F4|KEYC_SHIFT, "\033[29~", 0 }, - { KEYC_F5|KEYC_SHIFT, "\033[31~", 0 }, - { KEYC_F6|KEYC_SHIFT, "\033[32~", 0 }, - { KEYC_F7|KEYC_SHIFT, "\033[33~", 0 }, - { KEYC_F8|KEYC_SHIFT, "\033[34~", 0 }, - { KEYC_IC, "\033[2~", 0 }, - { KEYC_DC, "\033[3~", 0 }, - { KEYC_HOME, "\033[1~", 0 }, - { KEYC_END, "\033[4~", 0 }, - { KEYC_NPAGE, "\033[6~", 0 }, - { KEYC_PPAGE, "\033[5~", 0 }, - { KEYC_BTAB, "\033[Z", 0 }, + { .key = KEYC_F1, + .data = "\033OP" + }, + { .key = KEYC_F2, + .data = "\033OQ" + }, + { .key = KEYC_F3, + .data = "\033OR" + }, + { .key = KEYC_F4, + .data = "\033OS" + }, + { .key = KEYC_F5, + .data = "\033[15~" + }, + { .key = KEYC_F6, + .data = "\033[17~" + }, + { .key = KEYC_F7, + .data = "\033[18~" + }, + { .key = KEYC_F8, + .data = "\033[19~" + }, + { .key = KEYC_F9, + .data = "\033[20~" + }, + { .key = KEYC_F10, + .data = "\033[21~" + }, + { .key = KEYC_F11, + .data = "\033[23~" + }, + { .key = KEYC_F12, + .data = "\033[24~" + }, + { .key = KEYC_IC, + .data = "\033[2~" + }, + { .key = KEYC_DC, + .data = "\033[3~" + }, + { .key = KEYC_HOME, + .data = "\033[1~" + }, + { .key = KEYC_END, + .data = "\033[4~" + }, + { .key = KEYC_NPAGE, + .data = "\033[6~" + }, + { .key = KEYC_PPAGE, + .data = "\033[5~" + }, + { .key = KEYC_BTAB, + .data = "\033[Z" + }, - /* - * Arrow keys. Cursor versions must come first. The codes are toggled - * between CSI and SS3 versions when ctrl is pressed. - */ - { KEYC_UP|KEYC_CTRL, "\033[A", INPUTKEY_CURSOR }, - { KEYC_DOWN|KEYC_CTRL, "\033[B", INPUTKEY_CURSOR }, - { KEYC_RIGHT|KEYC_CTRL, "\033[C", INPUTKEY_CURSOR }, - { KEYC_LEFT|KEYC_CTRL, "\033[D", INPUTKEY_CURSOR }, + /* Arrow keys. */ + { .key = KEYC_UP|KEYC_CURSOR, + .data = "\033OA" + }, + { .key = KEYC_DOWN|KEYC_CURSOR, + .data = "\033OB" + }, + { .key = KEYC_RIGHT|KEYC_CURSOR, + .data = "\033OC" + }, + { .key = KEYC_LEFT|KEYC_CURSOR, + .data = "\033OD" + }, + { .key = KEYC_UP, + .data = "\033[A" + }, + { .key = KEYC_DOWN, + .data = "\033[B" + }, + { .key = KEYC_RIGHT, + .data = "\033[C" + }, + { .key = KEYC_LEFT, + .data = "\033[D" + }, - { KEYC_UP, "\033OA", INPUTKEY_CURSOR }, - { KEYC_DOWN, "\033OB", INPUTKEY_CURSOR }, - { KEYC_RIGHT, "\033OC", INPUTKEY_CURSOR }, - { KEYC_LEFT, "\033OD", INPUTKEY_CURSOR }, + /* Keypad keys. */ + { .key = KEYC_KP_SLASH|KEYC_KEYPAD, + .data = "\033Oo" + }, + { .key = KEYC_KP_STAR|KEYC_KEYPAD, + .data = "\033Oj" + }, + { .key = KEYC_KP_MINUS|KEYC_KEYPAD, + .data = "\033Om" + }, + { .key = KEYC_KP_SEVEN|KEYC_KEYPAD, + .data = "\033Ow" + }, + { .key = KEYC_KP_EIGHT|KEYC_KEYPAD, + .data = "\033Ox" + }, + { .key = KEYC_KP_NINE|KEYC_KEYPAD, + .data = "\033Oy" + }, + { .key = KEYC_KP_PLUS|KEYC_KEYPAD, + .data = "\033Ok" + }, + { .key = KEYC_KP_FOUR|KEYC_KEYPAD, + .data = "\033Ot" + }, + { .key = KEYC_KP_FIVE|KEYC_KEYPAD, + .data = "\033Ou" + }, + { .key = KEYC_KP_SIX|KEYC_KEYPAD, + .data = "\033Ov" + }, + { .key = KEYC_KP_ONE|KEYC_KEYPAD, + .data = "\033Oq" + }, + { .key = KEYC_KP_TWO|KEYC_KEYPAD, + .data = "\033Or" + }, + { .key = KEYC_KP_THREE|KEYC_KEYPAD, + .data = "\033Os" + }, + { .key = KEYC_KP_ENTER|KEYC_KEYPAD, + .data = "\033OM" + }, + { .key = KEYC_KP_ZERO|KEYC_KEYPAD, + .data = "\033Op" + }, + { .key = KEYC_KP_PERIOD|KEYC_KEYPAD, + .data = "\033On" + }, + { .key = KEYC_KP_SLASH, + .data = "/" + }, + { .key = KEYC_KP_STAR, + .data = "*" + }, + { .key = KEYC_KP_MINUS, + .data = "-" + }, + { .key = KEYC_KP_SEVEN, + .data = "7" + }, + { .key = KEYC_KP_EIGHT, + .data = "8" + }, + { .key = KEYC_KP_NINE, + .data = "9" + }, + { .key = KEYC_KP_PLUS, + .data = "+" + }, + { .key = KEYC_KP_FOUR, + .data = "4" + }, + { .key = KEYC_KP_FIVE, + .data = "5" + }, + { .key = KEYC_KP_SIX, + .data = "6" + }, + { .key = KEYC_KP_ONE, + .data = "1" + }, + { .key = KEYC_KP_TWO, + .data = "2" + }, + { .key = KEYC_KP_THREE, + .data = "3" + }, + { .key = KEYC_KP_ENTER, + .data = "\n" + }, + { .key = KEYC_KP_ZERO, + .data = "0" + }, + { .key = KEYC_KP_PERIOD, + .data = "." + }, - { KEYC_UP|KEYC_CTRL, "\033OA", 0 }, - { KEYC_DOWN|KEYC_CTRL, "\033OB", 0 }, - { KEYC_RIGHT|KEYC_CTRL, "\033OC", 0 }, - { KEYC_LEFT|KEYC_CTRL, "\033OD", 0 }, - - { KEYC_UP, "\033[A", 0 }, - { KEYC_DOWN, "\033[B", 0 }, - { KEYC_RIGHT, "\033[C", 0 }, - { KEYC_LEFT, "\033[D", 0 }, - - /* Keypad keys. Keypad versions must come first. */ - { KEYC_KP_SLASH, "\033Oo", INPUTKEY_KEYPAD }, - { KEYC_KP_STAR, "\033Oj", INPUTKEY_KEYPAD }, - { KEYC_KP_MINUS, "\033Om", INPUTKEY_KEYPAD }, - { KEYC_KP_SEVEN, "\033Ow", INPUTKEY_KEYPAD }, - { KEYC_KP_EIGHT, "\033Ox", INPUTKEY_KEYPAD }, - { KEYC_KP_NINE, "\033Oy", INPUTKEY_KEYPAD }, - { KEYC_KP_PLUS, "\033Ok", INPUTKEY_KEYPAD }, - { KEYC_KP_FOUR, "\033Ot", INPUTKEY_KEYPAD }, - { KEYC_KP_FIVE, "\033Ou", INPUTKEY_KEYPAD }, - { KEYC_KP_SIX, "\033Ov", INPUTKEY_KEYPAD }, - { KEYC_KP_ONE, "\033Oq", INPUTKEY_KEYPAD }, - { KEYC_KP_TWO, "\033Or", INPUTKEY_KEYPAD }, - { KEYC_KP_THREE, "\033Os", INPUTKEY_KEYPAD }, - { KEYC_KP_ENTER, "\033OM", INPUTKEY_KEYPAD }, - { KEYC_KP_ZERO, "\033Op", INPUTKEY_KEYPAD }, - { KEYC_KP_PERIOD, "\033On", INPUTKEY_KEYPAD }, - - { KEYC_KP_SLASH, "/", 0 }, - { KEYC_KP_STAR, "*", 0 }, - { KEYC_KP_MINUS, "-", 0 }, - { KEYC_KP_SEVEN, "7", 0 }, - { KEYC_KP_EIGHT, "8", 0 }, - { KEYC_KP_NINE, "9", 0 }, - { KEYC_KP_PLUS, "+", 0 }, - { KEYC_KP_FOUR, "4", 0 }, - { KEYC_KP_FIVE, "5", 0 }, - { KEYC_KP_SIX, "6", 0 }, - { KEYC_KP_ONE, "1", 0 }, - { KEYC_KP_TWO, "2", 0 }, - { KEYC_KP_THREE, "3", 0 }, - { KEYC_KP_ENTER, "\n", 0 }, - { KEYC_KP_ZERO, "0", 0 }, - { KEYC_KP_PERIOD, ".", 0 }, + /* Keys with an embedded modifier. */ + { .key = KEYC_F1|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_P" + }, + { .key = KEYC_F2|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_Q" + }, + { .key = KEYC_F3|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_R" + }, + { .key = KEYC_F4|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_S" + }, + { .key = KEYC_F5|KEYC_BUILD_MODIFIERS, + .data = "\033[15;_~" + }, + { .key = KEYC_F6|KEYC_BUILD_MODIFIERS, + .data = "\033[17;_~" + }, + { .key = KEYC_F7|KEYC_BUILD_MODIFIERS, + .data = "\033[18;_~" + }, + { .key = KEYC_F8|KEYC_BUILD_MODIFIERS, + .data = "\033[19;_~" + }, + { .key = KEYC_F9|KEYC_BUILD_MODIFIERS, + .data = "\033[20;_~" + }, + { .key = KEYC_F10|KEYC_BUILD_MODIFIERS, + .data = "\033[21;_~" + }, + { .key = KEYC_F11|KEYC_BUILD_MODIFIERS, + .data = "\033[23;_~" + }, + { .key = KEYC_F12|KEYC_BUILD_MODIFIERS, + .data = "\033[24;_~" + }, + { .key = KEYC_UP|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_A" + }, + { .key = KEYC_DOWN|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_B" + }, + { .key = KEYC_RIGHT|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_C" + }, + { .key = KEYC_LEFT|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_D" + }, + { .key = KEYC_HOME|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_H" + }, + { .key = KEYC_END|KEYC_BUILD_MODIFIERS, + .data = "\033[1;_F" + }, + { .key = KEYC_PPAGE|KEYC_BUILD_MODIFIERS, + .data = "\033[5;_~" + }, + { .key = KEYC_NPAGE|KEYC_BUILD_MODIFIERS, + .data = "\033[6;_~" + }, + { .key = KEYC_IC|KEYC_BUILD_MODIFIERS, + .data = "\033[2;_~" + }, + { .key = KEYC_DC|KEYC_BUILD_MODIFIERS, + .data = "\033[3;_~" + } }; +static const key_code input_key_modifiers[] = { + 0, + 0, + KEYC_SHIFT, + KEYC_META|KEYC_IMPLIED_META, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META, + KEYC_CTRL, + KEYC_SHIFT|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL +}; + +/* Input key comparison function. */ +static int +input_key_cmp(struct input_key_entry *ike1, struct input_key_entry *ike2) +{ + if (ike1->key < ike2->key) + return (-1); + if (ike1->key > ike2->key) + return (1); + return (0); +} + +/* Look for key in tree. */ +static struct input_key_entry * +input_key_get(key_code key) +{ + struct input_key_entry entry = { .key = key }; + + return (RB_FIND(input_key_tree, &input_key_tree, &entry)); +} /* Split a character into two UTF-8 bytes. */ static size_t -input_split2(u_int c, u_char *dst) +input_key_split2(u_int c, u_char *dst) { if (c > 0x7f) { dst[0] = (c >> 6) | 0xc0; @@ -148,31 +353,82 @@ input_split2(u_int c, u_char *dst) return (1); } -/* Translate a key code into an output key sequence. */ -int -input_key(struct window_pane *wp, key_code key, struct mouse_event *m) +/* Build input key tree. */ +void +input_key_build(void) { - const struct input_key_ent *ike; - u_int i; - size_t dlen; - char *out; - key_code justkey, newkey; - struct utf8_data ud; + struct input_key_entry *ike, *new; + u_int i, j; + char *data; + key_code key; - log_debug("writing key 0x%llx (%s) to %%%u", key, - key_string_lookup_key(key), wp->id); + for (i = 0; i < nitems(input_key_defaults); i++) { + ike = &input_key_defaults[i]; + if (~ike->key & KEYC_BUILD_MODIFIERS) { + RB_INSERT(input_key_tree, &input_key_tree, ike); + continue; + } + + for (j = 2; j < nitems(input_key_modifiers); j++) { + key = (ike->key & ~KEYC_BUILD_MODIFIERS); + data = xstrdup(ike->data); + data[strcspn(data, "_")] = '0' + j; + + new = xcalloc(1, sizeof *new); + new->key = key|input_key_modifiers[j]; + new->data = data; + RB_INSERT(input_key_tree, &input_key_tree, new); + } + } + + RB_FOREACH(ike, input_key_tree, &input_key_tree) { + log_debug("%s: 0x%llx (%s) is %s", __func__, ike->key, + key_string_lookup_key(ike->key, 1), ike->data); + } +} + +/* Translate a key code into an output key sequence for a pane. */ +int +input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m) +{ + if (log_get_level() != 0) { + log_debug("writing key 0x%llx (%s) to %%%u", key, + key_string_lookup_key(key, 1), wp->id); + } - /* If this is a mouse key, pass off to mouse function. */ if (KEYC_IS_MOUSE(key)) { if (m != NULL && m->wp != -1 && (u_int)m->wp == wp->id) input_key_mouse(wp, m); return (0); } + return (input_key(wp->screen, wp->event, key)); +} + +static void +input_key_write(const char *from, struct bufferevent *bev, const char *data, + size_t size) +{ + log_debug("%s: %.*s", from, (int)size, data); + bufferevent_write(bev, data, size); +} + +/* Translate a key code into an output key sequence. */ +int +input_key(struct screen *s, struct bufferevent *bev, key_code key) +{ + struct input_key_entry *ike; + key_code justkey, newkey, outkey; + struct utf8_data ud; + char tmp[64], modifier; + + /* Mouse keys need a pane. */ + if (KEYC_IS_MOUSE(key)) + return (0); /* Literal keys go as themselves (can't be more than eight bits). */ if (key & KEYC_LITERAL) { ud.data[0] = (u_char)key; - bufferevent_write(wp->event, &ud.data[0], 1); + input_key_write(__func__, bev, &ud.data[0], 1); return (0); } @@ -181,94 +437,137 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) newkey = options_get_number(global_options, "backspace"); if (newkey >= 0x7f) newkey = '\177'; - key = newkey|(key & KEYC_MASK_MOD); + key = newkey|(key & (KEYC_MASK_MODIFIERS|KEYC_MASK_FLAGS)); } /* * If this is a normal 7-bit key, just send it, with a leading escape * if necessary. If it is a UTF-8 key, split it and send it. */ - justkey = (key & ~(KEYC_XTERM|KEYC_ESCAPE)); + justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META)); if (justkey <= 0x7f) { - if (key & KEYC_ESCAPE) - bufferevent_write(wp->event, "\033", 1); + if (key & KEYC_META) + input_key_write(__func__, bev, "\033", 1); ud.data[0] = justkey; - bufferevent_write(wp->event, &ud.data[0], 1); + input_key_write(__func__, bev, &ud.data[0], 1); return (0); } - if (justkey > 0x7f && justkey < KEYC_BASE) { - if (utf8_split(justkey, &ud) != UTF8_DONE) - return (-1); - if (key & KEYC_ESCAPE) - bufferevent_write(wp->event, "\033", 1); - bufferevent_write(wp->event, ud.data, ud.size); + if (KEYC_IS_UNICODE(justkey)) { + if (key & KEYC_META) + input_key_write(__func__, bev, "\033", 1); + utf8_to_data(justkey, &ud); + input_key_write(__func__, bev, ud.data, ud.size); return (0); } /* - * Then try to look this up as an xterm key, if the flag to output them - * is set. + * Look up in the tree. If not in application keypad or cursor mode, + * remove the flags from the key. */ - if (options_get_number(wp->window->options, "xterm-keys")) { - if ((out = xterm_keys_lookup(key)) != NULL) { - bufferevent_write(wp->event, out, strlen(out)); - free(out); - return (0); + if (~s->mode & MODE_KKEYPAD) + key &= ~KEYC_KEYPAD; + if (~s->mode & MODE_KCURSOR) + key &= ~KEYC_CURSOR; + ike = input_key_get(key); + if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) + ike = input_key_get(key & ~KEYC_META); + if (ike == NULL && (key & KEYC_CURSOR)) + ike = input_key_get(key & ~KEYC_CURSOR); + if (ike == NULL && (key & KEYC_KEYPAD)) + ike = input_key_get(key & ~KEYC_KEYPAD); + if (ike != NULL) { + log_debug("found key 0x%llx: \"%s\"", key, ike->data); + if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) + input_key_write(__func__, bev, "\033", 1); + input_key_write(__func__, bev, ike->data, strlen(ike->data)); + return (0); + } + + /* No builtin key sequence; construct an extended key sequence. */ + if (~s->mode & MODE_KEXTENDED) { + if ((key & KEYC_MASK_MODIFIERS) != KEYC_CTRL) + goto missing; + justkey = (key & KEYC_MASK_KEY); + switch (justkey) { + case ' ': + case '2': + key = 0|(key & ~KEYC_MASK_KEY); + break; + case '|': + key = 28|(key & ~KEYC_MASK_KEY); + break; + case '6': + key = 30|(key & ~KEYC_MASK_KEY); + break; + case '-': + case '/': + key = 31|(key & ~KEYC_MASK_KEY); + break; + case '?': + key = 127|(key & ~KEYC_MASK_KEY); + break; + default: + if (justkey >= 'A' && justkey <= '_') + key = (justkey - 'A')|(key & ~KEYC_MASK_KEY); + else if (justkey >= 'a' && justkey <= '~') + key = (justkey - 96)|(key & ~KEYC_MASK_KEY); + else + return (0); + break; } + return (input_key(s, bev, key & ~KEYC_CTRL)); } - key &= ~KEYC_XTERM; - - /* Otherwise look the key up in the table. */ - for (i = 0; i < nitems(input_keys); i++) { - ike = &input_keys[i]; - - if ((ike->flags & INPUTKEY_KEYPAD) && - !(wp->screen->mode & MODE_KKEYPAD)) - continue; - if ((ike->flags & INPUTKEY_CURSOR) && - !(wp->screen->mode & MODE_KCURSOR)) - continue; - - if ((key & KEYC_ESCAPE) && (ike->key | KEYC_ESCAPE) == key) - break; - if (ike->key == key) - break; + outkey = (key & KEYC_MASK_KEY); + switch (key & KEYC_MASK_MODIFIERS) { + case KEYC_SHIFT: + modifier = '2'; + break; + case KEYC_META: + modifier = '3'; + break; + case KEYC_SHIFT|KEYC_META: + modifier = '4'; + break; + case KEYC_CTRL: + modifier = '5'; + break; + case KEYC_SHIFT|KEYC_CTRL: + modifier = '6'; + break; + case KEYC_META|KEYC_CTRL: + modifier = '7'; + break; + case KEYC_SHIFT|KEYC_META|KEYC_CTRL: + modifier = '8'; + break; + default: + goto missing; } - if (i == nitems(input_keys)) { - log_debug("key 0x%llx missing", key); - return (-1); - } - dlen = strlen(ike->data); - log_debug("found key 0x%llx: \"%s\"", key, ike->data); - - /* Prefix a \033 for escape. */ - if (key & KEYC_ESCAPE) - bufferevent_write(wp->event, "\033", 1); - bufferevent_write(wp->event, ike->data, dlen); + xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier); + input_key_write(__func__, bev, tmp, strlen(tmp)); return (0); + +missing: + log_debug("key 0x%llx missing", key); + return (-1); } -/* Translate mouse and output. */ -static void -input_key_mouse(struct window_pane *wp, struct mouse_event *m) +/* Get mouse event string. */ +int +input_key_get_mouse(struct screen *s, struct mouse_event *m, u_int x, u_int y, + const char **rbuf, size_t *rlen) { - struct screen *s = wp->screen; - int mode = s->mode; - char buf[40]; + static char buf[40]; size_t len; - u_int x, y; - if ((mode & ALL_MOUSE_MODES) == 0) - return; - if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) - return; - if (!window_pane_visible(wp)) - return; + *rbuf = NULL; + *rlen = 0; /* If this pane is not in button or all mode, discard motion events. */ - if (MOUSE_DRAG(m->b) && - (mode & (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL)) == 0) - return; + if (MOUSE_DRAG(m->b) && (s->mode & MOTION_MOUSE_MODES) == 0) + return (0); + if ((s->mode & ALL_MOUSE_MODES) == 0) + return (0); /* * If this event is a release event and not in all mode, discard it. @@ -279,14 +578,14 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m) if (m->sgr_type != ' ') { if (MOUSE_DRAG(m->sgr_b) && MOUSE_BUTTONS(m->sgr_b) == 3 && - (~mode & MODE_MOUSE_ALL)) - return; + (~s->mode & MODE_MOUSE_ALL)) + return (0); } else { if (MOUSE_DRAG(m->b) && MOUSE_BUTTONS(m->b) == 3 && MOUSE_BUTTONS(m->lb) == 3 && - (~mode & MODE_MOUSE_ALL)) - return; + (~s->mode & MODE_MOUSE_ALL)) + return (0); } /* @@ -303,19 +602,43 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m) m->sgr_b, x + 1, y + 1, m->sgr_type); } else if (s->mode & MODE_MOUSE_UTF8) { if (m->b > 0x7ff - 32 || x > 0x7ff - 33 || y > 0x7ff - 33) - return; + return (0); len = xsnprintf(buf, sizeof buf, "\033[M"); - len += input_split2(m->b + 32, &buf[len]); - len += input_split2(x + 33, &buf[len]); - len += input_split2(y + 33, &buf[len]); + len += input_key_split2(m->b + 32, &buf[len]); + len += input_key_split2(x + 33, &buf[len]); + len += input_key_split2(y + 33, &buf[len]); } else { if (m->b > 223) - return; + return (0); len = xsnprintf(buf, sizeof buf, "\033[M"); buf[len++] = m->b + 32; buf[len++] = x + 33; buf[len++] = y + 33; } - log_debug("writing mouse %.*s to %%%u", (int)len, buf, wp->id); - bufferevent_write(wp->event, buf, len); + + *rbuf = buf; + *rlen = len; + return (1); +} + +/* Translate mouse and output. */ +static void +input_key_mouse(struct window_pane *wp, struct mouse_event *m) +{ + struct screen *s = wp->screen; + u_int x, y; + const char *buf; + size_t len; + + /* Ignore events if no mouse mode or the pane is not visible. */ + if (m->ignore || (s->mode & ALL_MOUSE_MODES) == 0) + return; + if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) + return; + if (!window_pane_visible(wp)) + return; + if (!input_key_get_mouse(s, m, x, y, &buf, &len)) + return; + log_debug("writing mouse %.*s to %%%u", (int)len, buf, wp->id); + input_key_write(__func__, wp->event, buf, len); } diff --git a/input.c b/input.c index 13bedc34..88e1c3c7 100644 --- a/input.c +++ b/input.c @@ -65,7 +65,7 @@ struct input_param { INPUT_MISSING, INPUT_NUMBER, INPUT_STRING - } type; + } type; union { int num; char *str; @@ -75,12 +75,14 @@ struct input_param { /* Input parser context. */ struct input_ctx { struct window_pane *wp; + struct bufferevent *event; struct screen_write_ctx ctx; + struct colour_palette *palette; struct input_cell cell; struct input_cell old_cell; - u_int old_cx; + u_int old_cx; u_int old_cy; int old_mode; @@ -120,7 +122,7 @@ struct input_ctx { * All input received since we were last in the ground state. Sent to * control clients on connection. */ - struct evbuffer *since_ground; + struct evbuffer *since_ground; }; /* Helper functions. */ @@ -128,7 +130,7 @@ struct input_transition; static int input_split(struct input_ctx *); static int input_get(struct input_ctx *, u_int, int, int); static void printflike(2, 3) input_reply(struct input_ctx *, const char *, ...); -static void input_set_state(struct window_pane *, +static void input_set_state(struct input_ctx *, const struct input_transition *); static void input_reset_cell(struct input_ctx *); @@ -137,6 +139,8 @@ static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); static void input_osc_52(struct input_ctx *, const char *); static void input_osc_104(struct input_ctx *, const char *); +static void input_osc_110(struct input_ctx *, const char *); +static void input_osc_111(struct input_ctx *, const char *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -240,6 +244,8 @@ enum input_csi_type { INPUT_CSI_HPA, INPUT_CSI_ICH, INPUT_CSI_IL, + INPUT_CSI_MODOFF, + INPUT_CSI_MODSET, INPUT_CSI_RCP, INPUT_CSI_REP, INPUT_CSI_RM, @@ -253,6 +259,7 @@ enum input_csi_type { INPUT_CSI_TBC, INPUT_CSI_VPA, INPUT_CSI_WINOPS, + INPUT_CSI_XDA, }; /* Control (CSI) command table. */ @@ -287,8 +294,11 @@ static const struct input_table_entry input_csi_table[] = { { 'l', "", INPUT_CSI_RM }, { 'l', "?", INPUT_CSI_RM_PRIVATE }, { 'm', "", INPUT_CSI_SGR }, + { 'm', ">", INPUT_CSI_MODSET }, { 'n', "", INPUT_CSI_DSR }, + { 'n', ">", INPUT_CSI_MODOFF }, { 'q', " ", INPUT_CSI_DECSCUSR }, + { 'q', ">", INPUT_CSI_XDA }, { 'r', "", INPUT_CSI_DECSTBM }, { 's', "", INPUT_CSI_SCP }, { 't', "", INPUT_CSI_WINOPS }, @@ -731,10 +741,9 @@ static void input_timer_callback(__unused int fd, __unused short events, void *arg) { struct input_ctx *ictx = arg; - struct window_pane *wp = ictx->wp; - log_debug("%s: %%%u %s expired" , __func__, wp->id, ictx->state->name); - input_reset(wp, 0); + log_debug("%s: %s expired" , __func__, ictx->state->name); + input_reset(ictx, 0); } /* Start the timer. */ @@ -788,12 +797,16 @@ input_restore_state(struct input_ctx *ictx) } /* Initialise input parser. */ -void -input_init(struct window_pane *wp) +struct input_ctx * +input_init(struct window_pane *wp, struct bufferevent *bev, + struct colour_palette *palette) { struct input_ctx *ictx; - ictx = wp->ictx = xcalloc(1, sizeof *ictx); + ictx = xcalloc(1, sizeof *ictx); + ictx->wp = wp; + ictx->event = bev; + ictx->palette = palette; ictx->input_space = INPUT_BUF_START; ictx->input_buf = xmalloc(INPUT_BUF_START); @@ -804,15 +817,15 @@ input_init(struct window_pane *wp) evtimer_set(&ictx->timer, input_timer_callback, ictx); - input_reset(wp, 0); + input_reset(ictx, 0); + return (ictx); } /* Destroy input parser. */ void -input_free(struct window_pane *wp) +input_free(struct input_ctx *ictx) { - struct input_ctx *ictx = wp->ictx; - u_int i; + u_int i; for (i = 0; i < ictx->param_list_len; i++) { if (ictx->param_list[i].type == INPUT_STRING) @@ -825,23 +838,22 @@ input_free(struct window_pane *wp) evbuffer_free(ictx->since_ground); free(ictx); - wp->ictx = NULL; } /* Reset input state and clear screen. */ void -input_reset(struct window_pane *wp, int clear) +input_reset(struct input_ctx *ictx, int clear) { - struct input_ctx *ictx = wp->ictx; struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; input_reset_cell(ictx); - if (clear) { + if (clear && wp != NULL) { if (TAILQ_EMPTY(&wp->modes)) - screen_write_start(sctx, wp, &wp->base); + screen_write_start_pane(sctx, wp, &wp->base); else - screen_write_start(sctx, NULL, &wp->base); + screen_write_start(sctx, &wp->base); screen_write_reset(sctx); screen_write_stop(sctx); } @@ -856,17 +868,15 @@ input_reset(struct window_pane *wp, int clear) /* Return pending data. */ struct evbuffer * -input_pending(struct window_pane *wp) +input_pending(struct input_ctx *ictx) { - return (wp->ictx->since_ground); + return (ictx->since_ground); } /* Change input state. */ static void -input_set_state(struct window_pane *wp, const struct input_transition *itr) +input_set_state(struct input_ctx *ictx, const struct input_transition *itr) { - struct input_ctx *ictx = wp->ictx; - if (ictx->state->exit != NULL) ictx->state->exit(ictx); ictx->state = itr->state; @@ -874,46 +884,15 @@ input_set_state(struct window_pane *wp, const struct input_transition *itr) ictx->state->enter(ictx); } -/* Parse input. */ -void -input_parse(struct window_pane *wp) +/* Parse data. */ +static void +input_parse(struct input_ctx *ictx, u_char *buf, size_t len) { - struct evbuffer *evb = wp->event->input; - - input_parse_buffer(wp, EVBUFFER_DATA(evb), EVBUFFER_LENGTH(evb)); - evbuffer_drain(evb, EVBUFFER_LENGTH(evb)); -} - -/* Parse given input. */ -void -input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) -{ - struct input_ctx *ictx = wp->ictx; struct screen_write_ctx *sctx = &ictx->ctx; const struct input_state *state = NULL; const struct input_transition *itr = NULL; size_t off = 0; - if (len == 0) - return; - - window_update_activity(wp->window); - wp->flags |= PANE_CHANGED; - notify_input(wp, buf, len); - - /* - * Open the screen. Use NULL wp if there is a mode set as don't want to - * update the tty. - */ - if (TAILQ_EMPTY(&wp->modes)) - screen_write_start(sctx, wp, &wp->base); - else - screen_write_start(sctx, NULL, &wp->base); - ictx->wp = wp; - - log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id, - ictx->state->name, len, (int)len, buf); - /* Parse the input. */ while (off < len) { ictx->ch = buf[off++]; @@ -956,14 +935,64 @@ input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) /* And switch state, if necessary. */ if (itr->state != NULL) - input_set_state(wp, itr); + input_set_state(ictx, itr); /* If not in ground state, save input. */ if (ictx->state != &input_state_ground) evbuffer_add(ictx->since_ground, &ictx->ch, 1); } +} - /* Close the screen. */ +/* Parse input from pane. */ +void +input_parse_pane(struct window_pane *wp) +{ + void *new_data; + size_t new_size; + + new_data = window_pane_get_new_data(wp, &wp->offset, &new_size); + input_parse_buffer(wp, new_data, new_size); + window_pane_update_used_data(wp, &wp->offset, new_size); +} + +/* Parse given input. */ +void +input_parse_buffer(struct window_pane *wp, u_char *buf, size_t len) +{ + struct input_ctx *ictx = wp->ictx; + struct screen_write_ctx *sctx = &ictx->ctx; + + if (len == 0) + return; + + window_update_activity(wp->window); + wp->flags |= PANE_CHANGED; + + /* NULL wp if there is a mode set as don't want to update the tty. */ + if (TAILQ_EMPTY(&wp->modes)) + screen_write_start_pane(sctx, wp, &wp->base); + else + screen_write_start(sctx, &wp->base); + + log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id, + ictx->state->name, len, (int)len, buf); + + input_parse(ictx, buf, len); + screen_write_stop(sctx); +} + +/* Parse given input for screen. */ +void +input_parse_screen(struct input_ctx *ictx, struct screen *s, + screen_write_init_ctx_cb cb, void *arg, u_char *buf, size_t len) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + + if (len == 0) + return; + + screen_write_start_callback(sctx, s, cb, arg); + input_parse(ictx, buf, len); screen_write_stop(sctx); } @@ -1043,14 +1072,15 @@ input_get(struct input_ctx *ictx, u_int validx, int minval, int defval) static void input_reply(struct input_ctx *ictx, const char *fmt, ...) { - va_list ap; - char *reply; + struct bufferevent *bev = ictx->event; + va_list ap; + char *reply; va_start(ap, fmt); xvasprintf(&reply, fmt, ap); va_end(ap); - bufferevent_write(ictx->wp->event, reply, strlen(reply)); + bufferevent_write(bev, reply, strlen(reply)); free(reply); } @@ -1177,7 +1207,8 @@ input_c0_dispatch(struct input_ctx *ictx) case '\000': /* NUL */ break; case '\007': /* BEL */ - alerts_queue(wp->window, WINDOW_BELL); + if (wp != NULL) + alerts_queue(wp->window, WINDOW_BELL); break; case '\010': /* BS */ screen_write_backspace(sctx); @@ -1240,9 +1271,10 @@ input_esc_dispatch(struct input_ctx *ictx) switch (entry->type) { case INPUT_ESC_RIS: - window_pane_reset_palette(ictx->wp); + colour_palette_clear(ictx->palette); input_reset_cell(ictx); screen_write_reset(sctx); + screen_write_fullredraw(sctx); break; case INPUT_ESC_IND: screen_write_linefeed(sctx, 0, ictx->cell.cell.bg); @@ -1303,7 +1335,6 @@ input_csi_dispatch(struct input_ctx *ictx) struct input_table_entry *entry; int i, n, m; u_int cx, bg = ictx->cell.cell.bg; - char *copy, *cp; if (ictx->flags & INPUT_DISCARD) return (0); @@ -1358,6 +1389,21 @@ input_csi_dispatch(struct input_ctx *ictx) if (n != -1 && m != -1) screen_write_cursormove(sctx, m - 1, n - 1, 1); break; + case INPUT_CSI_MODSET: + n = input_get(ictx, 0, 0, 0); + m = input_get(ictx, 1, 0, 0); + if (options_get_number(global_options, "extended-keys") == 2) + break; + if (n == 0 || (n == 4 && m == 0)) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + else if (n == 4 && (m == 1 || m == 2)) + screen_write_mode_set(sctx, MODE_KEXTENDED); + break; + case INPUT_CSI_MODOFF: + n = input_get(ictx, 0, 0, 0); + if (n == 4) + screen_write_mode_clear(sctx, MODE_KEXTENDED); + break; case INPUT_CSI_WINOPS: input_csi_dispatch_winops(ictx); break; @@ -1435,13 +1481,6 @@ input_csi_dispatch(struct input_ctx *ictx) case 6: input_reply(ictx, "\033[%u;%uR", s->cy + 1, s->cx + 1); break; - case 1337: /* Terminal version, from iTerm2. */ - copy = xstrdup(getversion()); - for (cp = copy; *cp != '\0'; cp++) - *cp = toupper((u_char)*cp); - input_reply(ictx, "\033[TMUX %sn", copy); - free(copy); - break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; @@ -1512,6 +1551,10 @@ input_csi_dispatch(struct input_ctx *ictx) if (n == -1) break; + m = screen_size_x(s) - s->cx; + if (n > m) + n = m; + if (ictx->last == -1) break; ictx->ch = ictx->last; @@ -1576,6 +1619,12 @@ input_csi_dispatch(struct input_ctx *ictx) if (n != -1) screen_set_cursor_style(s, n); break; + case INPUT_CSI_XDA: + n = input_get(ictx, 0, 0, 0); + if (n == 0) + input_reply(ictx, "\033P>|tmux %s\033\\", getversion()); + break; + } ictx->last = -1; @@ -1597,7 +1646,7 @@ input_csi_dispatch_rm(struct input_ctx *ictx) screen_write_mode_clear(sctx, MODE_INSERT); break; case 34: - screen_write_mode_set(sctx, MODE_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_VERY_VISIBLE); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1611,7 +1660,7 @@ static void input_csi_dispatch_rm_private(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; - struct window_pane *wp = ictx->wp; + struct grid_cell *gc = &ictx->cell.cell; u_int i; for (i = 0; i < ictx->param_list_len; i++) { @@ -1623,7 +1672,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) break; case 3: /* DECCOLM */ screen_write_cursormove(sctx, 0, 0, 1); - screen_write_clearscreen(sctx, ictx->cell.cell.bg); + screen_write_clearscreen(sctx, gc->bg); break; case 6: /* DECOM */ screen_write_mode_clear(sctx, MODE_ORIGIN); @@ -1633,7 +1682,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) screen_write_mode_clear(sctx, MODE_WRAP); break; case 12: - screen_write_mode_clear(sctx, MODE_BLINKING); + screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING); break; case 25: /* TCEM */ screen_write_mode_clear(sctx, MODE_CURSOR); @@ -1655,10 +1704,10 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) break; case 47: case 1047: - window_pane_alternate_off(wp, &ictx->cell.cell, 0); + screen_write_alternateoff(sctx, gc, 0); break; case 1049: - window_pane_alternate_off(wp, &ictx->cell.cell, 1); + screen_write_alternateoff(sctx, gc, 1); break; case 2004: screen_write_mode_clear(sctx, MODE_BRACKETPASTE); @@ -1685,7 +1734,7 @@ input_csi_dispatch_sm(struct input_ctx *ictx) screen_write_mode_set(sctx, MODE_INSERT); break; case 34: - screen_write_mode_clear(sctx, MODE_BLINKING); + screen_write_mode_clear(sctx, MODE_CURSOR_VERY_VISIBLE); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1700,6 +1749,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; + struct grid_cell *gc = &ictx->cell.cell; u_int i; for (i = 0; i < ictx->param_list_len; i++) { @@ -1721,7 +1771,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) screen_write_mode_set(sctx, MODE_WRAP); break; case 12: - screen_write_mode_set(sctx, MODE_BLINKING); + screen_write_mode_set(sctx, MODE_CURSOR_BLINKING); break; case 25: /* TCEM */ screen_write_mode_set(sctx, MODE_CURSOR); @@ -1742,7 +1792,12 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) if (sctx->s->mode & MODE_FOCUSON) break; screen_write_mode_set(sctx, MODE_FOCUSON); - wp->flags |= PANE_FOCUSPUSH; /* force update */ + if (wp == NULL) + break; + if (wp->flags & PANE_FOCUSED) + bufferevent_write(wp->event, "\033[I", 3); + else + bufferevent_write(wp->event, "\033[O", 3); break; case 1005: screen_write_mode_set(sctx, MODE_MOUSE_UTF8); @@ -1752,10 +1807,10 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) break; case 47: case 1047: - window_pane_alternate_on(wp, &ictx->cell.cell, 0); + screen_write_alternateon(sctx, gc, 0); break; case 1049: - window_pane_alternate_on(wp, &ictx->cell.cell, 1); + screen_write_alternateon(sctx, gc, 1); break; case 2004: screen_write_mode_set(sctx, MODE_BRACKETPASTE); @@ -1772,7 +1827,9 @@ static void input_csi_dispatch_winops(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; + struct screen *s = sctx->s; struct window_pane *wp = ictx->wp; + u_int x = screen_size_x(s), y = screen_size_y(s); int n, m; m = 0; @@ -1823,12 +1880,16 @@ input_csi_dispatch_winops(struct input_ctx *ictx) case 0: case 2: screen_pop_title(sctx->s); - server_status_window(ictx->wp->window); + if (wp == NULL) + break; + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); break; } break; case 18: - input_reply(ictx, "\033[8;%u;%ut", wp->sy, wp->sx); + input_reply(ictx, "\033[8;%u;%ut", x, y); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1929,8 +1990,13 @@ input_csi_dispatch_sgr_colon(struct input_ctx *ictx, u_int i) free(copy); return; } - } else + } else { n++; + if (n == nitems(p)) { + free(copy); + return; + } + } log_debug("%s: %u = %d", __func__, n - 1, p[n - 1]); } free(copy); @@ -2043,6 +2109,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) gc->attr |= GRID_ATTR_UNDERSCORE; break; case 5: + case 6: gc->attr |= GRID_ATTR_BLINK; break; case 7: @@ -2054,6 +2121,10 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) case 9: gc->attr |= GRID_ATTR_STRIKETHROUGH; break; + case 21: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_2; + break; case 22: gc->attr &= ~(GRID_ATTR_BRIGHT|GRID_ATTR_DIM); break; @@ -2205,6 +2276,7 @@ static void input_exit_osc(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; u_char *p = ictx->input_buf; u_int option; @@ -2225,8 +2297,11 @@ input_exit_osc(struct input_ctx *ictx) switch (option) { case 0: case 2: - if (screen_set_title(sctx->s, p)) - server_status_window(ictx->wp->window); + if (screen_set_title(sctx->s, p) && wp != NULL) { + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } break; case 4: input_osc_4(ictx, p); @@ -2234,7 +2309,10 @@ input_exit_osc(struct input_ctx *ictx) case 7: if (utf8_isvalid(p)) { screen_set_path(sctx->s, p); - server_status_window(ictx->wp->window); + if (wp != NULL) { + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } } break; case 10: @@ -2253,6 +2331,12 @@ input_exit_osc(struct input_ctx *ictx) case 104: input_osc_104(ictx, p); break; + case 110: + input_osc_110(ictx, p); + break; + case 111: + input_osc_111(ictx, p); + break; case 112: if (*p == '\0') /* no arguments allowed */ screen_set_cursor_colour(sctx->s, ""); @@ -2279,13 +2363,17 @@ static void input_exit_apc(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; + struct window_pane *wp = ictx->wp; if (ictx->flags & INPUT_DISCARD) return; log_debug("%s: \"%s\"", __func__, ictx->input_buf); - if (screen_set_title(sctx->s, ictx->input_buf)) - server_status_window(ictx->wp->window); + if (screen_set_title(sctx->s, ictx->input_buf) && wp != NULL) { + notify_pane("pane-title-changed", wp); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); + } } /* Rename string started. */ @@ -2304,8 +2392,10 @@ static void input_exit_rename(struct input_ctx *ictx) { struct window_pane *wp = ictx->wp; - struct options_entry *oe; + struct options_entry *o; + if (wp == NULL) + return; if (ictx->flags & INPUT_DISCARD) return; if (!options_get_number(ictx->wp->options, "allow-rename")) @@ -2316,14 +2406,15 @@ input_exit_rename(struct input_ctx *ictx) return; if (ictx->input_len == 0) { - oe = options_get(wp->window->options, "automatic-rename"); - if (oe != NULL) - options_remove(oe); + o = options_get_only(wp->window->options, "automatic-rename"); + if (o != NULL) + options_remove_or_default(o, -1, NULL); return; } - window_set_name(ictx->wp->window, ictx->input_buf); - options_set_number(ictx->wp->window->options, "automatic-rename", 0); - server_status_window(ictx->wp->window); + window_set_name(wp->window, ictx->input_buf); + options_set_number(wp->window->options, "automatic-rename", 0); + server_redraw_window_borders(wp->window); + server_status_window(wp->window); } /* Open UTF-8 character. */ @@ -2364,133 +2455,185 @@ input_top_bit_set(struct input_ctx *ictx) /* Parse colour from OSC. */ static int -input_osc_parse_colour(const char *p, u_int *r, u_int *g, u_int *b) +input_osc_parse_colour(const char *p) { - u_int rsize, gsize, bsize; - const char *cp, *s = p; + double c, m, y, k = 0; + u_int r, g, b; + size_t len = strlen(p); + int colour = -1; + char *copy; - if (sscanf(p, "rgb:%x/%x/%x", r, g, b) != 3) - return (0); - p += 4; + if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || + (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || + sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) + colour = colour_join_rgb(r, g, b); + else if ((len == 18 && + sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || + (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) + colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); + else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || + sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && + c >= 0 && c <= 1 && m >= 0 && m <= 1 && + y >= 0 && y <= 1 && k >= 0 && k <= 1) { + colour = colour_join_rgb( + (1 - c) * (1 - k) * 255, + (1 - m) * (1 - k) * 255, + (1 - y) * (1 - k) * 255); + } else { + while (len != 0 && *p == ' ') { + p++; + len--; + } + while (len != 0 && p[len - 1] == ' ') + len--; + copy = xstrndup(p, len); + colour = colour_byname(copy); + free(copy); + } + log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); + return (colour); +} - cp = strchr(p, '/'); - rsize = cp - p; - if (rsize == 1) - (*r) = (*r) | ((*r) << 4); - else if (rsize == 3) - (*r) >>= 4; - else if (rsize == 4) - (*r) >>= 8; - else if (rsize != 2) - return (0); +/* Reply to a colour request. */ +static void +input_osc_colour_reply(struct input_ctx *ictx, u_int n, int c) +{ + u_char r, g, b; + const char *end; - p = cp + 1; - cp = strchr(p, '/'); - gsize = cp - p; - if (gsize == 1) - (*g) = (*g) | ((*g) << 4); - else if (gsize == 3) - (*g) >>= 4; - else if (gsize == 4) - (*g) >>= 8; - else if (gsize != 2) - return (0); + if (c == 8 || (~c & COLOUR_FLAG_RGB)) + return; + colour_split_rgb(c, &r, &g, &b); - bsize = strlen(cp + 1); - if (bsize == 1) - (*b) = (*b) | ((*b) << 4); - else if (bsize == 3) - (*b) >>= 4; - else if (bsize == 4) - (*b) >>= 8; - else if (bsize != 2) - return (0); - - log_debug("%s: %s = %02x%02x%02x", __func__, s, *r, *g, *b); - return (1); + if (ictx->input_end == INPUT_END_BEL) + end = "\007"; + else + end = "\033\\"; + input_reply(ictx, "\033]%u;rgb:%02hhx/%02hhx/%02hhx%s", n, r, g, b, end); } /* Handle the OSC 4 sequence for setting (multiple) palette entries. */ static void input_osc_4(struct input_ctx *ictx, const char *p) { - struct window_pane *wp = ictx->wp; - char *copy, *s, *next = NULL; - long idx; - u_int r, g, b; + char *copy, *s, *next = NULL; + long idx; + int c, bad = 0, redraw = 0; copy = s = xstrdup(p); while (s != NULL && *s != '\0') { idx = strtol(s, &next, 10); - if (*next++ != ';') - goto bad; - if (idx < 0 || idx >= 0x100) - goto bad; + if (*next++ != ';') { + bad = 1; + break; + } + if (idx < 0 || idx >= 256) { + bad = 1; + break; + } s = strsep(&next, ";"); - if (!input_osc_parse_colour(s, &r, &g, &b)) { + if ((c = input_osc_parse_colour(s)) == -1) { s = next; continue; } - - window_pane_set_palette(wp, idx, colour_join_rgb(r, g, b)); + if (colour_palette_set(ictx->palette, idx, c)) + redraw = 1; s = next; } - - free(copy); - return; - -bad: - log_debug("bad OSC 4: %s", p); + if (bad) + log_debug("bad OSC 4: %s", p); + if (redraw) + screen_write_fullredraw(&ictx->ctx); free(copy); } -/* Handle the OSC 10 sequence for setting foreground colour. */ +/* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; - u_int r, g, b; - char tmp[16]; + struct grid_cell defaults; + int c; - if (strcmp(p, "?") == 0) + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + tty_default_colours(&defaults, wp); + input_osc_colour_reply(ictx, 10, defaults.fg); + } return; + } - if (!input_osc_parse_colour(p, &r, &g, &b)) - goto bad; - xsnprintf(tmp, sizeof tmp, "fg=#%02x%02x%02x", r, g, b); - options_set_style(wp->options, "window-style", 1, tmp); - options_set_style(wp->options, "window-active-style", 1, tmp); - wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); - - return; - -bad: - log_debug("bad OSC 10: %s", p); + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 10: %s", p); + return; + } + if (ictx->palette != NULL) { + ictx->palette->fg = c; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } } -/* Handle the OSC 11 sequence for setting background colour. */ +/* Handle the OSC 110 sequence for resetting background colour. */ +static void +input_osc_110(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; + + if (*p != '\0') + return; + if (ictx->palette != NULL) { + ictx->palette->fg = 8; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} + +/* Handle the OSC 11 sequence for setting and querying background colour. */ static void input_osc_11(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; - u_int r, g, b; - char tmp[16]; + struct grid_cell defaults; + int c; - if (strcmp(p, "?") == 0) + if (strcmp(p, "?") == 0) { + if (wp != NULL) { + tty_default_colours(&defaults, wp); + input_osc_colour_reply(ictx, 11, defaults.bg); + } return; + } - if (!input_osc_parse_colour(p, &r, &g, &b)) - goto bad; - xsnprintf(tmp, sizeof tmp, "bg=#%02x%02x%02x", r, g, b); - options_set_style(wp->options, "window-style", 1, tmp); - options_set_style(wp->options, "window-active-style", 1, tmp); - wp->flags |= (PANE_REDRAW|PANE_STYLECHANGED); + if ((c = input_osc_parse_colour(p)) == -1) { + log_debug("bad OSC 11: %s", p); + return; + } + if (ictx->palette != NULL) { + ictx->palette->bg = c; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } +} - return; +/* Handle the OSC 111 sequence for resetting background colour. */ +static void +input_osc_111(struct input_ctx *ictx, const char *p) +{ + struct window_pane *wp = ictx->wp; -bad: - log_debug("bad OSC 11: %s", p); + if (*p != '\0') + return; + if (ictx->palette != NULL) { + ictx->palette->bg = 8; + if (wp != NULL) + wp->flags |= PANE_STYLECHANGED; + screen_write_fullredraw(&ictx->ctx); + } } /* Handle the OSC 52 sequence for setting the clipboard. */ @@ -2506,6 +2649,8 @@ input_osc_52(struct input_ctx *ictx, const char *p) struct screen_write_ctx ctx; struct paste_buffer *pb; + if (wp == NULL) + return; state = options_get_number(global_options, "set-clipboard"); if (state != 2) return; @@ -2530,13 +2675,13 @@ input_osc_52(struct input_ctx *ictx, const char *p) outlen = 0; out = NULL; } - bufferevent_write(wp->event, "\033]52;;", 6); + bufferevent_write(ictx->event, "\033]52;;", 6); if (outlen != 0) - bufferevent_write(wp->event, out, outlen); + bufferevent_write(ictx->event, out, outlen); if (ictx->input_end == INPUT_END_BEL) - bufferevent_write(wp->event, "\007", 1); + bufferevent_write(ictx->event, "\007", 1); else - bufferevent_write(wp->event, "\033\\", 2); + bufferevent_write(ictx->event, "\033\\", 2); free(out); return; } @@ -2551,7 +2696,7 @@ input_osc_52(struct input_ctx *ictx, const char *p) return; } - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, out, outlen); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -2563,31 +2708,35 @@ input_osc_52(struct input_ctx *ictx, const char *p) static void input_osc_104(struct input_ctx *ictx, const char *p) { - struct window_pane *wp = ictx->wp; - char *copy, *s; - long idx; + char *copy, *s; + long idx; + int bad = 0, redraw = 0; if (*p == '\0') { - window_pane_reset_palette(wp); + colour_palette_clear(ictx->palette); + screen_write_fullredraw(&ictx->ctx); return; } copy = s = xstrdup(p); while (*s != '\0') { idx = strtol(s, &s, 10); - if (*s != '\0' && *s != ';') - goto bad; - if (idx < 0 || idx >= 0x100) - goto bad; - - window_pane_unset_palette(wp, idx); + if (*s != '\0' && *s != ';') { + bad = 1; + break; + } + if (idx < 0 || idx >= 256) { + bad = 1; + break; + } + if (colour_palette_set(ictx->palette, idx, -1)) + redraw = 1; if (*s == ';') s++; } - free(copy); - return; - -bad: - log_debug("bad OSC 104: %s", p); + if (bad) + log_debug("bad OSC 104: %s", p); + if (redraw) + screen_write_fullredraw(&ictx->ctx); free(copy); } diff --git a/job.c b/job.c index 10883e8e..3624c679 100644 --- a/job.c +++ b/job.c @@ -17,7 +17,9 @@ */ #include +#include #include +#include #include #include @@ -48,6 +50,7 @@ struct job { char *cmd; pid_t pid; + char tty[TTY_NAME_MAX]; int status; int fd; @@ -64,22 +67,20 @@ struct job { /* All jobs list. */ static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); -/* Start a job running, if it isn't already. */ +/* Start a job running. */ struct job * -job_run(const char *cmd, struct session *s, const char *cwd, - job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, - void *data, int flags) +job_run(const char *cmd, int argc, char **argv, struct session *s, + const char *cwd, job_update_cb updatecb, job_complete_cb completecb, + job_free_cb freecb, void *data, int flags, int sx, int sy) { - struct job *job; - struct environ *env; - pid_t pid; - int nullfd, out[2]; - const char *home; - sigset_t set, oldset; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) - return (NULL); - log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, cwd == NULL ? "" : cwd); + struct job *job; + struct environ *env; + pid_t pid; + int nullfd, out[2], master; + const char *home; + sigset_t set, oldset; + struct winsize ws; + char **argvp, tty[TTY_NAME_MAX]; /* * Do not set TERM during .tmux.conf, it is nice to be able to use @@ -89,57 +90,86 @@ job_run(const char *cmd, struct session *s, const char *cwd, sigfillset(&set); sigprocmask(SIG_BLOCK, &set, &oldset); - switch (pid = fork()) { + + if (flags & JOB_PTY) { + memset(&ws, 0, sizeof ws); + ws.ws_col = sx; + ws.ws_row = sy; + pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws); + } else { + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) + goto fail; + pid = fork(); + } + if (cmd == NULL) { + cmd_log_argv(argc, argv, "%s:", __func__); + log_debug("%s: cwd=%s", __func__, cwd == NULL ? "" : cwd); + } else { + log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, + cwd == NULL ? "" : cwd); + } + + switch (pid) { case -1: - sigprocmask(SIG_SETMASK, &oldset, NULL); - environ_free(env); - close(out[0]); - close(out[1]); - return (NULL); + if (~flags & JOB_PTY) { + close(out[0]); + close(out[1]); + } + goto fail; case 0: proc_clear_signals(server_proc, 1); sigprocmask(SIG_SETMASK, &oldset, NULL); - if (cwd == NULL || chdir(cwd) != 0) { - if ((home = find_home()) == NULL || chdir(home) != 0) - chdir("/"); - } + if ((cwd == NULL || chdir(cwd) != 0) && + ((home = find_home()) == NULL || chdir(home) != 0) && + chdir("/") != 0) + fatal("chdir failed"); environ_push(env); environ_free(env); - if (dup2(out[1], STDIN_FILENO) == -1) - fatal("dup2 failed"); - if (dup2(out[1], STDOUT_FILENO) == -1) - fatal("dup2 failed"); - if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) - close(out[1]); - close(out[0]); - - nullfd = open(_PATH_DEVNULL, O_RDWR, 0); - if (nullfd == -1) - fatal("open failed"); - if (dup2(nullfd, STDERR_FILENO) == -1) - fatal("dup2 failed"); - if (nullfd != STDERR_FILENO) - close(nullfd); + if (~flags & JOB_PTY) { + if (dup2(out[1], STDIN_FILENO) == -1) + fatal("dup2 failed"); + if (dup2(out[1], STDOUT_FILENO) == -1) + fatal("dup2 failed"); + if (out[1] != STDIN_FILENO && out[1] != STDOUT_FILENO) + close(out[1]); + close(out[0]); + nullfd = open(_PATH_DEVNULL, O_RDWR, 0); + if (nullfd == -1) + fatal("open failed"); + if (dup2(nullfd, STDERR_FILENO) == -1) + fatal("dup2 failed"); + if (nullfd != STDERR_FILENO) + close(nullfd); + } closefrom(STDERR_FILENO + 1); - execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); - fatal("execl failed"); + if (cmd != NULL) { + execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); + fatal("execl failed"); + } else { + argvp = cmd_copy_argv(argc, argv); + execvp(argvp[0], argvp); + fatal("execvp failed"); + } } sigprocmask(SIG_SETMASK, &oldset, NULL); environ_free(env); - close(out[1]); job = xmalloc(sizeof *job); job->state = JOB_RUNNING; job->flags = flags; - job->cmd = xstrdup(cmd); + if (cmd != NULL) + job->cmd = xstrdup(cmd); + else + job->cmd = cmd_stringify_argv(argc, argv); job->pid = pid; + strlcpy(job->tty, tty, sizeof job->tty); job->status = 0; LIST_INSERT_HEAD(&all_jobs, job, entry); @@ -149,7 +179,11 @@ job_run(const char *cmd, struct session *s, const char *cwd, job->freecb = freecb; job->data = data; - job->fd = out[0]; + if (~flags & JOB_PTY) { + close(out[1]); + job->fd = out[0]; + } else + job->fd = master; setblocking(job->fd, 0); job->event = bufferevent_new(job->fd, job_read_callback, @@ -160,6 +194,37 @@ job_run(const char *cmd, struct session *s, const char *cwd, log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); return (job); + +fail: + sigprocmask(SIG_SETMASK, &oldset, NULL); + environ_free(env); + return (NULL); +} + +/* Take job's file descriptor and free the job. */ +int +job_transfer(struct job *job, pid_t *pid, char *tty, size_t ttylen) +{ + int fd = job->fd; + + log_debug("transfer job %p: %s", job, job->cmd); + + if (pid != NULL) + *pid = job->pid; + if (tty != NULL) + strlcpy(tty, job->tty, ttylen); + + LIST_REMOVE(job, entry); + free(job->cmd); + + if (job->freecb != NULL && job->data != NULL) + job->freecb(job->data); + + if (job->event != NULL) + bufferevent_free(job->event); + + free(job); + return (fd); } /* Kill and free an individual job. */ @@ -184,6 +249,24 @@ job_free(struct job *job) free(job); } +/* Resize job. */ +void +job_resize(struct job *job, u_int sx, u_int sy) +{ + struct winsize ws; + + if (job->fd == -1 || (~job->flags & JOB_PTY)) + return; + + log_debug("resize job %p: %ux%u", job, sx, sy); + + memset(&ws, 0, sizeof ws); + ws.ws_col = sx; + ws.ws_row = sy; + if (ioctl(job->fd, TIOCSWINSZ, &ws) == -1) + fatal("ioctl failed"); +} + /* Job buffer read callback. */ static void job_read_callback(__unused struct bufferevent *bufev, void *data) @@ -208,7 +291,7 @@ job_write_callback(__unused struct bufferevent *bufev, void *data) log_debug("job write %p: %s, pid %ld, output left %zu", job, job->cmd, (long) job->pid, len); - if (len == 0) { + if (len == 0 && (~job->flags & JOB_KEEPWRITE)) { shutdown(job->fd, SHUT_WR); bufferevent_disable(job->event, EV_WRITE); } @@ -245,6 +328,12 @@ job_check_died(pid_t pid, int status) } if (job == NULL) return; + if (WIFSTOPPED(status)) { + if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) + return; + killpg(job->pid, SIGCONT); + return; + } log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); job->status = status; diff --git a/key-bindings.c b/key-bindings.c index 4387c011..9f7e734a 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -29,39 +29,42 @@ " 'Previous' 'p' {switch-client -p}" \ " ''" \ " 'Renumber' 'N' {move-window -r}" \ - " 'Rename' 'n' {command-prompt -I \"#S\" \"rename-session -- '%%'\"}" \ + " 'Rename' 'n' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \ " ''" \ " 'New Session' 's' {new-session}" \ " 'New Window' 'w' {new-window}" #define DEFAULT_WINDOW_MENU \ - " 'Swap Left' 'l' {swap-window -t:-1}" \ - " 'Swap Right' 'r' {swap-window -t:+1}" \ + " '#{?#{>:#{session_windows},1},,-}Swap Left' 'l' {swap-window -t:-1}" \ + " '#{?#{>:#{session_windows},1},,-}Swap Right' 'r' {swap-window -t:+1}" \ " '#{?pane_marked_set,,-}Swap Marked' 's' {swap-window}" \ " ''" \ " 'Kill' 'X' {kill-window}" \ " 'Respawn' 'R' {respawn-window -k}" \ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \ - " 'Rename' 'n' {command-prompt -I \"#W\" \"rename-window -- '%%'\"}" \ + " 'Rename' 'n' {command-prompt -FI \"#W\" {rename-window -t '#{window_id}' -- '%%'}}" \ " ''" \ " 'New After' 'w' {new-window -a}" \ " 'New At End' 'W' {new-window}" #define DEFAULT_PANE_MENU \ - " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {copy-mode -t=; send -Xt= search-backward \"#{q:mouse_word}\"}" \ - " '#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}' 'C-y' {send-keys -l -- \"#{q:mouse_word}\"}" \ - " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {set-buffer -- \"#{q:mouse_word}\"}" \ - " '#{?mouse_line,Copy Line,}' 'l' {set-buffer -- \"#{q:mouse_line}\"}" \ + " '#{?#{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}" \ + " ''" \ + " '#{?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}\"}" \ + " '#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}' 'C-y' {copy-mode -q; send-keys -l -- \"#{q:mouse_word}\"}" \ + " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {copy-mode -q; set-buffer -- \"#{q:mouse_word}\"}" \ + " '#{?mouse_line,Copy Line,}' 'l' {copy-mode -q; set-buffer -- \"#{q:mouse_line}\"}" \ " ''" \ " 'Horizontal Split' 'h' {split-window -h}" \ " 'Vertical Split' 'v' {split-window -v}" \ " ''" \ - " 'Swap Up' 'u' {swap-pane -U}" \ - " 'Swap Down' 'd' {swap-pane -D}" \ + " '#{?#{>:#{window_panes},1},,-}Swap Up' 'u' {swap-pane -U}" \ + " '#{?#{>:#{window_panes},1},,-}Swap Down' 'd' {swap-pane -D}" \ " '#{?pane_marked_set,,-}Swap Marked' 's' {swap-pane}" \ " ''" \ " 'Kill' 'X' {kill-pane}" \ " 'Respawn' 'R' {respawn-pane -k}" \ " '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \ - " '#{?window_zoomed_flag,Unzoom,Zoom}' 'z' {resize-pane -Z}" + " '#{?#{>:#{window_panes},1},,-}#{?window_zoomed_flag,Unzoom,Zoom}' 'z' {resize-pane -Z}" static int key_bindings_cmp(struct key_binding *, struct key_binding *); RB_GENERATE_STATIC(key_bindings, key_binding, entry, key_bindings_cmp); @@ -86,9 +89,8 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) } static void -key_bindings_free(struct key_table *table, struct key_binding *bd) +key_bindings_free(struct key_binding *bd) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); cmd_list_free(bd->cmdlist); free((void *)bd->note); free(bd); @@ -107,6 +109,7 @@ key_bindings_get_table(const char *name, int create) table = xmalloc(sizeof *table); table->name = xstrdup(name); RB_INIT(&table->key_bindings); + RB_INIT(&table->default_key_bindings); table->references = 1; /* one reference in key_tables */ RB_INSERT(key_tables, &key_tables, table); @@ -135,8 +138,14 @@ key_bindings_unref_table(struct key_table *table) if (--table->references != 0) return; - RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) - key_bindings_free(table, bd); + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } + RB_FOREACH_SAFE(bd, key_bindings, &table->default_key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->default_key_bindings, bd); + key_bindings_free(bd); + } free((void *)table->name); free(table); @@ -151,6 +160,15 @@ key_bindings_get(struct key_table *table, key_code key) return (RB_FIND(key_bindings, &table->key_bindings, &bd)); } +struct key_binding * +key_bindings_get_default(struct key_table *table, key_code key) +{ + struct key_binding bd; + + bd.key = key; + return (RB_FIND(key_bindings, &table->default_key_bindings, &bd)); +} + struct key_binding * key_bindings_first(struct key_table *table) { @@ -169,15 +187,28 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, { struct key_table *table; struct key_binding *bd; + char *s; table = key_bindings_get_table(name, 1); - bd = key_bindings_get(table, key & ~KEYC_XTERM); - if (bd != NULL) - key_bindings_free(table, bd); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); + if (cmdlist == NULL) { + if (bd != NULL) { + free((void *)bd->note); + if (note != NULL) + bd->note = xstrdup(note); + else + bd->note = NULL; + } + return; + } + if (bd != NULL) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } bd = xcalloc(1, sizeof *bd); - bd->key = key; + bd->key = (key & ~KEYC_MASK_FLAGS); if (note != NULL) bd->note = xstrdup(note); RB_INSERT(key_bindings, &table->key_bindings, bd); @@ -185,6 +216,11 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, if (repeat) bd->flags |= KEY_BINDING_REPEAT; bd->cmdlist = cmdlist; + + s = cmd_list_print(bd->cmdlist, 0); + log_debug("%s: %#llx %s = %s", __func__, bd->key, + key_string_lookup_key(bd->key, 1), s); + free(s); } void @@ -197,17 +233,55 @@ key_bindings_remove(const char *name, key_code key) if (table == NULL) return; - bd = key_bindings_get(table, key & ~KEYC_XTERM); + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); if (bd == NULL) return; - key_bindings_free(table, bd); - if (RB_EMPTY(&table->key_bindings)) { + log_debug("%s: %#llx %s", __func__, bd->key, + key_string_lookup_key(bd->key, 1)); + + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + + if (RB_EMPTY(&table->key_bindings) && + RB_EMPTY(&table->default_key_bindings)) { RB_REMOVE(key_tables, &key_tables, table); key_bindings_unref_table(table); } } +void +key_bindings_reset(const char *name, key_code key) +{ + struct key_table *table; + struct key_binding *bd, *dd; + + table = key_bindings_get_table(name, 0); + if (table == NULL) + return; + + bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS); + if (bd == NULL) + return; + + dd = key_bindings_get_default(table, bd->key); + if (dd == NULL) { + key_bindings_remove(name, bd->key); + return; + } + + cmd_list_free(bd->cmdlist); + bd->cmdlist = dd->cmdlist; + bd->cmdlist->references++; + + free((void *)bd->note); + if (dd->note != NULL) + bd->note = xstrdup(dd->note); + else + bd->note = NULL; + bd->flags = dd->flags; +} + void key_bindings_remove_table(const char *name) { @@ -225,268 +299,349 @@ key_bindings_remove_table(const char *name) } } +void +key_bindings_reset_table(const char *name) +{ + struct key_table *table; + struct key_binding *bd, *bd1; + + table = key_bindings_get_table(name, 0); + if (table == NULL) + return; + if (RB_EMPTY(&table->default_key_bindings)) { + key_bindings_remove_table(name); + return; + } + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) + key_bindings_reset(name, bd->key); +} + +static enum cmd_retval +key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data) +{ + struct key_table *table; + struct key_binding *bd, *new_bd; + + RB_FOREACH(table, key_tables, &key_tables) { + RB_FOREACH(bd, key_bindings, &table->key_bindings) { + new_bd = xcalloc(1, sizeof *bd); + new_bd->key = bd->key; + if (bd->note != NULL) + new_bd->note = xstrdup(bd->note); + new_bd->flags = bd->flags; + new_bd->cmdlist = bd->cmdlist; + new_bd->cmdlist->references++; + RB_INSERT(key_bindings, &table->default_key_bindings, + new_bd); + } + } + return (CMD_RETURN_NORMAL); +} + void key_bindings_init(void) { static const char *defaults[] = { - "bind -N 'Send the prefix key' C-b send-prefix", - "bind -N 'Rotate through the panes' C-o rotate-window", - "bind -N 'Suspend the current client' C-z suspend-client", - "bind -N 'Select next layout' Space next-layout", - "bind -N 'Break pane to a new window' ! break-pane", - "bind -N 'Split window vertically' '\"' split-window", - "bind -N 'List all paste buffers' '#' list-buffers", - "bind -N 'Rename current session' '$' command-prompt -I'#S' \"rename-session -- '%%'\"", - "bind -N 'Split window horizontally' % split-window -h", - "bind -N 'Kill current window' & confirm-before -p\"kill-window #W? (y/n)\" kill-window", - "bind -N 'Prompt for window index to select' \"'\" command-prompt -pindex \"select-window -t ':%%'\"", - "bind -N 'Switch to previous client' ( switch-client -p", - "bind -N 'Switch to next client' ) switch-client -n", - "bind -N 'Rename current window' , command-prompt -I'#W' \"rename-window -- '%%'\"", - "bind -N 'Delete the most recent paste buffer' - delete-buffer", - "bind -N 'Move the current window' . command-prompt \"move-window -t '%%'\"", - "bind -N 'Describe key binding' '/' command-prompt -kpkey 'list-keys -1N \"%%%\"'", - "bind -N 'Select window 0' 0 select-window -t:=0", - "bind -N 'Select window 1' 1 select-window -t:=1", - "bind -N 'Select window 2' 2 select-window -t:=2", - "bind -N 'Select window 3' 3 select-window -t:=3", - "bind -N 'Select window 4' 4 select-window -t:=4", - "bind -N 'Select window 5' 5 select-window -t:=5", - "bind -N 'Select window 6' 6 select-window -t:=6", - "bind -N 'Select window 7' 7 select-window -t:=7", - "bind -N 'Select window 8' 8 select-window -t:=8", - "bind -N 'Select window 9' 9 select-window -t:=9", - "bind -N 'Prompt for a command' : command-prompt", - "bind -N 'Move to the previously active pane' \\; last-pane", - "bind -N 'Choose a paste buffer from a list' = choose-buffer -Z", - "bind -N 'List key bindings' ? list-keys -N", - "bind -N 'Choose a client from a list' D choose-client -Z", - "bind -N 'Spread panes out evenly' E select-layout -E", - "bind -N 'Switch to the last client' L switch-client -l", - "bind -N 'Clear the marked pane' M select-pane -M", - "bind -N 'Enter copy mode' [ copy-mode", - "bind -N 'Paste the most recent paste buffer' ] paste-buffer", - "bind -N 'Create a new window' c new-window", - "bind -N 'Detach the current client' d detach-client", - "bind -N 'Search for a pane' f command-prompt \"find-window -Z -- '%%'\"", - "bind -N 'Display window information' i display-message", - "bind -N 'Select the previously current window' l last-window", - "bind -N 'Toggle the marked pane' m select-pane -m", - "bind -N 'Select the next window' n next-window", - "bind -N 'Select the next pane' o select-pane -t:.+", - "bind -N 'Select the previous pane' p previous-window", - "bind -N 'Display pane numbers' q display-panes", - "bind -N 'Redraw the current client' r refresh-client", - "bind -N 'Choose a session from a list' s choose-tree -Zs", - "bind -N 'Show a clock' t clock-mode", - "bind -N 'Choose a window from a list' w choose-tree -Zw", - "bind -N 'Kill the active pane' x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", - "bind -N 'Zoom the active pane' z resize-pane -Z", - "bind -N 'Swap the active pane with the pane above' '{' swap-pane -U", - "bind -N 'Swap the active pane with the pane below' '}' swap-pane -D", - "bind -N 'Show messages' '~' show-messages", - "bind -N 'Enter copy mode and scroll up' PPage copy-mode -u", - "bind -N 'Select the pane above the active pane' -r Up select-pane -U", - "bind -N 'Select the pane below the active pane' -r Down select-pane -D", - "bind -N 'Select the pane to the left of the active pane' -r Left select-pane -L", - "bind -N 'Select the pane to the right of the active pane' -r Right select-pane -R", - "bind -N 'Set the even-horizontal layout' M-1 select-layout even-horizontal", - "bind -N 'Set the even-vertical layout' M-2 select-layout even-vertical", - "bind -N 'Set the main-horizontal layout' M-3 select-layout main-horizontal", - "bind -N 'Set the main-vertical layout' M-4 select-layout main-vertical", - "bind -N 'Select the tiled layout' M-5 select-layout tiled", - "bind -N 'Select the next window with an alert' M-n next-window -a", - "bind -N 'Rotate through the panes in reverse' M-o rotate-window -D", - "bind -N 'Select the previous window with an alert' M-p previous-window -a", - "bind -N 'Move the visible part of the window up' -r S-Up refresh-client -U 10", - "bind -N 'Move the visible part of the window down' -r S-Down refresh-client -D 10", - "bind -N 'Move the visible part of the window left' -r S-Left refresh-client -L 10", - "bind -N 'Move the visible part of the window right' -r S-Right refresh-client -R 10", - "bind -N 'Reset so the visible part of the window follows the cursor' -r DC refresh-client -c", - "bind -N 'Resize the pane up by 5' -r M-Up resize-pane -U 5", - "bind -N 'Resize the pane down by 5' -r M-Down resize-pane -D 5", - "bind -N 'Resize the pane left by 5' -r M-Left resize-pane -L 5", - "bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5", - "bind -N 'Resize the pane up' -r C-Up resize-pane -U", - "bind -N 'Resize the pane down' -r C-Down resize-pane -D", - "bind -N 'Resize the pane left' -r C-Left resize-pane -L", - "bind -N 'Resize the pane right' -r C-Right resize-pane -R", + /* Prefix keys. */ + "bind -N 'Send the prefix key' C-b { send-prefix }", + "bind -N 'Rotate through the panes' C-o { rotate-window }", + "bind -N 'Suspend the current client' C-z { suspend-client }", + "bind -N 'Select next layout' Space { next-layout }", + "bind -N 'Break pane to a new window' ! { break-pane }", + "bind -N 'Split window vertically' '\"' { split-window }", + "bind -N 'List all paste buffers' '#' { list-buffers }", + "bind -N 'Rename current session' '$' { command-prompt -I'#S' { rename-session -- '%%' } }", + "bind -N 'Split window horizontally' % { split-window -h }", + "bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }", + "bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }", + "bind -N 'Switch to previous client' ( { switch-client -p }", + "bind -N 'Switch to next client' ) { switch-client -n }", + "bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }", + "bind -N 'Delete the most recent paste buffer' - { delete-buffer }", + "bind -N 'Move the current window' . { command-prompt -T target { move-window -t '%%' } }", + "bind -N 'Describe key binding' '/' { command-prompt -kpkey { list-keys -1N '%%' } }", + "bind -N 'Select window 0' 0 { select-window -t:=0 }", + "bind -N 'Select window 1' 1 { select-window -t:=1 }", + "bind -N 'Select window 2' 2 { select-window -t:=2 }", + "bind -N 'Select window 3' 3 { select-window -t:=3 }", + "bind -N 'Select window 4' 4 { select-window -t:=4 }", + "bind -N 'Select window 5' 5 { select-window -t:=5 }", + "bind -N 'Select window 6' 6 { select-window -t:=6 }", + "bind -N 'Select window 7' 7 { select-window -t:=7 }", + "bind -N 'Select window 8' 8 { select-window -t:=8 }", + "bind -N 'Select window 9' 9 { select-window -t:=9 }", + "bind -N 'Prompt for a command' : { command-prompt }", + "bind -N 'Move to the previously active pane' \\; { last-pane }", + "bind -N 'Choose a paste buffer from a list' = { choose-buffer -Z }", + "bind -N 'List key bindings' ? { list-keys -N }", + "bind -N 'Choose a client from a list' D { choose-client -Z }", + "bind -N 'Spread panes out evenly' E { select-layout -E }", + "bind -N 'Switch to the last client' L { switch-client -l }", + "bind -N 'Clear the marked pane' M { select-pane -M }", + "bind -N 'Enter copy mode' [ { copy-mode }", + "bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }", + "bind -N 'Create a new window' c { new-window }", + "bind -N 'Detach the current client' d { detach-client }", + "bind -N 'Search for a pane' f { command-prompt { find-window -Z -- '%%' } }", + "bind -N 'Display window information' i { display-message }", + "bind -N 'Select the previously current window' l { last-window }", + "bind -N 'Toggle the marked pane' m { select-pane -m }", + "bind -N 'Select the next window' n { next-window }", + "bind -N 'Select the next pane' o { select-pane -t:.+ }", + "bind -N 'Customize options' C { customize-mode -Z }", + "bind -N 'Select the previous window' p { previous-window }", + "bind -N 'Display pane numbers' q { display-panes }", + "bind -N 'Redraw the current client' r { refresh-client }", + "bind -N 'Choose a session from a list' s { choose-tree -Zs }", + "bind -N 'Show a clock' t { clock-mode }", + "bind -N 'Choose a window from a list' w { choose-tree -Zw }", + "bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }", + "bind -N 'Zoom the active pane' z { resize-pane -Z }", + "bind -N 'Swap the active pane with the pane above' '{' { swap-pane -U }", + "bind -N 'Swap the active pane with the pane below' '}' { swap-pane -D }", + "bind -N 'Show messages' '~' { show-messages }", + "bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }", + "bind -N 'Select the pane above the active pane' -r Up { select-pane -U }", + "bind -N 'Select the pane below the active pane' -r Down { select-pane -D }", + "bind -N 'Select the pane to the left of the active pane' -r Left { select-pane -L }", + "bind -N 'Select the pane to the right of the active pane' -r Right { select-pane -R }", + "bind -N 'Set the even-horizontal layout' M-1 { select-layout even-horizontal }", + "bind -N 'Set the even-vertical layout' M-2 { select-layout even-vertical }", + "bind -N 'Set the main-horizontal layout' M-3 { select-layout main-horizontal }", + "bind -N 'Set the main-vertical layout' M-4 { select-layout main-vertical }", + "bind -N 'Select the tiled layout' M-5 { select-layout tiled }", + "bind -N 'Select the next window with an alert' M-n { next-window -a }", + "bind -N 'Rotate through the panes in reverse' M-o { rotate-window -D }", + "bind -N 'Select the previous window with an alert' M-p { previous-window -a }", + "bind -N 'Move the visible part of the window up' -r S-Up { refresh-client -U 10 }", + "bind -N 'Move the visible part of the window down' -r S-Down { refresh-client -D 10 }", + "bind -N 'Move the visible part of the window left' -r S-Left { refresh-client -L 10 }", + "bind -N 'Move the visible part of the window right' -r S-Right { refresh-client -R 10 }", + "bind -N 'Reset so the visible part of the window follows the cursor' -r DC { refresh-client -c }", + "bind -N 'Resize the pane up by 5' -r M-Up { resize-pane -U 5 }", + "bind -N 'Resize the pane down by 5' -r M-Down { resize-pane -D 5 }", + "bind -N 'Resize the pane left by 5' -r M-Left { resize-pane -L 5 }", + "bind -N 'Resize the pane right by 5' -r M-Right { resize-pane -R 5 }", + "bind -N 'Resize the pane up' -r C-Up { resize-pane -U }", + "bind -N 'Resize the pane down' -r C-Down { resize-pane -D }", + "bind -N 'Resize the pane left' -r C-Left { resize-pane -L }", + "bind -N 'Resize the pane right' -r C-Right { resize-pane -R }", - "bind -n MouseDown1Pane select-pane -t=\\; send-keys -M", - "bind -n MouseDrag1Border resize-pane -M", - "bind -n MouseDown1Status select-window -t=", - "bind -n WheelDownStatus next-window", - "bind -n WheelUpStatus previous-window", - "bind -n MouseDrag1Pane if -Ft= '#{mouse_any_flag}' 'if -Ft= \"#{pane_in_mode}\" \"copy-mode -M\" \"send-keys -M\"' 'copy-mode -M'", - "bind -n WheelUpPane if -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= \"#{pane_in_mode}\" \"send-keys -M\" \"copy-mode -et=\"'", + /* Menu keys */ + "bind < { display-menu -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU " }", + "bind > { display-menu -xP -yP -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", - "bind -n MouseDown3StatusLeft display-menu -t= -xM -yS -T \"#[align=centre]#{session_name}\" " DEFAULT_SESSION_MENU, - "bind -n MouseDown3Status display-menu -t= -xW -yS -T \"#[align=centre]#{window_index}:#{window_name}\" " DEFAULT_WINDOW_MENU, - "bind < display-menu -xW -yS -T \"#[align=centre]#{window_index}:#{window_name}\" " DEFAULT_WINDOW_MENU, - "bind -n MouseDown3Pane if -Ft= '#{||:#{mouse_any_flag},#{pane_in_mode}}' 'select-pane -t=; send-keys -M' {display-menu -t= -xM -yM -T \"#[align=centre]#{pane_index} (#{pane_id})\" " DEFAULT_PANE_MENU "}", - "bind -n M-MouseDown3Pane display-menu -t= -xM -yM -T \"#[align=centre]#{pane_index} (#{pane_id})\" " DEFAULT_PANE_MENU, - "bind > display-menu -xP -yP -T \"#[align=centre]#{pane_index} (#{pane_id})\" " DEFAULT_PANE_MENU, + /* Mouse button 1 down on pane. */ + "bind -n MouseDown1Pane { select-pane -t=; send -M }", - "bind -Tcopy-mode C-Space send -X begin-selection", - "bind -Tcopy-mode C-a send -X start-of-line", - "bind -Tcopy-mode C-c send -X cancel", - "bind -Tcopy-mode C-e send -X end-of-line", - "bind -Tcopy-mode C-f send -X cursor-right", - "bind -Tcopy-mode C-b send -X cursor-left", - "bind -Tcopy-mode C-g send -X clear-selection", - "bind -Tcopy-mode C-k send -X copy-end-of-line", - "bind -Tcopy-mode C-n send -X cursor-down", - "bind -Tcopy-mode C-p send -X cursor-up", - "bind -Tcopy-mode C-r command-prompt -ip'(search up)' -I'#{pane_search_string}' 'send -X search-backward-incremental \"%%%\"'", - "bind -Tcopy-mode C-s command-prompt -ip'(search down)' -I'#{pane_search_string}' 'send -X search-forward-incremental \"%%%\"'", - "bind -Tcopy-mode C-v send -X page-down", - "bind -Tcopy-mode C-w send -X copy-selection-and-cancel", - "bind -Tcopy-mode Escape send -X cancel", - "bind -Tcopy-mode Space send -X page-down", - "bind -Tcopy-mode , send -X jump-reverse", - "bind -Tcopy-mode \\; send -X jump-again", - "bind -Tcopy-mode F command-prompt -1p'(jump backward)' 'send -X jump-backward \"%%%\"'", - "bind -Tcopy-mode N send -X search-reverse", - "bind -Tcopy-mode R send -X rectangle-toggle", - "bind -Tcopy-mode T command-prompt -1p'(jump to backward)' 'send -X jump-to-backward \"%%%\"'", - "bind -Tcopy-mode f command-prompt -1p'(jump forward)' 'send -X jump-forward \"%%%\"'", - "bind -Tcopy-mode g command-prompt -p'(goto line)' 'send -X goto-line \"%%%\"'", - "bind -Tcopy-mode n send -X search-again", - "bind -Tcopy-mode q send -X cancel", - "bind -Tcopy-mode t command-prompt -1p'(jump to forward)' 'send -X jump-to-forward \"%%%\"'", - "bind -Tcopy-mode Home send -X start-of-line", - "bind -Tcopy-mode End send -X end-of-line", + /* Mouse button 1 drag on pane. */ + "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", + + /* Mouse wheel up on pane. */ + "bind -n WheelUpPane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }", + + /* Mouse button 2 down on pane. */ + "bind -n MouseDown2Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { paste -p } }", + + /* Mouse button 1 double click on pane. */ + "bind -n DoubleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel } }", + + /* Mouse button 1 triple click on pane. */ + "bind -n TripleClick1Pane { select-pane -t=; if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -H; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel } }", + + /* Mouse button 1 drag on border. */ + "bind -n MouseDrag1Border { resize-pane -M }", + + /* Mouse button 1 down on status line. */ + "bind -n MouseDown1Status { select-window -t= }", + + /* Mouse wheel down on status line. */ + "bind -n WheelDownStatus { next-window }", + + /* Mouse wheel up on status line. */ + "bind -n WheelUpStatus { previous-window }", + + /* Mouse button 3 down on status left. */ + "bind -n MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", + + /* Mouse button 3 down on status line. */ + "bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", + + /* Mouse button 3 down on pane. */ + "bind -n MouseDown3Pane { if -Ft= '#{||:#{mouse_any_flag},#{&&:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}' { select-pane -t=; send -M } { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " } }", + "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", + + /* Copy mode (emacs) keys. */ + "bind -Tcopy-mode C-Space { send -X begin-selection }", + "bind -Tcopy-mode C-a { send -X start-of-line }", + "bind -Tcopy-mode C-c { send -X cancel }", + "bind -Tcopy-mode C-e { send -X end-of-line }", + "bind -Tcopy-mode C-f { send -X cursor-right }", + "bind -Tcopy-mode C-b { send -X cursor-left }", + "bind -Tcopy-mode C-g { send -X clear-selection }", + "bind -Tcopy-mode C-k { send -X copy-pipe-end-of-line-and-cancel }", + "bind -Tcopy-mode C-n { send -X cursor-down }", + "bind -Tcopy-mode C-p { send -X cursor-up }", + "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental '%%' } }", + "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental '%%' } }", + "bind -Tcopy-mode C-v { send -X page-down }", + "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode Escape { send -X cancel }", + "bind -Tcopy-mode Space { send -X page-down }", + "bind -Tcopy-mode , { send -X jump-reverse }", + "bind -Tcopy-mode \\; { send -X jump-again }", + "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }", + "bind -Tcopy-mode N { send -X search-reverse }", + "bind -Tcopy-mode R { send -X rectangle-toggle }", + "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }", + "bind -Tcopy-mode X { send -X set-mark }", + "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }", + "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line '%%' } }", + "bind -Tcopy-mode n { send -X search-again }", + "bind -Tcopy-mode q { send -X cancel }", + "bind -Tcopy-mode r { send -X refresh-from-pane }", + "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }", + "bind -Tcopy-mode Home { send -X start-of-line }", + "bind -Tcopy-mode End { send -X end-of-line }", "bind -Tcopy-mode MouseDown1Pane select-pane", - "bind -Tcopy-mode MouseDrag1Pane select-pane\\; send -X begin-selection", - "bind -Tcopy-mode MouseDragEnd1Pane send -X copy-selection-and-cancel", - "bind -Tcopy-mode WheelUpPane select-pane\\; send -N5 -X scroll-up", - "bind -Tcopy-mode WheelDownPane select-pane\\; send -N5 -X scroll-down", - "bind -Tcopy-mode DoubleClick1Pane select-pane\\; send -X select-word", - "bind -Tcopy-mode TripleClick1Pane select-pane\\; send -X select-line", - "bind -Tcopy-mode NPage send -X page-down", - "bind -Tcopy-mode PPage send -X page-up", - "bind -Tcopy-mode Up send -X cursor-up", - "bind -Tcopy-mode Down send -X cursor-down", - "bind -Tcopy-mode Left send -X cursor-left", - "bind -Tcopy-mode Right send -X cursor-right", - "bind -Tcopy-mode M-1 command-prompt -Np'(repeat)' -I1 'send -N \"%%%\"'", - "bind -Tcopy-mode M-2 command-prompt -Np'(repeat)' -I2 'send -N \"%%%\"'", - "bind -Tcopy-mode M-3 command-prompt -Np'(repeat)' -I3 'send -N \"%%%\"'", - "bind -Tcopy-mode M-4 command-prompt -Np'(repeat)' -I4 'send -N \"%%%\"'", - "bind -Tcopy-mode M-5 command-prompt -Np'(repeat)' -I5 'send -N \"%%%\"'", - "bind -Tcopy-mode M-6 command-prompt -Np'(repeat)' -I6 'send -N \"%%%\"'", - "bind -Tcopy-mode M-7 command-prompt -Np'(repeat)' -I7 'send -N \"%%%\"'", - "bind -Tcopy-mode M-8 command-prompt -Np'(repeat)' -I8 'send -N \"%%%\"'", - "bind -Tcopy-mode M-9 command-prompt -Np'(repeat)' -I9 'send -N \"%%%\"'", - "bind -Tcopy-mode M-< send -X history-top", - "bind -Tcopy-mode M-> send -X history-bottom", - "bind -Tcopy-mode M-R send -X top-line", - "bind -Tcopy-mode M-b send -X previous-word", - "bind -Tcopy-mode C-M-b send -X previous-matching-bracket", - "bind -Tcopy-mode M-f send -X next-word-end", - "bind -Tcopy-mode C-M-f send -X next-matching-bracket", - "bind -Tcopy-mode M-m send -X back-to-indentation", - "bind -Tcopy-mode M-r send -X middle-line", - "bind -Tcopy-mode M-v send -X page-up", - "bind -Tcopy-mode M-w send -X copy-selection-and-cancel", - "bind -Tcopy-mode 'M-{' send -X previous-paragraph", - "bind -Tcopy-mode 'M-}' send -X next-paragraph", - "bind -Tcopy-mode M-Up send -X halfpage-up", - "bind -Tcopy-mode M-Down send -X halfpage-down", - "bind -Tcopy-mode C-Up send -X scroll-up", - "bind -Tcopy-mode C-Down send -X scroll-down", + "bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }", + "bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }", + "bind -Tcopy-mode WheelDownPane { select-pane; send -N5 -X scroll-down }", + "bind -Tcopy-mode DoubleClick1Pane { select-pane; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode TripleClick1Pane { select-pane; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode NPage { send -X page-down }", + "bind -Tcopy-mode PPage { send -X page-up }", + "bind -Tcopy-mode Up { send -X cursor-up }", + "bind -Tcopy-mode Down { send -X cursor-down }", + "bind -Tcopy-mode Left { send -X cursor-left }", + "bind -Tcopy-mode Right { send -X cursor-right }", + "bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode M-< { send -X history-top }", + "bind -Tcopy-mode M-> { send -X history-bottom }", + "bind -Tcopy-mode M-R { send -X top-line }", + "bind -Tcopy-mode M-b { send -X previous-word }", + "bind -Tcopy-mode C-M-b { send -X previous-matching-bracket }", + "bind -Tcopy-mode M-f { send -X next-word-end }", + "bind -Tcopy-mode C-M-f { send -X next-matching-bracket }", + "bind -Tcopy-mode M-m { send -X back-to-indentation }", + "bind -Tcopy-mode M-r { send -X middle-line }", + "bind -Tcopy-mode M-v { send -X page-up }", + "bind -Tcopy-mode M-w { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode M-x { send -X jump-to-mark }", + "bind -Tcopy-mode 'M-{' { send -X previous-paragraph }", + "bind -Tcopy-mode 'M-}' { send -X next-paragraph }", + "bind -Tcopy-mode M-Up { send -X halfpage-up }", + "bind -Tcopy-mode M-Down { send -X halfpage-down }", + "bind -Tcopy-mode C-Up { send -X scroll-up }", + "bind -Tcopy-mode C-Down { send -X scroll-down }", - "bind -Tcopy-mode-vi '#' send -FX search-backward '#{copy_cursor_word}'", - "bind -Tcopy-mode-vi * send -FX search-forward '#{copy_cursor_word}'", - "bind -Tcopy-mode-vi C-c send -X cancel", - "bind -Tcopy-mode-vi C-d send -X halfpage-down", - "bind -Tcopy-mode-vi C-e send -X scroll-down", - "bind -Tcopy-mode-vi C-b send -X page-up", - "bind -Tcopy-mode-vi C-f send -X page-down", - "bind -Tcopy-mode-vi C-h send -X cursor-left", - "bind -Tcopy-mode-vi C-j send -X copy-selection-and-cancel", - "bind -Tcopy-mode-vi Enter send -X copy-selection-and-cancel", - "bind -Tcopy-mode-vi C-u send -X halfpage-up", - "bind -Tcopy-mode-vi C-v send -X rectangle-toggle", - "bind -Tcopy-mode-vi C-y send -X scroll-up", - "bind -Tcopy-mode-vi Escape send -X clear-selection", - "bind -Tcopy-mode-vi Space send -X begin-selection", - "bind -Tcopy-mode-vi '$' send -X end-of-line", - "bind -Tcopy-mode-vi , send -X jump-reverse", - "bind -Tcopy-mode-vi / command-prompt -p'(search down)' 'send -X search-forward \"%%%\"'", - "bind -Tcopy-mode-vi 0 send -X start-of-line", - "bind -Tcopy-mode-vi 1 command-prompt -Np'(repeat)' -I1 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 2 command-prompt -Np'(repeat)' -I2 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 3 command-prompt -Np'(repeat)' -I3 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 4 command-prompt -Np'(repeat)' -I4 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 5 command-prompt -Np'(repeat)' -I5 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 6 command-prompt -Np'(repeat)' -I6 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 7 command-prompt -Np'(repeat)' -I7 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 8 command-prompt -Np'(repeat)' -I8 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi 9 command-prompt -Np'(repeat)' -I9 'send -N \"%%%\"'", - "bind -Tcopy-mode-vi : command-prompt -p'(goto line)' 'send -X goto-line \"%%%\"'", - "bind -Tcopy-mode-vi \\; send -X jump-again", - "bind -Tcopy-mode-vi ? command-prompt -p'(search up)' 'send -X search-backward \"%%%\"'", - "bind -Tcopy-mode-vi A send -X append-selection-and-cancel", - "bind -Tcopy-mode-vi B send -X previous-space", - "bind -Tcopy-mode-vi D send -X copy-end-of-line", - "bind -Tcopy-mode-vi E send -X next-space-end", - "bind -Tcopy-mode-vi F command-prompt -1p'(jump backward)' 'send -X jump-backward \"%%%\"'", - "bind -Tcopy-mode-vi G send -X history-bottom", - "bind -Tcopy-mode-vi H send -X top-line", - "bind -Tcopy-mode-vi J send -X scroll-down", - "bind -Tcopy-mode-vi K send -X scroll-up", - "bind -Tcopy-mode-vi L send -X bottom-line", - "bind -Tcopy-mode-vi M send -X middle-line", - "bind -Tcopy-mode-vi N send -X search-reverse", - "bind -Tcopy-mode-vi T command-prompt -1p'(jump to backward)' 'send -X jump-to-backward \"%%%\"'", - "bind -Tcopy-mode-vi V send -X select-line", - "bind -Tcopy-mode-vi W send -X next-space", - "bind -Tcopy-mode-vi ^ send -X back-to-indentation", - "bind -Tcopy-mode-vi b send -X previous-word", - "bind -Tcopy-mode-vi e send -X next-word-end", - "bind -Tcopy-mode-vi f command-prompt -1p'(jump forward)' 'send -X jump-forward \"%%%\"'", - "bind -Tcopy-mode-vi g send -X history-top", - "bind -Tcopy-mode-vi h send -X cursor-left", - "bind -Tcopy-mode-vi j send -X cursor-down", - "bind -Tcopy-mode-vi k send -X cursor-up", - "bind -Tcopy-mode-vi l send -X cursor-right", - "bind -Tcopy-mode-vi n send -X search-again", - "bind -Tcopy-mode-vi o send -X other-end", - "bind -Tcopy-mode-vi q send -X cancel", - "bind -Tcopy-mode-vi t command-prompt -1p'(jump to forward)' 'send -X jump-to-forward \"%%%\"'", - "bind -Tcopy-mode-vi v send -X rectangle-toggle", - "bind -Tcopy-mode-vi w send -X next-word", - "bind -Tcopy-mode-vi '{' send -X previous-paragraph", - "bind -Tcopy-mode-vi '}' send -X next-paragraph", - "bind -Tcopy-mode-vi % send -X next-matching-bracket", - "bind -Tcopy-mode-vi MouseDown1Pane select-pane", - "bind -Tcopy-mode-vi MouseDrag1Pane select-pane\\; send -X begin-selection", - "bind -Tcopy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel", - "bind -Tcopy-mode-vi WheelUpPane select-pane\\; send -N5 -X scroll-up", - "bind -Tcopy-mode-vi WheelDownPane select-pane\\; send -N5 -X scroll-down", - "bind -Tcopy-mode-vi DoubleClick1Pane select-pane\\; send -X select-word", - "bind -Tcopy-mode-vi TripleClick1Pane select-pane\\; send -X select-line", - "bind -Tcopy-mode-vi BSpace send -X cursor-left", - "bind -Tcopy-mode-vi NPage send -X page-down", - "bind -Tcopy-mode-vi PPage send -X page-up", - "bind -Tcopy-mode-vi Up send -X cursor-up", - "bind -Tcopy-mode-vi Down send -X cursor-down", - "bind -Tcopy-mode-vi Left send -X cursor-left", - "bind -Tcopy-mode-vi Right send -X cursor-right", - "bind -Tcopy-mode-vi C-Up send -X scroll-up", - "bind -Tcopy-mode-vi C-Down send -X scroll-down", + /* Copy mode (vi) keys. */ + "bind -Tcopy-mode-vi '#' { send -FX search-backward '#{copy_cursor_word}' }", + "bind -Tcopy-mode-vi * { send -FX search-forward '#{copy_cursor_word}' }", + "bind -Tcopy-mode-vi C-c { send -X cancel }", + "bind -Tcopy-mode-vi C-d { send -X halfpage-down }", + "bind -Tcopy-mode-vi C-e { send -X scroll-down }", + "bind -Tcopy-mode-vi C-b { send -X page-up }", + "bind -Tcopy-mode-vi C-f { send -X page-down }", + "bind -Tcopy-mode-vi C-h { send -X cursor-left }", + "bind -Tcopy-mode-vi C-j { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode-vi Enter { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode-vi C-u { send -X halfpage-up }", + "bind -Tcopy-mode-vi C-v { send -X rectangle-toggle }", + "bind -Tcopy-mode-vi C-y { send -X scroll-up }", + "bind -Tcopy-mode-vi Escape { send -X clear-selection }", + "bind -Tcopy-mode-vi Space { send -X begin-selection }", + "bind -Tcopy-mode-vi '$' { send -X end-of-line }", + "bind -Tcopy-mode-vi , { send -X jump-reverse }", + "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward '%%' } }", + "bind -Tcopy-mode-vi 0 { send -X start-of-line }", + "bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }", + "bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }", + "bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }", + "bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }", + "bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }", + "bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }", + "bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }", + "bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }", + "bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }", + "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line '%%' } }", + "bind -Tcopy-mode-vi \\; { send -X jump-again }", + "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward '%%' } }", + "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }", + "bind -Tcopy-mode-vi B { send -X previous-space }", + "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }", + "bind -Tcopy-mode-vi E { send -X next-space-end }", + "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }", + "bind -Tcopy-mode-vi G { send -X history-bottom }", + "bind -Tcopy-mode-vi H { send -X top-line }", + "bind -Tcopy-mode-vi J { send -X scroll-down }", + "bind -Tcopy-mode-vi K { send -X scroll-up }", + "bind -Tcopy-mode-vi L { send -X bottom-line }", + "bind -Tcopy-mode-vi M { send -X middle-line }", + "bind -Tcopy-mode-vi N { send -X search-reverse }", + "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }", + "bind -Tcopy-mode-vi V { send -X select-line }", + "bind -Tcopy-mode-vi W { send -X next-space }", + "bind -Tcopy-mode-vi X { send -X set-mark }", + "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }", + "bind -Tcopy-mode-vi b { send -X previous-word }", + "bind -Tcopy-mode-vi e { send -X next-word-end }", + "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }", + "bind -Tcopy-mode-vi g { send -X history-top }", + "bind -Tcopy-mode-vi h { send -X cursor-left }", + "bind -Tcopy-mode-vi j { send -X cursor-down }", + "bind -Tcopy-mode-vi k { send -X cursor-up }", + "bind -Tcopy-mode-vi l { send -X cursor-right }", + "bind -Tcopy-mode-vi n { send -X search-again }", + "bind -Tcopy-mode-vi o { send -X other-end }", + "bind -Tcopy-mode-vi q { send -X cancel }", + "bind -Tcopy-mode-vi r { send -X refresh-from-pane }", + "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }", + "bind -Tcopy-mode-vi v { send -X rectangle-toggle }", + "bind -Tcopy-mode-vi w { send -X next-word }", + "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }", + "bind -Tcopy-mode-vi '}' { send -X next-paragraph }", + "bind -Tcopy-mode-vi % { send -X next-matching-bracket }", + "bind -Tcopy-mode-vi MouseDown1Pane { select-pane }", + "bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }", + "bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }", + "bind -Tcopy-mode-vi WheelDownPane { select-pane; send -N5 -X scroll-down }", + "bind -Tcopy-mode-vi DoubleClick1Pane { select-pane; send -X select-word; run -d0.3; send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode-vi TripleClick1Pane { select-pane; send -X select-line; run -d0.3; send -X copy-pipe-and-cancel }", + "bind -Tcopy-mode-vi BSpace { send -X cursor-left }", + "bind -Tcopy-mode-vi NPage { send -X page-down }", + "bind -Tcopy-mode-vi PPage { send -X page-up }", + "bind -Tcopy-mode-vi Up { send -X cursor-up }", + "bind -Tcopy-mode-vi Down { send -X cursor-down }", + "bind -Tcopy-mode-vi Left { send -X cursor-left }", + "bind -Tcopy-mode-vi Right { send -X cursor-right }", + "bind -Tcopy-mode-vi M-x { send -X jump-to-mark }", + "bind -Tcopy-mode-vi C-Up { send -X scroll-up }", + "bind -Tcopy-mode-vi C-Down { send -X scroll-down }", }; u_int i; struct cmd_parse_result *pr; for (i = 0; i < nitems(defaults); i++) { pr = cmd_parse_from_string(defaults[i], NULL); - if (pr->status != CMD_PARSE_SUCCESS) + if (pr->status != CMD_PARSE_SUCCESS) { + log_debug("%s", pr->error); fatalx("bad default key: %s", defaults[i]); - cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL, NULL, 0)); + } + cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL)); cmd_list_free(pr->cmdlist); } + cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL)); } static enum cmd_retval @@ -498,27 +653,24 @@ key_bindings_read_only(struct cmdq_item *item, __unused void *data) struct cmdq_item * key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, - struct client *c, struct mouse_event *m, struct cmd_find_state *fs) + struct client *c, struct key_event *event, struct cmd_find_state *fs) { - struct cmd *cmd; struct cmdq_item *new_item; - int readonly; + struct cmdq_state *new_state; + int readonly, flags = 0; if (c == NULL || (~c->flags & CLIENT_READONLY)) readonly = 1; - else { - readonly = 1; - TAILQ_FOREACH(cmd, &bd->cmdlist->list, qentry) { - if (~cmd->entry->flags & CMD_READONLY) - readonly = 0; - } - } + else + readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY); if (!readonly) new_item = cmdq_get_callback(key_bindings_read_only, NULL); else { - new_item = cmdq_get_command(bd->cmdlist, fs, m, 0); if (bd->flags & KEY_BINDING_REPEAT) - new_item->shared->flags |= CMDQ_SHARED_REPEAT; + flags |= CMDQ_STATE_REPEAT; + new_state = cmdq_new_state(fs, event, flags); + new_item = cmdq_get_command(bd->cmdlist, new_state); + cmdq_free_state(new_state); } if (item != NULL) new_item = cmdq_insert_after(item, new_item); diff --git a/key-string.c b/key-string.c index d2b31e03..4f7be858 100644 --- a/key-string.c +++ b/key-string.c @@ -18,7 +18,9 @@ #include +#include #include +#include #include "tmux.h" @@ -30,30 +32,30 @@ static const struct { key_code key; } key_string_table[] = { /* Function keys. */ - { "F1", KEYC_F1 }, - { "F2", KEYC_F2 }, - { "F3", KEYC_F3 }, - { "F4", KEYC_F4 }, - { "F5", KEYC_F5 }, - { "F6", KEYC_F6 }, - { "F7", KEYC_F7 }, - { "F8", KEYC_F8 }, - { "F9", KEYC_F9 }, - { "F10", KEYC_F10 }, - { "F11", KEYC_F11 }, - { "F12", KEYC_F12 }, - { "IC", KEYC_IC }, - { "Insert", KEYC_IC }, - { "DC", KEYC_DC }, - { "Delete", KEYC_DC }, - { "Home", KEYC_HOME }, - { "End", KEYC_END }, - { "NPage", KEYC_NPAGE }, - { "PageDown", KEYC_NPAGE }, - { "PgDn", KEYC_NPAGE }, - { "PPage", KEYC_PPAGE }, - { "PageUp", KEYC_PPAGE }, - { "PgUp", KEYC_PPAGE }, + { "F1", KEYC_F1|KEYC_IMPLIED_META }, + { "F2", KEYC_F2|KEYC_IMPLIED_META }, + { "F3", KEYC_F3|KEYC_IMPLIED_META }, + { "F4", KEYC_F4|KEYC_IMPLIED_META }, + { "F5", KEYC_F5|KEYC_IMPLIED_META }, + { "F6", KEYC_F6|KEYC_IMPLIED_META }, + { "F7", KEYC_F7|KEYC_IMPLIED_META }, + { "F8", KEYC_F8|KEYC_IMPLIED_META }, + { "F9", KEYC_F9|KEYC_IMPLIED_META }, + { "F10", KEYC_F10|KEYC_IMPLIED_META }, + { "F11", KEYC_F11|KEYC_IMPLIED_META }, + { "F12", KEYC_F12|KEYC_IMPLIED_META }, + { "IC", KEYC_IC|KEYC_IMPLIED_META }, + { "Insert", KEYC_IC|KEYC_IMPLIED_META }, + { "DC", KEYC_DC|KEYC_IMPLIED_META }, + { "Delete", KEYC_DC|KEYC_IMPLIED_META }, + { "Home", KEYC_HOME|KEYC_IMPLIED_META }, + { "End", KEYC_END|KEYC_IMPLIED_META }, + { "NPage", KEYC_NPAGE|KEYC_IMPLIED_META }, + { "PageDown", KEYC_NPAGE|KEYC_IMPLIED_META }, + { "PgDn", KEYC_NPAGE|KEYC_IMPLIED_META }, + { "PPage", KEYC_PPAGE|KEYC_IMPLIED_META }, + { "PageUp", KEYC_PPAGE|KEYC_IMPLIED_META }, + { "PgUp", KEYC_PPAGE|KEYC_IMPLIED_META }, { "Tab", '\011' }, { "BTab", KEYC_BTAB }, { "Space", ' ' }, @@ -62,28 +64,28 @@ static const struct { { "Escape", '\033' }, /* Arrow keys. */ - { "Up", KEYC_UP }, - { "Down", KEYC_DOWN }, - { "Left", KEYC_LEFT }, - { "Right", KEYC_RIGHT }, + { "Up", KEYC_UP|KEYC_CURSOR|KEYC_IMPLIED_META }, + { "Down", KEYC_DOWN|KEYC_CURSOR|KEYC_IMPLIED_META }, + { "Left", KEYC_LEFT|KEYC_CURSOR|KEYC_IMPLIED_META }, + { "Right", KEYC_RIGHT|KEYC_CURSOR|KEYC_IMPLIED_META }, /* Numeric keypad. */ - { "KP/", KEYC_KP_SLASH }, - { "KP*", KEYC_KP_STAR }, - { "KP-", KEYC_KP_MINUS }, - { "KP7", KEYC_KP_SEVEN }, - { "KP8", KEYC_KP_EIGHT }, - { "KP9", KEYC_KP_NINE }, - { "KP+", KEYC_KP_PLUS }, - { "KP4", KEYC_KP_FOUR }, - { "KP5", KEYC_KP_FIVE }, - { "KP6", KEYC_KP_SIX }, - { "KP1", KEYC_KP_ONE }, - { "KP2", KEYC_KP_TWO }, - { "KP3", KEYC_KP_THREE }, - { "KPEnter", KEYC_KP_ENTER }, - { "KP0", KEYC_KP_ZERO }, - { "KP.", KEYC_KP_PERIOD }, + { "KP/", KEYC_KP_SLASH|KEYC_KEYPAD }, + { "KP*", KEYC_KP_STAR|KEYC_KEYPAD }, + { "KP-", KEYC_KP_MINUS|KEYC_KEYPAD }, + { "KP7", KEYC_KP_SEVEN|KEYC_KEYPAD }, + { "KP8", KEYC_KP_EIGHT|KEYC_KEYPAD }, + { "KP9", KEYC_KP_NINE|KEYC_KEYPAD }, + { "KP+", KEYC_KP_PLUS|KEYC_KEYPAD }, + { "KP4", KEYC_KP_FOUR|KEYC_KEYPAD }, + { "KP5", KEYC_KP_FIVE|KEYC_KEYPAD }, + { "KP6", KEYC_KP_SIX|KEYC_KEYPAD }, + { "KP1", KEYC_KP_ONE|KEYC_KEYPAD }, + { "KP2", KEYC_KP_TWO|KEYC_KEYPAD }, + { "KP3", KEYC_KP_THREE|KEYC_KEYPAD }, + { "KPEnter", KEYC_KP_ENTER|KEYC_KEYPAD }, + { "KP0", KEYC_KP_ZERO|KEYC_KEYPAD }, + { "KP.", KEYC_KP_PERIOD|KEYC_KEYPAD }, /* Mouse keys. */ KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1), @@ -100,6 +102,9 @@ static const struct { KEYC_MOUSE_STRING(MOUSEDRAGEND3, MouseDragEnd3), KEYC_MOUSE_STRING(WHEELUP, WheelUp), KEYC_MOUSE_STRING(WHEELDOWN, WheelDown), + KEYC_MOUSE_STRING(SECONDCLICK1, SecondClick1), + KEYC_MOUSE_STRING(SECONDCLICK2, SecondClick2), + KEYC_MOUSE_STRING(SECONDCLICK3, SecondClick3), KEYC_MOUSE_STRING(DOUBLECLICK1, DoubleClick1), KEYC_MOUSE_STRING(DOUBLECLICK2, DoubleClick2), KEYC_MOUSE_STRING(DOUBLECLICK3, DoubleClick3), @@ -140,7 +145,7 @@ key_string_get_modifiers(const char **string) break; case 'M': case 'm': - modifiers |= KEYC_ESCAPE; + modifiers |= KEYC_META; break; case 'S': case 's': @@ -159,14 +164,14 @@ key_string_get_modifiers(const char **string) key_code key_string_lookup_string(const char *string) { - static const char *other = "!#()+,-.0123456789:;<=>'\r\t"; - key_code key; - u_int u; - key_code modifiers; - struct utf8_data ud; - u_int i; + static const char *other = "!#()+,-.0123456789:;<=>'\r\t\177`/"; + key_code key, modifiers; + u_int u, i; + struct utf8_data ud, *udp; enum utf8_state more; - wchar_t wc; + utf8_char uc; + char m[MB_LEN_MAX + 1]; + int mlen; /* Is this no key or any key? */ if (strcasecmp(string, "None") == 0) @@ -176,11 +181,25 @@ key_string_lookup_string(const char *string) /* Is this a hexadecimal value? */ if (string[0] == '0' && string[1] == 'x') { - if (sscanf(string + 2, "%x", &u) != 1) - return (KEYC_UNKNOWN); - if (u > 0x1fffff) - return (KEYC_UNKNOWN); - return (u); + if (sscanf(string + 2, "%x", &u) != 1) + return (KEYC_UNKNOWN); + if (u < 32) + return (u); + mlen = wctomb(m, u); + if (mlen <= 0 || mlen > MB_LEN_MAX) + return (KEYC_UNKNOWN); + m[mlen] = '\0'; + + udp = utf8_fromcstr(m); + if (udp == NULL || + udp[0].size == 0 || + udp[1].size != 0 || + utf8_from_data(&udp[0], &uc) != UTF8_DONE) { + free(udp); + return (KEYC_UNKNOWN); + } + free(udp); + return (uc); } /* Check for modifiers. */ @@ -207,42 +226,48 @@ key_string_lookup_string(const char *string) more = utf8_append(&ud, (u_char)string[i]); if (more != UTF8_DONE) return (KEYC_UNKNOWN); - if (utf8_combine(&ud, &wc) != UTF8_DONE) + if (utf8_from_data(&ud, &uc) != UTF8_DONE) return (KEYC_UNKNOWN); - return (wc | modifiers); + return (uc|modifiers); } /* Otherwise look the key up in the table. */ key = key_string_search_table(string); if (key == KEYC_UNKNOWN) return (KEYC_UNKNOWN); + if (~modifiers & KEYC_META) + key &= ~KEYC_IMPLIED_META; } /* Convert the standard control keys. */ - if (key < KEYC_BASE && (modifiers & KEYC_CTRL) && !strchr(other, key)) { + if (key <= 127 && + (modifiers & KEYC_CTRL) && + strchr(other, key) == NULL && + key != 9 && + key != 13 && + key != 27) { if (key >= 97 && key <= 122) key -= 96; else if (key >= 64 && key <= 95) - key -= 64; + key -= 64; else if (key == 32) key = 0; - else if (key == '?') - key = 127; else if (key == 63) - key = KEYC_BSPACE; + key = 127; else return (KEYC_UNKNOWN); modifiers &= ~KEYC_CTRL; } - return (key | modifiers); + return (key|modifiers); } /* Convert a key code into string format, with prefix if necessary. */ const char * -key_string_lookup_key(key_code key) +key_string_lookup_key(key_code key, int with_flags) { - static char out[32]; + key_code saved = key; + static char out[64]; char tmp[8]; const char *s; u_int i; @@ -254,21 +279,27 @@ key_string_lookup_key(key_code key) /* Literal keys are themselves. */ if (key & KEYC_LITERAL) { snprintf(out, sizeof out, "%c", (int)(key & 0xff)); - return (out); + goto out; } + /* Display C-@ as C-Space. */ + if ((key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) == 0) + key = ' '|KEYC_CTRL; + /* Fill in the modifiers. */ if (key & KEYC_CTRL) strlcat(out, "C-", sizeof out); - if (key & KEYC_ESCAPE) + if (key & KEYC_META) strlcat(out, "M-", sizeof out); if (key & KEYC_SHIFT) strlcat(out, "S-", sizeof out); key &= KEYC_MASK_KEY; /* Handle no key. */ - if (key == KEYC_NONE) - return ("None"); + if (key == KEYC_NONE) { + s = "None"; + goto append; + } /* Handle special keys. */ if (key == KEYC_UNKNOWN) { @@ -326,42 +357,32 @@ key_string_lookup_key(key_code key) if (key >= KEYC_USER && key < KEYC_USER + KEYC_NUSER) { snprintf(tmp, sizeof tmp, "User%u", (u_int)(key - KEYC_USER)); strlcat(out, tmp, sizeof out); - return (out); + goto out; } - /* - * Special case: display C-@ as C-Space. Could do this below in - * the (key >= 0 && key <= 32), but this way we let it be found - * in key_string_table, for the unlikely chance that we might - * change its name. - */ - if ((key & KEYC_MASK_KEY) == 0) - key = ' ' | KEYC_CTRL | (key & KEYC_MASK_MOD); - /* Try the key against the string table. */ for (i = 0; i < nitems(key_string_table); i++) { - if (key == key_string_table[i].key) + if (key == (key_string_table[i].key & KEYC_MASK_KEY)) break; } if (i != nitems(key_string_table)) { strlcat(out, key_string_table[i].string, sizeof out); - return (out); + goto out; } - /* Is this a UTF-8 key? */ - if (key > 127 && key < KEYC_BASE) { - if (utf8_split(key, &ud) == UTF8_DONE) { - off = strlen(out); - memcpy(out + off, ud.data, ud.size); - out[off + ud.size] = '\0'; - return (out); - } + /* Is this a Unicode key? */ + if (KEYC_IS_UNICODE(key)) { + utf8_to_data(key, &ud); + off = strlen(out); + memcpy(out + off, ud.data, ud.size); + out[off + ud.size] = '\0'; + goto out; } /* Invalid keys are errors. */ if (key > 255) { - snprintf(out, sizeof out, "Invalid#%llx", key); - return (out); + snprintf(out, sizeof out, "Invalid#%llx", saved); + goto out; } /* Check for standard or control key. */ @@ -379,9 +400,25 @@ key_string_lookup_key(key_code key) xsnprintf(tmp, sizeof tmp, "\\%llo", key); strlcat(out, tmp, sizeof out); - return (out); + goto out; append: strlcat(out, s, sizeof out); + +out: + if (with_flags && (saved & KEYC_MASK_FLAGS) != 0) { + strlcat(out, "[", sizeof out); + if (saved & KEYC_LITERAL) + strlcat(out, "L", sizeof out); + if (saved & KEYC_KEYPAD) + strlcat(out, "K", sizeof out); + if (saved & KEYC_CURSOR) + strlcat(out, "C", sizeof out); + if (saved & KEYC_IMPLIED_META) + strlcat(out, "I", sizeof out); + if (saved & KEYC_BUILD_MODIFIERS) + strlcat(out, "B", sizeof out); + strlcat(out, "]", sizeof out); + } return (out); } diff --git a/layout-custom.c b/layout-custom.c index 097dabe6..e7fb4253 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -233,7 +233,7 @@ layout_parse(struct window *w, const char *layout) /* Update pane offsets and sizes. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); recalculate_sizes(); layout_print_cell(lc, __func__, 0); diff --git a/layout-set.c b/layout-set.c index f712b059..c702817d 100644 --- a/layout-set.c +++ b/layout-set.c @@ -18,6 +18,7 @@ #include +#include #include #include "tmux.h" @@ -159,7 +160,7 @@ layout_set_even(struct window *w, enum layout_type type) /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); @@ -186,6 +187,8 @@ layout_set_main_h(struct window *w) struct window_pane *wp; struct layout_cell *lc, *lcmain, *lcother, *lcchild; u_int n, mainh, otherh, sx, sy; + char *cause; + const char *s; layout_print_cell(w->layout_root, __func__, 1); @@ -198,8 +201,15 @@ layout_set_main_h(struct window *w) /* Find available height - take off one line for the border. */ sy = w->sy - 1; - /* Get the main pane height and work out the other pane height. */ - mainh = options_get_number(w->options, "main-pane-height"); + /* Get the main pane height. */ + s = options_get_string(w->options, "main-pane-height"); + mainh = args_string_percentage(s, 0, sy, sy, &cause); + if (cause != NULL) { + mainh = 24; + free(cause); + } + + /* Work out the other pane height. */ if (mainh + PANE_MINIMUM >= sy) { if (sy <= PANE_MINIMUM + PANE_MINIMUM) mainh = PANE_MINIMUM; @@ -207,10 +217,12 @@ layout_set_main_h(struct window *w) mainh = sy - PANE_MINIMUM; otherh = PANE_MINIMUM; } else { - otherh = options_get_number(w->options, "other-pane-height"); - if (otherh == 0) + s = options_get_string(w->options, "other-pane-height"); + otherh = args_string_percentage(s, 0, sy, sy, &cause); + if (cause != NULL || otherh == 0) { otherh = sy - mainh; - else if (otherh > sy || sy - otherh < mainh) + free(cause); + } else if (otherh > sy || sy - otherh < mainh) otherh = sy - mainh; else mainh = sy - otherh; @@ -258,7 +270,7 @@ layout_set_main_h(struct window *w) /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); @@ -273,6 +285,8 @@ layout_set_main_v(struct window *w) struct window_pane *wp; struct layout_cell *lc, *lcmain, *lcother, *lcchild; u_int n, mainw, otherw, sx, sy; + char *cause; + const char *s; layout_print_cell(w->layout_root, __func__, 1); @@ -285,8 +299,15 @@ layout_set_main_v(struct window *w) /* Find available width - take off one line for the border. */ sx = w->sx - 1; - /* Get the main pane width and work out the other pane width. */ - mainw = options_get_number(w->options, "main-pane-width"); + /* Get the main pane width. */ + s = options_get_string(w->options, "main-pane-width"); + mainw = args_string_percentage(s, 0, sx, sx, &cause); + if (cause != NULL) { + mainw = 80; + free(cause); + } + + /* Work out the other pane width. */ if (mainw + PANE_MINIMUM >= sx) { if (sx <= PANE_MINIMUM + PANE_MINIMUM) mainw = PANE_MINIMUM; @@ -294,10 +315,12 @@ layout_set_main_v(struct window *w) mainw = sx - PANE_MINIMUM; otherw = PANE_MINIMUM; } else { - otherw = options_get_number(w->options, "other-pane-width"); - if (otherw == 0) + s = options_get_string(w->options, "other-pane-width"); + otherw = args_string_percentage(s, 0, sx, sx, &cause); + if (cause != NULL || otherw == 0) { otherw = sx - mainw; - else if (otherw > sx || sx - otherw < mainw) + free(cause); + } else if (otherw > sx || sx - otherw < mainw) otherw = sx - mainw; else mainw = sx - otherw; @@ -345,7 +368,7 @@ layout_set_main_v(struct window *w) /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); @@ -454,7 +477,7 @@ layout_set_tiled(struct window *w) /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); layout_print_cell(w->layout_root, __func__, 1); diff --git a/layout.c b/layout.c index 37214d02..04a13b0c 100644 --- a/layout.c +++ b/layout.c @@ -286,7 +286,7 @@ layout_add_border(struct window *w, struct layout_cell *lc, int status) /* Update pane offsets and sizes based on their cells. */ void -layout_fix_panes(struct window *w) +layout_fix_panes(struct window *w, struct window_pane *skip) { struct window_pane *wp; struct layout_cell *lc; @@ -294,7 +294,7 @@ layout_fix_panes(struct window *w) status = options_get_number(w->options, "pane-border-status"); TAILQ_FOREACH(wp, &w->panes, entry) { - if ((lc = wp->layout_cell) == NULL) + if ((lc = wp->layout_cell) == NULL || wp == skip) continue; wp->xoff = lc->xoff; @@ -482,7 +482,7 @@ layout_init(struct window *w, struct window_pane *wp) lc = w->layout_root = layout_create_cell(NULL); layout_set_size(lc, w->sx, w->sy, 0, 0); layout_make_leaf(lc, wp); - layout_fix_panes(w); + layout_fix_panes(w, NULL); } void @@ -540,7 +540,7 @@ layout_resize(struct window *w, u_int sx, u_int sy) /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); } /* Resize a pane to an absolute size. */ @@ -600,7 +600,7 @@ layout_resize_layout(struct window *w, struct layout_cell *lc, /* Fix cell offsets. */ layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); notify_window("window-layout-changed", w); } @@ -704,10 +704,14 @@ layout_resize_pane_shrink(struct window *w, struct layout_cell *lc, /* Assign window pane to newly split cell. */ void -layout_assign_pane(struct layout_cell *lc, struct window_pane *wp) +layout_assign_pane(struct layout_cell *lc, struct window_pane *wp, + int do_not_resize) { layout_make_leaf(lc, wp); - layout_fix_panes(wp->window); + if (do_not_resize) + layout_fix_panes(wp->window, wp); + else + layout_fix_panes(wp->window, NULL); } /* Calculate the new pane size for resized parent. */ @@ -1040,7 +1044,7 @@ layout_close_pane(struct window_pane *wp) /* Fix pane offsets and sizes. */ if (w->layout_root != NULL) { layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); } notify_window("window-layout-changed", w); } @@ -1109,7 +1113,7 @@ layout_spread_out(struct window_pane *wp) do { if (layout_spread_cell(w, parent)) { layout_fix_offsets(w); - layout_fix_panes(w); + layout_fix_panes(w, NULL); break; } } while ((parent = parent->parent) != NULL); diff --git a/log.c b/log.c index f87cab92..d4436672 100644 --- a/log.c +++ b/log.c @@ -29,9 +29,6 @@ static FILE *log_file; static int log_level; -static void log_event_cb(int, const char *); -static void log_vwrite(const char *, va_list); - /* Log callback for libevent. */ static void log_event_cb(__unused int severity, const char *msg) @@ -100,28 +97,28 @@ log_close(void) } /* Write a log message. */ -static void -log_vwrite(const char *msg, va_list ap) +static void printflike(1, 0) +log_vwrite(const char *msg, va_list ap, const char *prefix) { - char *fmt, *out; + char *s, *out; struct timeval tv; if (log_file == NULL) return; - if (vasprintf(&fmt, msg, ap) == -1) - exit(1); - if (stravis(&out, fmt, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL) == -1) - exit(1); + if (vasprintf(&s, msg, ap) == -1) + return; + if (stravis(&out, s, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL) == -1) { + free(s); + return; + } + free(s); gettimeofday(&tv, NULL); - if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec, - (int)tv.tv_usec, out) == -1) - exit(1); - fflush(log_file); - + if (fprintf(log_file, "%lld.%06d %s%s\n", (long long)tv.tv_sec, + (int)tv.tv_usec, prefix, out) != -1) + fflush(log_file); free(out); - free(fmt); } /* Log a debug message. */ @@ -134,7 +131,7 @@ log_debug(const char *msg, ...) return; va_start(ap, msg); - log_vwrite(msg, ap); + log_vwrite(msg, ap, ""); va_end(ap); } @@ -142,14 +139,16 @@ log_debug(const char *msg, ...) __dead void fatal(const char *msg, ...) { - char *fmt; + char tmp[256]; va_list ap; + if (snprintf(tmp, sizeof tmp, "fatal: %s: ", strerror(errno)) < 0) + exit (1); + va_start(ap, msg); - if (asprintf(&fmt, "fatal: %s: %s", msg, strerror(errno)) == -1) - exit(1); - log_vwrite(fmt, ap); + log_vwrite(msg, ap, tmp); va_end(ap); + exit(1); } @@ -157,13 +156,11 @@ fatal(const char *msg, ...) __dead void fatalx(const char *msg, ...) { - char *fmt; va_list ap; va_start(ap, msg); - if (asprintf(&fmt, "fatal: %s", msg) == -1) - exit(1); - log_vwrite(fmt, ap); + log_vwrite(msg, ap, "fatal: "); va_end(ap); + exit(1); } diff --git a/logo/icons/128x128/tmux.png b/logo/icons/128x128/tmux.png new file mode 100644 index 00000000..1999ee9c Binary files /dev/null and b/logo/icons/128x128/tmux.png differ diff --git a/logo/icons/16x16/tmux.png b/logo/icons/16x16/tmux.png new file mode 100644 index 00000000..e0cf391b Binary files /dev/null and b/logo/icons/16x16/tmux.png differ diff --git a/logo/icons/24x24/tmux.png b/logo/icons/24x24/tmux.png new file mode 100644 index 00000000..a9a8c999 Binary files /dev/null and b/logo/icons/24x24/tmux.png differ diff --git a/logo/icons/32x32/tmux.png b/logo/icons/32x32/tmux.png new file mode 100644 index 00000000..2fa700cc Binary files /dev/null and b/logo/icons/32x32/tmux.png differ diff --git a/logo/icons/48x48/tmux.png b/logo/icons/48x48/tmux.png new file mode 100644 index 00000000..dc19bcdd Binary files /dev/null and b/logo/icons/48x48/tmux.png differ diff --git a/logo/icons/64x64/tmux.png b/logo/icons/64x64/tmux.png new file mode 100644 index 00000000..c48fd833 Binary files /dev/null and b/logo/icons/64x64/tmux.png differ diff --git a/logo/icons/96x96/tmux.png b/logo/icons/96x96/tmux.png new file mode 100644 index 00000000..5707b808 Binary files /dev/null and b/logo/icons/96x96/tmux.png differ diff --git a/menu.c b/menu.c index 6024ba02..043dafdd 100644 --- a/menu.c +++ b/menu.c @@ -73,7 +73,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, return; if (fs != NULL) - s = format_single(qitem, item->name, c, fs->s, fs->wl, fs->wp); + s = format_single_from_state(qitem, item->name, c, fs); else s = format_single(qitem, item->name, c, NULL, NULL, NULL); if (*s == '\0') { /* no item if empty after format expanded */ @@ -81,7 +81,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, return; } if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) { - key = key_string_lookup_key(item->key); + key = key_string_lookup_key(item->key, 0); xasprintf(&name, "%s#[default] #[align=right](%s)", s, key); } else xasprintf(&name, "%s", s); @@ -91,7 +91,7 @@ menu_add_item(struct menu *menu, const struct menu_item *item, cmd = item->command; if (cmd != NULL) { if (fs != NULL) - s = format_single(qitem, cmd, c, fs->s, fs->wl, fs->wp); + s = format_single_from_state(qitem, cmd, c, fs); else s = format_single(qitem, cmd, c, NULL, NULL, NULL); } else @@ -111,6 +111,7 @@ menu_create(const char *title) menu = xcalloc(1, sizeof *menu); menu->title = xstrdup(title); + menu->width = format_width(title); return (menu); } @@ -130,35 +131,57 @@ menu_free(struct menu *menu) free(menu); } -static void -menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) +struct screen * +menu_mode_cb(__unused struct client *c, void *data, __unused u_int *cx, + __unused u_int *cy) { - struct menu_data *md = c->overlay_data; + struct menu_data *md = data; + + return (&md->s); +} + +int +menu_check_cb(__unused struct client *c, void *data, u_int px, u_int py) +{ + struct menu_data *md = data; + struct menu *menu = md->menu; + + if (px < md->px || px > md->px + menu->width + 3) + return (1); + if (py < md->py || py > md->py + menu->count + 1) + return (1); + return (0); +} + +void +menu_draw_cb(struct client *c, void *data, + __unused struct screen_redraw_ctx *rctx) +{ + 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, py; + u_int i, px = md->px, py = md->py; + struct grid_cell gc; - screen_write_start(&ctx, NULL, s); + style_apply(&gc, c->session->curw->window->options, "mode-style", NULL); + + screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); - screen_write_menu(&ctx, menu, md->choice); + screen_write_menu(&ctx, menu, md->choice, &gc); screen_write_stop(&ctx); - px = md->px; - py = md->py; - - for (i = 0; i < screen_size_y(&md->s); i++) - tty_draw_line(tty, NULL, s, 0, i, menu->width + 4, px, py + i); - - if (~md->flags & MENU_NOMOUSE) - tty_update_mode(tty, MODE_MOUSE_ALL, NULL); + for (i = 0; i < screen_size_y(&md->s); i++) { + tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, + &grid_default_cell, NULL); + } } -static void -menu_free_cb(struct client *c) +void +menu_free_cb(__unused struct client *c, void *data) { - struct menu_data *md = c->overlay_data; + struct menu_data *md = data; if (md->item != NULL) cmdq_continue(md->item); @@ -171,18 +194,19 @@ menu_free_cb(struct client *c) free(md); } -static int -menu_key_cb(struct client *c, struct key_event *event) +int +menu_key_cb(struct client *c, void *data, struct key_event *event) { - struct menu_data *md = c->overlay_data; + 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; + const char *name = NULL; const struct menu_item *item; - struct cmdq_item *new_item; - struct cmd_parse_result *pr; - const char *name; + struct cmdq_state *state; + enum cmd_parse_status status; + char *error; if (KEYC_IS_MOUSE(event->key)) { if (md->flags & MENU_NOMOUSE) { @@ -194,16 +218,28 @@ menu_key_cb(struct client *c, struct key_event *event) m->x > md->px + 4 + menu->width || m->y < md->py + 1 || m->y > md->py + 1 + count - 1) { - if (MOUSE_RELEASE(m->b)) - return (1); + if (~md->flags & MENU_STAYOPEN) { + if (MOUSE_RELEASE(m->b)) + return (1); + } else { + if (!MOUSE_RELEASE(m->b) && + MOUSE_WHEEL(m->b) == 0 && + !MOUSE_DRAG(m->b)) + return (1); + } if (md->choice != -1) { md->choice = -1; c->flags |= CLIENT_REDRAWOVERLAY; } return (0); } - if (MOUSE_RELEASE(m->b)) - goto chosen; + if (~md->flags & MENU_STAYOPEN) { + if (MOUSE_RELEASE(m->b)) + goto chosen; + } else { + if (MOUSE_WHEEL(m->b) == 0 && !MOUSE_DRAG(m->b)) + goto chosen; + } md->choice = m->y - (md->py + 1); if (md->choice != old) c->flags |= CLIENT_REDRAWOVERLAY; @@ -218,7 +254,7 @@ menu_key_cb(struct client *c, struct key_event *event) goto chosen; } } - switch (event->key) { + switch (event->key & ~KEYC_MASK_FLAGS) { case KEYC_UP: case 'k': if (old == -1) @@ -232,6 +268,16 @@ menu_key_cb(struct client *c, struct key_event *event) } while ((name == NULL || *name == '-') && md->choice != old); c->flags |= CLIENT_REDRAWOVERLAY; return (0); + case KEYC_BSPACE: + if (~md->flags & MENU_TAB) + break; + return (1); + case '\011': /* Tab */ + if (~md->flags & MENU_TAB) + break; + if (md->choice == count - 1) + return (1); + /* FALLTHROUGH */ case KEYC_DOWN: case 'j': if (old == -1) @@ -245,6 +291,31 @@ menu_key_cb(struct client *c, struct key_event *event) } while ((name == NULL || *name == '-') && md->choice != old); c->flags |= CLIENT_REDRAWOVERLAY; return (0); + case 'g': + case KEYC_PPAGE: + case '\002': /* C-b */ + if (md->choice > 5) + md->choice -= 5; + else + md->choice = 0; + while (md->choice != count && (name == NULL || *name == '-')) + md->choice++; + if (md->choice == count) + md->choice = -1; + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case 'G': + case KEYC_NPAGE: + if (md->choice > count - 6) + md->choice = count - 1; + else + md->choice += 5; + while (md->choice != -1 && (name == NULL || *name == '-')) + md->choice--; + c->flags |= CLIENT_REDRAWOVERLAY; + break; + case '\006': /* C-f */ + break; case '\r': goto chosen; case '\033': /* Escape */ @@ -259,37 +330,82 @@ chosen: if (md->choice == -1) return (1); item = &menu->items[md->choice]; - if (item->name == NULL || *item->name == '-') + if (item->name == NULL || *item->name == '-') { + if (md->flags & MENU_STAYOPEN) + return (0); return (1); + } if (md->cb != NULL) { md->cb(md->menu, md->choice, item->key, md->data); md->cb = NULL; return (1); } - pr = cmd_parse_from_string(item->command, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - new_item = NULL; - break; - case CMD_PARSE_ERROR: - new_item = cmdq_get_error(pr->error); - free(pr->error); - cmdq_append(c, new_item); - break; - case CMD_PARSE_SUCCESS: - if (md->item != NULL) - m = &md->item->shared->mouse; - else - m = NULL; - new_item = cmdq_get_command(pr->cmdlist, &md->fs, m, 0); - cmd_list_free(pr->cmdlist); - cmdq_append(c, new_item); - break; + if (md->item != NULL) + event = cmdq_get_event(md->item); + else + event = NULL; + state = cmdq_new_state(&md->fs, event, 0); + + status = cmd_parse_and_append(item->command, NULL, c, state, &error); + if (status == CMD_PARSE_ERROR) { + cmdq_append(c, cmdq_get_error(error)); + free(error); } + cmdq_free_state(state); + return (1); } +struct menu_data * +menu_prepare(struct menu *menu, int flags, struct cmdq_item *item, u_int px, + u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, + void *data) +{ + struct menu_data *md; + u_int i; + const char *name; + + 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; + + md = xcalloc(1, sizeof *md); + md->item = item; + md->flags = flags; + + if (fs != NULL) + cmd_find_copy_state(&md->fs, fs); + screen_init(&md->s, menu->width + 4, menu->count + 2, 0); + if (~md->flags & MENU_NOMOUSE) + md->s.mode |= (MODE_MOUSE_ALL|MODE_MOUSE_BUTTON); + md->s.mode &= ~MODE_CURSOR; + + md->px = px; + md->py = py; + + md->menu = menu; + if (md->flags & MENU_NOMOUSE) { + for (i = 0; i < menu->count; i++) { + name = menu->items[i].name; + if (name != NULL && *name != '-') + break; + } + if (i != menu->count) + md->choice = i; + else + md->choice = -1; + } else + md->choice = -1; + + md->cb = cb; + md->data = data; + return (md); +} + int menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, u_int py, struct client *c, struct cmd_find_state *fs, menu_choice_cb cb, @@ -297,27 +413,10 @@ menu_display(struct menu *menu, int flags, struct cmdq_item *item, u_int px, { struct menu_data *md; - if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) + md = menu_prepare(menu, flags, item, px, py, c, fs, cb, data); + if (md == NULL) return (-1); - - md = xcalloc(1, sizeof *md); - md->item = item; - md->flags = flags; - - if (fs != NULL) - cmd_find_copy_state(&md->fs, fs); - screen_init(&md->s, menu->width + 4, menu->count + 2, 0); - - md->px = px; - md->py = py; - - md->menu = menu; - md->choice = -1; - - md->cb = cb; - md->data = data; - - server_client_set_overlay(c, 0, menu_draw_cb, menu_key_cb, menu_free_cb, - md); + server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, + menu_key_cb, menu_free_cb, NULL, md); return (0); } diff --git a/mode-tree.c b/mode-tree.c index b9fa5f65..c92f7cff 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -45,6 +45,8 @@ struct mode_tree_data { mode_tree_draw_cb drawcb; mode_tree_search_cb searchcb; mode_tree_menu_cb menucb; + mode_tree_height_cb heightcb; + mode_tree_key_cb keycb; struct mode_tree_list children; struct mode_tree_list saved; @@ -73,6 +75,10 @@ struct mode_tree_item { void *itemdata; u_int line; + key_code key; + const char *keystr; + size_t keylen; + uint64_t tag; const char *name; const char *text; @@ -80,6 +86,9 @@ struct mode_tree_item { int expanded; int tagged; + int draw_as_parent; + int no_tag; + struct mode_tree_list children; TAILQ_ENTRY(mode_tree_item) entry; }; @@ -131,6 +140,7 @@ mode_tree_free_item(struct mode_tree_item *mti) free((void *)mti->name); free((void *)mti->text); + free((void *)mti->keystr); free(mti); } @@ -189,6 +199,26 @@ mode_tree_build_lines(struct mode_tree_data *mtd, flat = 0; if (mti->expanded) mode_tree_build_lines(mtd, &mti->children, depth + 1); + + if (mtd->keycb != NULL) { + mti->key = mtd->keycb(mtd->modedata, mti->itemdata, + mti->line); + if (mti->key == KEYC_UNKNOWN) + mti->key = KEYC_NONE; + } else if (mti->line < 10) + mti->key = '0' + mti->line; + else if (mti->line < 36) + mti->key = KEYC_META|('a' + mti->line - 10); + else + mti->key = KEYC_NONE; + if (mti->key != KEYC_NONE) { + mti->keystr = xstrdup(key_string_lookup_key(mti->key, + 0)); + mti->keylen = strlen(mti->keystr); + } else { + mti->keystr = NULL; + mti->keylen = 0; + } } TAILQ_FOREACH(mti, mtl, entry) { for (i = 0; i < mtd->line_size; i++) { @@ -210,7 +240,7 @@ mode_tree_clear_tagged(struct mode_tree_list *mtl) } } -static void +void mode_tree_up(struct mode_tree_data *mtd, int wrap) { if (mtd->current == 0) { @@ -247,6 +277,12 @@ mode_tree_get_current(struct mode_tree_data *mtd) return (mtd->line_list[mtd->current].item->itemdata); } +const char * +mode_tree_get_current_name(struct mode_tree_data *mtd) +{ + return (mtd->line_list[mtd->current].item->name); +} + void mode_tree_expand_current(struct mode_tree_data *mtd) { @@ -257,7 +293,16 @@ mode_tree_expand_current(struct mode_tree_data *mtd) } void -mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) +mode_tree_collapse_current(struct mode_tree_data *mtd) +{ + if (mtd->line_list[mtd->current].item->expanded) { + mtd->line_list[mtd->current].item->expanded = 0; + mode_tree_build(mtd); + } +} + +static int +mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found) { u_int i; @@ -266,15 +311,41 @@ mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) break; } if (i != mtd->line_size) { - mtd->current = i; + *found = i; + return (1); + } + return (0); +} + +void +mode_tree_expand(struct mode_tree_data *mtd, uint64_t tag) +{ + u_int found; + + if (!mode_tree_get_tag(mtd, tag, &found)) + return; + if (!mtd->line_list[found].item->expanded) { + mtd->line_list[found].item->expanded = 1; + mode_tree_build(mtd); + } +} + +int +mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) +{ + u_int found; + + if (mode_tree_get_tag(mtd, tag, &found)) { + mtd->current = found; if (mtd->current > mtd->height - 1) mtd->offset = mtd->current - mtd->height + 1; else mtd->offset = 0; - } else { - mtd->current = 0; - mtd->offset = 0; + return (1); } + mtd->current = 0; + mtd->offset = 0; + return (0); } u_int @@ -317,7 +388,8 @@ mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, struct mode_tree_data * mode_tree_start(struct window_pane *wp, struct args *args, mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, - mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata, + mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, + mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata, const struct menu_item *menu, const char **sort_list, u_int sort_size, struct screen **s) { @@ -355,6 +427,8 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->drawcb = drawcb; mtd->searchcb = searchcb; mtd->menucb = menucb; + mtd->heightcb = heightcb; + mtd->keycb = keycb; TAILQ_INIT(&mtd->children); @@ -378,6 +452,27 @@ mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) mtd->zoomed = -1; } +static void +mode_tree_set_height(struct mode_tree_data *mtd) +{ + struct screen *s = &mtd->screen; + u_int height; + + if (mtd->heightcb != NULL) { + height = mtd->heightcb(mtd, screen_size_y(s)); + if (height < screen_size_y(s)) + mtd->height = screen_size_y(s) - height; + } else { + mtd->height = (screen_size_y(s) / 3) * 2; + if (mtd->height > mtd->line_size) + mtd->height = screen_size_y(s) / 2; + } + if (mtd->height < 10) + mtd->height = screen_size_y(s); + if (screen_size_y(s) - mtd->height < 2) + mtd->height = screen_size_y(s); +} + void mode_tree_build(struct mode_tree_data *mtd) { @@ -408,15 +503,9 @@ mode_tree_build(struct mode_tree_data *mtd) mode_tree_set_current(mtd, tag); mtd->width = screen_size_x(s); - if (mtd->preview) { - mtd->height = (screen_size_y(s) / 3) * 2; - if (mtd->height > mtd->line_size) - mtd->height = screen_size_y(s) / 2; - if (mtd->height < 10) - mtd->height = screen_size_y(s); - if (screen_size_y(s) - mtd->height < 2) - mtd->height = screen_size_y(s); - } else + if (mtd->preview) + mode_tree_set_height(mtd); + else mtd->height = screen_size_y(s); mode_tree_check_selected(mtd); } @@ -468,7 +557,7 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, struct mode_tree_item *mti, *saved; log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag, - name, text); + name, (text == NULL ? "" : text)); mti = xcalloc(1, sizeof *mti); mti->parent = parent; @@ -476,7 +565,8 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, mti->tag = tag; mti->name = xstrdup(name); - mti->text = xstrdup(text); + if (text != NULL) + mti->text = xstrdup(text); saved = mode_tree_find_item(&mtd->saved, tag); if (saved != NULL) { @@ -498,6 +588,18 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, return (mti); } +void +mode_tree_draw_as_parent(struct mode_tree_item *mti) +{ + mti->draw_as_parent = 1; +} + +void +mode_tree_no_tag(struct mode_tree_item *mti) +{ + mti->no_tag = 1; +} + void mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti) { @@ -521,46 +623,48 @@ mode_tree_draw(struct mode_tree_data *mtd) struct screen_write_ctx ctx; struct grid_cell gc0, gc; u_int w, h, i, j, sy, box_x, box_y, width; - char *text, *start, key[7]; + char *text, *start, *key; const char *tag, *symbol; size_t size, n; - int keylen; + int keylen, pad; if (mtd->line_size == 0) return; memcpy(&gc0, &grid_default_cell, sizeof gc0); memcpy(&gc, &grid_default_cell, sizeof gc); - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); w = mtd->width; h = mtd->height; - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); - if (mtd->line_size > 10) - keylen = 6; - else - keylen = 4; + keylen = 0; + for (i = 0; i < mtd->line_size; i++) { + mti = mtd->line_list[i].item; + if (mti->key == KEYC_NONE) + continue; + if ((int)mti->keylen + 3 > keylen) + keylen = mti->keylen + 3; + } for (i = 0; i < mtd->line_size; i++) { if (i < mtd->offset) continue; if (i > mtd->offset + h - 1) break; - line = &mtd->line_list[i]; mti = line->item; screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); - if (i < 10) - snprintf(key, sizeof key, "(%c) ", '0' + i); - else if (i < 36) - snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10)); + pad = keylen - 2 - mti->keylen; + if (mti->key != KEYC_NONE) + xasprintf(&key, "(%s)%*s", mti->keystr, pad, ""); else - *key = '\0'; + key = xstrdup(""); if (line->flat) symbol = ""; @@ -595,8 +699,8 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, - tag); + xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name, + tag, (mti->text != NULL) ? ": " : "" ); width = utf8_cstrwidth(text); if (width > w) width = w; @@ -610,13 +714,20 @@ mode_tree_draw(struct mode_tree_data *mtd) if (i != mtd->current) { screen_write_clearendofline(&ctx, 8); screen_write_nputs(&ctx, w, &gc0, "%s", text); - format_draw(&ctx, &gc0, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc0, w - width, mti->text, + NULL); + } } else { screen_write_clearendofline(&ctx, gc.bg); screen_write_nputs(&ctx, w, &gc, "%s", text); - format_draw(&ctx, &gc, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc, w - width, mti->text, + NULL); + } } free(text); + free(key); if (mti->tagged) { gc.attr ^= GRID_ATTR_BRIGHT; @@ -632,13 +743,18 @@ mode_tree_draw(struct mode_tree_data *mtd) line = &mtd->line_list[mtd->current]; mti = line->item; + if (mti->draw_as_parent) + mti = mti->parent; screen_write_cursormove(&ctx, 0, h, 0); screen_write_box(&ctx, w, sy - h); - xasprintf(&text, " %s (sort: %s%s)", mti->name, - mtd->sort_list[mtd->sort_crit.field], - mtd->sort_crit.reversed ? ", reversed" : ""); + if (mtd->sort_list != NULL) { + xasprintf(&text, " %s (sort: %s%s)", mti->name, + mtd->sort_list[mtd->sort_crit.field], + mtd->sort_crit.reversed ? ", reversed" : ""); + } else + xasprintf(&text, " %s", mti->name); if (w - 2 >= strlen(text)) { screen_write_cursormove(&ctx, 1, h, 0); screen_write_puts(&ctx, &gc0, "%s", text); @@ -654,7 +770,8 @@ mode_tree_draw(struct mode_tree_data *mtd) else screen_write_puts(&ctx, &gc0, "active"); screen_write_puts(&ctx, &gc0, ") "); - } + } else + screen_write_puts(&ctx, &gc0, " "); } free(text); @@ -805,7 +922,7 @@ mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, if (mti->itemdata != mtm->itemdata) goto out; mtd->current = mtm->line; - mtd->menucb (mtd->modedata, mtm->c, key); + mtd->menucb(mtd->modedata, mtm->c, key); out: mode_tree_remove_ref(mtd); @@ -847,6 +964,10 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, mtm->itemdata = mti->itemdata; mtd->references++; + if (x >= (menu->width + 4) / 2) + x -= (menu->width + 4) / 2; + else + x = 0; if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, mtm) != 0) menu_free(menu); @@ -857,10 +978,9 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, struct mouse_event *m, u_int *xp, u_int *yp) { struct mode_tree_line *line; - struct mode_tree_item *current, *parent; + struct mode_tree_item *current, *parent, *mti; u_int i, x, y; int choice; - key_code tmp; if (KEYC_IS_MOUSE(*key) && m != NULL) { if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { @@ -902,12 +1022,11 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, current = line->item; choice = -1; - if (*key >= '0' && *key <= '9') - choice = (*key) - '0'; - else if (((*key) & KEYC_MASK_MOD) == KEYC_ESCAPE) { - tmp = (*key) & KEYC_MASK_KEY; - if (tmp >= 'a' && tmp <= 'z') - choice = 10 + (tmp - 'a'); + for (i = 0; i < mtd->line_size; i++) { + if (*key == mtd->line_list[i].item->key) { + choice = i; + break; + } } if (choice != -1) { if ((u_int)choice > mtd->line_size - 1) { @@ -970,6 +1089,8 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, * Do not allow parents and children to both be tagged: untag * all parents and children of current. */ + if (current->no_tag) + break; if (!current->tagged) { parent = current->parent; while (parent != NULL) { @@ -989,7 +1110,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case '\024': /* C-t */ for (i = 0; i < mtd->line_size; i++) { - if (mtd->line_list[i].item->parent == NULL) + if ((mtd->line_list[i].item->parent == NULL && + !mtd->line_list[i].item->no_tag) || + (mtd->line_list[i].item->parent != NULL && + mtd->line_list[i].item->parent->no_tag)) mtd->line_list[i].item->tagged = 1; else mtd->line_list[i].item->tagged = 0; @@ -997,7 +1121,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case 'O': mtd->sort_crit.field++; - if (mtd->sort_crit.field == mtd->sort_size) + if (mtd->sort_crit.field >= mtd->sort_size) mtd->sort_crit.field = 0; mode_tree_build(mtd); break; @@ -1028,22 +1152,32 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_build(mtd); } break; + case '-'|KEYC_META: + TAILQ_FOREACH(mti, &mtd->children, entry) + mti->expanded = 0; + mode_tree_build(mtd); + break; + case '+'|KEYC_META: + TAILQ_FOREACH(mti, &mtd->children, entry) + mti->expanded = 1; + mode_tree_build(mtd); + break; case '?': case '/': case '\023': /* C-s */ mtd->references++; - status_prompt_set(c, "(search) ", "", + status_prompt_set(c, NULL, "(search) ", "", mode_tree_search_callback, mode_tree_search_free, mtd, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; case 'n': mode_tree_search_set(mtd); break; case 'f': mtd->references++; - status_prompt_set(c, "(filter) ", mtd->filter, + status_prompt_set(c, NULL, "(filter) ", mtd->filter, mode_tree_filter_callback, mode_tree_filter_free, mtd, - PROMPT_NOFORMAT); + PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; case 'v': mtd->preview = !mtd->preview; @@ -1059,33 +1193,22 @@ void mode_tree_run_command(struct client *c, struct cmd_find_state *fs, const char *template, const char *name) { - struct cmdq_item *new_item; - char *command; - struct cmd_parse_result *pr; + struct cmdq_state *state; + char *command, *error; + enum cmd_parse_status status; command = cmd_template_replace(template, name, 1); - if (command == NULL || *command == '\0') { - free(command); - return; - } - - pr = cmd_parse_from_string(command, NULL); - switch (pr->status) { - case CMD_PARSE_EMPTY: - break; - case CMD_PARSE_ERROR: - if (c != NULL) { - *pr->error = toupper((u_char)*pr->error); - status_message_set(c, "%s", pr->error); + if (command != NULL && *command != '\0') { + state = cmdq_new_state(fs, NULL, 0); + status = cmd_parse_and_append(command, NULL, c, state, &error); + if (status == CMD_PARSE_ERROR) { + if (c != NULL) { + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, "%s", error); + } + free(error); } - free(pr->error); - break; - case CMD_PARSE_SUCCESS: - new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0); - cmdq_append(c, new_item); - cmd_list_free(pr->cmdlist); - break; + cmdq_free_state(state); } - free(command); } diff --git a/names.c b/names.c index 661ba06e..aeb67338 100644 --- a/names.c +++ b/names.c @@ -96,6 +96,7 @@ check_window_name(struct window *w) if (strcmp(name, w->name) != 0) { log_debug("@%u new name %s (was %s)", w->id, name, w->name); window_set_name(w, name); + server_redraw_window_borders(w); server_status_window(w); } else log_debug("@%u name not changed (still %s)", w->id, w->name); @@ -106,8 +107,10 @@ check_window_name(struct window *w) char * default_window_name(struct window *w) { - char *cmd, *s; + char *cmd, *s; + if (w->active == NULL) + return (xstrdup("")); cmd = cmd_stringify_argv(w->active->argc, w->active->argv); if (cmd != NULL && *cmd != '\0') s = parse_window_name(cmd); @@ -141,6 +144,10 @@ parse_window_name(const char *in) char *copy, *name, *ptr; name = copy = xstrdup(in); + if (*name == '"') + name++; + name[strcspn(name, "\"")] = '\0'; + if (strncmp(name, "exec ", (sizeof "exec ") - 1) == 0) name = name + (sizeof "exec ") - 1; diff --git a/notify.c b/notify.c index 200e23d6..9c55d0b8 100644 --- a/notify.c +++ b/notify.c @@ -25,39 +25,22 @@ struct notify_entry { const char *name; + struct cmd_find_state fs; + struct format_tree *formats; struct client *client; struct session *session; struct window *window; int pane; - - struct cmd_find_state fs; }; -static void -notify_hook_formats(struct cmdq_item *item, struct session *s, struct window *w, - int pane) -{ - if (s != NULL) { - cmdq_format(item, "hook_session", "$%u", s->id); - cmdq_format(item, "hook_session_name", "%s", s->name); - } - if (w != NULL) { - cmdq_format(item, "hook_window", "@%u", w->id); - cmdq_format(item, "hook_window_name", "%s", w->name); - } - if (pane != -1) - cmdq_format(item, "hook_pane", "%%%d", pane); -} - static void notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) { struct cmd_find_state fs; struct options *oo; struct cmdq_item *new_item; - struct session *s = ne->session; - struct window *w = ne->window; + struct cmdq_state *new_state; struct options_entry *o; struct options_array_item *a; struct cmd_list *cmdlist; @@ -75,24 +58,31 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) else oo = fs.s->options; o = options_get(oo, ne->name); + if (o == NULL && fs.wp != NULL) { + oo = fs.wp->options; + o = options_get(oo, ne->name); + } + if (o == NULL && fs.wl != NULL) { + oo = fs.wl->window->options; + o = options_get(oo, ne->name); + } if (o == NULL) return; + new_state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS); + cmdq_add_formats(new_state, ne->formats); + a = options_array_first(o); while (a != NULL) { cmdlist = options_array_item_value(a)->cmdlist; - if (cmdlist == NULL) { - a = options_array_next(a); - continue; + if (cmdlist != NULL) { + new_item = cmdq_get_command(cmdlist, new_state); + item = cmdq_insert_after(item, new_item); } - - new_item = cmdq_get_command(cmdlist, &fs, NULL, CMDQ_NOHOOKS); - cmdq_format(new_item, "hook", "%s", ne->name); - notify_hook_formats(new_item, s, w, ne->pane); - item = cmdq_insert_after(item, new_item); - a = options_array_next(a); } + + cmdq_free_state(new_state); } static enum cmd_retval @@ -116,6 +106,8 @@ notify_callback(struct cmdq_item *item, void *data) control_notify_window_renamed(ne->window); if (strcmp(ne->name, "client-session-changed") == 0) control_notify_client_session_changed(ne->client); + if (strcmp(ne->name, "client-detached") == 0) + control_notify_client_detached(ne->client); if (strcmp(ne->name, "session-renamed") == 0) control_notify_session_renamed(ne->session); if (strcmp(ne->name, "session-created") == 0) @@ -137,6 +129,7 @@ notify_callback(struct cmdq_item *item, void *data) if (ne->fs.s != NULL) session_remove_ref(ne->fs.s, __func__); + format_free(ne->formats); free((void *)ne->name); free(ne); @@ -148,7 +141,11 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, struct session *s, struct window *w, struct window_pane *wp) { struct notify_entry *ne; - struct cmdq_item *new_item; + struct cmdq_item *item; + + item = cmdq_running(NULL); + if (item != NULL && (cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS)) + return; ne = xcalloc(1, sizeof *ne); ne->name = xstrdup(name); @@ -156,11 +153,23 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, ne->client = c; ne->session = s; ne->window = w; + ne->pane = (wp != NULL ? wp->id : -1); + ne->formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + format_add(ne->formats, "hook", "%s", name); + if (c != NULL) + format_add(ne->formats, "hook_client", "%s", c->name); + if (s != NULL) { + format_add(ne->formats, "hook_session", "$%u", s->id); + format_add(ne->formats, "hook_session_name", "%s", s->name); + } + if (w != NULL) { + format_add(ne->formats, "hook_window", "@%u", w->id); + format_add(ne->formats, "hook_window_name", "%s", w->name); + } if (wp != NULL) - ne->pane = wp->id; - else - ne->pane = -1; + format_add(ne->formats, "hook_pane", "%%%d", wp->id); + format_log_debug(ne->formats, __func__); if (c != NULL) c->references++; @@ -173,37 +182,31 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, if (ne->fs.s != NULL) /* cmd_find_valid_state needs session */ session_add_ref(ne->fs.s, __func__); - new_item = cmdq_get_callback(notify_callback, ne); - cmdq_append(NULL, new_item); + cmdq_append(NULL, cmdq_get_callback(notify_callback, ne)); } void notify_hook(struct cmdq_item *item, const char *name) { - struct notify_entry ne; + struct cmd_find_state *target = cmdq_get_target(item); + struct notify_entry ne; memset(&ne, 0, sizeof ne); ne.name = name; - cmd_find_copy_state(&ne.fs, &item->target); + cmd_find_copy_state(&ne.fs, target); - ne.client = item->client; - ne.session = item->target.s; - ne.window = item->target.w; - ne.pane = item->target.wp->id; + ne.client = cmdq_get_client(item); + ne.session = target->s; + ne.window = target->w; + ne.pane = (target->wp != NULL ? target->wp->id : -1); + + ne.formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + format_add(ne.formats, "hook", "%s", name); + format_log_debug(ne.formats, __func__); notify_insert_hook(item, &ne); -} - -void -notify_input(struct window_pane *wp, const u_char *buf, size_t len) -{ - struct client *c; - - TAILQ_FOREACH(c, &clients, entry) { - if (c->flags & CLIENT_CONTROL) - control_notify_input(c, wp, buf, len); - } + format_free(ne.formats); } void diff --git a/options-table.c b/options-table.c index be0d220d..76c2b053 100644 --- a/options-table.c +++ b/options-table.c @@ -45,7 +45,7 @@ static const char *options_table_status_keys_list[] = { "emacs", "vi", NULL }; static const char *options_table_status_justify_list[] = { - "left", "centre", "right", NULL + "left", "centre", "right", "absolute-centre", NULL }; static const char *options_table_status_position_list[] = { "top", "bottom", NULL @@ -59,16 +59,28 @@ static const char *options_table_visual_bell_list[] = { static const char *options_table_pane_status_list[] = { "off", "top", "bottom", NULL }; +static const char *options_table_pane_lines_list[] = { + "single", "double", "heavy", "simple", "number", NULL +}; static const char *options_table_set_clipboard_list[] = { "off", "external", "on", NULL }; static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", "latest", NULL }; +static const char *options_table_remain_on_exit_list[] = { + "off", "on", "failed", NULL +}; +static const char *options_table_detach_on_destroy_list[] = { + "off", "on", "no-detached", NULL +}; +static const char *options_table_extended_keys_list[] = { + "off", "on", "always", NULL +}; /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ - "#[align=left range=left #{status-left-style}]" \ + "#[align=left range=left #{E:status-left-style}]" \ "#[push-default]" \ "#{T;=/#{status-left-length}:status-left}" \ "#[pop-default]" \ @@ -77,20 +89,20 @@ static const char *options_table_window_size_list[] = { "#[list=left-marker]<#[list=right-marker]>#[list=on]" \ "#{W:" \ "#[range=window|#{window_index} " \ - "#{window-status-style}" \ + "#{E:window-status-style}" \ "#{?#{&&:#{window_last_flag}," \ - "#{!=:#{window-status-last-style},default}}, " \ - "#{window-status-last-style}," \ + "#{!=:#{E:window-status-last-style},default}}, " \ + "#{E:window-status-last-style}," \ "}" \ "#{?#{&&:#{window_bell_flag}," \ - "#{!=:#{window-status-bell-style},default}}, " \ - "#{window-status-bell-style}," \ + "#{!=:#{E:window-status-bell-style},default}}, " \ + "#{E:window-status-bell-style}," \ "#{?#{&&:#{||:#{window_activity_flag}," \ "#{window_silence_flag}}," \ "#{!=:" \ - "#{window-status-activity-style}," \ + "#{E:window-status-activity-style}," \ "default}}, " \ - "#{window-status-activity-style}," \ + "#{E:window-status-activity-style}," \ "}" \ "}" \ "]" \ @@ -101,23 +113,23 @@ static const char *options_table_window_size_list[] = { "#{?window_end_flag,,#{window-status-separator}}" \ "," \ "#[range=window|#{window_index} list=focus " \ - "#{?#{!=:#{window-status-current-style},default}," \ - "#{window-status-current-style}," \ - "#{window-status-style}" \ - "}" \ + "#{?#{!=:#{E:window-status-current-style},default}," \ + "#{E:window-status-current-style}," \ + "#{E:window-status-style}" \ + "}" \ "#{?#{&&:#{window_last_flag}," \ - "#{!=:#{window-status-last-style},default}}, " \ - "#{window-status-last-style}," \ + "#{!=:#{E:window-status-last-style},default}}, " \ + "#{E:window-status-last-style}," \ "}" \ "#{?#{&&:#{window_bell_flag}," \ - "#{!=:#{window-status-bell-style},default}}, " \ - "#{window-status-bell-style}," \ + "#{!=:#{E:window-status-bell-style},default}}, " \ + "#{E:window-status-bell-style}," \ "#{?#{&&:#{||:#{window_activity_flag}," \ "#{window_silence_flag}}," \ "#{!=:" \ - "#{window-status-activity-style}," \ + "#{E:window-status-activity-style}," \ "default}}, " \ - "#{window-status-activity-style}," \ + "#{E:window-status-activity-style}," \ "}" \ "}" \ "]" \ @@ -127,7 +139,7 @@ static const char *options_table_window_size_list[] = { "#[norange list=on default]" \ "#{?window_end_flag,,#{window-status-separator}}" \ "}" \ - "#[nolist align=right range=right #{status-right-style}]" \ + "#[nolist align=right range=right #{E:status-right-style}]" \ "#[push-default]" \ "#{T;=/#{status-right-length}:status-right}" \ "#[pop-default]" \ @@ -139,7 +151,7 @@ static const char *options_table_status_format_default[] = { OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, NULL }; -/* Helper for hook options. */ +/* Helpers for hook options. */ #define OPTIONS_TABLE_HOOK(hook_name, default_value) \ { .name = hook_name, \ .type = OPTIONS_TABLE_COMMAND, \ @@ -149,6 +161,33 @@ static const char *options_table_status_format_default[] = { .separator = "" \ } +#define OPTIONS_TABLE_PANE_HOOK(hook_name, default_value) \ + { .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 = "" \ + } + +#define OPTIONS_TABLE_WINDOW_HOOK(hook_name, default_value) \ + { .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 = "" \ + } + +/* Map of name conversions. */ +const struct options_name_map options_other_names[] = { + { "display-panes-color", "display-panes-colour" }, + { "display-panes-active-color", "display-panes-active-colour" }, + { "clock-mode-color", "clock-mode-colour" }, + { "pane-colors", "pane-colours" }, + { NULL, NULL } +}; + /* Top-level options. */ const struct options_table_entry options_table[] = { /* Server options. */ @@ -156,6 +195,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SERVER, .default_num = '\177', + .text = "The key to send for backspace." }, { .name = "buffer-limit", @@ -163,7 +203,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 1, .maximum = INT_MAX, - .default_num = 50 + .default_num = 50, + .text = "The maximum number of automatic buffers. " + "When this is reached, the oldest buffer is deleted." }, { .name = "command-alias", @@ -176,13 +218,31 @@ const struct options_table_entry options_table[] = { "info=show-messages -JT," "choose-window=choose-tree -w," "choose-session=choose-tree -s", - .separator = "," + .separator = ",", + .text = "Array of command aliases. " + "Each entry is an alias and a command separated by '='." + }, + + { .name = "copy-command", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "", + .text = "Shell command run when text is copied. " + "If empty, no command is run." }, { .name = "default-terminal", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "screen" + .default_str = TMUX_TERM, + .text = "Default for the 'TERM' environment variable." + }, + + { .name = "editor", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .default_str = _PATH_VI, + .text = "Editor run to edit files." }, { .name = "escape-time", @@ -190,31 +250,47 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 500 + .default_num = 500, + .text = "Time to wait before assuming a key is Escape." }, { .name = "exit-empty", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 1 + .default_num = 1, + .text = "Whether the server should exit if there are no sessions." }, { .name = "exit-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether the server should exit if there are no attached " + "clients." + }, + + { .name = "extended-keys", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_SERVER, + .choices = options_table_extended_keys_list, + .default_num = 0, + .text = "Whether to request extended key sequences from terminals " + "that support it." }, { .name = "focus-events", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether to send focus events to applications." }, { .name = "history-file", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "" + .default_str = "", + .text = "Location of the command prompt history file. " + "Empty does not write a history file." }, { .name = "message-limit", @@ -222,24 +298,47 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 100 + .default_num = 1000, + .text = "Maximum number of server messages to keep." + }, + + { .name = "prompt-history-limit", + .type = OPTIONS_TABLE_NUMBER, + .scope = OPTIONS_TABLE_SERVER, + .minimum = 0, + .maximum = INT_MAX, + .default_num = 100, + .text = "Maximum number of commands to keep in history." }, { .name = "set-clipboard", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, .choices = options_table_set_clipboard_list, - .default_num = 1 + .default_num = 1, + .text = "Whether to attempt to set the system clipboard ('on' or " + "'external') and whether to allow applications to create " + "paste buffers with an escape sequence ('on' only)." }, { .name = "terminal-overrides", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, - .default_str = "xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007" - ":Cs=\\E]12;%p1%s\\007:Cr=\\E]112\\007" - ":Ss=\\E[%p1%d q:Se=\\E[2 q,screen*:XT", - .separator = "," + .default_str = "", + .separator = ",", + .text = "List of terminal capabilities overrides." + }, + + { .name = "terminal-features", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_str = "xterm*:clipboard:ccolour:cstyle:focus:title," + "screen*:title", + .separator = ",", + .text = "List of terminal features, used if they cannot be " + "automatically detected." }, { .name = "user-keys", @@ -247,7 +346,10 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", - .separator = "," + .separator = ",", + .text = "User key assignments. " + "Each sequence in the list is translated into a key: " + "'User0', 'User1' and so on." }, /* Session options. */ @@ -255,7 +357,8 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on an activity alert." }, { .name = "assume-paste-time", @@ -264,6 +367,9 @@ const struct options_table_entry options_table[] = { .minimum = 0, .maximum = INT_MAX, .default_num = 1, + .unit = "milliseconds", + .text = "Maximum time between input to assume it is pasting rather " + "than typing." }, { .name = "base-index", @@ -271,57 +377,70 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Default index of the first window in each session." }, { .name = "bell-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_ANY + .default_num = ALERT_ANY, + .text = "Action to take on a bell alert." }, { .name = "default-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "" + .default_str = "", + .text = "Default command to run in new panes. If empty, a shell is " + "started." }, { .name = "default-shell", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = _PATH_BSHELL + .default_str = _PATH_BSHELL, + .text = "Location of default shell." }, { .name = "default-size", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .pattern = "[0-9]*x[0-9]*", - .default_str = "80x24" + .default_str = "80x24", + .text = "Initial size of new sessions." }, { .name = "destroy-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to destroy sessions when they have no attached " + "clients." }, { .name = "detach-on-destroy", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .choices = options_table_detach_on_destroy_list, + .default_num = 1, + .text = "Whether to detach when a session is destroyed, or switch " + "the client to another session if any exist." }, { .name = "display-panes-active-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .default_num = 1, + .text = "Colour of the active pane for 'display-panes'." }, { .name = "display-panes-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 4 + .default_num = 4, + .text = "Colour of not active panes for 'display-panes'." }, { .name = "display-panes-time", @@ -329,7 +448,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 1, .maximum = INT_MAX, - .default_num = 1000 + .default_num = 1000, + .unit = "milliseconds", + .text = "Time for which 'display-panes' should show pane numbers." }, { .name = "display-time", @@ -337,7 +458,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 750 + .default_num = 750, + .unit = "milliseconds", + .text = "Time for which status line messages should appear." }, { .name = "history-limit", @@ -345,13 +468,19 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 2000 + .default_num = 2000, + .unit = "lines", + .text = "Maximum number of lines to keep in the history for each " + "pane. " + "If changed, the new value applies only to new panes." }, { .name = "key-table", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "root" + .default_str = "root", + .text = "Default key table. " + "Key presses are first looked up in this table." }, { .name = "lock-after-time", @@ -359,49 +488,66 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .unit = "seconds", + .text = "Time after which a client is locked if not used." }, { .name = "lock-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "lock -np" + .default_str = "lock -np", + .text = "Shell command to run to lock a client." }, { .name = "message-command-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=black,fg=yellow" + .default_str = "bg=black,fg=yellow", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the command prompt when in command mode, if " + "'mode-keys' is set to 'vi'." }, { .name = "message-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=yellow,fg=black" + .default_str = "bg=yellow,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the command prompt." }, { .name = "mouse", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether the mouse is recognised and mouse key bindings are " + "executed. " + "Applications inside panes can use the mouse even when 'off'." }, { .name = "prefix", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = '\002', + .text = "The prefix key." }, { .name = "prefix2", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = KEYC_NONE, + .text = "A second prefix key." }, { .name = "renumber-windows", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether windows are automatically renumbered rather than " + "leaving gaps." }, { .name = "repeat-time", @@ -409,45 +555,56 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 500 + .default_num = 500, + .unit = "milliseconds", + .text = "Time to wait for a key binding to repeat, if it is bound " + "with the '-r' flag." }, { .name = "set-titles", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to set the terminal title, if supported." }, { .name = "set-titles-string", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "#S:#I:#W - \"#T\" #{session_alerts}" + .default_str = "#S:#I:#W - \"#T\" #{session_alerts}", + .text = "Format of the terminal title to set." }, { .name = "silence-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on a silence alert." }, { .name = "status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_list, - .default_num = 1 + .default_num = 1, + .text = "Number of lines in the status line." }, { .name = "status-bg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 2, + .default_num = 8, + .text = "Background colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-fg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, + .default_num = 8, + .text = "Foreground colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-format", @@ -455,6 +612,11 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_arr = options_table_status_format_default, + .text = "Formats for the status lines. " + "Each array member is the format for one status line. " + "The default status line is made up of several components " + "which may be configured individually with other options such " + "as 'status-left'." }, { .name = "status-interval", @@ -462,27 +624,32 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 15 + .default_num = 15, + .unit = "seconds", + .text = "Number of seconds between status line updates." }, { .name = "status-justify", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_justify_list, - .default_num = 0 + .default_num = 0, + .text = "Position of the window list in the status line." }, { .name = "status-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set to use at the command prompt." }, { .name = "status-left", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "[#S] " + .default_str = "[#{session_name}] ", + .text = "Contents of the left side of the status line." }, { .name = "status-left-length", @@ -490,28 +657,35 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 10 + .default_num = 10, + .text = "Maximum width of the left side of the status line." }, { .name = "status-left-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the left side of the status line." }, { .name = "status-position", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_position_list, - .default_num = 1 + .default_num = 1, + .text = "Position of the status line." }, { .name = "status-right", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .default_str = "#{?window_bigger," - "[#{window_offset_x}#,#{window_offset_y}] ,}" - "\"#{=21:pane_title}\" %H:%M %d-%b-%y" + "[#{window_offset_x}#,#{window_offset_y}] ,}" + "\"#{=21:pane_title}\" %H:%M %d-%b-%y", + .text = "Contents of the right side of the status line." + }, { .name = "status-right-length", @@ -519,19 +693,26 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 40 + .default_num = 40, + .text = "Maximum width of the right side of the status line." }, { .name = "status-right-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the right side of the status line." }, { .name = "status-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "bg=green,fg=black" + .default_str = "bg=green,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the status line." }, { .name = "update-environment", @@ -539,120 +720,178 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "DISPLAY KRB5CCNAME SSH_ASKPASS SSH_AUTH_SOCK " - "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY" + "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY", + .text = "List of environment variables to update in the session " + "environment when a client is attached." }, { .name = "visual-activity", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How activity alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-bell", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How bell alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-silence", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How silence alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "word-separators", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " " + /* + * The set of non-alphanumeric printable ASCII characters minus the + * underscore. + */ + .default_str = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", + .text = "Characters considered to separate words." }, /* Window options. */ { .name = "aggressive-resize", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "When 'window-size' is 'smallest', whether the maximum size " + "of a window is the smallest attached session where it is " + "the current window ('on') or the smallest session it is " + "linked to ('off')." }, { .name = "allow-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .default_num = 0, + .text = "Whether applications are allowed to use the escape sequence " + "to rename windows." }, { .name = "alternate-screen", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 1 + .default_num = 1, + .text = "Whether applications are allowed to use the alternate " + "screen." }, { .name = "automatic-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether windows are automatically renamed." }, { .name = "automatic-rename-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_in_mode,[tmux],#{pane_current_command}}" - "#{?pane_dead,[dead],}" + "#{?pane_dead,[dead],}", + .text = "Format used to automatically rename windows." }, { .name = "clock-mode-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 4 + .default_num = 4, + .text = "Colour of the clock in clock mode." }, { .name = "clock-mode-style", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_clock_mode_style_list, - .default_num = 1 + .default_num = 1, + .text = "Time format of the clock in clock mode." + }, + + { .name = "copy-mode-match-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=cyan,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of search matches in copy mode." + }, + + { .name = "copy-mode-current-match-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=magenta,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the current search match in copy mode." + }, + + { .name = "copy-mode-mark-style", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_WINDOW, + .default_str = "bg=red,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the marked line in copy mode." }, { .name = "main-pane-height", - .type = OPTIONS_TABLE_NUMBER, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .minimum = 1, - .maximum = INT_MAX, - .default_num = 24 + .default_str = "24", + .text = "Height of the main pane in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "main-pane-width", - .type = OPTIONS_TABLE_NUMBER, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .minimum = 1, - .maximum = INT_MAX, - .default_num = 80 + .default_str = "80", + .text = "Width of the main pane in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "mode-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_mode_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set used in copy mode." }, { .name = "mode-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "bg=yellow,fg=black" + .default_str = "bg=yellow,fg=black", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of indicators and highlighting in modes." }, { .name = "monitor-activity", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "Whether an alert is triggered by activity." }, { .name = "monitor-bell", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether an alert is triggered by a bell." }, { .name = "monitor-silence", @@ -660,29 +899,35 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Time after which an alert is triggered by silence. " + "Zero means no alert." + }, { .name = "other-pane-height", - .type = OPTIONS_TABLE_NUMBER, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .minimum = 0, - .maximum = INT_MAX, - .default_num = 0 + .default_str = "0", + .text = "Height of the other panes in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "other-pane-width", - .type = OPTIONS_TABLE_NUMBER, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .minimum = 0, - .maximum = INT_MAX, - .default_num = 0 + .default_str = "0", + .text = "Height of the other panes in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "pane-active-border-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "fg=green" + .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the active pane border." }, { .name = "pane-base-index", @@ -690,118 +935,180 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = USHRT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Index of the first pane in each window." }, { .name = "pane-border-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] " - "\"#{pane_title}\"" + "\"#{pane_title}\"", + .text = "Format of text in the pane status lines." + }, + + { .name = "pane-border-lines", + .type = OPTIONS_TABLE_CHOICE, + .scope = OPTIONS_TABLE_WINDOW, + .choices = options_table_pane_lines_list, + .default_num = PANE_LINES_SINGLE, + .text = "Type of characters used to draw pane border lines. Some of " + "these are only supported on terminals with UTF-8 support." }, { .name = "pane-border-status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_status_list, - .default_num = PANE_STATUS_OFF + .default_num = PANE_STATUS_OFF, + .text = "Position of the pane status lines." }, { .name = "pane-border-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the pane status lines." + }, + + { .name = "pane-colours", + .type = OPTIONS_TABLE_COLOUR, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_str = "", + .flags = OPTIONS_TABLE_IS_ARRAY, + .text = "The default colour palette for colours zero to 255." }, { .name = "remain-on-exit", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .choices = options_table_remain_on_exit_list, + .default_num = 0, + .text = "Whether panes should remain ('on') or be automatically " + "killed ('off' or 'failed') when the program inside exits." }, { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_num = 0, + .text = "Whether typing should be sent to all panes simultaneously." }, { .name = "window-active-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default style of the active pane." }, { .name = "window-size", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_window_size_list, - .default_num = WINDOW_SIZE_LATEST + .default_num = WINDOW_SIZE_LATEST, + .text = "How window size is calculated. " + "'latest' uses the size of the most recently used client, " + "'largest' the largest client, 'smallest' the smallest " + "client and 'manual' a size set by the 'resize-window' " + "command." }, { .name = "window-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Default style of panes that are not the active pane." }, { .name = "window-status-activity-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "reverse" + .default_str = "reverse", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of windows in the status line with an activity alert." }, { .name = "window-status-bell-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "reverse" + .default_str = "reverse", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of windows in the status line with a bell alert." }, { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of the current window in the status line." }, { .name = "window-status-current-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the current window in the status line." }, { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of windows in the status line, except the current " + "window." }, { .name = "window-status-last-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of the last window in the status line." }, { .name = "window-status-separator", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = " " + .default_str = " ", + .text = "Separator between windows in the status line." }, { .name = "window-status-style", - .type = OPTIONS_TABLE_STYLE, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" + .default_str = "default", + .flags = OPTIONS_TABLE_IS_STYLE, + .separator = ",", + .text = "Style of windows in the status line, except the current and " + "last windows." }, { .name = "wrap-search", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether searching in copy mode should wrap at the top or " + "bottom." }, - { .name = "xterm-keys", + { .name = "xterm-keys", /* no longer used */ .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether xterm-style function key sequences should be sent. " + "This option is no longer used." }, /* Hook options. */ @@ -846,24 +1153,28 @@ const struct options_table_entry options_table[] = { 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("pane-died", ""), - OPTIONS_TABLE_HOOK("pane-exited", ""), - OPTIONS_TABLE_HOOK("pane-focus-in", ""), - OPTIONS_TABLE_HOOK("pane-focus-out", ""), - OPTIONS_TABLE_HOOK("pane-mode-changed", ""), - OPTIONS_TABLE_HOOK("pane-set-clipboard", ""), + 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-set-clipboard", ""), + 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_HOOK("window-layout-changed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-layout-changed", ""), OPTIONS_TABLE_HOOK("window-linked", ""), - OPTIONS_TABLE_HOOK("window-pane-changed", ""), - OPTIONS_TABLE_HOOK("window-renamed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-renamed", ""), OPTIONS_TABLE_HOOK("window-unlinked", ""), { .name = NULL } diff --git a/options.c b/options.c index 7402b724..e32db774 100644 --- a/options.c +++ b/options.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -53,6 +54,9 @@ struct options_entry { const struct options_table_entry *tableentry; union options_value value; + int cached; + struct style style; + RB_ENTRY(options_entry) entry; }; @@ -62,6 +66,7 @@ struct options { }; static struct options_entry *options_add(struct options *, const char *); +static void options_remove(struct options_entry *); #define OPTIONS_IS_STRING(o) \ ((o)->tableentry == NULL || \ @@ -73,9 +78,6 @@ static struct options_entry *options_add(struct options *, const char *); (o)->tableentry->type == OPTIONS_TABLE_COLOUR || \ (o)->tableentry->type == OPTIONS_TABLE_FLAG || \ (o)->tableentry->type == OPTIONS_TABLE_CHOICE)) -#define OPTIONS_IS_STYLE(o) \ - ((o)->tableentry != NULL && \ - (o)->tableentry->type == OPTIONS_TABLE_STYLE) #define OPTIONS_IS_COMMAND(o) \ ((o)->tableentry != NULL && \ (o)->tableentry->type == OPTIONS_TABLE_COMMAND) @@ -93,6 +95,18 @@ options_cmp(struct options_entry *lhs, struct options_entry *rhs) return (strcmp(lhs->name, rhs->name)); } +static const char * +options_map_name(const char *name) +{ + const struct options_name_map *map; + + for (map = options_other_names; map->from != NULL; map++) { + if (strcmp(map->from, name) == 0) + return (map->to); + } + return (name); +} + static const struct options_table_entry * options_parent_table_entry(struct options *oo, const char *s) { @@ -116,22 +130,20 @@ options_value_free(struct options_entry *o, union options_value *ov) } static char * -options_value_tostring(struct options_entry *o, union options_value *ov, +options_value_to_string(struct options_entry *o, union options_value *ov, int numeric) { char *s; if (OPTIONS_IS_COMMAND(o)) return (cmd_list_print(ov->cmdlist, 0)); - if (OPTIONS_IS_STYLE(o)) - return (xstrdup(style_tostring(&ov->style))); if (OPTIONS_IS_NUMBER(o)) { switch (o->tableentry->type) { case OPTIONS_TABLE_NUMBER: xasprintf(&s, "%lld", ov->number); break; case OPTIONS_TABLE_KEY: - s = xstrdup(key_string_lookup_key(ov->number)); + s = xstrdup(key_string_lookup_key(ov->number, 0)); break; case OPTIONS_TABLE_COLOUR: s = xstrdup(colour_tostring(ov->number)); @@ -145,9 +157,7 @@ options_value_tostring(struct options_entry *o, union options_value *ov, case OPTIONS_TABLE_CHOICE: s = xstrdup(o->tableentry->choices[ov->number]); break; - case OPTIONS_TABLE_STRING: - case OPTIONS_TABLE_STYLE: - case OPTIONS_TABLE_COMMAND: + default: fatalx("not a number option type"); } return (s); @@ -178,6 +188,12 @@ options_free(struct options *oo) free(oo); } +struct options * +options_get_parent(struct options *oo) +{ + return (oo->parent); +} + void options_set_parent(struct options *oo, struct options *parent) { @@ -199,10 +215,14 @@ options_next(struct options_entry *o) struct options_entry * options_get_only(struct options *oo, const char *name) { - struct options_entry o; + struct options_entry o = { .name = name }, *found; - o.name = name; - return (RB_FIND(options_tree, &oo->tree, &o)); + found = RB_FIND(options_tree, &oo->tree, &o); + if (found == NULL) { + o.name = options_map_name(name); + return (RB_FIND(options_tree, &oo->tree, &o)); + } + return (found); } struct options_entry * @@ -258,10 +278,6 @@ options_default(struct options *oo, const struct options_table_entry *oe) case OPTIONS_TABLE_STRING: ov->string = xstrdup(oe->default_str); break; - case OPTIONS_TABLE_STYLE: - style_set(&ov->style, &grid_default_cell); - style_parse(&ov->style, &grid_default_cell, oe->default_str); - break; default: ov->number = oe->default_num; break; @@ -269,6 +285,37 @@ options_default(struct options *oo, const struct options_table_entry *oe) return (o); } +char * +options_default_to_string(const struct options_table_entry *oe) +{ + char *s; + + switch (oe->type) { + case OPTIONS_TABLE_STRING: + case OPTIONS_TABLE_COMMAND: + s = xstrdup(oe->default_str); + break; + case OPTIONS_TABLE_NUMBER: + xasprintf(&s, "%lld", oe->default_num); + break; + case OPTIONS_TABLE_KEY: + s = xstrdup(key_string_lookup_key(oe->default_num, 0)); + break; + case OPTIONS_TABLE_COLOUR: + s = xstrdup(colour_tostring(oe->default_num)); + break; + case OPTIONS_TABLE_FLAG: + s = xstrdup(oe->default_num ? "on" : "off"); + break; + case OPTIONS_TABLE_CHOICE: + s = xstrdup(oe->choices[oe->default_num]); + break; + default: + fatalx("unknown option type"); + } + return (s); +} + static struct options_entry * options_add(struct options *oo, const char *name) { @@ -286,7 +333,7 @@ options_add(struct options *oo, const char *name) return (o); } -void +static void options_remove(struct options_entry *o) { struct options *oo = o->owner; @@ -306,6 +353,12 @@ options_name(struct options_entry *o) return (o->name); } +struct options * +options_owner(struct options_entry *o) +{ + return (o->owner); +} + const struct options_table_entry * options_table_entry(struct options_entry *o) { @@ -349,7 +402,7 @@ options_array_clear(struct options_entry *o) return; RB_FOREACH_SAFE(a, options_array, &o->value.array, a1) - options_array_free(o, a); + options_array_free(o, a); } union options_value * @@ -372,6 +425,7 @@ options_array_set(struct options_entry *o, u_int idx, const char *value, struct options_array_item *a; char *new; struct cmd_parse_result *pr; + long long number; if (!OPTIONS_IS_ARRAY(o)) { if (cause != NULL) @@ -389,10 +443,6 @@ options_array_set(struct options_entry *o, u_int idx, const char *value, if (OPTIONS_IS_COMMAND(o)) { pr = cmd_parse_from_string(value, NULL); switch (pr->status) { - case CMD_PARSE_EMPTY: - if (cause != NULL) - *cause = xstrdup("empty command"); - return (-1); case CMD_PARSE_ERROR: if (cause != NULL) *cause = pr->error; @@ -426,6 +476,20 @@ options_array_set(struct options_entry *o, u_int idx, const char *value, return (0); } + if (o->tableentry->type == OPTIONS_TABLE_COLOUR) { + if ((number = colour_fromstring(value)) == -1) { + xasprintf(cause, "bad colour: %s", value); + return (-1); + } + a = options_array_item(o, idx); + if (a == NULL) + a = options_array_new(o, idx); + else + options_value_free(o, &a->value); + a->value.number = number; + return (0); + } + if (cause != NULL) *cause = xstrdup("wrong array type"); return (-1); @@ -499,19 +563,19 @@ options_array_item_value(struct options_array_item *a) } int -options_isarray(struct options_entry *o) +options_is_array(struct options_entry *o) { return (OPTIONS_IS_ARRAY(o)); } int -options_isstring(struct options_entry *o) +options_is_string(struct options_entry *o) { return (OPTIONS_IS_STRING(o)); } char * -options_tostring(struct options_entry *o, int idx, int numeric) +options_to_string(struct options_entry *o, int idx, int numeric) { struct options_array_item *a; @@ -521,9 +585,9 @@ options_tostring(struct options_entry *o, int idx, int numeric) a = options_array_item(o, idx); if (a == NULL) return (xstrdup("")); - return (options_value_tostring(o, &a->value, numeric)); + return (options_value_to_string(o, &a->value, numeric)); } - return (options_value_tostring(o, &o->value, numeric)); + return (options_value_to_string(o, &o->value, numeric)); } char * @@ -572,19 +636,21 @@ char * options_match(const char *s, int *idx, int *ambiguous) { const struct options_table_entry *oe, *found; - char *name; + char *parsed; + const char *name; size_t namelen; - name = options_parse(s, idx); - if (name == NULL) + parsed = options_parse(s, idx); + if (parsed == NULL) return (NULL); - namelen = strlen(name); - - if (*name == '@') { + if (*parsed == '@') { *ambiguous = 0; - return (name); + return (parsed); } + name = options_map_name(parsed); + namelen = strlen(name); + found = NULL; for (oe = options_table; oe->name != NULL; oe++) { if (strcmp(oe->name, name) == 0) { @@ -594,13 +660,13 @@ options_match(const char *s, int *idx, int *ambiguous) if (strncmp(oe->name, name, namelen) == 0) { if (found != NULL) { *ambiguous = 1; - free(name); + free(parsed); return (NULL); } found = oe; } } - free(name); + free(parsed); if (found == NULL) { *ambiguous = 0; return (NULL); @@ -649,29 +715,17 @@ options_get_number(struct options *oo, const char *name) if (o == NULL) fatalx("missing option %s", name); if (!OPTIONS_IS_NUMBER(o)) - fatalx("option %s is not a number", name); + fatalx("option %s is not a number", name); return (o->value.number); } -struct style * -options_get_style(struct options *oo, const char *name) -{ - struct options_entry *o; - - o = options_get(oo, name); - if (o == NULL) - fatalx("missing option %s", name); - if (!OPTIONS_IS_STYLE(o)) - fatalx("option %s is not a style", name); - return (&o->value.style); -} - struct options_entry * options_set_string(struct options *oo, const char *name, int append, const char *fmt, ...) { struct options_entry *o; va_list ap; + const char *separator = ""; char *s, *value; va_start(ap, fmt); @@ -680,7 +734,12 @@ options_set_string(struct options *oo, const char *name, int append, o = options_get_only(oo, name); if (o != NULL && append && OPTIONS_IS_STRING(o)) { - xasprintf(&value, "%s%s", o->value.string, s); + if (*name != '@') { + separator = o->tableentry->separator; + if (separator == NULL) + separator = ""; + } + xasprintf(&value, "%s%s%s", o->value.string, separator, s); free(s); } else value = s; @@ -696,6 +755,7 @@ options_set_string(struct options *oo, const char *name, int append, fatalx("option %s is not a string", name); free(o->value.string); o->value.string = value; + o->cached = 0; return (o); } @@ -720,35 +780,6 @@ options_set_number(struct options *oo, const char *name, long long value) return (o); } -struct options_entry * -options_set_style(struct options *oo, const char *name, int append, - const char *value) -{ - struct options_entry *o; - struct style sy; - - if (*name == '@') - fatalx("user option %s must be a string", name); - - o = options_get_only(oo, name); - if (o != NULL && append && OPTIONS_IS_STYLE(o)) - style_copy(&sy, &o->value.style); - else - style_set(&sy, &grid_default_cell); - if (style_parse(&sy, &grid_default_cell, value) == -1) - return (NULL); - if (o == NULL) { - o = options_default(oo, options_parent_table_entry(oo, name)); - if (o == NULL) - return (NULL); - } - - if (!OPTIONS_IS_STYLE(o)) - fatalx("option %s is not a style", name); - style_copy(&o->value.style, &sy); - return (o); -} - int options_scope_from_name(struct args *args, int window, const char *name, struct cmd_find_state *fs, struct options **oo, @@ -874,3 +905,257 @@ options_scope_from_flags(struct args *args, int window, return (OPTIONS_TABLE_SESSION); } } + +struct style * +options_string_to_style(struct options *oo, const char *name, + struct format_tree *ft) +{ + struct options_entry *o; + const char *s; + char *expanded; + + o = options_get(oo, name); + if (o == NULL || !OPTIONS_IS_STRING(o)) + return (NULL); + + if (o->cached) + return (&o->style); + s = o->value.string; + log_debug("%s: %s is '%s'", __func__, name, s); + + style_set(&o->style, &grid_default_cell); + o->cached = (strstr(s, "#{") == NULL); + + if (ft != NULL && !o->cached) { + expanded = format_expand(ft, s); + if (style_parse(&o->style, &grid_default_cell, expanded) != 0) { + free(expanded); + return (NULL); + } + free(expanded); + } else { + if (style_parse(&o->style, &grid_default_cell, s) != 0) + return (NULL); + } + return (&o->style); +} + +static int +options_from_string_check(const struct options_table_entry *oe, + const char *value, char **cause) +{ + struct style sy; + + if (oe == NULL) + return (0); + if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { + xasprintf(cause, "not a suitable shell: %s", value); + return (-1); + } + if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { + xasprintf(cause, "value is invalid: %s", value); + return (-1); + } + if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && + strstr(value, "#{") == NULL && + style_parse(&sy, &grid_default_cell, value) != 0) { + xasprintf(cause, "invalid style: %s", value); + return (-1); + } + return (0); +} + +static int +options_from_string_flag(struct options *oo, const char *name, + const char *value, char **cause) +{ + int flag; + + if (value == NULL || *value == '\0') + flag = !options_get_number(oo, name); + else if (strcmp(value, "1") == 0 || + strcasecmp(value, "on") == 0 || + strcasecmp(value, "yes") == 0) + flag = 1; + else if (strcmp(value, "0") == 0 || + strcasecmp(value, "off") == 0 || + strcasecmp(value, "no") == 0) + flag = 0; + else { + xasprintf(cause, "bad value: %s", value); + return (-1); + } + options_set_number(oo, name, flag); + return (0); +} + +static int +options_from_string_choice(const struct options_table_entry *oe, + struct options *oo, const char *name, const char *value, char **cause) +{ + const char **cp; + int n, choice = -1; + + if (value == NULL) { + choice = options_get_number(oo, name); + if (choice < 2) + choice = !choice; + } else { + n = 0; + for (cp = oe->choices; *cp != NULL; cp++) { + if (strcmp(*cp, value) == 0) + choice = n; + n++; + } + if (choice == -1) { + xasprintf(cause, "unknown value: %s", value); + return (-1); + } + } + options_set_number(oo, name, choice); + return (0); +} + +int +options_from_string(struct options *oo, const struct options_table_entry *oe, + const char *name, const char *value, int append, char **cause) +{ + enum options_table_type type; + long long number; + const char *errstr, *new; + char *old; + key_code key; + + if (oe != NULL) { + if (value == NULL && + oe->type != OPTIONS_TABLE_FLAG && + oe->type != OPTIONS_TABLE_CHOICE) { + xasprintf(cause, "empty value"); + return (-1); + } + type = oe->type; + } else { + if (*name != '@') { + xasprintf(cause, "bad option name"); + return (-1); + } + type = OPTIONS_TABLE_STRING; + } + + switch (type) { + case OPTIONS_TABLE_STRING: + old = xstrdup(options_get_string(oo, name)); + options_set_string(oo, name, append, "%s", value); + + new = options_get_string(oo, name); + if (options_from_string_check(oe, new, cause) != 0) { + options_set_string(oo, name, 0, "%s", old); + free(old); + return (-1); + } + free(old); + return (0); + case OPTIONS_TABLE_NUMBER: + number = strtonum(value, oe->minimum, oe->maximum, &errstr); + if (errstr != NULL) { + xasprintf(cause, "value is %s: %s", errstr, value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_KEY: + key = key_string_lookup_string(value); + if (key == KEYC_UNKNOWN) { + xasprintf(cause, "bad key: %s", value); + return (-1); + } + options_set_number(oo, name, key); + return (0); + case OPTIONS_TABLE_COLOUR: + if ((number = colour_fromstring(value)) == -1) { + xasprintf(cause, "bad colour: %s", value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_FLAG: + return (options_from_string_flag(oo, name, value, cause)); + case OPTIONS_TABLE_CHOICE: + return (options_from_string_choice(oe, oo, name, value, cause)); + case OPTIONS_TABLE_COMMAND: + break; + } + return (-1); +} + +void +options_push_changes(const char *name) +{ + struct client *loop; + struct session *s; + struct window *w; + struct window_pane *wp; + + if (strcmp(name, "automatic-rename") == 0) { + RB_FOREACH(w, windows, &windows) { + if (w->active == NULL) + continue; + if (options_get_number(w->options, "automatic-rename")) + w->active->flags |= PANE_CHANGED; + } + } + if (strcmp(name, "key-table") == 0) { + TAILQ_FOREACH(loop, &clients, entry) + server_client_set_key_table(loop, NULL); + } + if (strcmp(name, "user-keys") == 0) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->tty.flags & TTY_OPENED) + tty_keys_build(&loop->tty); + } + } + if (strcmp(name, "status") == 0 || + strcmp(name, "status-interval") == 0) + status_timer_start_all(); + if (strcmp(name, "monitor-silence") == 0) + alerts_reset_all(); + if (strcmp(name, "window-style") == 0 || + strcmp(name, "window-active-style") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + wp->flags |= PANE_STYLECHANGED; + } + if (strcmp(name, "pane-colours") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + colour_palette_from_option(&wp->palette, wp->options); + } + if (strcmp(name, "pane-border-status") == 0) { + RB_FOREACH(w, windows, &windows) + layout_fix_panes(w, NULL); + } + RB_FOREACH(s, sessions, &sessions) + status_update_cache(s); + + recalculate_sizes(); + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session != NULL) + server_redraw_client(loop); + } +} + +int +options_remove_or_default(struct options_entry *o, int idx, char **cause) +{ + struct options *oo = o->owner; + + if (idx == -1) { + if (o->tableentry != NULL && + (oo == global_options || + oo == global_s_options || + oo == global_w_options)) + options_default(oo, o->tableentry); + else + options_remove(o); + } else if (options_array_set(o, idx, NULL, 0, cause) != 0) + return (-1); + return (0); +} diff --git a/osdep-cygwin.c b/osdep-cygwin.c index 60630b33..4346373c 100644 --- a/osdep-cygwin.c +++ b/osdep-cygwin.c @@ -19,7 +19,6 @@ #include #include -#include #include #include #include diff --git a/osdep-darwin.c b/osdep-darwin.c index d4a88028..a2b125ad 100644 --- a/osdep-darwin.c +++ b/osdep-darwin.c @@ -20,12 +20,13 @@ #include #include -#include #include #include #include #include +#include "compat.h" + char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); @@ -61,7 +62,7 @@ osdep_get_name(int fd, __unused char *tty) size = sizeof kp; if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) return (NULL); - if (*kp.kp_proc.p_comm == '\0') + if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') return (NULL); return (strdup(kp.kp_proc.p_comm)); diff --git a/osdep-dragonfly.c b/osdep-dragonfly.c index 879034e8..02a4d18e 100644 --- a/osdep-dragonfly.c +++ b/osdep-dragonfly.c @@ -23,7 +23,6 @@ #include #include -#include #include #include #include diff --git a/osdep-freebsd.c b/osdep-freebsd.c index a7d02930..0f347f9a 100644 --- a/osdep-freebsd.c +++ b/osdep-freebsd.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include diff --git a/osdep-haiku.c b/osdep-haiku.c new file mode 100644 index 00000000..7b1f800a --- /dev/null +++ b/osdep-haiku.c @@ -0,0 +1,52 @@ +/* $OpenBSD$ */ + +/* + * 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" + +char * +osdep_get_name(int fd, __unused char *tty) +{ + pid_t tid; + team_info tinfo; + + if ((tid = tcgetpgrp(fd)) == -1) + return (NULL); + + if (get_team_info(tid, &tinfo) != B_OK) + return (NULL); + + /* Up to the first 64 characters. */ + return (xstrdup(tinfo.args)); +} + +char * +osdep_get_cwd(int fd) +{ + return (NULL); +} + +struct event_base * +osdep_event_init(void) +{ + return (event_init()); +} diff --git a/osdep-hpux.c b/osdep-hpux.c index 16993b93..64e33784 100644 --- a/osdep-hpux.c +++ b/osdep-hpux.c @@ -18,8 +18,6 @@ #include -#include - #include "tmux.h" char * diff --git a/osdep-linux.c b/osdep-linux.c index 5f0d9352..7dbab1f0 100644 --- a/osdep-linux.c +++ b/osdep-linux.c @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/osdep-netbsd.c b/osdep-netbsd.c index 67894175..b473e017 100644 --- a/osdep-netbsd.c +++ b/osdep-netbsd.c @@ -22,7 +22,6 @@ #include #include -#include #include #include #include diff --git a/osdep-openbsd.c b/osdep-openbsd.c index b21a6628..f5c61372 100644 --- a/osdep-openbsd.c +++ b/osdep-openbsd.c @@ -23,11 +23,12 @@ #include #include -#include #include #include #include +#include "compat.h" + #ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #endif diff --git a/osdep-sunos.c b/osdep-sunos.c index 07043a9b..c3563ca4 100644 --- a/osdep-sunos.c +++ b/osdep-sunos.c @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -97,5 +96,17 @@ osdep_get_cwd(int fd) struct event_base * osdep_event_init(void) { - return (event_init()); + struct event_base *base; + + /* + * On Illumos, evports don't seem to work properly. It is not clear if + * this a problem in libevent, with the way tmux uses file descriptors, + * or with some types of file descriptor. But using poll instead is + * fine. + */ + setenv("EVENT_NOEVPORT", "1", 1); + + base = event_init(); + unsetenv("EVENT_NOEVPORT"); + return (base); } diff --git a/osdep-unknown.c b/osdep-unknown.c index bc59f569..440f619e 100644 --- a/osdep-unknown.c +++ b/osdep-unknown.c @@ -18,8 +18,6 @@ #include -#include - #include "tmux.h" char * diff --git a/paste.c b/paste.c index c1036ad9..51ae2b1d 100644 --- a/paste.c +++ b/paste.c @@ -296,13 +296,22 @@ paste_set(char *data, size_t size, const char *name, char **cause) return (0); } +/* Set paste data without otherwise changing it. */ +void +paste_replace(struct paste_buffer *pb, char *data, size_t size) +{ + free(pb->data); + pb->data = data; + pb->size = size; +} + /* Convert start of buffer into a nice string. */ char * paste_make_sample(struct paste_buffer *pb) { char *buf; size_t len, used; - const int flags = VIS_OCTAL|VIS_TAB|VIS_NL; + const int flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; const size_t width = 200; len = pb->size; diff --git a/popup.c b/popup.c new file mode 100644 index 00000000..3f64a852 --- /dev/null +++ b/popup.c @@ -0,0 +1,734 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 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 +#include +#include + +#include "tmux.h" + +struct popup_data { + struct client *c; + struct cmdq_item *item; + int flags; + + struct screen s; + struct colour_palette palette; + struct job *job; + struct input_ctx *ictx; + int status; + popup_close_cb cb; + void *arg; + + struct menu *menu; + struct menu_data *md; + int close; + + /* Current position and size. */ + u_int px; + u_int py; + u_int sx; + u_int sy; + + /* Preferred position and size. */ + u_int ppx; + u_int ppy; + u_int psx; + u_int psy; + + enum { OFF, MOVE, SIZE } dragging; + u_int dx; + u_int dy; + + u_int lx; + u_int ly; + u_int lb; +}; + +struct popup_editor { + char *path; + popup_finish_edit_cb cb; + void *arg; +}; + +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 const struct menu_item popup_internal_menu_items[] = { + { "Close", 'q', NULL }, + { "", KEYC_NONE, NULL }, + { "Fill Space", 'F', NULL }, + { "Centre", 'C', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + +static void +popup_redraw_cb(const struct tty_ctx *ttyctx) +{ + struct popup_data *pd = ttyctx->arg; + + pd->c->flags |= CLIENT_REDRAWOVERLAY; +} + +static int +popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c) +{ + struct popup_data *pd = ttyctx->arg; + + if (c != pd->c) + return (0); + if (pd->c->flags & CLIENT_REDRAWOVERLAY) + return (0); + + ttyctx->bigger = 0; + ttyctx->wox = 0; + ttyctx->woy = 0; + ttyctx->wsx = c->tty.sx; + ttyctx->wsy = c->tty.sy; + + if (pd->flags & POPUP_NOBORDER) { + ttyctx->xoff = ttyctx->rxoff = pd->px; + ttyctx->yoff = ttyctx->ryoff = pd->py; + } else { + ttyctx->xoff = ttyctx->rxoff = pd->px + 1; + ttyctx->yoff = ttyctx->ryoff = pd->py + 1; + } + + return (1); +} + +static void +popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) +{ + struct popup_data *pd = ctx->arg; + + ttyctx->palette = &pd->palette; + ttyctx->redraw_cb = popup_redraw_cb; + ttyctx->set_client_cb = popup_set_client_cb; + ttyctx->arg = pd; +} + +static struct screen * +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->flags & POPUP_NOBORDER) { + *cx = pd->px + pd->s.cx; + *cy = pd->py + pd->s.cy; + } else { + *cx = pd->px + 1 + pd->s.cx; + *cy = pd->py + 1 + pd->s.cy; + } + return (&pd->s); +} + +static int +popup_check_cb(struct client *c, void *data, u_int px, u_int py) +{ + struct popup_data *pd = data; + + if (pd->md != NULL && menu_check_cb(c, pd->md, px, py) == 0) + return (0); + if (px < pd->px || px > pd->px + pd->sx - 1) + return (1); + if (py < pd->py || py > pd->py + pd->sy - 1) + return (1); + return (0); +} + +static void +popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx) +{ + struct popup_data *pd = data; + struct tty *tty = &c->tty; + struct screen s; + struct screen_write_ctx ctx; + u_int i, px = pd->px, py = pd->py; + struct colour_palette *palette = &pd->palette; + struct grid_cell gc; + + screen_init(&s, pd->sx, pd->sy, 0); + screen_write_start(&ctx, &s); + screen_write_clearscreen(&ctx, 8); + + if (pd->flags & POPUP_NOBORDER) { + screen_write_cursormove(&ctx, 0, 0, 0); + screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy); + } else if (pd->sx > 2 && pd->sy > 2) { + screen_write_box(&ctx, pd->sx, pd->sy); + screen_write_cursormove(&ctx, 1, 1, 0); + screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2, + pd->sy - 2); + } + screen_write_stop(&ctx); + + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = pd->palette.fg; + gc.bg = pd->palette.bg; + + if (pd->md != NULL) { + c->overlay_check = menu_check_cb; + c->overlay_data = pd->md; + } else { + 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, &gc, palette); + if (pd->md != NULL) { + c->overlay_check = NULL; + c->overlay_data = NULL; + menu_draw_cb(c, pd->md, rctx); + } + c->overlay_check = popup_check_cb; + c->overlay_data = pd; +} + +static void +popup_free_cb(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); + + if (item != NULL) { + if (cmdq_get_client(item) != NULL && + cmdq_get_client(item)->session == NULL) + cmdq_get_client(item)->retval = pd->status; + cmdq_continue(item); + } + server_client_unref(pd->c); + + if (pd->job != NULL) + job_free(pd->job); + input_free(pd->ictx); + + screen_free(&pd->s); + colour_palette_free(&pd->palette); + + free(pd); +} + +static void +popup_resize_cb(__unused struct client *c, void *data) +{ + struct popup_data *pd = data; + struct tty *tty = &c->tty; + + if (pd == NULL) + return; + if (pd->md != NULL) + menu_free_cb(c, pd->md); + + /* Adjust position and size. */ + if (pd->psy > tty->sy) + pd->sy = tty->sy; + else + pd->sy = pd->psy; + if (pd->psx > tty->sx) + pd->sx = tty->sx; + else + pd->sx = pd->psx; + if (pd->ppy + pd->sy > tty->sy) + pd->py = tty->sy - pd->sy; + else + pd->py = pd->ppy; + if (pd->ppx + pd->sx > tty->sx) + pd->px = tty->sx - pd->sx; + else + pd->px = pd->ppx; + + /* Avoid zero size screens. */ + if (pd->flags & POPUP_NOBORDER) { + screen_resize(&pd->s, pd->sx, pd->sy, 0); + if (pd->job != NULL) + job_resize(pd->job, pd->sx, pd->sy ); + } else if (pd->sx > 2 && pd->sy > 2) { + screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0); + if (pd->job != NULL) + job_resize(pd->job, pd->sx - 2, pd->sy - 2); + } +} + +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); + + lc = layout_split_pane(wp, type, -1, 0); + 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); + + 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); + 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) +{ + u_int px, py; + + if (!MOUSE_DRAG(m->b)) + pd->dragging = OFF; + else if (pd->dragging == MOVE) { + if (m->x < pd->dx) + px = 0; + else if (m->x - pd->dx + pd->sx > c->tty.sx) + px = c->tty.sx - pd->sx; + else + px = m->x - pd->dx; + if (m->y < pd->dy) + py = 0; + else if (m->y - pd->dy + pd->sy > c->tty.sy) + py = c->tty.sy - pd->sy; + else + py = m->y - pd->dy; + pd->px = px; + pd->py = py; + pd->dx = m->x - pd->px; + pd->dy = m->y - pd->py; + pd->ppx = px; + pd->ppy = py; + server_redraw_client(c); + } else if (pd->dragging == SIZE) { + if (pd->flags & POPUP_NOBORDER) { + if (m->x < pd->px + 1) + return; + if (m->y < pd->py + 1) + return; + } else { + if (m->x < pd->px + 3) + return; + if (m->y < pd->py + 3) + return; + } + pd->sx = m->x - pd->px; + pd->sy = m->y - pd->py; + pd->psx = pd->sx; + pd->psy = pd->sy; + + if (pd->flags & POPUP_NOBORDER) { + screen_resize(&pd->s, pd->sx, pd->sy, 0); + if (pd->job != NULL) + job_resize(pd->job, pd->sx, pd->sy); + } else { + screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0); + if (pd->job != NULL) + job_resize(pd->job, pd->sx - 2, pd->sy - 2); + } + server_redraw_client(c); + } +} + +static int +popup_key_cb(struct client *c, void *data, struct key_event *event) +{ + struct popup_data *pd = data; + struct mouse_event *m = &event->m; + const char *buf; + size_t len; + u_int px, py, x; + 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); + goto out; + } + if (m->x < pd->px || + m->x > pd->px + pd->sx - 1 || + m->y < pd->py || + m->y > pd->py + pd->sy - 1) { + if (MOUSE_BUTTONS(m->b) == 2) + goto menu; + return (0); + } + if (~pd->flags & POPUP_NOBORDER) { + if (m->x == pd->px) + border = LEFT; + else if (m->x == pd->px + pd->sx - 1) + border = RIGHT; + else if (m->y == pd->py) + border = TOP; + else if (m->y == pd->py + pd->sy - 1) + border = BOTTOM; + } + if ((m->b & MOUSE_MASK_MODIFIERS) == 0 && + MOUSE_BUTTONS(m->b) == 2 && + (border == LEFT || border == TOP)) + goto menu; + if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) || + border != NONE) { + if (!MOUSE_DRAG(m->b)) + goto out; + if (MOUSE_BUTTONS(m->lb) == 0) + pd->dragging = MOVE; + else if (MOUSE_BUTTONS(m->lb) == 2) + pd->dragging = SIZE; + pd->dx = m->lx - pd->px; + pd->dy = m->ly - pd->py; + goto out; + } + } + if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) || + pd->job == NULL) && + (event->key == '\033' || event->key == '\003')) + return (1); + if (pd->job != NULL) { + if (KEYC_IS_MOUSE(event->key)) { + /* Must be inside, checked already. */ + if (pd->flags & POPUP_NOBORDER) { + px = m->x - pd->px; + py = m->y - pd->py; + } else { + px = m->x - pd->px - 1; + py = m->y - pd->py - 1; + } + if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len)) + return (0); + bufferevent_write(job_get_event(pd->job), buf, len); + return (0); + } + input_key(&pd->s, job_get_event(pd->job), event->key); + } + return (0); + +menu: + pd->menu = menu_create(""); + if (pd->flags & POPUP_INTERNAL) { + menu_add_items(pd->menu, popup_internal_menu_items, NULL, NULL, + NULL); + } else + menu_add_items(pd->menu, popup_menu_items, NULL, NULL, 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, NULL, x, m->y, c, NULL, + popup_menu_done, pd); + c->flags |= CLIENT_REDRAWOVERLAY; + +out: + pd->lx = m->x; + pd->ly = m->y; + pd->lb = m->b; + return (0); +} + +static void +popup_job_update_cb(struct job *job) +{ + struct popup_data *pd = job_get_data(job); + struct evbuffer *evb = job_get_event(job)->input; + struct client *c = pd->c; + struct screen *s = &pd->s; + void *data = EVBUFFER_DATA(evb); + size_t size = EVBUFFER_LENGTH(evb); + + 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; + } + input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size); + c->overlay_check = popup_check_cb; + c->overlay_data = pd; + + evbuffer_drain(evb, size); +} + +static void +popup_job_complete_cb(struct job *job) +{ + struct popup_data *pd = job_get_data(job); + int status; + + status = job_get_status(pd->job); + if (WIFEXITED(status)) + pd->status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + pd->status = WTERMSIG(status); + else + pd->status = 0; + pd->job = NULL; + + if ((pd->flags & POPUP_CLOSEEXIT) || + ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0)) + server_client_clear_overlay(pd->c); +} + +int +popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, + u_int sy, const char *shellcmd, int argc, char **argv, const char *cwd, + struct client *c, struct session *s, popup_close_cb cb, void *arg) +{ + struct popup_data *pd; + u_int jx, jy; + + if (flags & POPUP_NOBORDER) { + if (sx < 1 || sy < 1) + return (-1); + jx = sx; + jy = sy; + } else { + if (sx < 3 || sy < 3) + return (-1); + jx = sx - 2; + jy = sy - 2; + } + if (c->tty.sx < sx || c->tty.sy < sy) + return (-1); + + pd = xcalloc(1, sizeof *pd); + pd->item = item; + pd->flags = flags; + + pd->c = c; + pd->c->references++; + + pd->cb = cb; + pd->arg = arg; + pd->status = 128 + SIGHUP; + + screen_init(&pd->s, sx - 2, sy - 2, 0); + colour_palette_init(&pd->palette); + colour_palette_from_option(&pd->palette, global_w_options); + + pd->px = px; + pd->py = py; + pd->sx = sx; + pd->sy = sy; + + pd->ppx = px; + pd->ppy = py; + pd->psx = sx; + pd->psy = sy; + + pd->job = job_run(shellcmd, argc, argv, s, cwd, + popup_job_update_cb, popup_job_complete_cb, NULL, pd, + JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, jx, jy); + pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette); + + server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, + popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd); + return (0); +} + +static void +popup_editor_free(struct popup_editor *pe) +{ + unlink(pe->path); + free(pe->path); + free(pe); +} + +static void +popup_editor_close_cb(int status, void *arg) +{ + struct popup_editor *pe = arg; + FILE *f; + char *buf = NULL; + off_t len = 0; + + if (status != 0) { + pe->cb(NULL, 0, pe->arg); + popup_editor_free(pe); + return; + } + + f = fopen(pe->path, "r"); + if (f != NULL) { + fseeko(f, 0, SEEK_END); + len = ftello(f); + fseeko(f, 0, SEEK_SET); + + if (len == 0 || + (uintmax_t)len > (uintmax_t)SIZE_MAX || + (buf = malloc(len)) == NULL || + fread(buf, len, 1, f) != 1) { + free(buf); + buf = NULL; + len = 0; + } + fclose(f); + } + pe->cb(buf, len, pe->arg); /* callback now owns buffer */ + popup_editor_free(pe); +} + +int +popup_editor(struct client *c, const char *buf, size_t len, + popup_finish_edit_cb cb, void *arg) +{ + struct popup_editor *pe; + int fd; + FILE *f; + char *cmd; + char path[] = _PATH_TMP "tmux.XXXXXXXX"; + const char *editor; + u_int px, py, sx, sy; + + editor = options_get_string(global_options, "editor"); + if (*editor == '\0') + return (-1); + + fd = mkstemp(path); + if (fd == -1) + return (-1); + f = fdopen(fd, "w"); + if (fwrite(buf, len, 1, f) != 1) { + fclose(f); + return (-1); + } + fclose(f); + + pe = xcalloc(1, sizeof *pe); + pe->path = xstrdup(path); + pe->cb = cb; + pe->arg = arg; + + sx = c->tty.sx * 9 / 10; + sy = c->tty.sy * 9 / 10; + px = (c->tty.sx / 2) - (sx / 2); + py = (c->tty.sy / 2) - (sy / 2); + + xasprintf(&cmd, "%s %s", editor, path); + if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, NULL, px, py, sx, sy, + cmd, 0, NULL, _PATH_TMP, c, NULL, popup_editor_close_cb, pe) != 0) { + popup_editor_free(pe); + free(cmd); + return (-1); + } + free(cmd); + return (0); +} diff --git a/proc.c b/proc.c index 111c3730..958a9483 100644 --- a/proc.c +++ b/proc.c @@ -17,16 +17,20 @@ */ #include +#include #include #include #include -#include #include #include #include #include +#if defined(HAVE_NCURSES_H) +#include +#endif + #include "tmux.h" struct tmuxproc { @@ -35,6 +39,7 @@ struct tmuxproc { void (*signalcb)(int); + struct event ev_sigint; struct event ev_sighup; struct event ev_sigchld; struct event ev_sigcont; @@ -42,6 +47,8 @@ struct tmuxproc { struct event ev_sigusr1; struct event ev_sigusr2; struct event ev_sigwinch; + + TAILQ_HEAD(, tmuxpeer) peers; }; struct tmuxpeer { @@ -55,6 +62,8 @@ struct tmuxpeer { void (*dispatchcb)(struct imsg *, void *); void *arg; + + TAILQ_ENTRY(tmuxpeer) entry; }; static int peer_check_version(struct tmuxpeer *, struct imsg *); @@ -182,11 +191,23 @@ proc_start(const char *name) log_debug("%s started (%ld): version %s, socket %s, protocol %d", name, (long)getpid(), getversion(), socket_path, PROTOCOL_VERSION); - log_debug("on %s %s %s; libevent %s (%s)", u.sysname, u.release, - u.version, event_get_version(), event_get_method()); + log_debug("on %s %s %s", u.sysname, u.release, u.version); + log_debug("using libevent %s (%s)" +#ifdef HAVE_UTF8PROC + "; utf8proc %s" +#endif +#ifdef NCURSES_VERSION + "; ncurses " NCURSES_VERSION +#endif + , event_get_version(), event_get_method() +#ifdef HAVE_UTF8PROC + , utf8proc_version () +#endif + ); tp = xcalloc(1, sizeof *tp); tp->name = xstrdup(name); + TAILQ_INIT(&tp->peers); return (tp); } @@ -204,6 +225,10 @@ proc_loop(struct tmuxproc *tp, int (*loopcb)(void)) void proc_exit(struct tmuxproc *tp) { + struct tmuxpeer *peer; + + TAILQ_FOREACH(peer, &tp->peers, entry) + imsg_flush(&peer->ibuf); tp->exit = 1; } @@ -219,10 +244,14 @@ proc_set_signals(struct tmuxproc *tp, void (*signalcb)(int)) sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_IGN; - sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGTTIN, &sa, NULL); + sigaction(SIGTTOU, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + signal_set(&tp->ev_sigint, SIGINT, proc_signal_cb, tp); + signal_add(&tp->ev_sigint, NULL); signal_set(&tp->ev_sighup, SIGHUP, proc_signal_cb, tp); signal_add(&tp->ev_sighup, NULL); signal_set(&tp->ev_sigchld, SIGCHLD, proc_signal_cb, tp); @@ -249,10 +278,10 @@ proc_clear_signals(struct tmuxproc *tp, int defaults) sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTSTP, &sa, NULL); + signal_del(&tp->ev_sigint); signal_del(&tp->ev_sighup); signal_del(&tp->ev_sigchld); signal_del(&tp->ev_sigcont); @@ -262,6 +291,8 @@ proc_clear_signals(struct tmuxproc *tp, int defaults) signal_del(&tp->ev_sigwinch); if (defaults) { + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGCHLD, &sa, NULL); sigaction(SIGCONT, &sa, NULL); @@ -288,6 +319,7 @@ proc_add_peer(struct tmuxproc *tp, int fd, event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); log_debug("add peer %p: %d (%p)", peer, fd, arg); + TAILQ_INSERT_TAIL(&tp->peers, peer, entry); proc_update_event(peer); return (peer); @@ -296,6 +328,7 @@ proc_add_peer(struct tmuxproc *tp, int fd, void proc_remove_peer(struct tmuxpeer *peer) { + TAILQ_REMOVE(&peer->parent->peers, peer, entry); log_debug("remove peer %p", peer); event_del(&peer->event); @@ -316,3 +349,27 @@ proc_toggle_log(struct tmuxproc *tp) { log_toggle(tp->name); } + +pid_t +proc_fork_and_daemon(int *fd) +{ + pid_t pid; + int pair[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); + switch (pid = fork()) { + case -1: + fatal("fork failed"); + case 0: + close(pair[0]); + *fd = pair[1]; + if (daemon(1, 0) != 0) + fatal("daemon failed"); + return (0); + default: + close(pair[1]); + *fd = pair[0]; + return (pid); + } +} diff --git a/regress/xenl-terminal.sh b/regress/am-terminal.sh similarity index 92% rename from regress/xenl-terminal.sh rename to regress/am-terminal.sh index 07469ceb..94033468 100644 --- a/regress/xenl-terminal.sh +++ b/regress/am-terminal.sh @@ -13,7 +13,7 @@ TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX2 -f/dev/null new -d || exit 1 -$TMUX2 set -as terminal-overrides ',*:xenl@' || exit 1 +$TMUX2 set -as terminal-overrides ',*:am@' || exit 1 $TMUX2 set -g status-right 'RRR' || exit 1 $TMUX2 set -g status-left 'LLL' || exit 1 $TMUX2 set -g window-status-current-format 'WWW' || exit 1 diff --git a/regress/capture-pane-sgr0.sh b/regress/capture-pane-sgr0.sh index 79d96a38..0dd9cd82 100644 --- a/regress/capture-pane-sgr0.sh +++ b/regress/capture-pane-sgr0.sh @@ -13,11 +13,18 @@ $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 -$TMUX -f/dev/null new -d \ - "printf '\033[31;42;1mabc\033[0;31mdef'; $TMUX capturep -peS0 -E0 >$TMP" +$TMUX -f/dev/null new -d " + printf '\033[31;42;1mabc\033[0;31mdef\n' + printf '\033[m\033[100m bright bg \033[m' + $TMUX capturep -peS0 -E1 >>$TMP" + + sleep 1 -printf '\033[1m\033[31m\033[42mabc\033[0m\033[31m\033[49mdef\033[39m\n'| \ - cmp - $TMP || exit 1 + +( + printf '\033[1m\033[31m\033[42mabc\033[0m\033[31m\033[49mdef\033[39m\n' + printf '\033[100m bright bg \033[49m\n' +) | cmp - $TMP || exit 1 $TMUX has 2>/dev/null && exit 1 diff --git a/regress/conf/01840240e807e837dbf76d85b4b938de.conf b/regress/conf/01840240e807e837dbf76d85b4b938de.conf new file mode 100644 index 00000000..dcd4899e --- /dev/null +++ b/regress/conf/01840240e807e837dbf76d85b4b938de.conf @@ -0,0 +1,1496 @@ +# Options {{{1 +# Server {{{2 + +# Don't pile up more than 10 buffers (down from 50 by default).{{{ +# +# Rationale: Keeping a tidy stack, with relevant information, could help us +# integrate tmux buffers in our workflow more often. +# +# However, maybe we could keep a big stack of buffers, and filter them by +# pressing `f` in the window opened by `choose-buffer`. +# Alternatively, we could also try to use fzf to fuzzy search through their +# contents... +#}}} +set -s buffer-limit 10 + + +# What does this option control?{{{ +# +# It sets the time in milliseconds for which tmux waits after an escape is input +# to determine if it is part of a function or meta key sequences. +# The default is 500 millisec onds. +#}}} +# Why do you reset it?{{{ +# +# The default value introduces lag when we use Vim and escape from insert to +# normal mode. We want to reduce the timeout. +#}}} +# Why don't you set it to 0 ?{{{ +# +# > Some people set it to zero but I consider that risky if you're connecting +# > over a wide-area network or there is anything else that might insert small +# > delays between the delivery of chars in such a sequence. +# +# Source: https://github.com/tmux/tmux/issues/353#issuecomment-294570322 +# +# Basically, we should still let a few ms to be sure all the keys in a control +# sequence will have enough time to reach tmux. +#}}} +set -s escape-time 10 + +# If the terminal supports focus events, they will be requested by the tmux +# client and passed through to the tmux server, then to the programs it runs. +# Necessary to be able to listen to `FocusGained` and `FocusLost`. +# Also necessary for `pane-focus-[in|out]` hooks. +set -s focus-events on + +# history of tmux commands (pfx :) +set -s history-file "$HOME/.tmux/command_history" + +# What does this do?{{{ +# +# It makes tmux sometimes send an OSC 52 sequence – which sets the terminal +# clipboard content – if there is an `Ms` entry in the terminfo description of +# the outer terminal. +#}}} +# What are the possible values of this option?{{{ +# +# - `on` +# - `external` +# - `off` +# +# --- +# +# There are 3 ways to create a tmux buffer: +# +# 1. invoke the `set-buffer` or `load-buffer` tmux commands +# 2. copy text in copy mode (`send -X copy-selection`, `copy-pipe`, ...) +# 3. send an OSC 52 sequence from an application inside tmux (e.g. `$ printf ...`) +# +# `1.` always creates a tmux buffer; never sets the X clipboard. +# `2.` always creates a tmux buffer; sets the X clipboard via OSC 52 iff `set-clipboard` is not `off`. +# `3.` creates a tmux buffer *and* sets the X clipboard via OSC 52 iff `set-clipboard` is `on`. +# +# IOW, `external` makes tmux *automatically* set the X clipboard when you yank +# sth in copy mode via OSC 52, while `on` does the same, but also allows an +# application to *manually* set a tmux buffer/the X clipboard via a OSC 52. +# +# Source: https://unix.stackexchange.com/a/560464/289772 +#}}} +# What is the possible drawback of the value `on`?{{{ +# +# https://github.com/tmux/tmux/wiki/Clipboard#security-concerns +#}}} +# How to disable OSC 52 for some terminals, but not all?{{{ +# +# # or use 'external' +# set -s set-clipboard on +# set -as terminal-overrides 'yourTERMname:Ms@' +# ^ +# man terminfo /Similar Terminals/;/canceled +#}}} +set -s set-clipboard external + +# Why do you move your 'terminal-overrides' settings in another file?{{{ +# +# It makes it easier to source the settings only when the tmux server is started; +# not when we manually re-source `tmux.conf`. +#}}} +# Why do you need them to be sourced only once?{{{ +# +# We *append* strings to the value of the 'terminal-overrides' option. +# I don't want to append the same strings again and again every time I re-source +# `tmux.conf`. +#}}} +# Why don't you simply reset the option before appending a value?{{{ +# +# That would make us lose the default value of the option: +# +# terminal-overrides[0] "xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007:Cs=\\E]12;%p1%s\\007:Cr=\\E]112\\007:Ss=\\E[%p1%d q:Se=\\E[2 q" +# terminal-overrides[1] screen*:XT +# +# --- +# +# Besides, one of the value we append to 'terminal-overrides' depends on the value of `$TERM`. +# +# if '[ "$TERM" != "st-256color" ]' 'set -as terminal-overrides ",*:Cr=\\E]112\\007"' +# ^----------------------^ +# +# And the value of `$TERM` will be correct only the first time we source `tmux.conf`. +#}}} + +# What's the value of `$TERM` here?{{{ +# +# The first time our `tmux.conf` is sourced, it matches the `$TERM` of the +# outer terminal; the next times, it's the value of 'default-terminal' (i.e. +# 'tmux-256color'). +#}}} +# What's the meaning of this `if` guard?{{{ +# +# The condition `[ "$TERM" != "tmux-256color" ]` is true only the first time our +# `tmux.conf` is sourced. +# So, this part of the guard means: “if this is the first time the file is sourced”. +# This is equivalent to `has('vim_starting')` in Vim. +# +# The condition `[ -n "$DISPLAY" ]` is true only in a GUI environment. +# So, this part of the guard means: “don't source the file if we are in a console”. +# Indeed, I doubt a linux console is able to understand any sequence we might +# want to use. +#}}} +# Could we use `%if` instead of `if`?{{{ +# +# You could try this: +# +# %if "#{!=:$TERM,#{default-terminal}}" +# source-file "$HOME/.config/tmux/terminal-overrides.conf" +# %endif +# +# But it doesn't seem to work; the guard would not prevent the file from being re-sourced. +# I think that's because, in this case, `$TERM` refers to the value in the +# environment of the tmux server process; and for the latter, `TERM` always +# matches the outer terminal. +#}}} +if '[ "$TERM" != "#{default-terminal}" -a -n "$DISPLAY" ]' { source "$HOME/.config/tmux/terminal-overrides.conf" } + +# Leave `default-terminal` at the end.{{{ +# +# In my limited testing, moving it above would not cause an issue, but better be +# safe than sorry. +# In particular, I want to be sure that the value of `$TERM` is not +# 'tmux-256color' the first time our `tmux.conf` is sourced; otherwise +# `terminal-overrides.conf` would never be sourced. +#}}} +# Why `-s` instead of `-g`? {{{ +# +# Since tmux 2.1, `default-terminal` is a server option, not a session option. +# +# > As a side effect this changes default-terminal to be a server rather than a +# > session option. +# +# https://github.com/tmux/tmux/commit/7382ba82c5b366be84ca55c7842426bcf3d1f521 +# Confirmed by the fact that `default-terminal` is described in the section of +# the server options in the man page. +# Also confirmed by the fact that it's listed in the output of: +# +# tmux show -s +# +# However, according to nicm: +# +# > You do not have to use -s or -w for set-option except for user options. +# > tmux can work it out from the option name. +# > For show-option you do need it. +# +# So, we could omit `-s`, but I prefer to be explicit. +#}}} +# Why not let tmux use the default value `screen` (for `$TERM`)?{{{ +# +# By default, most terminals set `$TERM` to `xterm` because the `xterm` entry is +# present and set in the terminfo db of most machines. +# tmux sets it to `screen`, again, because it's a popular entry (more than the +# `tmux` one). +# The `xterm`/`screen` value implies that the terminal will declare supporting +# only 8 colors; confirmed by `$ tput colors`. +# +# Because of this, the theme of some programs might be off (including Vim and +# the terminal itself). We want the terminal to declare it supports 256 colors, +# which anyway is usually true. +#}}} +# Do we need `$TERM` to contain `tmux`?{{{ +# +# Yes. To support italics: +# +# The `screen-256color` entry in the terminfo db doesn't have a `sitm` field. +# IOW, the db reports that screen is unable to support italics, which is true. +# So, if we set `$TERM` to `screen-256color`, when an application will want to +# make some text appear italicized, it will think it's not possible. +# But it *is* possible, because we use tmux, not screen. And tmux *does* +# support the italics style. +# The solution is to set `$TERM` to `tmux-256color` so that when an application +# queries the terminfo db, it finds the field `sitm` with the right value +# `\E[3m`. +# +# See also: +# https://github.com/tmux/tmux/wiki/FAQ#i-dont-see-italics-or-italics-and-reverse-are-the-wrong-way-round +# https://github.com/tmux/tmux/issues/175#issuecomment-152719805 +#}}} +# `256color`?{{{ +# +# For a Vim color scheme to be correctly applied, no. +# Because it seems that our current theme automatically sets the number of +# colors to 256: +# +# :runtime colors/seoul256.vim +# :echo &t_Co +# +# But, for the color schemes of other programs, maybe. +#}}} +set -s default-terminal tmux-256color + +# Session {{{2 + +# Don't ring the bell in the current window. +set -g bell-action other + +# Why?{{{ +# +# If a new window is created without any command to execute, tmux reads the +# session option `default-command` to find one. +# By default, its value is an empty string which instructs tmux to create a +# *login* shell using the value of the default-shell option. +# The default value of the latter is `$SHELL` (atm: `/usr/local/bin/zsh`). +# +# When we create a new window, we want a *non*-login shell, because a login zsh +# shell sources `~/.zprofile`, which we use to execute code specific to a +# virtual *console* (set the background to white in the console). +# This code is not suited to a virtual *terminal*. +# +# More generally, we don't want a *non*-login shell to source login files +# (`.profile`, `.zprofile`, `.zlogin`). +# +# So, we give the value zsh to `default-command` to prevent tmux from starting a +# login shell. +#}}} +set -g default-command "$SHELL" + +# Don't detach the client when the current session is killed. +set -g detach-on-destroy off + +# display status line messages and other on-screen indicators for 1s +# (or until a key is pressed) +set -g display-time 1000 +# display the indicators shown by the display-panes command for 5s +set -g display-panes-time 5000 + +# increase scrollback buffer (2000 → 50000) +# +# `history-limit` has nothing to do with the history of executed tmux commands. +# It controls the amount of lines you can scroll back when you enter copy mode. +set -g history-limit 50000 + +# Index options +# When we create a new window, tmux looks for an unused index, starting from 0.{{{ +# +# I prefer 1, because: +# +# - I usually count from 1, not 0 +# - this lets us run `:movew -t :0` to move the current window in first position +# +# Note that you can't run `:movew -t :1` to move the window in first position, +# because 1 is already taken by the first window. +# `:movew` expects an index which is “free” (i.e. not used by any existing window). +# +# Also, note that when running `:movew -t :0`, tmux renumbers all windows +# automatically from whatever value is assigned to the 'base-index' option. +#}}} +set -g base-index 1 +# │ +# └ must be applied globally to all sessions +# +# same thing for the panes +set -gw pane-base-index 1 +# ││ +# │└ window option +# └ must be applied globally to all windows + +# make tmux capture the mouse and allow mouse events to be bound as key bindings +set -g mouse on + +# use `M-space` as a prefix +set -g prefix M-space + +# renumber windows, when:{{{ +# +# - we destroy one of them +# - we move the first window (index 1) at the end (index 99), by running `movew -t :99` +# +# to prevent holes in the sequence of indexes +#}}} +set -g renumber-windows on + +# time for repeating of a hotkey bound using the -r flag without having to type the prefix again; default: 500 +set -g repeat-time 1000 + +# Some consoles don't like attempts to set the window title. +# This might cause tmux to freeze the terminal when you attach to a session. +# https://github.com/tmux/tmux/wiki/FAQ#tmux-freezes-my-terminal-when-i-attach-to-a-session-i-have-to-kill--9-the-shell-it-was-started-from-to-recover +set -g set-titles off + +# update the status line every 5 seconds (instead of 15s by default) +set -g status-interval 5 + +# emacs key bindings in tmux command prompt (prefix + :) are better than vi keys, +# even for vim users. +set -g status-keys emacs + +# color of status line +set -g -F status-style "bg=#{?#{==:$DISPLAY,},blue,colour138}" + +# Center the position of the window list component of the status line +set -g status-justify centre + +# cache the number of cpu cores in `~/.ncore`, which a shell command in 'status-right' is going to need +if '[ ! -s "${HOME}/.ncore" ]' \ + { run "lscpu | awk '/^CPU\\(s\\):\\s*[0-9]/ { print $2 }' >\"${HOME}/.ncore\"" } + +# set the contents of the status line +# What's `#[...]`?{{{ +# +# It lets you embed some styles. +# If you want to apply the same style all over the left part or the right part +# of the status line, you can also use `status-left-style` or `status-right-style`: +# +# set -g status-left '#[fg=colour15,bold] #S' +# ⇔ +# set -g status-left ' #S' +# set -g status-left-style '#[fg=colour15,bold]' +# +# However, I prefer embedding the styles inside the value of `status-left` and +# `status-right`, because: +# +# - it's more concise +# - it's more powerful: you can set the style of an arbitrary *portion* of the status line +# +# --- +# +# Note that you can use this syntax only in the value of an option which sets +# the *contents* of sth, not its style. +# So, this is *not* a valid syntax: +# +# # ✘ +# set -g status-left-style '#[fg=colour15,bold]' +# +# Here, you must get rid of `#[...]`: +# +# # ✔ +# set -g status-left-style 'fg=colour15,bold' +#}}} +# What's `#{?...}`?{{{ +# +# A conditional: +# +# #{?test,val1,val2} +# +# For example: +# +# {?client_prefix,#[bold],} +# +# This will be evaluated into the style `bold` if the prefix has been pressed, +# or nothing otherwise. +#}}} +# Why do you use `nobold`?{{{ +# +# We set the style `bold` for some part of the status line. +# But a style applies to *all* the remaining text in the status line. +# We need `nobold` to reset the style. +#}}} +# How can I include the time of the day or the hour in the status line?{{{ +# +# Use `date(1)` and `%` items: +# +# %a = day of week +# %d = day of month +# %b = month +# %R = hour +# +# See `man date`. +#}}} +set -g status-left ' #[fg=colour7]#{?client_prefix,#[fg=colour0],}#S#[fg=colour7]' + +# Which alternative could I use to get the cpu load?{{{ +# +# $ uptime|awk '{split(substr($0, index($0, "load")), a, ":"); print a[2]}' +# +# https://github.com/tmux/tmux/wiki/FAQ#what-is-the-best-way-to-display-the-load-average-why-no-l +#}}} +# Why don't you write the code in a script and invoke it with `#(my-script.sh)`?{{{ +# +# A script needs another shell to be interpreted. +# The latter adds overhead, which would almost double the time the code needs to +# be run. +# +# Check this: +# +# $ cat <<'EOF' >/tmp/sh.sh +# awk 'BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }' +# EOF +# $ chmod +x /tmp/sh.sh +# +# $ time zsh -c 'repeat 100 /tmp/sh.sh' +# ... 0,420 total˜ +# $ time zsh -c 'repeat 100 awk '\''BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }'\''' +# ... 0,193 total˜ +#}}} +# Why do you double the percent sign in `printf("%%d", ...)`?{{{ +# +# The code is going to be expanded inside the value of 'status-right'. +# And the latter is always passed to `strftime(3)` for which the percent sign +# has a special meaning. +# As a result, if you don't double the percent sign – to make it literal – `%d` +# will be replaced with the current day of the month (01, 02, ..., 31). +# +# From `man tmux /status-right`: +# +# > As with status-left, string will be passed to strftime(3) and character +# > pairs are replaced. +#}}} +cpu='awk '\''BEGIN { \ + getline load <"/proc/loadavg"; \ + getline ncore <(ENVIRON["HOME"]"/.ncore"); \ + printf("%%d", 100 * load / ncore) }'\''' + +mem='free | awk '\''/Mem/ { total = $2; used = $3 }; \ + END { printf("%%d", 100 * used / total) }'\''' +# TODO: Maybe we could get rid of `free(1)`, by inspecting `/proc/meminfo`. +# However, I can find the second field ("total") of `free(1)` in this file +# (first line: "MemTotal"), but not the third one ("used"). +# +# Update: From `man free`: +# +# used Used memory (calculated as total - free - buffers - cache) +# +# You would have to read 4 files to compute the `used` field. +# Make some tests (with `time(1)`) to see whether the resulting `awk(1)` command +# is slower than what we currently run. + +# TODO: Color the numbers in red if they exceed some threshold. +# Try to capture the output of the 2 shell commands in tmux variables, so that +# we can test them in a conditional. +set -g status-right '#[fg=colour235]M #[fg=colour15,bold]' +set -ga status-right "#($mem) " +set -ga status-right '#[fg=colour235,nobold]C #[fg=colour15,bold]' +set -ga status-right "#($cpu) " + +setenv -gu cpu +setenv -gu mem + +# Why do you want `COLORTERM` to be automatically updated?{{{ +# +# It can be useful to detect a terminal which lies about its identity. +# E.g., xfce4-terminal advertises itself as `xterm-256color`, but the value of +# its `COLORTERM` variable is 'xfce4-terminal'. +# +# So, the more reliable `COLORTERM` is, the better we can detect that we're in +# xfce4-terminal, and react appropriately. +# This can be useful, for example, to prevent vim-term from sending `CSI 2 SPC q` +# when we're leaving Vim from xfce4-terminal on Ubuntu 16.04. +# The latter doesn't understand this sequence, and once sent to the terminal, +# tmux will regularly reprint the sequence wherever our cursor is. +#}}} +# Why could I be tempted to run the same command for `LESSOPEN`, `LESSCLOSE`, `LS_COLORS`?{{{ +# +# setenv -gu LESSOPEN +# setenv -gu LESSCLOSE +# setenv -gu LS_COLORS +# +# These environment variables are set in `~/.zshenv`, but only on the condition +# they've not been set yet. +# The purpose of the condition is to make the shell quicker to start. +# Indeed, setting these variables adds around 8ms to the shell's startup time. +# However, if they are set in the tmux global environment, then they'll never be +# reset when we start a new shell, because the condition will never be +# satisfied. +# +# This means that we can't change the value of these variables by simply editing +# `~/.zshenv`, which can be an issue. +#}}} +# Why don't you do it?{{{ +# +# Because it would add around 8ms to the startup time of every shell we ask tmux +# to open. +#}}} +#  Isn't this an issue?{{{ +# +# No. +# If you want to modify one of these variables, and if you want the change to be +# applied immediately without restarting the tmux server, do it in the context +# of the tmux global environment: +# +# $ tmux setenv -g LESSOPEN new_value +#}}} +if '[ "$TERM" != "#{default-terminal}" ]' { set -ga update-environment COLORTERM } +# TODO: We have several similar `if` conditions. Maybe we should write only one, +# and put inside all the commands we want to run. +# This would make tmux start a little faster. + +# display a message when activity is detected in a window +# Why?{{{ +# +# We haven't customized the status line to include an indicator when some +# activity is detected in a window, because by default we don't monitor activity +# ('monitor-activity' is off). +# Indeed, generally, most windows will produce some output and have some activity. +# Seeing a lot of indicators in the status line, all the time, is meaningless. +# +# But we do have a key binding to temporarily toggle 'monitor-activity' in the +# current window; so, we need a way to be notified when some activity is later +# detected in it. +#}}} +set -g visual-activity on + +# Window {{{2 + +# Use vi key bindings in copy mode. +set -gw mode-keys vi + +# colors of *borders* of focused and non-focused panes +set -gw pane-active-border-style 'fg=colour138,bg=#cacaca' +set -gw pane-border-style 'fg=colour138,bg=#cacaca' + +# How to insert the index of a window?{{{ +# +# Use the alias `#I`. +#}}} +# How to insert its flags, like `Z` for a zoomed window?{{{ +# +# Use the alias `#F`. +#}}} +# Set what to display for the current window (then for the other ones), and how, +# in the status line window list. +# See: https://github.com/tmux/tmux/issues/74#issuecomment-129130023 +set -gw window-status-current-format '#[fg=colour232,bg=colour253]#W#F#{?window_zoomed_flag, #[fg=blue]#P/#{window_panes},}' +set -gw window-status-format '#[fg=colour232#,bg=colour248]#W#F#[bg=default]' + +# Pane {{{2 + +# colors of focused and non-focused panes +# Because we use `-gw`, the colors will affect any pane.{{{ +# +# But, at runtime, you could use `-p` to set the color of a given pane when it's +# (un)focused differently. +#}}} +set -gw window-active-style 'bg=#dbd6d1' +set -gw window-style 'bg=#cacaca' +# }}}1 +# Key Bindings {{{1 +# root {{{2 + +# `F1`, ..., `F10` are used in `htop(1)`. +# `F11` and `F12` are used in WeeChat to scroll in the nicklist bar. +bind -T root S-F8 run -b 'tmux_log' + +# Why do you rebind `command-prompt` to `pfx + ;`? It's already bound to `pfx + :`...{{{ +# +# We often press the prefix key by accident, then press `:` to open Vim's +# command-line. As a result, we open tmux command-line; it's distracting. +#}}} +bind ';' command-prompt +unbind : +# Do *not* bind `command-prompt` to `M-:`; we press it by accident too frequently. + +# focus next/previous window +# I'm frequently pressing these key bindings in Vim's insert mode. It's distracting!{{{ +# +# Idea1: Use `pfx w` to focus an arbitrary window, and supercharge `pfx h/l` to +# focus the next/previous pane or window. +# +# bind -r h if -F '#{pane_at_left}' 'prev' 'selectp -L' +# bind -r l if -F '#{pane_at_right}' 'next' 'selectp -R' +# +# Idea2: Use `M-h/l` twice when the current pane is running Vim. +# +# set -g @foo 0 +# bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; next } { set -g @foo 1 } } { next } +# bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; prev } { set -g @foo 1 } } { prev } +# +# Idea3: Make tmux send `M-h` or `M-l` when Vim is running in the current pane, +# and let Vim decide what to do, based on whether we're in insert mode or normal +# mode. +# +# noremap! m_hl('h') +# noremap! m_hl('l') +# nno m_hl('h') +# nno m_hl('l') +# xno m_hl('h') +# xno m_hl('l') +# +# fu s:m_hl(seq) abort +# if mode() =~# '^[ic]$' +# return '' +# endif +# call system('tmux '..(a:seq is# 'l' ? 'next' : 'prev')) +# return '' +# endfu +# bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' 'send M-l' 'next' +# bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' 'send M-h' 'prev' +# +# Problem: When we're running Vim without config (`-Nu NONE`), we can't focus another tmux window. +# This is because those custom mappings are not installed then. +#}}} +# Why do you inspect these window flags?{{{ +# +# To prevent the commands from wrapping around the edges. +# They do that by default, and that bothers me at the moment. +# +# For example, I often want to focus the next window, and press `¹` by accident +# instead of `²`. It still works, because we only have 2 windows, but that +# prevents me from learning to press the correct keys. +# +# Once you're confident that those keys are well-chosen, and they've passed into +# your muscle memory, you could just install: +# +# bind -T root ¹ prev +# bind -T root ² next +#}}} +bind -T root ¹ if -F '#{window_start_flag}' {} { prev } +bind -T root ² if -F '#{window_end_flag}' {} { next } +# TODO: Sometimes, we press these keys by accident. Find better ones?{{{ +# +# In particular, when we press `[ox` (where `x` is some character) to toggle +# some Vim setting, we need to press `AltGr` to produce `[`; but sometimes, we +# don't release the key before pressing `o`. +# As a result, we press `AltGr + o` which produce `²`; tmux intercepts the +# keypress, and tries to visit the previous window, while in reality, we wanted +# to toggle some Vim setting. +#}}} + +# `M-s` to enter copy mode +# Do *not* bind `M-s` to anything while in copy mode!{{{ +# +# This would make you lose an interesting feature of the `copy-mode` command +# while you're already in copy mode, reading the output of some command such as +# `list-keys`. +# +# The default behavior makes tmux show you the contents of the original window: +# +# # you're reading a file +# :list-keys +# :copy-mode +# # the window shows again the file you were reading +# # press `q`, and you get back the output of `:list-keys` +# +# I don't know where this is documented. +# And I don't know why the `copy-mode` command is invoked when we press `M-s` +# while in copy mode. +# We only have one key binding using `M-s` as its lhs, and it's in the root +# table, not in the copy-mode table. +# +# Note that this feature is not specific to our `M-s` key binding. +# I can reproduce with no config (and `C-b [` instead of `M-s`). +#}}} +bind -T root M-s copy-mode + +bind -T root M-z resize-pane -Z +bind -T root M-Z lastp \; resize-pane -Z + +# Do *not* exit copy mode when when we reach the bottom of the scrollback buffer.{{{ +# +# We remove the `-e` flag which is passed to `copy-mode` by default, so that if +# we enter copy mode by scrolling the mouse wheel upward, and we press a key +# which reaches the bottom of the scrollback buffer, we don't quit copy mode. +# +# If you leave `-e` in the default key binding, here's what could happen: +# you scroll the wheel upward to enter copy mode; at one point, you keep +# pressing `C-d` to scroll toward the bottom; once you reach the bottom, you'll +# quit copy mode, and `C-d` will close the shell if the command-line is empty. +#}}} +# Where did you find the code?{{{ +# +# $ tmux -Lx -f/dev/null start \; lsk | grep 'root.*WheelUpPane' +#}}} +bind -T root WheelUpPane \ + if -F -t= '#{mouse_any_flag}' \ + { send -M } \ + { if -Ft= '#{pane_in_mode}' 'send -M' 'copy-mode -t=' } + +# But *do* exit copy mode if we scroll downward with the mouse wheel and reach the bottom of the buffer. +bind -T copy-mode-vi WheelDownPane \ + selectp \; \ + send -X -N 5 scroll-down \; \ + if -F '#{scroll_position}' '' 'send -X cancel' + +# copy-mode-vi {{{2 + +bind -T copy-mode-vi C-Space send -X set-mark +# actually, it should be named "exchange-point-and-mark"... +bind -T copy-mode-vi C-x send -X jump-to-mark + +# jump Back to the Beginning of the previous shell command{{{ +# +# Look for the previous shell prompt, to get to the beginning of the last +# command output. After pressing the key binding, you can visit all the other +# prompts by pressing `n` or `N`. +# +# Inspiration: https://www.youtube.com/watch?v=uglorjY0Ntg +#}}} +bind -T copy-mode-vi ! send -X start-of-line \; send -X search-backward '٪' + +# Make tmux use the prefix 'buf_' instead of 'buffer' when naming the buffer +# storing the copied selection. +bind -T copy-mode-vi Enter send -X copy-selection-and-cancel 'buf_' + +bind -T copy-mode-vi g switchc -T g-prefix +bind -T g-prefix g send -X history-top +# Open a visually selected text (filepath or url) by pressing `gf` or `gx`. +# TODO: The key binding will break if the file name contains double quotes.{{{ +# +# Find a way to escape special characters. +# +# I tried `$ tmux display -p '#{q:#(tmux pasteb)}'`, but it doesn't work. +# You probably need `q:`, but a modifier needs to be followed by the name of a +# replacement variable and `#(tmux pasteb)` is not one. +# +# Update: +# I don't think you should use `q:`. +# Maybe you should try to find a shell utility which quotes special characters +# in a given text. +# Does such a tool exist? +# If it does, maybe you could try: `#(magic_tool $(tmux pasteb))`. +# +# Update: +# `#{}` can be used for a user option (`@foo`). +# You could temporarily set a user option with the the filepath/url, and quote +# it with `q:`. +# +# Make some tests on that: +# +# http://example.org/foo/bar"baz.html +# http://example.org/foo/bar'baz.html +# https://www.reddit.com/r/Fantasy/ +# +# bind -T copy-mode-vi x send -X copy-selection-and-cancel \; \ +# run 'tmux set @copied_url "$(tmux showb)"' \; \ +# run 'xdg-open "#{q:@copied_url}"' +# +# For some reason, we need 2 `run-shell`, otherwise, it seems that the key +# binding doesn't update the url, when we try a new one. +# +# For some reason, when we try to open this: +# +# http://example.org/a'b.html +# +# tmux opens this instead: +# +# http://example.org/a/'b.html +# ^ +# ✘ +# +# For some reason, when we try to open this: +# +# http://example.org/a"b.html +# +# tmux doesn't escape the double quote. +# +# Text ended before matching quote was found for ".˜ +# (The text was '/usr/bin/firefox "http://example.org/foo/bar"baz.html"')˜ +# +# $ tmux set @foo "a'b" \; display -p '#{q:@foo}' +# a\'b˜ +# +# $ tmux set @foo "a'b" \; run 'echo #{q:@foo}' +# a'b˜ +# +# Why doesn't `run-shell` expand the `#{q:}` format? +# +# Update: you need to quote the format: +# +# $ tmux set @foo "a'b" \; run 'echo "#{q:@foo}"' +# a\'b˜ +# +# Update: +# I don't think it's possible. +# Try to open the urls via `urlscan(1)`. +# The latter fails for the first urls. +# If a specialized tool fails, I doubt we can do better. +#}}} +# FIXME: `gf` fails to open a file path starting with `~/`. +bind -T g-prefix f send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" +bind -T g-prefix x send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" + +bind -T copy-mode-vi v if -F '#{selection_present}' { send -X clear-selection } { send -X begin-selection } +bind -T copy-mode-vi V if -F '#{selection_present}' { send -X clear-selection } { send -X select-line } +bind -T copy-mode-vi C-v if -F '#{selection_present}' \ + { if -F '#{rectangle_toggle}' \ + { send -X rectangle-toggle ; send -X clear-selection } \ + { send -X rectangle-toggle } \ + } { send -X begin-selection ; send -X rectangle-toggle } + +# set -s copy-command 'xsel -i' +# if there's a selection, make `y` yank it +# without selection, make `yy` yank the current line and `yiw` the current word +bind -T copy-mode-vi y if -F '#{selection_present}' \ + { send -X copy-pipe-and-cancel 'xsel -i -b' 'buf_' } \ + { switchc -T operator-pending-and-cancel } +# y**y** +bind -T operator-pending-and-cancel y send -X copy-line 'buf_' +# y**iw** +bind -T operator-pending-and-cancel i switchc -T text-object-and-cancel +# Do *not* use `select-word`: https://github.com/tmux/tmux/issues/2126 +bind -T text-object-and-cancel w \ + { send -X cursor-right + send -X previous-word + send -X begin-selection + send -X next-word-end + send -X copy-selection-and-cancel 'buf_' } + +bind -T copy-mode-vi . run "zsh -c \"tmux source =(sed -n '/^# #{@dot_command}/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" + +# **"A**yiw **"A**yy v_**"A**y +bind -T copy-mode-vi '"' switchc -T specify-register +bind -T specify-register A switchc -T operate-on-register +# Why the `if 'tmux showb'`?{{{ +# +# `append-selection` doesn't accept the optional prefix buffer name argument. +# If there's no buffer, `append-selection` will create a buffer with the prefix +# name `buffer`; we want the prefix `buf_`. +#}}} +# "A**y**iw "A**y**y v_"A**y** +bind -T operate-on-register y if -F '#{selection_present}' \ + { if 'tmux showb' { send -X append-selection } { send -X copy-selection 'buf_' }} \ + { switchc -T operator-pending } +# "Ay**i**w +bind -T operator-pending i switchc -T text-object +# "Ay**y** +bind -T operator-pending y { run "zsh -c \"tmux source =(sed -n '/^# yy/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } + +# v**i**w +bind -T copy-mode-vi i switchc -T text-object +# Why pass a second command to the first `if`?{{{ +# +# To support `"Ayiw`. +#}}} +# vi**w** "Ayi**w** +bind -T text-object w if -F '#{selection_present}' \ + { send -X stop-selection + send -X cursor-right + send -X previous-word + send -X begin-selection + send -X next-word-end } \ + { run "zsh -c \"tmux source =(sed -n '/^# yiw/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } + +bind -T copy-mode-vi Y send -X copy-end-of-line 'buf_' + +# Why don't you pass `-b` to run?{{{ +# +# There's no need to. +# `pipe-and-cancel` doesn't block. +# The shell command passed as an argument is forked. +#}}} +bind -T copy-mode-vi S send -X pipe-and-cancel \ + "xargs -I {} tmux run 'xdg-open \"https://www.startpage.com/do/dsearch?query={}\"'" + +# Why? {{{ +# +# `search-backward-incremental` is better than `search-forward`, because it +# highlights all the matches as you type (like in Vim when 'hlsearch' and +# 'incsearch' are both set); you need to pass `-i` to `command-prompt` for it to work. +# +# Also, these key bindings make the prompt less noisy (`/` is shorter than `search down`). +# +# Inspired from the default emacs key bindings in copy mode. +# }}} +bind -T copy-mode-vi / command-prompt -ip '/' { send -X search-forward-incremental '%%' } +bind -T copy-mode-vi ? command-prompt -ip '?' { send -X search-backward-incremental '%%' } + +bind -T copy-mode-vi % send -X next-matching-bracket +bind -T copy-mode-vi _ send -X start-of-line + +# move current window position forward/backward +# Why not using `h` and `l`, or `j` and `k`?{{{ +# +# `M-C-[jk]` could be more useful for something else (WeeChat?), and doesn't +# match a horizontal motion. +# `M-C-[hl]` conflicts with our window manager (move virtual desktop). +# `M-C-[HL]` is hard to press. +#}}} +bind -r > if -F '#{window_end_flag}' { movew -t :0 } { swapw -t :+ ; selectw -n } +bind -r < if -F '#{window_start_flag}' { movew -t :99 } { swapw -t :- ; selectw -p } +# If you don't like `:99`, you could write this instead:{{{ +# +# bind -r < if -F '#{window_start_flag}' \ +# { run 'tmux movew -t:$((#{W:#{?window_end_flag,#I,}}+1))' } \ +# { swapw -t :- ; selectw -p } +#}}} + +# prefix {{{2 + +# NOTE: `C-\` is free. + +# cycle through predefined layouts +# Do *not* pass `-r` to `bind`!{{{ +# +# We use Space in Vim as a prefix key. +# If you use `-r` here, when you're in Vim there's a risk that when you press +# Space as a prefix key, it's consumed by tmux to run `nextl` instead. +#}}} +bind Space nextl + +# focus last pane, without breaking the zoomed state of the window +bind M-Space lastp -Z + +# display short description for the next keypress (inspired from a default key binding) +bind M-h command-prompt -k -p key { lsk -1N '%%' } +# ├┘ ├─┘{{{ +# │ └ -N instead show keys and attached notes in the root and prefix key tables; +# │ with -1 only the first matching key and note is shown +# └ -k is like -1 but the key press is translated to a key name +#}}} +# What is the side effect of `-1`?{{{ +# +# Not only does it limit the output of the command to the first matching key and +# note, but it also redirects where it's displayed. +# Without `-1`, it's displayed on the terminal in copy-mode. +# With `-1`, it's displayed as a message on the tmux status line. +#}}} + +# display short description for the next 2 keypresses +bind M-l command-prompt -1p 'key1,key2' \ + { run "tmux lsk | awk '/-T prefix\s+%1\s+/ { print \$NF }' | xargs -I {} tmux lsk -1N -P 'M-Space %1 ' -T {} '%2'" } + +# repeat last shell command in last active pane +# Warning: This overrides a default key binding:{{{ +# +# bind-key -T prefix . command-prompt -T target "move-window -t '%%'" +#}}} +bind . if -F -t '{last}' '#{m:*sh,#{pane_current_command}}' { send -t '{last}' Up Enter } + +# copy clipboard selection into tmux buffer +# Why `run`?{{{ +# +# To make the shell evaluate the command substitution. +#}}} +# `--`?{{{ +# +# The evaluation of the substitution command could start with a hyphen. +# And if that happens, tmux could parse the text as an option passed to +# `set-buffer` (i.e. `-a`, `-b`, or `-n`). +#}}} +# `-o`?{{{ +# +# To make `xsel(1x)` output something. +#}}} +bind b switchc -T buffer +bind -T buffer -N 'copy clipboard into tmux buffer' > run 'tmux setb -- "$(xsel -ob)"' \; display 'clipboard copied into tmux buffer' +bind -T buffer -N 'copy tmux buffer into clipboard' < choose-buffer -F '#{buffer_sample}' \ + { run 'tmux showb -b "%%" | xsel -ib' ; display 'tmux buffer copied into clipboard' } + +# We use `*` instead of `q` because it's more consistent with `#`. +# They both show information. Besides, if I hit `pfx q` by accident (which +# happens often), I won't be distracted by the panes numbers. + +bind * displayp + +bind ! show-messages + +# make these default key bindings repeatable +# Do *not* pass `-r` to `bind`!{{{ +# +# Suppose you're in the 'study' session, Vim is running, and the Vim window is +# horizontally split in 2 viewports. +# You press `pfx + )` to switch to the 'fun' session, then you press `)` to go +# back to the 'study' session. +# Finally you press C-j to focus the Vim split below; it won't work because of this: +# +# bind -r C-j resizep -D 5 +#}}} +bind ( switchc -p +bind ) switchc -n + +# Problem: tmux-fingers doesn't let us search outside the current screen. +# Solution: Install a key binding which lets us search through the scrollback buffer, in a Vim buffer. +# What does `-J` do for `capture-pane`?{{{ +# +# It joins wrapped lines. +# +# Suppose that we have a long line in a file, which doesn't fit on a single line +# of the terminal, but on two. +# If you run `$ cat file`, this too-long line will be displayed on two +# consecutive lines of the terminal. +# Without `-J`, tmux would copy – in one of its buffers – the two consecutive +# lines on two different lines. +# +# But that's not what we want. +# We want the buffer to join back these two lines, as they were originally in the file. +#}}} +# `-S -`? {{{ +# +# `-S` specifies the starting line number, from where to copy. +# The special value `-` refers to the start of the history. +# Without this, `capture-pane` would capture only from the first visible line in +# the pane; we want the *whole* scrollback buffer. +#}}} +# Why don't you use `split-window` instead of `popup`?{{{ +# +# With `split-window`, you would also need to run `resize-pane -Z`. +# But what if there's already a zoomed pane in the current window? +# After quitting Vim, the latter would be unzoomed. +# So, we use `popup` to preserve a possible zoomed pane in the current window. +#}}} +bind -T root M-c if -F \ + '#{||:#{m:*vim,#{pane_current_command}},#{==:#{pane_current_command},man}}' \ + '' \ + { capture-pane -b scrollback -J -S - + popup -E -xC -yC -w75% -h75% -d '#{pane_current_path}' \ + "tmux showb -b scrollback | vim --not-a-term +'call tmux#formatCapture#main()' - ; \ + tmux deleteb -b scrollback" } + +# split window vertically / horizontally +# When the window is zoomed, we often forget that it's already split, and wrongly press `pfx _`.{{{ +# +# Make `pfx _` smarter; i.e. if the window is already split and zoomed, don't +# split it again, instead focus the previous pane. +#}}} +bind _ if -F '#{window_zoomed_flag}' 'lastp' 'split-window -v -c "#{pane_current_path}"' +bind | if -F '#{window_zoomed_flag}' 'lastp' 'split-window -h -c "#{pane_current_path}"' +bind - if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fv -c "#{pane_current_path}"' +bind '\' if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fh -c "#{pane_current_path}"' +# ├───────────────────────┘ +# └ keep current working directory + +bind -N 'bring arbitrary pane in current window' [ command-prompt -p 'join pane from:' { join-pane -s '%%' } +bind -N 'send current pane in arbitrary window' ] command-prompt -p 'send pane to:' { join-pane -t '%%' } +bind T breakp + +# Why a space before every shell command (` cmus`, ` weechat`, ...)?{{{ +# +# It's useful to prevent zsh from saving the command in the history when we +# cancel the search with `C-c` or `C-d` (`setopt HIST_IGNORE_SPACE`). +#}}} +# What's the `-n` option passed to `neww`?{{{ +# +# It sets the name of the window. +#}}} +# What about the `-c` option?{{{ +# +# It sets the cwd of the shell. +#}}} +bind M-1 rename -t 0 fun \; \ + renamew -t 1 music \; \ + send ' cmus' 'Enter' '2' 'Enter' 'Enter' \; \ + neww -n irc -c $HOME \; \ + send ' weechat' 'Enter' \; \ + new -s study \; \ + send ' nv' 'Enter' + +# you need the display command to force tmux to clear the log (`set message-limit 0` is not enough) +bind C set -F @message-limit-save '#{message-limit}' \; \ + set message-limit 0 \; display 'message log cleared' \; \ + set -F message-limit '#{@message-limit-save}' \; set -u @message-limit-save + +# Note that `clear-history` doesn't clear *all* the history.{{{ +# +# The last lines of the scrollback buffer which fits in one screen are preserved. +# So, if you enter copy mode, you'll still be able to scroll back *some* lines, +# but not more than a few dozens. +#}}} +# We can't use `C-l` for the lhs, because we already use it in another key binding:{{{ +# +# bind -r C-l resizep -R 5 +#}}} +bind C-c send C-l \; clear-history +# │ │ +# │ └ clear tmux scrollback buffer +# └ clear terminal screen + +# Do *not* use `q` in the prefix table; pressed by accident too frequently. +bind Q switchc -T Q-prefix +bind -T Q-prefix q confirm 'kill-pane' +bind -T Q-prefix Q confirm 'kill-window' + +# Why `TERM="#{client_termname}"` in the 'sourced files' entry?{{{ +# +# When `$ tmux -v -Ldm` is started, it inherits the TERM of the current tmux +# server, which is set by 'default-terminal'. +# As a result, the condition `[ if '[ "$TERM" != #{default-terminal} ]'` is +# true, and several files which we expect to be sourced, won't be sourced. +# +# We want to see all files which would be sourced if we were to start tmux from +# a regular shell; so we need to reset TERM. +#}}} +# Why don't you pass `-v` to `show-options`?{{{ +# +# If you do, it would considerably simplify our commands; we wouldn't need `sed(1)` at all. +# Unfortunately, it would also fuck up the output if one of the alias is defined +# on several lines. +# +# By avoiding `-v`, we make sure that each alias is output on a single line. +#}}} +bind i display-menu -x 0 -y 0 \ + 'server information' i info \ + 'key bindings' K lsk \ + 'aliases' a { run 'tmux show -s command-alias | column -t -s= | sed "s/^command-alias\[[0-9]*]\s*//; s/^\"\|\"$//g"' } \ + 'sourced files' f { run 'cd "$(mktemp -d /tmp/.tmux.XXXXXXXXXX)" \ + ; TERM="#{client_termname}" tmux -v -Ldm start \ + ; grep loading tmux-server*.log | grep -v grep | sed "s/.*loading \(.*\)/\1/"' } \ + '' \ + 'options' o { display-menu -y 0 \ + 'server options' C-s { show -s } \ + 'global session options' s { show -g } \ + 'local session options' S { show } \ + '' \ + 'global window options' w { show -gw } \ + 'local window options' W { show -w } \ + 'local pane options' p { show -p } \ + } \ + '' \ + 'global hooks' h { show-hooks -g } \ + 'local hooks' H { show-hooks } \ + '' \ + 'global environment' e { showenv -g } \ + 'local environment' E { showenv } \ + '' \ + 'outer terminfo description' t { run 'infocmp -1x "#{client_termname}" | sort' } \ + 'inner terminfo description' T { run 'infocmp -1x "#{default-terminal}" | sort' } \ + '' \ + 'default settings' d { display-menu -y 0 \ + 'key bindings' K { run 'tmux -Ldm -f/dev/null start \; lsk' } \ + 'aliases' a { run 'tmux -Ldm -f/dev/null start \; show -sv command-alias | column -t -s= | sed "s/^command-alias\\[[0-9]]\\s*//; s/^\"\|\"$//g"' } \ + '' \ + 'server options' C-s { run 'tmux -Ldm -f/dev/null start \; show -s' } \ + 'window options' w { run 'tmux -Ldm -f/dev/null start \; show -gw' } \ + 'session options' s { run 'tmux -Ldm -f/dev/null start \; show -g' } \ + } + +# By default, `detach-session` is bound to `d`. +# I find that too easy to press, so we move it to `@`. +# Why `@`? +# I didn't find anything better, and it seems hard to press by accident... +bind @ detach + +# resize pane +bind -r C-h resizep -L 5 +bind -r C-j resizep -D 5 +bind -r C-k resizep -U 5 +bind -r C-l resizep -R 5 + +# focus neighboring panes +# Do *not* make them repeatable. It leads to a confusing user experience.{{{ +# +# Example: Press `pfx k` to focus the pane above which is running Vim, then press `j`. +# Expected: Vim scrolls downward. +# Actual Result: tmux focuses back the pane below. +# +# If you have many panes, and you need to focus one, try `display-panes` +# (currently bound to `pfx *`), then press the index of the desired pane. +#}}} +bind h selectp -L +bind l selectp -R +bind j selectp -D +bind k selectp -U + +# move pane to the far right/left/bottom/top +bind H split-window -fhb \; swap-pane -t ! \; kill-pane -t ! +bind L split-window -fh \; swap-pane -t ! \; kill-pane -t ! +bind J split-window -fv \; swap-pane -t ! \; kill-pane -t ! +bind K split-window -fvb \; swap-pane -t ! \; kill-pane -t ! + +# Toggle mouse. +# Temporarily preventing tmux from handling the mouse can be useful in some +# terminals to copy text in the clipboard. +# Why `M` for the lhs?{{{ +# +# It provides a good mnemonic for “mouse”. +# I don't use `C-m` nor `M-m` because, atm, they're used to display some default menus. +# +# However, note that `pfx M` is used by default to clear the marked pane (`select-pane -M`). +# It's not a big deal to lose it, because we can get the same result by focusing +# the marked pane and then pressing `pfx m` (`select-pane -m`). +# The latter marks the pane if it's not already, or clears the mark otherwise. +#}}} +bind M set -g mouse \; display 'mouse: #{?#{mouse},ON,OFF}' + +# toggle 'monitor-activity' in current window +bind C-a set -w monitor-activity \; display 'monitor-activity: #{?#{monitor-activity},ON,OFF}' + +# kill all panes except the current one (similar to `:only` or `C-w o` in Vim) +bind o kill-pane -a + +# paste last tmux buffer +# Do *not* choose a key too easy to type.{{{ +# +# It's dangerous. +# In Vim, the contents of the buffer will be typed, which will have unexpected +# results, unless you're in insert mode. +#}}} +bind C-p paste-buffer -p + +# choose and paste arbitrary tmux buffer +# What's `-Z`?{{{ +# +# It makes tmux zoom the pane so that it takes the whole window. +#}}} +# `-F`?{{{ +# +# It specifies the format with which each buffer should be displayed. +# In it, you can use these replacement variables: +# +# ┌────────────────┬─────────────────────────────┐ +# │ buffer_created │ creation date of the buffer │ +# ├────────────────┼─────────────────────────────┤ +# │ buffer_name │ name of the buffer │ +# ├────────────────┼─────────────────────────────┤ +# │ buffer_sample │ starting text of the buffer │ +# ├────────────────┼─────────────────────────────┤ +# │ buffer_size │ size of the buffer │ +# └────────────────┴─────────────────────────────┘ +# +# Note that even with an empty format, tmux will still display the name of a +# buffer followed by a colon. +# So, `buffer_name` is not very useful (unless you want to print the name of a +# buffer twice). +#}}} +# the `-p` argument passed to `paste-buffer`?{{{ +# +# It prevents the shell from automatically running a pasted text which contains +# a newline. +# +# See `man tmux /^\s*paste-buffer` +#}}} +bind p choose-buffer -Z -F '#{buffer_sample}' "paste-buffer -p -b '%%'" + +# similar to `C-w r` and `C-w R` in Vim +bind -r r rotate-window -D \; selectp -t :.+ +bind -r R rotate-window -U \; selectp -t :.- + +# reload tmux config +bind C-r source "$HOME/.config/tmux/tmux.conf" \; display 'Configuration reloaded' + +# You need to install the `urlscan(1)` utility for this key binding to work. +# Why do you include `deleteb` inside the shell cmd run by `split-window`?{{{ +# +# If you move it outside: +# +# bind u capture-pane \; split-window -l 10 'urlscan =(tmux showb)' \; deleteb +# +# `urlscan(1)` can't find any link. +# +# This is because: +# +# 1. deleteb is run before `$ tmux showb` +# 2. thus `$ tmux showb` outputs nothing +# 3. urlscan finds no url +# +# You can get the same effect by running: +# +# $ tmux split-window 'urlscan =(echo "")' +#}}} +# We name the tmux buffer so that we can remove it reliably at the end.{{{ +# +# Indeed, you might copy some text while the urlscan pane is opened (not +# necessarily in the latter; in any session, window, pane), creating a new +# buffer at the top of the stack. +# If you just run `$ tmux deleteb`, it would remove that buffer instead of the +# buffer created by `capture-pane`. +#}}} +# Why `head -c -1`?{{{ +# +# If there is no url in the tmux buffer, we want tmux to automatically close the +# pane. That's why we use `ifne(1)` later; it runs the second `urlscan(1)` on +# the condition that the output of the previous one is empty. The problem is +# that even when the first `urlscan(1)` fails to find any url, it still outputs +# a single newline. We need to remove it, so that `ifne(1)` works as expected. +#}}} +bind u capture-pane -b urlscan \; \ + split-window -l 10 " + tmux showb -b urlscan | \ + urlscan --no-browser | \ + head -c -1 | \ + ifne urlscan --compact \ + --dedupe \ + --nohelp \ + --regex \"(http|ftp)s?://[^ '\\\">)}\\\\]]+\" \ + ; tmux deleteb -b urlscan + " + +# focus next pane +bind -r C-w selectp -t :.+ + +# similar to `C-w x` in Vim +bind x swap-pane -U +bind X swap-pane -D +# }}}1 +# Hooks {{{1 +# Don't use this hook: `set-hook -g after-split-window 'selectl even-vertical'`{{{ +# +# You wouldn't be able to split vertically anymore. +# Splitting vertically would result in an horizontal split no matter what. +# +# The hook is given as an example in `man tmux`; its purpose is to resize +# equally all the panes whenever you split a pane horizontally. +#}}} + +set-hook -g pane-focus-out '' + +# Plugins {{{1 + +# Why the guard?{{{ +# +# To prevent the plugins from re-installing their key bindings every time we +# resource `tmux.conf`. +# Indeed, we only unbind the key bindings from the copy-mode table once. +# +# Besides, it's probably a bad idea to resource plugins. +#}}} +if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/plugins/run" } + +# Rebind {{{1 + +# Purpose:{{{ +# +# The tmux-yank plugin installs this key binding: +# +# bind-key -T copy-mode-vi Y send-keys -X copy-pipe-and-cancel "tmux paste-buffer" +# +# It copies the selection, quit copy mode, then paste the buffer. +# +# However, it doesn't support the bracketed paste mode. +# So we redefine the key binding, and pass `-p` to `paste-buffer` to surround +# the text with the sequences `Esc [ 200 ~` and `Esc [ 201 ~`. +# This way, if we select a text containing a newline, then press `p`, it's not +# automatically run by the shell. +# +# From `man tmux /^\s*paste-buffer`: +# +# > If -p is specified, paste bracket control codes are inserted around the +# > buffer if the application has requested bracketed paste mode. +# +# Note that this requires that the shell supports the bracketed paste mode. +# I.e. if you're using zsh, you need zsh version 5.1 or greater, and if you're +# using bash, you need bash 4.4 or greater. +# +# --- +# +# The `-p` option was added to tmux in the commit `f4fdddc`. +# According to the changelog, this was somewhere between tmux 1.6 and 1.7. +# +# --- +# +# Note that the original key binding used `copy-pipe-and-cancel` which – while +# working – doesn't make sense; you can't pipe anything to `$ tmux paste-buffer`, +# since it doesn't read its input. +#}}} +bind -T copy-mode-vi p send -X copy-selection-and-cancel \; paste-buffer -p \; deleteb +# ^^ + +# Unbind {{{1 + +# How to find the default key bindings installed with no config?{{{ +# +# $ tmux -Lx -f/dev/null new +# C-b ? +# VG$ +# Enter +# $ vim +# i +# C-b ] +# +# Make sure to release `Ctrl` before pressing `]`. +#}}} +# How to unbind `#`, `~`, `'`, `"`?{{{ +# +# Quote the key (with single or double quotes). +# +# From `man tmux /^KEY BINDINGS`: +# +# > Note that to bind the ‘"’ or ‘'’ keys, quotation marks are necessary. +#}}} +# How to unbind `;`?{{{ +# +# Escape it. +# +# From `man tmux /^COMMANDS`: +# +# > A literal semicolon may be included by escaping it with a backslash (for +# > example, when specifying a command sequence to bind-key). +#}}} + +# TODO: +# Remove all default key bindings which you're not interested in. +# Some of them could be hit by accident. +# Keep only the ones you really use. +# Besides, it will give us a smaller table of key bindings, which will be easier +# to read when we have an issue with one of our key bindings. +# Have a look at `~/Desktop/tmux.md`. + +# prefix {{{2 + +# send-prefix +unbind C-b +# rotate-window +unbind C-o +# show-messages +unbind '~' +# split-window +unbind '"' +# choose-buffer -Z +unbind = +# detach-client +unbind d +# next-window +unbind n +# swap-pane -[UD] +unbind '{' +unbind '}' +# select-pane -[UDLR] +unbind Up +unbind Down +unbind Left +unbind Right +# tmux clear-history (tmux-logging) +unbind M-c +# rotate-window -D +unbind M-o +# resize-pane -U 5 +unbind M-up +# resize-pane -D 5 +unbind M-down +# resize-pane -L 5 +unbind M-left +# resize-pane -R 5 +unbind M-right +# resize-pane -[UDLR] +unbind C-Up +unbind C-Down +unbind C-Left +unbind C-Right +# run ~/.config/tmux/plugins/tmux-logging/scripts/screen_capture.sh (tmux-logging) +unbind M-p +# run ~/.config/tmux/plugins/tmux-logging/scripts/save_complete_history.sh (tmux-logging) +unbind M-P +# resize-pane -Z +unbind z +# command-prompt -i -p / { send-keys -X search-forward-incremental "%%" } +unbind / + +# copy-mode-vi {{{2 + +# By default, it's bound to `send-keys -X copy-pipe-and-cancel`.{{{ +# +# I don't like that, because I often select some text with the mouse by accident +# (or when I'm bored); when that happens, obviously, tmux creates a buffer. +# +# This pollutes our list of buffers, and makes the interesting ones harder to +# find. Besides, if when I want to copy some text, I will certainly not do it +# with the mouse (not accurate enough). +#}}} +unbind -T copy-mode-vi MouseDragEnd1Pane +# send-keys -X begin-selection +unbind -T copy-mode-vi Space +# send-keys -X copy-pipe-and-cancel +unbind -T copy-mode-vi C-j +# send -X copy-pipe-and-cancel 'xsel -i --clipboard; tmux paste-buffer' (tmux-yank) +unbind -T copy-mode-vi M-y +# (tmux-yank) +unbind -T copy-mode-vi Y + +# copy-mode {{{2 + +# We don't need the key bindings from the copy-mode table; we use the copy-mode-*vi* table. +# Why the guard?{{{ +# +# Once the table is empty, it's removed. +# So, if you later try to unbind a key binding from it, an error will be raised: +# +# Table copy-mode doesn't exist +# +# Which can be repeated for every key binding you try to remove: +# +# Table copy-mode doesn't exist +# Table copy-mode doesn't exist +# ... +# +# Run `show-messages` to see them. +# +# This is annoying when you reload `tmux.conf`. +#}}} +if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/unbind-copy-mode.conf" } +# }}}1 diff --git a/regress/conf/872441a98b06444acc5ce08eb24aabde.conf b/regress/conf/872441a98b06444acc5ce08eb24aabde.conf new file mode 100644 index 00000000..b074ad76 --- /dev/null +++ b/regress/conf/872441a98b06444acc5ce08eb24aabde.conf @@ -0,0 +1,26 @@ +bind -r Up if -F '#{pane_at_top}' '' 'selectp -U' +bind -r Down if -F '#{pane_at_bottom}' '' 'selectp -D' +bind -r Left if -F '#{pane_at_left}' '' 'selectp -L' +bind -r Right if -F '#{pane_at_right}' '' 'selectp -R' + +bind -n WheelUpPane if -Ft= "#{mouse_any_flag}" "send -M" "send Up" +bind -n WheelDownPane if -Ft= "#{mouse_any_flag}" "send -M" "send Down" + +bind w run 'tmux choose-tree -Nwf"##{==:##{session_name},#{session_name}}"' + +bind C { + splitw -f -l30% '' + set-hook -p pane-mode-changed 'if -F "#{!=:#{pane_mode},copy-mode}" "kill-pane"' + copy-mode -s'{last}' +} + +set -g word-separators "" +bind -n C-DoubleClick1Pane if -F '#{m/r:^[^:]*:[0-9]+:,#{mouse_word}}' { + run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' + emacs `echo #{mouse_word}|awk -F: "{print \"+\"\\$2,\\$1}"` + ' } +} { + run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' + emacs "#{mouse_word}" + ' } +} diff --git a/regress/conf/f4f1cdb9d518c2f7808a4915299f2524.conf b/regress/conf/f4f1cdb9d518c2f7808a4915299f2524.conf new file mode 100644 index 00000000..de01b812 --- /dev/null +++ b/regress/conf/f4f1cdb9d518c2f7808a4915299f2524.conf @@ -0,0 +1,47 @@ +bind m-4 run -C '#{@layout-vertical-two}' + +set -g @layout-vertical-two { + selectl main-vertical + if -F '#{==:#{@vertical-two-active},true}' { + set -wu @vertical-two-active + } { + if -F '#{&&:#{==:#{N/s:layout_overflow},0},#{e|>=:#{n:#{P:x}},3}}' { + run -C '#{@layout-vertical-two-init}' + } + } +} + +set -g @layout-vertical-two-init { + set -gF @total_panes '#{n:#{P:x}}' + set -gF @cur_window '#S:#I' + new -ds layout_overflow + run -C '\ + swapw -t layout_overflow: -s . ;\ + splitw -fh -l 40% -t #{@cur_window} ;\ + splitw -h -t #{@cur_window}.2 ;\ + swapp -s #{@cur_window}.1 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ + swapp -s #{@cur_window}.2 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ + swapp -s #{@cur_window}.3 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ + #{@layout-vertical-two-loop}' +} + +set -g @layout-vertical-two-cleanup { + set -gu @cur_window + set -gu @total_panes '#{n:#{P:x}}' + set -w @vertical-two-active true + selectp -t .1 +} + +# (x - 1) % 2 == 0 ? (x - 1) / 2 + 1 : x +# #{?#{==:#{e|%:#{e|-:#{cur_panes},1},2},0} <-- TODO: inserting horizontally shuffles windows. +# ,#{e|+:#{e|/:#{e|-:#{cur_panes},1},2},1} <-- end of first column +# ,#{cur_panes}} <-- end of second column +set -g @layout-vertical-two-loop { + # count(panes) < count(original.panes) + if -F '#{e|<:#{n:#{P:x}},#{@total_panes}}' { + run -C "joinp -s layout_overflow:1.1 -vt '#{@cur_window}.#{?#{==:#{e|%:#{e|-:#{#{n:#{P:x}}},1},2},0},#{e|+:#{e|/:#{e|-:#{#{n:#{P:x}}},1},2},1},#{#{n:#{P:x}}}}' ;\ + selectl -E ; #{@layout-vertical-two-loop}" + } { + run -C '#{@layout-vertical-two-cleanup}' + } +} diff --git a/regress/control-client-sanity.sh b/regress/control-client-sanity.sh index bf76b4d5..48ffd0ee 100644 --- a/regress/control-client-sanity.sh +++ b/regress/control-client-sanity.sh @@ -8,13 +8,13 @@ TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) -OUT=$(mktemp) -trap "rm -f $TMP $OUT" 0 1 15 +trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x200 -y200 || exit 1 $TMUX -f/dev/null splitw || exit 1 sleep 1 cat <$TMP +refresh-client -C 200x200 selectp -t%0 splitw neww diff --git a/regress/control-client-size.sh b/regress/control-client-size.sh index 5847ede3..dc275e52 100644 --- a/regress/control-client-size.sh +++ b/regress/control-client-size.sh @@ -20,9 +20,9 @@ sleep 1 cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 100,50 -ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT +$TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":80 24\n:100 50\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null @@ -31,18 +31,18 @@ sleep 1 cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 80,24 -ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT +$TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":80 24\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 80,24 -ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT +$TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":100 50\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null diff --git a/regress/copy-mode-test-emacs.sh b/regress/copy-mode-test-emacs.sh new file mode 100644 index 00000000..63293963 --- /dev/null +++ b/regress/copy-mode-test-emacs.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -f/dev/null -Ltest" +$TMUX kill-server 2>/dev/null + +$TMUX new -d -x40 -y10 \ + "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +# Enter copy mode and go to the first column of the first row. +$TMUX set-window-option -g mode-keys emacs +$TMUX set-window-option -g word-separators "" +$TMUX copy-mode +$TMUX send-keys -X history-top +$TMUX send-keys -X start-of-line + +# Test that `previous-word` and `previous-space` +# do not go past the start of text. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X previous-space +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer 2>/dev/null)" = "" ] || exit 1 + +# Test that `next-word-end` does not skip single-letter words. +$TMUX send-keys -X next-word-end +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "A" ] || exit 1 + +# Test that `next-word-end` wraps around indented line breaks. +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "words\n Indented")" ] || exit 1 + +# Test that `next-word` wraps around un-indented line breaks. +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "line\n")" ] || exit 1 + +# Test that `next-word-end` treats periods as letters. +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "line..." ] || exit 1 + +# Test that `previous-word` and `next-word` treat periods as letters. +$TMUX send-keys -X previous-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "line...\n")" ] || exit 1 + +# Test that `previous-space` and `next-space` treat periods as letters. +$TMUX send-keys -X previous-space +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-space +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "line...\n")" ] || exit 1 + +# Test that `next-word` and `next-word-end` treat other symbols as letters. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "... @nd then \$ym_bols[]{}" ] || exit 1 + +# Test that `previous-word` treats other symbols as letters +# and `next-word` wraps around for indented symbols +$TMUX send-keys -X previous-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "\$ym_bols[]{}\n ")" ] || exit 1 + +# Test that `next-word-end` treats digits as letters +$TMUX send-keys -X next-word-end +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = " 500xyz" ] || exit 1 + +# Test that `previous-word` treats digits as letters +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 + +# Test that `next-word` and `next-word-end` stop at the end of text. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word +$TMUX send-keys -X next-space +$TMUX send-keys -X next-space-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/copy-mode-test-vi.sh b/regress/copy-mode-test-vi.sh new file mode 100644 index 00000000..0eca3fa6 --- /dev/null +++ b/regress/copy-mode-test-vi.sh @@ -0,0 +1,115 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -f/dev/null -Ltest" +$TMUX kill-server 2>/dev/null + +$TMUX new -d -x40 -y10 \ + "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +# Enter copy mode and go to the first column of the first row. +$TMUX set-window-option -g mode-keys vi +$TMUX copy-mode +$TMUX send-keys -X history-top +$TMUX send-keys -X start-of-line + +# Test that `previous-word` and `previous-space` +# do not go past the start of text. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X previous-space +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "A" ] || exit 1 + +# Test that `next-word-end` skips single-letter words +# and `previous-word` does not skip multi-letter words. +$TMUX send-keys -X next-word-end +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "line" ] || exit 1 + +# Test that `next-word-end` wraps around indented line breaks. +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "words\n Indented")" ] || exit 1 + +# Test that `next-word` wraps around un-indented line breaks. +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "line\nA")" ] || exit 1 + +# Test that `next-word-end` does not treat periods as letters. +$TMUX send-keys -X next-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "line" ] || exit 1 + +# Test that `next-space-end` treats periods as letters. +$TMUX send-keys -X previous-word +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-space-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "line..." ] || exit 1 + +# Test that `previous-space` and `next-space` treat periods as letters. +$TMUX send-keys -X previous-space +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-space +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "line...\n.")" ] || exit 1 + +# Test that `next-word` and `next-word-end` do not treat other symbols as letters. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "... @nd then" ] || exit 1 + +# Test that `next-space` wraps around for indented symbols +$TMUX send-keys -X next-space +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-space +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "$(echo -e "\$ym_bols[]{}\n ?")" ] || exit 1 + +# Test that `next-word-end` treats digits as letters +$TMUX send-keys -X next-word-end +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "? 500xyz" ] || exit 1 + +# Test that `previous-word` treats digits as letters +$TMUX send-keys -X begin-selection +$TMUX send-keys -X previous-word +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 + +# Test that `next-word`, `next-word-end`, +# `next-space`, and `next-space-end` stop at the end of text. +$TMUX send-keys -X begin-selection +$TMUX send-keys -X next-word +$TMUX send-keys -X next-word-end +$TMUX send-keys -X next-word +$TMUX send-keys -X next-space +$TMUX send-keys -X next-space-end +$TMUX send-keys -X copy-selection +[ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/copy-mode-test.txt b/regress/copy-mode-test.txt new file mode 100644 index 00000000..a804f1f8 --- /dev/null +++ b/regress/copy-mode-test.txt @@ -0,0 +1,5 @@ +A line of words + Indented line +Another line... +... @nd then $ym_bols[]{} + ?? 500xyz diff --git a/regress/format-strings.sh b/regress/format-strings.sh index 726b46bc..0ae27386 100644 --- a/regress/format-strings.sh +++ b/regress/format-strings.sh @@ -125,14 +125,13 @@ test_conditional_with_pane_in_mode "#{?pane_in_mode,[abc,xyz],bonus}" "[abc" "xy test_conditional_with_pane_in_mode "#{?pane_in_mode,[abc#,xyz],bonus}" "[abc,xyz]" "bonus" -# Escape comma inside of #(...) -# Note: #() commands are run asynchronous and are substituted with result of the -# *previous* run or a placeholder (like "<'echo ,' not ready") if the command -# has not been run before. The format is updated as soon as the command -# finishes. As we are printing the message only once it never gets updated -# and the displayed message is "<'echo ,' not ready>" +# Escape comma inside of #(...) Note: #() commands are run asynchronous and are +# substituted with result of the *previous* run, an empty string if the command +# is new, or a placeholder after a few seconds. The format is updated as soon +# as the command finishes. As we are printing the message only once it never +# gets updated and the displayed message is empty. test_format "#{?pane_in_mode,#(echo #,),xyz}" "xyz" -test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "<'echo ,' not ready>" "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "" "xyz" # This caching does not work :-( #$TMUX display-message -p "#(echo #,)" > /dev/null #test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "," "xyz" diff --git a/regress/new-session-command.sh b/regress/new-session-command.sh index 02ba55d9..8dec322a 100644 --- a/regress/new-session-command.sh +++ b/regress/new-session-command.sh @@ -11,7 +11,7 @@ $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 - + cat <$TMP new sleep 101 new -- sleep 102 diff --git a/regress/new-window-command.sh b/regress/new-window-command.sh index 176bffb5..b83376e4 100644 --- a/regress/new-window-command.sh +++ b/regress/new-window-command.sh @@ -11,7 +11,7 @@ $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 - + cat <$TMP new neww sleep 101 diff --git a/regress/osc-11colours.sh b/regress/osc-11colours.sh new file mode 100644 index 00000000..a049a49a --- /dev/null +++ b/regress/osc-11colours.sh @@ -0,0 +1,244 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null + +$TMUX new -d +$TMUX set -g remain-on-exit on + +do_test() { + $TMUX splitw "printf '$1'" + sleep 0.25 + c="$($TMUX display -p '#{pane_bg}')" + $TMUX kill-pane + [ "$c" != "$2" ] && return 1 + return 0 +} + +do_test '\033]11;rgb:ff/ff/ff\007' '#ffffff' || exit 1 +do_test '\033]11;rgb:ff/ff/ff\007\033]111\007' 'default' || exit 1 + +do_test '\033]11;cmy:0.9373/0.6941/0.4549\007' '#0f4e8b' || exit 1 +do_test '\033]11;cmyk:0.88/0.44/0.00/0.45\007' '#104e8c' || exit 1 + +do_test '\033]11;16,78,139\007' '#104e8b' || exit 1 +do_test '\033]11;#104E8B\007' '#104e8b' || exit 1 +do_test '\033]11;#10004E008B00\007' '#104e8b' || exit 1 +do_test '\033]11;DodgerBlue4\007' '#104e8b' || exit 1 +do_test '\033]11;DodgerBlue4 \007' '#104e8b' || exit 1 +do_test '\033]11; DodgerBlue4\007' '#104e8b' || exit 1 +do_test '\033]11;rgb:10/4E/8B\007' '#104e8b' || exit 1 +do_test '\033]11;rgb:1000/4E00/8B00\007' '#104e8b' || exit 1 + +do_test '\033]11;grey\007' '#bebebe' || exit 1 +do_test '\033]11;grey0\007' '#000000' || exit 1 +do_test '\033]11;grey1\007' '#030303' || exit 1 +do_test '\033]11;grey2\007' '#050505' || exit 1 +do_test '\033]11;grey3\007' '#080808' || exit 1 +do_test '\033]11;grey4\007' '#0a0a0a' || exit 1 +do_test '\033]11;grey5\007' '#0d0d0d' || exit 1 +do_test '\033]11;grey6\007' '#0f0f0f' || exit 1 +do_test '\033]11;grey7\007' '#121212' || exit 1 +do_test '\033]11;grey8\007' '#141414' || exit 1 +do_test '\033]11;grey9\007' '#171717' || exit 1 +do_test '\033]11;grey10\007' '#1a1a1a' || exit 1 +do_test '\033]11;grey11\007' '#1c1c1c' || exit 1 +do_test '\033]11;grey12\007' '#1f1f1f' || exit 1 +do_test '\033]11;grey13\007' '#212121' || exit 1 +do_test '\033]11;grey14\007' '#242424' || exit 1 +do_test '\033]11;grey15\007' '#262626' || exit 1 +do_test '\033]11;grey16\007' '#292929' || exit 1 +do_test '\033]11;grey17\007' '#2b2b2b' || exit 1 +do_test '\033]11;grey18\007' '#2e2e2e' || exit 1 +do_test '\033]11;grey19\007' '#303030' || exit 1 +do_test '\033]11;grey20\007' '#333333' || exit 1 +do_test '\033]11;grey21\007' '#363636' || exit 1 +do_test '\033]11;grey22\007' '#383838' || exit 1 +do_test '\033]11;grey23\007' '#3b3b3b' || exit 1 +do_test '\033]11;grey24\007' '#3d3d3d' || exit 1 +do_test '\033]11;grey25\007' '#404040' || exit 1 +do_test '\033]11;grey26\007' '#424242' || exit 1 +do_test '\033]11;grey27\007' '#454545' || exit 1 +do_test '\033]11;grey28\007' '#474747' || exit 1 +do_test '\033]11;grey29\007' '#4a4a4a' || exit 1 +do_test '\033]11;grey30\007' '#4d4d4d' || exit 1 +do_test '\033]11;grey31\007' '#4f4f4f' || exit 1 +do_test '\033]11;grey32\007' '#525252' || exit 1 +do_test '\033]11;grey33\007' '#545454' || exit 1 +do_test '\033]11;grey34\007' '#575757' || exit 1 +do_test '\033]11;grey35\007' '#595959' || exit 1 +do_test '\033]11;grey36\007' '#5c5c5c' || exit 1 +do_test '\033]11;grey37\007' '#5e5e5e' || exit 1 +do_test '\033]11;grey38\007' '#616161' || exit 1 +do_test '\033]11;grey39\007' '#636363' || exit 1 +do_test '\033]11;grey40\007' '#666666' || exit 1 +do_test '\033]11;grey41\007' '#696969' || exit 1 +do_test '\033]11;grey42\007' '#6b6b6b' || exit 1 +do_test '\033]11;grey43\007' '#6e6e6e' || exit 1 +do_test '\033]11;grey44\007' '#707070' || exit 1 +do_test '\033]11;grey45\007' '#737373' || exit 1 +do_test '\033]11;grey46\007' '#757575' || exit 1 +do_test '\033]11;grey47\007' '#787878' || exit 1 +do_test '\033]11;grey48\007' '#7a7a7a' || exit 1 +do_test '\033]11;grey49\007' '#7d7d7d' || exit 1 +do_test '\033]11;grey50\007' '#7f7f7f' || exit 1 +do_test '\033]11;grey51\007' '#828282' || exit 1 +do_test '\033]11;grey52\007' '#858585' || exit 1 +do_test '\033]11;grey53\007' '#878787' || exit 1 +do_test '\033]11;grey54\007' '#8a8a8a' || exit 1 +do_test '\033]11;grey55\007' '#8c8c8c' || exit 1 +do_test '\033]11;grey56\007' '#8f8f8f' || exit 1 +do_test '\033]11;grey57\007' '#919191' || exit 1 +do_test '\033]11;grey58\007' '#949494' || exit 1 +do_test '\033]11;grey59\007' '#969696' || exit 1 +do_test '\033]11;grey60\007' '#999999' || exit 1 +do_test '\033]11;grey61\007' '#9c9c9c' || exit 1 +do_test '\033]11;grey62\007' '#9e9e9e' || exit 1 +do_test '\033]11;grey63\007' '#a1a1a1' || exit 1 +do_test '\033]11;grey64\007' '#a3a3a3' || exit 1 +do_test '\033]11;grey65\007' '#a6a6a6' || exit 1 +do_test '\033]11;grey66\007' '#a8a8a8' || exit 1 +do_test '\033]11;grey67\007' '#ababab' || exit 1 +do_test '\033]11;grey68\007' '#adadad' || exit 1 +do_test '\033]11;grey69\007' '#b0b0b0' || exit 1 +do_test '\033]11;grey70\007' '#b3b3b3' || exit 1 +do_test '\033]11;grey71\007' '#b5b5b5' || exit 1 +do_test '\033]11;grey72\007' '#b8b8b8' || exit 1 +do_test '\033]11;grey73\007' '#bababa' || exit 1 +do_test '\033]11;grey74\007' '#bdbdbd' || exit 1 +do_test '\033]11;grey75\007' '#bfbfbf' || exit 1 +do_test '\033]11;grey76\007' '#c2c2c2' || exit 1 +do_test '\033]11;grey77\007' '#c4c4c4' || exit 1 +do_test '\033]11;grey78\007' '#c7c7c7' || exit 1 +do_test '\033]11;grey79\007' '#c9c9c9' || exit 1 +do_test '\033]11;grey80\007' '#cccccc' || exit 1 +do_test '\033]11;grey81\007' '#cfcfcf' || exit 1 +do_test '\033]11;grey82\007' '#d1d1d1' || exit 1 +do_test '\033]11;grey83\007' '#d4d4d4' || exit 1 +do_test '\033]11;grey84\007' '#d6d6d6' || exit 1 +do_test '\033]11;grey85\007' '#d9d9d9' || exit 1 +do_test '\033]11;grey86\007' '#dbdbdb' || exit 1 +do_test '\033]11;grey87\007' '#dedede' || exit 1 +do_test '\033]11;grey88\007' '#e0e0e0' || exit 1 +do_test '\033]11;grey89\007' '#e3e3e3' || exit 1 +do_test '\033]11;grey90\007' '#e5e5e5' || exit 1 +do_test '\033]11;grey91\007' '#e8e8e8' || exit 1 +do_test '\033]11;grey92\007' '#ebebeb' || exit 1 +do_test '\033]11;grey93\007' '#ededed' || exit 1 +do_test '\033]11;grey94\007' '#f0f0f0' || exit 1 +do_test '\033]11;grey95\007' '#f2f2f2' || exit 1 +do_test '\033]11;grey96\007' '#f5f5f5' || exit 1 +do_test '\033]11;grey97\007' '#f7f7f7' || exit 1 +do_test '\033]11;grey98\007' '#fafafa' || exit 1 +do_test '\033]11;grey99\007' '#fcfcfc' || exit 1 +do_test '\033]11;grey100\007' '#ffffff' || exit 1 + +do_test '\033]11;gray\007' '#bebebe' || exit 1 +do_test '\033]11;gray0\007' '#000000' || exit 1 +do_test '\033]11;gray1\007' '#030303' || exit 1 +do_test '\033]11;gray2\007' '#050505' || exit 1 +do_test '\033]11;gray3\007' '#080808' || exit 1 +do_test '\033]11;gray4\007' '#0a0a0a' || exit 1 +do_test '\033]11;gray5\007' '#0d0d0d' || exit 1 +do_test '\033]11;gray6\007' '#0f0f0f' || exit 1 +do_test '\033]11;gray7\007' '#121212' || exit 1 +do_test '\033]11;gray8\007' '#141414' || exit 1 +do_test '\033]11;gray9\007' '#171717' || exit 1 +do_test '\033]11;gray10\007' '#1a1a1a' || exit 1 +do_test '\033]11;gray11\007' '#1c1c1c' || exit 1 +do_test '\033]11;gray12\007' '#1f1f1f' || exit 1 +do_test '\033]11;gray13\007' '#212121' || exit 1 +do_test '\033]11;gray14\007' '#242424' || exit 1 +do_test '\033]11;gray15\007' '#262626' || exit 1 +do_test '\033]11;gray16\007' '#292929' || exit 1 +do_test '\033]11;gray17\007' '#2b2b2b' || exit 1 +do_test '\033]11;gray18\007' '#2e2e2e' || exit 1 +do_test '\033]11;gray19\007' '#303030' || exit 1 +do_test '\033]11;gray20\007' '#333333' || exit 1 +do_test '\033]11;gray21\007' '#363636' || exit 1 +do_test '\033]11;gray22\007' '#383838' || exit 1 +do_test '\033]11;gray23\007' '#3b3b3b' || exit 1 +do_test '\033]11;gray24\007' '#3d3d3d' || exit 1 +do_test '\033]11;gray25\007' '#404040' || exit 1 +do_test '\033]11;gray26\007' '#424242' || exit 1 +do_test '\033]11;gray27\007' '#454545' || exit 1 +do_test '\033]11;gray28\007' '#474747' || exit 1 +do_test '\033]11;gray29\007' '#4a4a4a' || exit 1 +do_test '\033]11;gray30\007' '#4d4d4d' || exit 1 +do_test '\033]11;gray31\007' '#4f4f4f' || exit 1 +do_test '\033]11;gray32\007' '#525252' || exit 1 +do_test '\033]11;gray33\007' '#545454' || exit 1 +do_test '\033]11;gray34\007' '#575757' || exit 1 +do_test '\033]11;gray35\007' '#595959' || exit 1 +do_test '\033]11;gray36\007' '#5c5c5c' || exit 1 +do_test '\033]11;gray37\007' '#5e5e5e' || exit 1 +do_test '\033]11;gray38\007' '#616161' || exit 1 +do_test '\033]11;gray39\007' '#636363' || exit 1 +do_test '\033]11;gray40\007' '#666666' || exit 1 +do_test '\033]11;gray41\007' '#696969' || exit 1 +do_test '\033]11;gray42\007' '#6b6b6b' || exit 1 +do_test '\033]11;gray43\007' '#6e6e6e' || exit 1 +do_test '\033]11;gray44\007' '#707070' || exit 1 +do_test '\033]11;gray45\007' '#737373' || exit 1 +do_test '\033]11;gray46\007' '#757575' || exit 1 +do_test '\033]11;gray47\007' '#787878' || exit 1 +do_test '\033]11;gray48\007' '#7a7a7a' || exit 1 +do_test '\033]11;gray49\007' '#7d7d7d' || exit 1 +do_test '\033]11;gray50\007' '#7f7f7f' || exit 1 +do_test '\033]11;gray51\007' '#828282' || exit 1 +do_test '\033]11;gray52\007' '#858585' || exit 1 +do_test '\033]11;gray53\007' '#878787' || exit 1 +do_test '\033]11;gray54\007' '#8a8a8a' || exit 1 +do_test '\033]11;gray55\007' '#8c8c8c' || exit 1 +do_test '\033]11;gray56\007' '#8f8f8f' || exit 1 +do_test '\033]11;gray57\007' '#919191' || exit 1 +do_test '\033]11;gray58\007' '#949494' || exit 1 +do_test '\033]11;gray59\007' '#969696' || exit 1 +do_test '\033]11;gray60\007' '#999999' || exit 1 +do_test '\033]11;gray61\007' '#9c9c9c' || exit 1 +do_test '\033]11;gray62\007' '#9e9e9e' || exit 1 +do_test '\033]11;gray63\007' '#a1a1a1' || exit 1 +do_test '\033]11;gray64\007' '#a3a3a3' || exit 1 +do_test '\033]11;gray65\007' '#a6a6a6' || exit 1 +do_test '\033]11;gray66\007' '#a8a8a8' || exit 1 +do_test '\033]11;gray67\007' '#ababab' || exit 1 +do_test '\033]11;gray68\007' '#adadad' || exit 1 +do_test '\033]11;gray69\007' '#b0b0b0' || exit 1 +do_test '\033]11;gray70\007' '#b3b3b3' || exit 1 +do_test '\033]11;gray71\007' '#b5b5b5' || exit 1 +do_test '\033]11;gray72\007' '#b8b8b8' || exit 1 +do_test '\033]11;gray73\007' '#bababa' || exit 1 +do_test '\033]11;gray74\007' '#bdbdbd' || exit 1 +do_test '\033]11;gray75\007' '#bfbfbf' || exit 1 +do_test '\033]11;gray76\007' '#c2c2c2' || exit 1 +do_test '\033]11;gray77\007' '#c4c4c4' || exit 1 +do_test '\033]11;gray78\007' '#c7c7c7' || exit 1 +do_test '\033]11;gray79\007' '#c9c9c9' || exit 1 +do_test '\033]11;gray80\007' '#cccccc' || exit 1 +do_test '\033]11;gray81\007' '#cfcfcf' || exit 1 +do_test '\033]11;gray82\007' '#d1d1d1' || exit 1 +do_test '\033]11;gray83\007' '#d4d4d4' || exit 1 +do_test '\033]11;gray84\007' '#d6d6d6' || exit 1 +do_test '\033]11;gray85\007' '#d9d9d9' || exit 1 +do_test '\033]11;gray86\007' '#dbdbdb' || exit 1 +do_test '\033]11;gray87\007' '#dedede' || exit 1 +do_test '\033]11;gray88\007' '#e0e0e0' || exit 1 +do_test '\033]11;gray89\007' '#e3e3e3' || exit 1 +do_test '\033]11;gray90\007' '#e5e5e5' || exit 1 +do_test '\033]11;gray91\007' '#e8e8e8' || exit 1 +do_test '\033]11;gray92\007' '#ebebeb' || exit 1 +do_test '\033]11;gray93\007' '#ededed' || exit 1 +do_test '\033]11;gray94\007' '#f0f0f0' || exit 1 +do_test '\033]11;gray95\007' '#f2f2f2' || exit 1 +do_test '\033]11;gray96\007' '#f5f5f5' || exit 1 +do_test '\033]11;gray97\007' '#f7f7f7' || exit 1 +do_test '\033]11;gray98\007' '#fafafa' || exit 1 +do_test '\033]11;gray99\007' '#fcfcfc' || exit 1 +do_test '\033]11;gray100\007' '#ffffff' || exit 1 + +$TMUX -f/dev/null kill-server 2>/dev/null +exit 0 diff --git a/regress/style-trim.sh b/regress/style-trim.sh new file mode 100644 index 00000000..720d99fd --- /dev/null +++ b/regress/style-trim.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" +$TMUX kill-server 2>/dev/null +TMUX2="$TEST_TMUX -Ltest2" +$TMUX2 kill-server 2>/dev/null + +$TMUX2 -f/dev/null new -d "$TMUX -f/dev/null new" +sleep 2 +$TMUX set -g status-style fg=default,bg=default + +check() { + v=$($TMUX display -p "$1") + $TMUX set -g status-format[0] "$1" + sleep 1 + r=$($TMUX2 capturep -Cep|tail -1|sed 's|\\033\[||g') + + if [ "$v" != "$2" -o "$r" != "$3" ]; then + printf "$1 = [$v = $2] [$r = $3]" + printf " \033[31mbad\033[0m\n" + exit 1 + fi +} + +# drawn as #0 +$TMUX setenv -g V '#0' +check '#{V} #{w:V}' '#0 2' '#0 2' +check '#{=3:V}' '#0' '#0' +check '#{=-3:V}' '#0' '#0' + +# drawn as #0 +$TMUX setenv -g V '###[bg=yellow]0' +check '#{V} #{w:V}' '###[bg=yellow]0 2' '#43m0 249m' +check '#{=3:V}' '###[bg=yellow]0' '#43m049m' +check '#{=-3:V}' '###[bg=yellow]0' '#43m049m' + +# drawn as #0123456 +$TMUX setenv -g V '#0123456' +check '#{V} #{w:V}' '#0123456 8' '#0123456 8' +check '#{=3:V}' '#01' '#01' +check '#{=-3:V}' '456' '456' + +# drawn as #0123456 +$TMUX setenv -g V '##0123456' +check '#{V} #{w:V}' '##0123456 8' '#0123456 8' +check '#{=3:V}' '##01' '#01' +check '#{=-3:V}' '456' '456' + +# drawn as ##0123456 +$TMUX setenv -g V '###0123456' +check '#{V} #{w:V}' '###0123456 9' '##0123456 9' +check '#{=3:V}' '####0' '##0' +check '#{=-3:V}' '456' '456' + +# drawn as 0123456 +$TMUX setenv -g V '#[bg=yellow]0123456' +check '#{V} #{w:V}' '#[bg=yellow]0123456 7' '43m0123456 749m' +check '#{=3:V}' '#[bg=yellow]012' '43m01249m' +check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' + +# drawn as #[bg=yellow]0123456 +$TMUX setenv -g V '##[bg=yellow]0123456' +check '#{V} #{w:V}' '##[bg=yellow]0123456 19' '#[bg=yellow]0123456 19' +check '#{=3:V}' '##[b' '#[b' +check '#{=-3:V}' '456' '456' + +# drawn as #0123456 +$TMUX setenv -g V '###[bg=yellow]0123456' +check '#{V} #{w:V}' '###[bg=yellow]0123456 8' '#43m0123456 849m' +check '#{=3:V}' '###[bg=yellow]01' '#43m0149m' +check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' + +# drawn as ##[bg=yellow]0123456 +$TMUX setenv -g V '####[bg=yellow]0123456' +check '#{V} #{w:V}' '####[bg=yellow]0123456 20' '##[bg=yellow]0123456 20' +check '#{=3:V}' '####[' '##[' +check '#{=-3:V}' '456' '456' + +# drawn as ###0123456 +$TMUX setenv -g V '#####[bg=yellow]0123456' +check '#{V} #{w:V}' '#####[bg=yellow]0123456 9' '##43m0123456 949m' +check '#{=3:V}' '#####[bg=yellow]0' '##43m049m' +check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' + +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null +exit 0 diff --git a/regsub.c b/regsub.c index 22e236dc..4039b9be 100644 --- a/regsub.c +++ b/regsub.c @@ -24,8 +24,7 @@ #include "tmux.h" static void -regsub_copy(char **buf, size_t *len, const char *text, size_t start, - size_t end) +regsub_copy(char **buf, size_t *len, const char *text, size_t start, size_t end) { size_t add = end - start; diff --git a/resize.c b/resize.c index 96d733f0..8416ad6a 100644 --- a/resize.c +++ b/resize.c @@ -51,7 +51,7 @@ resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) if (sy < w->layout_root->sy) sy = w->layout_root->sy; window_resize(w, sx, sy, xpixel, ypixel); - log_debug("%s: @%u resized to %u,%u; layout %u,%u", __func__, w->id, + log_debug("%s: @%u resized to %ux%u; layout %ux%u", __func__, w->id, sx, sy, w->layout_root->sx, w->layout_root->sy); /* Restore the window zoom state. */ @@ -61,6 +61,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); notify_window("window-layout-changed", w); + w->flags &= ~WINDOW_RESIZE; } static int @@ -72,149 +73,261 @@ ignore_client_size(struct client *c) return (1); if (c->flags & CLIENT_NOSIZEFLAGS) return (1); - if (c->flags & CLIENT_READONLY) { + if (c->flags & CLIENT_IGNORESIZE) { /* - * Ignore readonly clients if there are any attached clients - * that aren't readonly. + * Ignore flagged clients if there are any attached clients + * that aren't flagged. */ TAILQ_FOREACH (loop, &clients, entry) { if (loop->session == NULL) continue; if (loop->flags & CLIENT_NOSIZEFLAGS) continue; - if (~loop->flags & CLIENT_READONLY) + if (~loop->flags & CLIENT_IGNORESIZE) return (1); } } - if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED)) + if ((c->flags & CLIENT_CONTROL) && + (~c->flags & CLIENT_SIZECHANGED) && + (~c->flags & CLIENT_WINDOWSIZECHANGED)) + return (1); + return (0); +} + +static u_int +clients_with_window(struct window *w) +{ + struct client *loop; + u_int n = 0; + + TAILQ_FOREACH(loop, &clients, entry) { + if (ignore_client_size(loop) || !session_has(loop->session, w)) + continue; + if (++n > 1) + break; + } + return (n); +} + +static int +clients_calculate_size(int type, int current, struct client *c, + struct session *s, struct window *w, int (*skip_client)(struct client *, + int, int, struct session *, struct window *), u_int *sx, u_int *sy, + u_int *xpixel, u_int *ypixel) +{ + struct client *loop; + struct client_window *cw; + u_int cx, cy, n = 0; + + /* + * Start comparing with 0 for largest and UINT_MAX for smallest or + * latest. + */ + if (type == WINDOW_SIZE_LARGEST) { + *sx = 0; + *sy = 0; + } else if (type == WINDOW_SIZE_MANUAL) { + *sx = w->manual_sx; + *sy = w->manual_sy; + log_debug("%s: manual size %ux%u", __func__, *sx, *sy); + } else { + *sx = UINT_MAX; + *sy = UINT_MAX; + } + *xpixel = *ypixel = 0; + + /* + * For latest, count the number of clients with this window. We only + * care if there is more than one. + */ + if (type == WINDOW_SIZE_LATEST && w != NULL) + n = clients_with_window(w); + + /* Skip setting the size if manual */ + if (type == WINDOW_SIZE_MANUAL) + goto skip; + + /* Loop over the clients and work out the size. */ + TAILQ_FOREACH(loop, &clients, entry) { + if (loop != c && ignore_client_size(loop)) { + log_debug("%s: ignoring %s (1)", __func__, loop->name); + continue; + } + if (loop != c && skip_client(loop, type, current, s, w)) { + log_debug("%s: skipping %s (1)", __func__, loop->name); + continue; + } + + /* + * If there are multiple clients attached, only accept the + * latest client; otherwise let the only client be chosen as + * for smallest. + */ + if (type == WINDOW_SIZE_LATEST && n > 1 && loop != w->latest) { + log_debug("%s: %s is not latest", __func__, loop->name); + continue; + } + + /* + * If the client has a per-window size, use this instead if it is + * smaller. + */ + if (w != NULL) + cw = server_client_get_client_window(loop, w->id); + else + cw = NULL; + + /* Work out this client's size. */ + if (cw != NULL) { + cx = cw->sx; + cy = cw->sy; + } else { + cx = loop->tty.sx; + cy = loop->tty.sy - status_line_size(loop); + } + + /* + * If it is larger or smaller than the best so far, update the + * new size. + */ + if (type == WINDOW_SIZE_LARGEST) { + if (cx > *sx) + *sx = cx; + if (cy > *sy) + *sy = cy; + } else { + if (cx < *sx) + *sx = cx; + if (cy < *sy) + *sy = cy; + } + if (loop->tty.xpixel > *xpixel && loop->tty.ypixel > *ypixel) { + *xpixel = loop->tty.xpixel; + *ypixel = loop->tty.ypixel; + } + log_debug("%s: after %s (%ux%u), size is %ux%u", __func__, + loop->name, cx, cy, *sx, *sy); + } + if (*sx != UINT_MAX && *sy != UINT_MAX) + log_debug("%s: calculated size %ux%u", __func__, *sx, *sy); + else + log_debug("%s: no calculated size", __func__); + +skip: + /* + * Do not allow any size to be larger than the per-client window size + * if one exists. + */ + if (w != NULL) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop != c && ignore_client_size(loop)) + continue; + if (loop != c && skip_client(loop, type, current, s, w)) + continue; + + /* Look up per-window size if any. */ + if (~loop->flags & CLIENT_WINDOWSIZECHANGED) + continue; + cw = server_client_get_client_window(loop, w->id); + if (cw == NULL) + continue; + + /* Clamp the size. */ + log_debug("%s: %s size for @%u is %ux%u", __func__, + loop->name, w->id, cw->sx, cw->sy); + if (cw->sx != 0 && *sx > cw->sx) + *sx = cw->sx; + if (cw->sy != 0 && *sy > cw->sy) + *sy = cw->sy; + } + } + if (*sx != UINT_MAX && *sy != UINT_MAX) + log_debug("%s: calculated size %ux%u", __func__, *sx, *sy); + else + log_debug("%s: no calculated size", __func__); + + /* Return whether a suitable size was found. */ + if (type == WINDOW_SIZE_MANUAL) { + log_debug("%s: type is manual", __func__); + return (1); + } + if (type == WINDOW_SIZE_LARGEST) { + log_debug("%s: type is largest", __func__); + return (*sx != 0 && *sy != 0); + } + if (type == WINDOW_SIZE_LATEST) + log_debug("%s: type is latest", __func__); + else + log_debug("%s: type is smallest", __func__); + return (*sx != UINT_MAX && *sy != UINT_MAX); +} + +static int +default_window_size_skip_client(struct client *loop, int type, + __unused int current, struct session *s, struct window *w) +{ + /* + * Latest checks separately, so do not check here. Otherwise only + * include clients where the session contains the window or where the + * session is the given session. + */ + if (type == WINDOW_SIZE_LATEST) + return (0); + if (w != NULL && !session_has(loop->session, w)) + return (1); + if (w == NULL && loop->session != s) return (1); return (0); } void default_window_size(struct client *c, struct session *s, struct window *w, - u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel, int type) + u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel, int type) { - struct client *loop; - u_int cx, cy, n; const char *value; + /* Get type if not provided. */ if (type == -1) type = options_get_number(global_w_options, "window-size"); - switch (type) { - case WINDOW_SIZE_LARGEST: - *sx = *sy = 0; - *xpixel = *ypixel = 0; - TAILQ_FOREACH(loop, &clients, entry) { - if (ignore_client_size(loop)) - continue; - if (w != NULL && !session_has(loop->session, w)) - continue; - if (w == NULL && loop->session != s) - continue; - cx = loop->tty.sx; - cy = loop->tty.sy - status_line_size(loop); - - if (cx > *sx) - *sx = cx; - if (cy > *sy) - *sy = cy; - - if (loop->tty.xpixel > *xpixel && - loop->tty.ypixel > *ypixel) { - *xpixel = loop->tty.xpixel; - *ypixel = loop->tty.ypixel; - } - } - if (*sx == 0 || *sy == 0) - goto manual; - break; - case WINDOW_SIZE_SMALLEST: - *sx = *sy = UINT_MAX; - *xpixel = *ypixel = 0; - TAILQ_FOREACH(loop, &clients, entry) { - if (ignore_client_size(loop)) - continue; - if (w != NULL && !session_has(loop->session, w)) - continue; - if (w == NULL && loop->session != s) - continue; - - cx = loop->tty.sx; - cy = loop->tty.sy - status_line_size(loop); - - if (cx < *sx) - *sx = cx; - if (cy < *sy) - *sy = cy; - - if (loop->tty.xpixel > *xpixel && - loop->tty.ypixel > *ypixel) { - *xpixel = loop->tty.xpixel; - *ypixel = loop->tty.ypixel; - } - } - if (*sx == UINT_MAX || *sy == UINT_MAX) - goto manual; - break; - case WINDOW_SIZE_LATEST: - if (c != NULL && !ignore_client_size(c)) { - *sx = c->tty.sx; - *sy = c->tty.sy - status_line_size(c); - *xpixel = c->tty.xpixel; - *ypixel = c->tty.ypixel; - } else { - if (w == NULL) - goto manual; - n = 0; - TAILQ_FOREACH(loop, &clients, entry) { - if (!ignore_client_size(loop) && - session_has(loop->session, w)) { - if (++n > 1) - break; - } - } - *sx = *sy = UINT_MAX; - *xpixel = *ypixel = 0; - TAILQ_FOREACH(loop, &clients, entry) { - if (ignore_client_size(loop)) - continue; - if (n > 1 && loop != w->latest) - continue; - s = loop->session; - - cx = loop->tty.sx; - cy = loop->tty.sy - status_line_size(loop); - - if (cx < *sx) - *sx = cx; - if (cy < *sy) - *sy = cy; - - if (loop->tty.xpixel > *xpixel && - loop->tty.ypixel > *ypixel) { - *xpixel = loop->tty.xpixel; - *ypixel = loop->tty.ypixel; - } - } - if (*sx == UINT_MAX || *sy == UINT_MAX) - goto manual; - } - break; - case WINDOW_SIZE_MANUAL: - goto manual; + /* + * Latest clients can use the given client if suitable. If there is no + * client and no window, use the default size as for manual type. + */ + if (type == WINDOW_SIZE_LATEST && c != NULL && !ignore_client_size(c)) { + *sx = c->tty.sx; + *sy = c->tty.sy - status_line_size(c); + *xpixel = c->tty.xpixel; + *ypixel = c->tty.ypixel; + log_debug("%s: using %ux%u from %s", __func__, *sx, *sy, + c->name); + goto done; } - goto done; -manual: - value = options_get_string(s->options, "default-size"); - if (sscanf(value, "%ux%u", sx, sy) != 2) { - *sx = 80; - *sy = 24; + /* + * Ignore the given client if it is a control client - the creating + * client should only affect the size if it is not a control client. + */ + if (c != NULL && (c->flags & CLIENT_CONTROL)) + c = NULL; + + /* + * Look for a client to base the size on. If none exists (or the type + * is manual), use the default-size option. + */ + if (!clients_calculate_size(type, 0, c, s, w, + default_window_size_skip_client, sx, sy, xpixel, ypixel)) { + value = options_get_string(s->options, "default-size"); + if (sscanf(value, "%ux%u", sx, sy) != 2) { + *sx = 80; + *sy = 24; + } + log_debug("%s: using %ux%u from default-size", __func__, *sx, + *sy); } done: + /* Make sure the limits are enforced. */ if (*sx < WINDOW_MINIMUM) *sx = WINDOW_MINIMUM; if (*sx > WINDOW_MAXIMUM) @@ -223,143 +336,98 @@ done: *sy = WINDOW_MINIMUM; if (*sy > WINDOW_MAXIMUM) *sy = WINDOW_MAXIMUM; + log_debug("%s: resulting size is %ux%u", __func__, *sx, *sy); +} + +static int +recalculate_size_skip_client(struct client *loop, __unused int type, + int current, __unused struct session *s, struct window *w) +{ + /* + * If the current flag is set, then skip any client where this window + * is not the current window - this is used for aggressive-resize. + * Otherwise skip any session that doesn't contain the window. + */ + if (current) + return (loop->session->curw->window != w); + return (session_has(loop->session, w) == 0); } void -recalculate_size(struct window *w) +recalculate_size(struct window *w, int now) { - struct session *s; - struct client *c; - u_int sx, sy, cx, cy, xpixel = 0, ypixel = 0, n; - int type, current, has, changed; + u_int sx, sy, xpixel = 0, ypixel = 0; + int type, current, changed; + /* + * Do not attempt to resize windows which have no pane, they must be on + * the way to destruction. + */ if (w->active == NULL) return; - log_debug("%s: @%u is %u,%u", __func__, w->id, w->sx, w->sy); + log_debug("%s: @%u is %ux%u", __func__, w->id, w->sx, w->sy); + /* + * Type is manual, smallest, largest, latest. Current is the + * aggressive-resize option (do not resize based on clients where the + * window is not the current window). + */ type = options_get_number(w->options, "window-size"); current = options_get_number(w->options, "aggressive-resize"); - changed = 1; - switch (type) { - case WINDOW_SIZE_LARGEST: - sx = sy = 0; - TAILQ_FOREACH(c, &clients, entry) { - if (ignore_client_size(c)) - continue; - s = c->session; + /* Look for a suitable client and get the new size. */ + changed = clients_calculate_size(type, current, NULL, NULL, w, + recalculate_size_skip_client, &sx, &sy, &xpixel, &ypixel); - if (current) - has = (s->curw->window == w); - else - has = session_has(s, w); - if (!has) - continue; - - cx = c->tty.sx; - cy = c->tty.sy - status_line_size(c); - - if (cx > sx) - sx = cx; - if (cy > sy) - sy = cy; - - if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) { - xpixel = c->tty.xpixel; - ypixel = c->tty.ypixel; - } - } - if (sx == 0 || sy == 0) + /* + * Make sure the size has actually changed. If the window has already + * got a resize scheduled, then use the new size; otherwise the old. + */ + if (w->flags & WINDOW_RESIZE) { + if (!now && changed && w->new_sx == sx && w->new_sy == sy) changed = 0; - break; - case WINDOW_SIZE_SMALLEST: - sx = sy = UINT_MAX; - TAILQ_FOREACH(c, &clients, entry) { - if (ignore_client_size(c)) - continue; - s = c->session; - - if (current) - has = (s->curw->window == w); - else - has = session_has(s, w); - if (!has) - continue; - - cx = c->tty.sx; - cy = c->tty.sy - status_line_size(c); - - if (cx < sx) - sx = cx; - if (cy < sy) - sy = cy; - - if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) { - xpixel = c->tty.xpixel; - ypixel = c->tty.ypixel; - } - } - if (sx == UINT_MAX || sy == UINT_MAX) + } else { + if (!now && changed && w->sx == sx && w->sy == sy) changed = 0; - break; - case WINDOW_SIZE_LATEST: - n = 0; - TAILQ_FOREACH(c, &clients, entry) { - if (!ignore_client_size(c) && - session_has(c->session, w)) { - if (++n > 1) - break; - } - } - sx = sy = UINT_MAX; - TAILQ_FOREACH(c, &clients, entry) { - if (ignore_client_size(c)) - continue; - if (n > 1 && c != w->latest) - continue; - s = c->session; - - if (current) - has = (s->curw->window == w); - else - has = session_has(s, w); - if (!has) - continue; - - cx = c->tty.sx; - cy = c->tty.sy - status_line_size(c); - - if (cx < sx) - sx = cx; - if (cy < sy) - sy = cy; - - if (c->tty.xpixel > xpixel && c->tty.ypixel > ypixel) { - xpixel = c->tty.xpixel; - ypixel = c->tty.ypixel; - } - } - if (sx == UINT_MAX || sy == UINT_MAX) - changed = 0; - break; - case WINDOW_SIZE_MANUAL: - changed = 0; - break; } - if (changed && w->sx == sx && w->sy == sy) - changed = 0; + /* + * If the size hasn't changed, update the window offset but not the + * size. + */ if (!changed) { + log_debug("%s: @%u no size change", __func__, w->id); tty_update_window_offset(w); return; } - log_debug("%s: @%u changed to %u,%u (%ux%u)", __func__, w->id, sx, sy, - xpixel, ypixel); - resize_window(w, sx, sy, xpixel, ypixel); + + /* + * If the now flag is set or if the window is sized manually, change + * the size immediately. Otherwise set the flag and it will be done + * later. + */ + log_debug("%s: @%u new size %ux%u", __func__, w->id, sx, sy); + if (now || type == WINDOW_SIZE_MANUAL) + resize_window(w, sx, sy, xpixel, ypixel); + else { + w->new_sx = sx; + w->new_sy = sy; + w->new_xpixel = xpixel; + w->new_ypixel = ypixel; + + w->flags |= WINDOW_RESIZE; + tty_update_window_offset(w); + } } void recalculate_sizes(void) +{ + recalculate_sizes_now(0); +} + +void +recalculate_sizes_now(int now) { struct session *s; struct client *c; @@ -392,5 +460,5 @@ recalculate_sizes(void) /* Walk each window and adjust the size. */ RB_FOREACH(w, windows, &windows) - recalculate_size(w); + recalculate_size(w, now); } diff --git a/screen-redraw.c b/screen-redraw.c index 03b784b1..9860c0de 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -28,10 +28,12 @@ static void screen_redraw_draw_panes(struct screen_redraw_ctx *); static void screen_redraw_draw_status(struct screen_redraw_ctx *); static void screen_redraw_draw_pane(struct screen_redraw_ctx *, struct window_pane *); +static void screen_redraw_set_context(struct client *, + struct screen_redraw_ctx *); #define CELL_INSIDE 0 -#define CELL_LEFTRIGHT 1 -#define CELL_TOPBOTTOM 2 +#define CELL_TOPBOTTOM 1 +#define CELL_LEFTRIGHT 2 #define CELL_TOPLEFT 3 #define CELL_TOPRIGHT 4 #define CELL_BOTTOMLEFT 5 @@ -45,73 +47,302 @@ static void screen_redraw_draw_pane(struct screen_redraw_ctx *, #define CELL_BORDERS " xqlkmjwvtun~" -/* Check if cell is on the border of a particular pane. */ -static int -screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py) +#define START_ISOLATE "\342\201\246" +#define END_ISOLATE "\342\201\251" + +static const struct utf8_data screen_redraw_double_borders[] = { + { "", 0, 0, 0 }, + { "\342\225\221", 0, 3, 1 }, /* U+2551 */ + { "\342\225\220", 0, 3, 1 }, /* U+2550 */ + { "\342\225\224", 0, 3, 1 }, /* U+2554 */ + { "\342\225\227", 0, 3, 1 }, /* U+2557 */ + { "\342\225\232", 0, 3, 1 }, /* U+255A */ + { "\342\225\235", 0, 3, 1 }, /* U+255D */ + { "\342\225\246", 0, 3, 1 }, /* U+2566 */ + { "\342\225\251", 0, 3, 1 }, /* U+2569 */ + { "\342\225\240", 0, 3, 1 }, /* U+2560 */ + { "\342\225\243", 0, 3, 1 }, /* U+2563 */ + { "\342\225\254", 0, 3, 1 }, /* U+256C */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +static const struct utf8_data screen_redraw_heavy_borders[] = { + { "", 0, 0, 0 }, + { "\342\224\203", 0, 3, 1 }, /* U+2503 */ + { "\342\224\201", 0, 3, 1 }, /* U+2501 */ + { "\342\224\223", 0, 3, 1 }, /* U+2513 */ + { "\342\224\217", 0, 3, 1 }, /* U+250F */ + { "\342\224\227", 0, 3, 1 }, /* U+2517 */ + { "\342\224\233", 0, 3, 1 }, /* U+251B */ + { "\342\224\263", 0, 3, 1 }, /* U+2533 */ + { "\342\224\273", 0, 3, 1 }, /* U+253B */ + { "\342\224\243", 0, 3, 1 }, /* U+2523 */ + { "\342\224\253", 0, 3, 1 }, /* U+252B */ + { "\342\225\213", 0, 3, 1 }, /* U+254B */ + { "\302\267", 0, 2, 1 } /* U+00B7 */ +}; + +enum screen_redraw_border_type { + SCREEN_REDRAW_OUTSIDE, + SCREEN_REDRAW_INSIDE, + SCREEN_REDRAW_BORDER +}; + +/* Get cell border character. */ +static void +screen_redraw_border_set(struct window_pane *wp, int pane_lines, int cell_type, + struct grid_cell *gc) { - /* Inside pane. */ - if (px >= wp->xoff && px < wp->xoff + wp->sx && - py >= wp->yoff && py < wp->yoff + wp->sy) + u_int idx; + + switch (pane_lines) { + case PANE_LINES_NUMBER: + if (cell_type == CELL_OUTSIDE) { + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]); + break; + } + gc->attr &= ~GRID_ATTR_CHARSET; + if (wp != NULL && window_pane_index(wp, &idx) == 0) + utf8_set(&gc->data, '0' + (idx % 10)); + else + utf8_set(&gc->data, '*'); + break; + case PANE_LINES_DOUBLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, &screen_redraw_double_borders[cell_type]); + break; + case PANE_LINES_HEAVY: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_copy(&gc->data, &screen_redraw_heavy_borders[cell_type]); + break; + case PANE_LINES_SIMPLE: + gc->attr &= ~GRID_ATTR_CHARSET; + utf8_set(&gc->data, " |-+++++++++."[cell_type]); + break; + default: + gc->attr |= GRID_ATTR_CHARSET; + utf8_set(&gc->data, CELL_BORDERS[cell_type]); + break; + } +} + +/* Return if window has only two panes. */ +static int +screen_redraw_two_panes(struct window *w, int direction) +{ + struct window_pane *wp; + + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + if (wp == NULL) + return (0); /* one pane */ + if (TAILQ_NEXT(wp, entry) != NULL) + return (0); /* more than two panes */ + if (direction == 0 && wp->xoff == 0) return (0); + if (direction == 1 && wp->yoff == 0) + return (0); + return (1); +} + +/* Check if cell is on the border of a pane. */ +static enum screen_redraw_border_type +screen_redraw_pane_border(struct window_pane *wp, u_int px, u_int py, + int pane_status) +{ + u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; + + /* Inside pane. */ + if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey) + return (SCREEN_REDRAW_INSIDE); /* Left/right borders. */ - if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) { - if (wp->xoff != 0 && px == wp->xoff - 1) - return (1); - if (px == wp->xoff + wp->sx) - return (2); + if (pane_status == PANE_STATUS_OFF) { + if (screen_redraw_two_panes(wp->window, 0)) { + if (wp->xoff == 0 && px == wp->sx && py <= wp->sy / 2) + return (SCREEN_REDRAW_BORDER); + if (wp->xoff != 0 && + px == wp->xoff - 1 && + py > wp->sy / 2) + return (SCREEN_REDRAW_BORDER); + } else { + if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { + if (wp->xoff != 0 && px == wp->xoff - 1) + return (SCREEN_REDRAW_BORDER); + if (px == ex) + return (SCREEN_REDRAW_BORDER); + } + } + } else { + if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { + if (wp->xoff != 0 && px == wp->xoff - 1) + return (SCREEN_REDRAW_BORDER); + if (px == ex) + return (SCREEN_REDRAW_BORDER); + } } /* Top/bottom borders. */ - if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) { - if (wp->yoff != 0 && py == wp->yoff - 1) - return (3); - if (py == wp->yoff + wp->sy) - return (4); + if (pane_status == PANE_STATUS_OFF) { + if (screen_redraw_two_panes(wp->window, 1)) { + if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2) + return (SCREEN_REDRAW_BORDER); + if (wp->yoff != 0 && + py == wp->yoff - 1 && + px > wp->sx / 2) + return (SCREEN_REDRAW_BORDER); + } else { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (wp->yoff != 0 && py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER); + if (py == ey) + return (SCREEN_REDRAW_BORDER); + } + } + } else if (pane_status == PANE_STATUS_TOP) { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (wp->yoff != 0 && py == wp->yoff - 1) + return (SCREEN_REDRAW_BORDER); + } + } else { + if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= ex) { + if (py == ey) + return (SCREEN_REDRAW_BORDER); + } } /* Outside pane. */ - return (-1); + return (SCREEN_REDRAW_OUTSIDE); } -/* Check if a cell is on the pane border. */ +/* Check if a cell is on a border. */ static int -screen_redraw_cell_border(struct client *c, u_int px, u_int py) +screen_redraw_cell_border(struct client *c, u_int px, u_int py, int pane_status) { struct window *w = c->session->curw->window; struct window_pane *wp; - int retval; + + /* Outside the window? */ + if (px > w->sx || py > w->sy) + return (0); + + /* On the window border? */ + if (px == w->sx || py == w->sy) + return (1); /* Check all the panes. */ TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; - if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1) - return (!!retval); + switch (screen_redraw_pane_border(wp, px, py, pane_status)) { + case SCREEN_REDRAW_INSIDE: + return (0); + case SCREEN_REDRAW_BORDER: + return (1); + case SCREEN_REDRAW_OUTSIDE: + break; + } } return (0); } +/* Work out type of border cell from surrounding cells. */ +static int +screen_redraw_type_of_cell(struct client *c, u_int px, u_int py, + int pane_status) +{ + struct window *w = c->session->curw->window; + u_int sx = w->sx, sy = w->sy; + int borders = 0; + + /* Is this outside the window? */ + if (px > sx || py > sy) + return (CELL_OUTSIDE); + + /* + * Construct a bitmask of whether the cells to the left (bit 4), right, + * top, and bottom (bit 1) of this cell are borders. + */ + if (px == 0 || screen_redraw_cell_border(c, px - 1, py, pane_status)) + borders |= 8; + if (px <= sx && screen_redraw_cell_border(c, px + 1, py, pane_status)) + borders |= 4; + if (pane_status == PANE_STATUS_TOP) { + if (py != 0 && + screen_redraw_cell_border(c, px, py - 1, pane_status)) + borders |= 2; + if (screen_redraw_cell_border(c, px, py + 1, pane_status)) + borders |= 1; + } else if (pane_status == PANE_STATUS_BOTTOM) { + if (py == 0 || + screen_redraw_cell_border(c, px, py - 1, pane_status)) + borders |= 2; + if (py != sy - 1 && + screen_redraw_cell_border(c, px, py + 1, pane_status)) + borders |= 1; + } else { + if (py == 0 || + screen_redraw_cell_border(c, px, py - 1, pane_status)) + borders |= 2; + if (screen_redraw_cell_border(c, px, py + 1, pane_status)) + borders |= 1; + } + + /* + * Figure out what kind of border this cell is. Only one bit set + * doesn't make sense (can't have a border cell with no others + * connected). + */ + switch (borders) { + case 15: /* 1111, left right top bottom */ + return (CELL_JOIN); + case 14: /* 1110, left right top */ + return (CELL_BOTTOMJOIN); + case 13: /* 1101, left right bottom */ + return (CELL_TOPJOIN); + case 12: /* 1100, left right */ + return (CELL_LEFTRIGHT); + case 11: /* 1011, left top bottom */ + return (CELL_RIGHTJOIN); + case 10: /* 1010, left top */ + return (CELL_BOTTOMRIGHT); + case 9: /* 1001, left bottom */ + return (CELL_TOPRIGHT); + case 7: /* 0111, right top bottom */ + return (CELL_LEFTJOIN); + case 6: /* 0110, right top */ + return (CELL_BOTTOMLEFT); + case 5: /* 0101, right bottom */ + return (CELL_TOPLEFT); + case 3: /* 0011, top bottom */ + return (CELL_TOPBOTTOM); + } + return (CELL_OUTSIDE); +} + /* Check if cell inside a pane. */ static int screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, struct window_pane **wpp) { struct window *w = c->session->curw->window; - struct window_pane *wp; - int borders; + struct window_pane *wp, *active; + int border; u_int right, line; *wpp = NULL; if (px > w->sx || py > w->sy) return (CELL_OUTSIDE); + if (px == w->sx || py == w->sy) /* window border */ + return (screen_redraw_type_of_cell(c, px, py, pane_status)); if (pane_status != PANE_STATUS_OFF) { - TAILQ_FOREACH(wp, &w->panes, entry) { + active = wp = server_client_get_pane(c); + do { if (!window_pane_visible(wp)) - continue; + goto next1; if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; @@ -121,153 +352,76 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, if (py == line && px >= wp->xoff + 2 && px <= right) return (CELL_INSIDE); - } + + next1: + wp = TAILQ_NEXT(wp, entry); + if (wp == NULL) + wp = TAILQ_FIRST(&w->panes); + } while (wp != active); } - TAILQ_FOREACH(wp, &w->panes, entry) { + active = wp = server_client_get_pane(c); + do { if (!window_pane_visible(wp)) - continue; + goto next2; *wpp = wp; - /* If outside the pane and its border, skip it. */ - if ((wp->xoff != 0 && px < wp->xoff - 1) || - px > wp->xoff + wp->sx || - (wp->yoff != 0 && py < wp->yoff - 1) || - py > wp->yoff + wp->sy) - continue; - - /* If definitely inside, return so. */ - if (!screen_redraw_cell_border(c, px, py)) + /* + * If definitely inside, return. If not on border, skip. + * Otherwise work out the cell. + */ + border = screen_redraw_pane_border(wp, px, py, pane_status); + if (border == SCREEN_REDRAW_INSIDE) return (CELL_INSIDE); + if (border == SCREEN_REDRAW_OUTSIDE) + goto next2; + return (screen_redraw_type_of_cell(c, px, py, pane_status)); - /* - * Construct a bitmask of whether the cells to the left (bit - * 4), right, top, and bottom (bit 1) of this cell are borders. - */ - borders = 0; - if (px == 0 || screen_redraw_cell_border(c, px - 1, py)) - borders |= 8; - if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py)) - borders |= 4; - if (pane_status == PANE_STATUS_TOP) { - if (py != 0 && screen_redraw_cell_border(c, px, py - 1)) - borders |= 2; - } else { - if (py == 0 || screen_redraw_cell_border(c, px, py - 1)) - borders |= 2; - } - if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1)) - borders |= 1; - - /* - * Figure out what kind of border this cell is. Only one bit - * set doesn't make sense (can't have a border cell with no - * others connected). - */ - switch (borders) { - case 15: /* 1111, left right top bottom */ - return (CELL_JOIN); - case 14: /* 1110, left right top */ - return (CELL_BOTTOMJOIN); - case 13: /* 1101, left right bottom */ - return (CELL_TOPJOIN); - case 12: /* 1100, left right */ - return (CELL_TOPBOTTOM); - case 11: /* 1011, left top bottom */ - return (CELL_RIGHTJOIN); - case 10: /* 1010, left top */ - return (CELL_BOTTOMRIGHT); - case 9: /* 1001, left bottom */ - return (CELL_TOPRIGHT); - case 7: /* 0111, right top bottom */ - return (CELL_LEFTJOIN); - case 6: /* 0110, right top */ - return (CELL_BOTTOMLEFT); - case 5: /* 0101, right bottom */ - return (CELL_TOPLEFT); - case 3: /* 0011, top bottom */ - return (CELL_LEFTRIGHT); - } - } + next2: + wp = TAILQ_NEXT(wp, entry); + if (wp == NULL) + wp = TAILQ_FIRST(&w->panes); + } while (wp != active); return (CELL_OUTSIDE); } /* Check if the border of a particular pane. */ static int -screen_redraw_check_is(u_int px, u_int py, int type, int pane_status, - struct window *w, struct window_pane *wantwp, struct window_pane *wp) +screen_redraw_check_is(u_int px, u_int py, int pane_status, + struct window_pane *wp) { - int border; + enum screen_redraw_border_type border; - /* Is this off the active pane border? */ - border = screen_redraw_cell_border1(wantwp, px, py); - if (border == 0 || border == -1) - return (0); - if (pane_status == PANE_STATUS_TOP && border == 4) - return (0); - if (pane_status == PANE_STATUS_BOTTOM && border == 3) - return (0); - - /* If there are more than two panes, that's enough. */ - if (window_count_panes(w) != 2) + border = screen_redraw_pane_border(wp, px, py, pane_status); + if (border == SCREEN_REDRAW_BORDER) return (1); - - /* Else if the cell is not a border cell, forget it. */ - if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE)) - return (1); - - /* With status lines mark the entire line. */ - if (pane_status != PANE_STATUS_OFF) - return (1); - - /* Check if the pane covers the whole width. */ - if (wp->xoff == 0 && wp->sx == w->sx) { - /* This can either be the top pane or the bottom pane. */ - if (wp->yoff == 0) { /* top pane */ - if (wp == wantwp) - return (px <= wp->sx / 2); - return (px > wp->sx / 2); - } - return (0); - } - - /* Check if the pane covers the whole height. */ - if (wp->yoff == 0 && wp->sy == w->sy) { - /* This can either be the left pane or the right pane. */ - if (wp->xoff == 0) { /* left pane */ - if (wp == wantwp) - return (py <= wp->sy / 2); - return (py > wp->sy / 2); - } - return (0); - } - - return (1); + return (0); } /* Update pane status. */ static int -screen_redraw_make_pane_status(struct client *c, struct window *w, - struct window_pane *wp) +screen_redraw_make_pane_status(struct client *c, struct window_pane *wp, + struct screen_redraw_ctx *rctx, int pane_lines) { + struct window *w = wp->window; struct grid_cell gc; const char *fmt; struct format_tree *ft; char *expanded; - u_int width, i; + int pane_status = rctx->pane_status; + u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; - if (wp == w->active) - style_apply(&gc, w->options, "pane-active-border-style"); - else - style_apply(&gc, w->options, "pane-border-style"); - - fmt = options_get_string(w->options, "pane-border-format"); - ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); - format_defaults(ft, c, NULL, NULL, wp); + format_defaults(ft, c, c->session, c->session->curw, wp); + + if (wp == server_client_get_pane(c)) + style_apply(&gc, w->options, "pane-active-border-style", ft); + else + style_apply(&gc, w->options, "pane-border-style", ft); + fmt = options_get_string(w->options, "pane-border-format"); expanded = format_expand_time(ft, fmt); if (wp->sx < 4) @@ -279,11 +433,18 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, screen_init(&wp->status_screen, width, 1, 0); wp->status_screen.mode = 0; - screen_write_start(&ctx, NULL, &wp->status_screen); + screen_write_start(&ctx, &wp->status_screen); - gc.attr |= GRID_ATTR_CHARSET; - for (i = 0; i < width; i++) - screen_write_putc(&ctx, &gc, 'q'); + for (i = 0; i < width; i++) { + px = wp->xoff + 2 + i; + if (rctx->pane_status == PANE_STATUS_TOP) + py = wp->yoff - 1; + else + py = wp->yoff + wp->sy; + cell_type = screen_redraw_type_of_cell(c, px, py, pane_status); + screen_redraw_border_set(wp, pane_lines, cell_type, &gc); + screen_write_cell(&ctx, &gc); + } gc.attr &= ~GRID_ATTR_CHARSET; screen_write_cursormove(&ctx, 0, 0, 0); @@ -356,7 +517,8 @@ screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) if (ctx->statustop) yoff += ctx->statuslines; - tty_draw_line(tty, NULL, s, i, 0, width, x, yoff - ctx->oy); + tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy, + &grid_default_cell, NULL); } tty_cursor(tty, 0, 0); } @@ -368,7 +530,8 @@ screen_redraw_update(struct client *c, int flags) struct window *w = c->session->curw->window; struct window_pane *wp; struct options *wo = w->options; - int redraw; + int redraw, lines; + struct screen_redraw_ctx ctx; if (c->message_string != NULL) redraw = status_message_redraw(c); @@ -383,9 +546,11 @@ screen_redraw_update(struct client *c, int flags) flags |= CLIENT_REDRAWOVERLAY; if (options_get_number(wo, "pane-border-status") != PANE_STATUS_OFF) { + screen_redraw_set_context(c, &ctx); + lines = options_get_number(wo, "pane-border-lines"); redraw = 0; TAILQ_FOREACH(wp, &w->panes, entry) { - if (screen_redraw_make_pane_status(c, w, wp)) + if (screen_redraw_make_pane_status(c, wp, &ctx, lines)) redraw = 1; } if (redraw) @@ -415,6 +580,7 @@ screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) ctx->statuslines = lines; ctx->pane_status = options_get_number(wo, "pane-border-status"); + ctx->pane_lines = options_get_number(wo, "pane-border-lines"); tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); @@ -434,20 +600,33 @@ screen_redraw_screen(struct client *c) return; flags = screen_redraw_update(c, c->flags); + if ((flags & CLIENT_ALLREDRAWFLAGS) == 0) + return; + screen_redraw_set_context(c, &ctx); + tty_sync_start(&c->tty); + tty_update_mode(&c->tty, c->tty.mode, NULL); if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { + log_debug("%s: redrawing borders", c->name); if (ctx.pane_status != PANE_STATUS_OFF) screen_redraw_draw_pane_status(&ctx); screen_redraw_draw_borders(&ctx); } - if (flags & CLIENT_REDRAWWINDOW) + if (flags & CLIENT_REDRAWWINDOW) { + log_debug("%s: redrawing panes", c->name); screen_redraw_draw_panes(&ctx); + } if (ctx.statuslines != 0 && - (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) + (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) { + log_debug("%s: redrawing status", c->name); screen_redraw_draw_status(&ctx); - if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) - c->overlay_draw(c, &ctx); + } + if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) { + log_debug("%s: redrawing overlay", c->name); + c->overlay_draw(c, c->overlay_data, &ctx); + } + tty_reset(&c->tty); } @@ -457,51 +636,106 @@ screen_redraw_pane(struct client *c, struct window_pane *wp) { struct screen_redraw_ctx ctx; - if (c->overlay_draw != NULL || !window_pane_visible(wp)) + if (!window_pane_visible(wp)) return; screen_redraw_set_context(c, &ctx); + tty_sync_start(&c->tty); + tty_update_mode(&c->tty, c->tty.mode, NULL); screen_redraw_draw_pane(&ctx, wp); + tty_reset(&c->tty); } -/* Draw a border cell. */ -static void -screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j, - struct grid_cell *m_active_gc, struct grid_cell *active_gc, - struct grid_cell *m_other_gc, struct grid_cell *other_gc) +/* Get border cell style. */ +static const struct grid_cell * +screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x, + u_int y, struct window_pane *wp) { struct client *c = ctx->c; struct session *s = c->session; struct window *w = s->curw->window; - struct tty *tty = &c->tty; - struct window_pane *wp; - struct window_pane *active = w->active; - struct window_pane *marked = marked_pane.wp; - u_int type, x = ctx->ox + i, y = ctx->oy + j; - int flag, pane_status = ctx->pane_status; + struct window_pane *active = server_client_get_pane(c); + struct options *oo = w->options; + struct format_tree *ft; - type = screen_redraw_check_cell(c, x, y, pane_status, &wp); - if (type == CELL_INSIDE) - return; - flag = screen_redraw_check_is(x, y, type, pane_status, w, active, wp); + if (wp->border_gc_set) + return (&wp->border_gc); + wp->border_gc_set = 1; - if (server_is_marked(s, s->curw, marked_pane.wp) && - screen_redraw_check_is(x, y, type, pane_status, w, marked, wp)) { - if (flag) - tty_attributes(tty, m_active_gc, NULL); - else - tty_attributes(tty, m_other_gc, NULL); - } else if (flag) - tty_attributes(tty, active_gc, NULL); + ft = format_create_defaults(NULL, c, s, s->curw, wp); + if (screen_redraw_check_is(x, y, ctx->pane_status, active)) + style_apply(&wp->border_gc, oo, "pane-active-border-style", ft); else - tty_attributes(tty, other_gc, NULL); + style_apply(&wp->border_gc, oo, "pane-border-style", ft); + format_free(ft); + + return (&wp->border_gc); +} + +/* Draw a border cell. */ +static void +screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j) +{ + struct client *c = ctx->c; + struct session *s = c->session; + struct window *w = s->curw->window; + struct options *oo = w->options; + struct tty *tty = &c->tty; + struct format_tree *ft; + struct window_pane *wp; + u_int cell_type, x = ctx->ox + i, y = ctx->oy + j; + int pane_status = ctx->pane_status, isolates; + struct grid_cell gc; + const struct grid_cell *tmp; + + if (c->overlay_check != NULL && + !c->overlay_check(c, c->overlay_data, x, y)) + return; + + cell_type = screen_redraw_check_cell(c, x, y, pane_status, &wp); + if (cell_type == CELL_INSIDE) + return; + + if (wp == NULL) { + if (!ctx->no_pane_gc_set) { + ft = format_create_defaults(NULL, c, s, s->curw, NULL); + memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc); + style_add(&ctx->no_pane_gc, oo, "pane-border-style", + ft); + format_free(ft); + ctx->no_pane_gc_set = 1; + } + memcpy(&gc, &ctx->no_pane_gc, sizeof gc); + } else { + tmp = screen_redraw_draw_borders_style(ctx, x, y, wp); + if (tmp == NULL) + return; + memcpy(&gc, tmp, sizeof gc); + + if (server_is_marked(s, s->curw, marked_pane.wp) && + screen_redraw_check_is(x, y, pane_status, marked_pane.wp)) + gc.attr ^= GRID_ATTR_REVERSE; + } + screen_redraw_border_set(wp, ctx->pane_lines, cell_type, &gc); + + if (cell_type == CELL_TOPBOTTOM && + (c->flags & CLIENT_UTF8) && + tty_term_has(tty->term, TTYC_BIDI)) + isolates = 1; + else + isolates = 0; + if (ctx->statustop) tty_cursor(tty, i, ctx->statuslines + j); else tty_cursor(tty, i, j); - tty_putc(tty, CELL_BORDERS[type]); + if (isolates) + tty_puts(tty, END_ISOLATE); + tty_cell(tty, &gc, &grid_default_cell, NULL); + if (isolates) + tty_puts(tty, START_ISOLATE); } /* Draw the borders. */ @@ -511,27 +745,17 @@ screen_redraw_draw_borders(struct screen_redraw_ctx *ctx) struct client *c = ctx->c; struct session *s = c->session; struct window *w = s->curw->window; - struct tty *tty = &c->tty; - struct options *oo = w->options; - struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; + struct window_pane *wp; u_int i, j; log_debug("%s: %s @%u", __func__, c->name, w->id); - style_apply(&other_gc, oo, "pane-border-style"); - style_apply(&active_gc, oo, "pane-active-border-style"); - active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET; + TAILQ_FOREACH(wp, &w->panes, entry) + wp->border_gc_set = 0; - memcpy(&m_other_gc, &other_gc, sizeof m_other_gc); - m_other_gc.attr ^= GRID_ATTR_REVERSE; - memcpy(&m_active_gc, &active_gc, sizeof m_active_gc); - m_active_gc.attr ^= GRID_ATTR_REVERSE; - - for (j = 0; j < tty->sy - ctx->statuslines; j++) { - for (i = 0; i < tty->sx; i++) { - screen_redraw_draw_borders_cell(ctx, i, j, - &m_active_gc, &active_gc, &m_other_gc, &other_gc); - } + for (j = 0; j < c->tty.sy - ctx->statuslines; j++) { + for (i = 0; i < c->tty.sx; i++) + screen_redraw_draw_borders_cell(ctx, i, j); } } @@ -567,19 +791,23 @@ screen_redraw_draw_status(struct screen_redraw_ctx *ctx) y = 0; else y = c->tty.sy - ctx->statuslines; - for (i = 0; i < ctx->statuslines; i++) - tty_draw_line(tty, NULL, s, 0, i, UINT_MAX, 0, y + i); + for (i = 0; i < ctx->statuslines; i++) { + tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i, + &grid_default_cell, NULL); + } } /* Draw one pane. */ static void screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct client *c = ctx->c; - struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; - struct screen *s = wp->screen; - u_int i, j, top, x, y, width; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s = wp->screen; + struct colour_palette *palette = &wp->palette; + struct grid_cell defaults; + u_int i, j, top, x, y, width; log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); @@ -589,7 +817,6 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) top = ctx->statuslines; else top = 0; - for (j = 0; j < wp->sy; j++) { if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; @@ -621,8 +848,8 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", __func__, c->name, wp->id, i, j, x, y, width); - tty_draw_line(tty, wp, s, i, j, width, x, y); + tty_default_colours(&defaults, wp); + tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette); } - tty_draw_images(tty, wp, s); } diff --git a/screen-write.c b/screen-write.c index cffe71ba..07e66ca5 100644 --- a/screen-write.c +++ b/screen-write.c @@ -23,36 +23,57 @@ #include "tmux.h" -static void screen_write_initctx(struct screen_write_ctx *, - struct tty_ctx *); +static struct screen_write_citem *screen_write_collect_trim( + struct screen_write_ctx *, u_int, u_int, u_int, int *); static void screen_write_collect_clear(struct screen_write_ctx *, u_int, u_int); -static void screen_write_collect_scroll(struct screen_write_ctx *); -static void screen_write_collect_flush(struct screen_write_ctx *, int); +static void screen_write_collect_scroll(struct screen_write_ctx *, u_int); +static void screen_write_collect_flush(struct screen_write_ctx *, int, + const char *); static int screen_write_overwrite(struct screen_write_ctx *, struct grid_cell *, u_int); static const struct grid_cell *screen_write_combine(struct screen_write_ctx *, const struct utf8_data *, u_int *); -static const struct grid_cell screen_write_pad_cell = { - { { 0 }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 0, 8, 8 +struct screen_write_citem { + u_int x; + int wrapped; + + enum { TEXT, CLEAR } type; + u_int used; + u_int bg; + + struct grid_cell gc; + + TAILQ_ENTRY(screen_write_citem) entry; }; - -struct screen_write_collect_item { - u_int x; - int wrapped; - - u_int used; - char data[256]; - - struct grid_cell gc; - - TAILQ_ENTRY(screen_write_collect_item) entry; -}; -struct screen_write_collect_line { - TAILQ_HEAD(, screen_write_collect_item) items; +struct screen_write_cline { + char *data; + TAILQ_HEAD(, screen_write_citem) items; }; +TAILQ_HEAD(, screen_write_citem) screen_write_citem_freelist = + TAILQ_HEAD_INITIALIZER(screen_write_citem_freelist); + +static struct screen_write_citem * +screen_write_get_citem(void) +{ + struct screen_write_citem *ci; + + ci = TAILQ_FIRST(&screen_write_citem_freelist); + if (ci != NULL) { + TAILQ_REMOVE(&screen_write_citem_freelist, ci, entry); + memset(ci, 0, sizeof *ci); + return (ci); + } + return (xcalloc(1, sizeof *ci)); +} + +static void +screen_write_free_citem(struct screen_write_citem *ci) +{ + TAILQ_INSERT_TAIL(&screen_write_citem_freelist, ci, entry); +} static void screen_write_offset_timer(__unused int fd, __unused short events, void *data) @@ -95,39 +116,186 @@ screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy) evtimer_add(&w->offset_timer, &tv); } -/* Initialize writing with a window. */ +/* Do a full redraw. */ +static void +screen_write_redraw_cb(const struct tty_ctx *ttyctx) +{ + struct window_pane *wp = ttyctx->arg; + + if (wp != NULL) + wp->flags |= PANE_REDRAW; +} + +/* Update context for client. */ +static int +screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) +{ + struct window_pane *wp = ttyctx->arg; + + if (c->session->curw->window != wp->window) + return (0); + if (wp->layout_cell == NULL) + return (0); + + if (wp->flags & (PANE_REDRAW|PANE_DROP)) + return (-1); + if (c->flags & CLIENT_REDRAWPANES) { + /* + * Redraw is already deferred to redraw another pane - redraw + * this one also when that happens. + */ + log_debug("%s: adding %%%u to deferred redraw", __func__, + wp->id); + wp->flags |= PANE_REDRAW; + return (-1); + } + + ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, + &ttyctx->wsx, &ttyctx->wsy); + + ttyctx->xoff = ttyctx->rxoff = wp->xoff; + ttyctx->yoff = ttyctx->ryoff = wp->yoff; + + if (status_at_line(c) == 0) + ttyctx->yoff += status_line_size(c); + + return (1); +} + +/* Set up context for TTY command. */ +static void +screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx, + int sync) +{ + struct screen *s = ctx->s; + + memset(ttyctx, 0, sizeof *ttyctx); + + ttyctx->s = s; + ttyctx->sx = screen_size_x(s); + ttyctx->sy = screen_size_y(s); + + ttyctx->ocx = s->cx; + ttyctx->ocy = s->cy; + ttyctx->orlower = s->rlower; + ttyctx->orupper = s->rupper; + + memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults); + if (ctx->init_ctx_cb != NULL) { + ctx->init_ctx_cb(ctx, ttyctx); + if (ttyctx->palette != NULL) { + ttyctx->defaults.fg = ttyctx->palette->fg; + ttyctx->defaults.bg = ttyctx->palette->bg; + } + } else { + ttyctx->redraw_cb = screen_write_redraw_cb; + if (ctx->wp != NULL) { + tty_default_colours(&ttyctx->defaults, ctx->wp); + ttyctx->palette = &ctx->wp->palette; + ttyctx->set_client_cb = screen_write_set_client_cb; + ttyctx->arg = ctx->wp; + } + } + + if (~ctx->flags & SCREEN_WRITE_SYNC) { + /* + * For the active pane or for an overlay (no pane), we want to + * only use synchronized updates if requested (commands that + * move the cursor); for other panes, always use it, since the + * cursor will have to move. + */ + if (ctx->wp != NULL) { + if (ctx->wp != ctx->wp->window->active) + ttyctx->num = 1; + else + ttyctx->num = sync; + } else + ttyctx->num = 0x10|sync; + tty_write(tty_cmd_syncstart, ttyctx); + ctx->flags |= SCREEN_WRITE_SYNC; + } +} + +/* Make write list. */ void -screen_write_start(struct screen_write_ctx *ctx, struct window_pane *wp, - struct screen *s) +screen_write_make_list(struct screen *s) { u_int y; + s->write_list = xcalloc(screen_size_y(s), sizeof *s->write_list); + for (y = 0; y < screen_size_y(s); y++) + TAILQ_INIT(&s->write_list[y].items); +} + +/* Free write list. */ +void +screen_write_free_list(struct screen *s) +{ + u_int y; + + for (y = 0; y < screen_size_y(s); y++) + free(s->write_list[y].data); + free(s->write_list); +} + +/* Set up for writing. */ +static void +screen_write_init(struct screen_write_ctx *ctx, struct screen *s) +{ memset(ctx, 0, sizeof *ctx); - ctx->wp = wp; - if (wp != NULL && s == NULL) - ctx->s = wp->screen; - else - ctx->s = s; + ctx->s = s; - ctx->list = xcalloc(screen_size_y(ctx->s), sizeof *ctx->list); - for (y = 0; y < screen_size_y(ctx->s); y++) - TAILQ_INIT(&ctx->list[y].items); - ctx->item = xcalloc(1, sizeof *ctx->item); + if (ctx->s->write_list == NULL) + screen_write_make_list(ctx->s); + ctx->item = screen_write_get_citem(); ctx->scrolled = 0; ctx->bg = 8; +} + +/* Initialize writing with a pane. */ +void +screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp, + struct screen *s) +{ + if (s == NULL) + s = wp->screen; + screen_write_init(ctx, s); + ctx->wp = wp; if (log_get_level() != 0) { - if (wp != NULL) { - log_debug("%s: size %ux%u, pane %%%u (at %u,%u)", - __func__, screen_size_x(ctx->s), - screen_size_y(ctx->s), wp->id, wp->xoff, wp->yoff); - } else { - log_debug("%s: size %ux%u, no pane", - __func__, screen_size_x(ctx->s), - screen_size_y(ctx->s)); - } + log_debug("%s: size %ux%u, pane %%%u (at %u,%u)", + __func__, screen_size_x(ctx->s), screen_size_y(ctx->s), + wp->id, wp->xoff, wp->yoff); + } +} + +/* Initialize writing with a callback. */ +void +screen_write_start_callback(struct screen_write_ctx *ctx, struct screen *s, + screen_write_init_ctx_cb cb, void *arg) +{ + screen_write_init(ctx, s); + + ctx->init_ctx_cb = cb; + ctx->arg = arg; + + if (log_get_level() != 0) { + log_debug("%s: size %ux%u, with callback", __func__, + screen_size_x(ctx->s), screen_size_y(ctx->s)); + } +} + +/* Initialize writing. */ +void +screen_write_start(struct screen_write_ctx *ctx, struct screen *s) +{ + screen_write_init(ctx, s); + + if (log_get_level() != 0) { + log_debug("%s: size %ux%u, no pane", __func__, + screen_size_x(ctx->s), screen_size_y(ctx->s)); } } @@ -136,13 +304,9 @@ void screen_write_stop(struct screen_write_ctx *ctx) { screen_write_collect_end(ctx); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); - log_debug("%s: %u cells (%u written, %u skipped)", __func__, - ctx->cells, ctx->written, ctx->skipped); - - free(ctx->item); - free(ctx->list); /* flush will have emptied */ + screen_write_free_citem(ctx->item); } /* Reset screen state. */ @@ -213,7 +377,97 @@ screen_write_strlen(const char *fmt, ...) return (size); } -/* Write simple string (no UTF-8 or maximum length). */ +/* Write string wrapped over lines. */ +int +screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width, + u_int lines, int more, const struct grid_cell *gcp, const char *fmt, ...) +{ + struct screen *s = ctx->s; + va_list ap; + char *tmp; + u_int cy = s->cy, i, end, next, idx = 0, at, left; + struct utf8_data *text; + struct grid_cell gc; + + memcpy(&gc, gcp, sizeof gc); + + va_start(ap, fmt); + xvasprintf(&tmp, fmt, ap); + va_end(ap); + + text = utf8_fromcstr(tmp); + free(tmp); + + left = (cx + width) - s->cx; + for (;;) { + /* Find the end of what can fit on the line. */ + at = 0; + for (end = idx; text[end].size != 0; end++) { + if (text[end].size == 1 && text[end].data[0] == '\n') + break; + if (at + text[end].width > left) + break; + at += text[end].width; + } + + /* + * If we're on a space, that's the end. If not, walk back to + * try and find one. + */ + if (text[end].size == 0) + next = end; + else if (text[end].size == 1 && text[end].data[0] == '\n') + next = end + 1; + else if (text[end].size == 1 && text[end].data[0] == ' ') + next = end + 1; + else { + for (i = end; i > idx; i--) { + if (text[i].size == 1 && text[i].data[0] == ' ') + break; + } + if (i != idx) { + next = i + 1; + end = i; + } else + next = end; + } + + /* Print the line. */ + for (i = idx; i < end; i++) { + utf8_copy(&gc.data, &text[i]); + screen_write_cell(ctx, &gc); + } + + /* If at the bottom, stop. */ + idx = next; + if (s->cy == cy + lines - 1 || text[idx].size == 0) + break; + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + left = width; + } + + /* + * Fail if on the last line and there is more to come or at the end, or + * if the text was not entirely consumed. + */ + if ((s->cy == cy + lines - 1 && (!more || s->cx == cx + width)) || + text[idx].size != 0) { + free(text); + return (0); + } + free(text); + + /* + * If no more to come, move to the next line. Otherwise, leave on + * the same line (except if at the end). + */ + if (!more || s->cx == cx + width) + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + return (1); +} + +/* Write simple string (no maximum length). */ void screen_write_puts(struct screen_write_ctx *ctx, const struct grid_cell *gcp, const char *fmt, ...) @@ -280,7 +534,10 @@ screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen, if (*ptr == '\001') gc.attr ^= GRID_ATTR_CHARSET; - else if (*ptr > 0x1f && *ptr < 0x7f) { + else if (*ptr == '\n') { + screen_write_linefeed(ctx, 0, 8); + screen_write_carriagereturn(ctx); + } else if (*ptr > 0x1f && *ptr < 0x7f) { size++; screen_write_putc(ctx, &gc, *ptr); } @@ -291,44 +548,9 @@ screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen, free(msg); } -/* Copy from another screen. Assumes target region is big enough. */ -void -screen_write_copy(struct screen_write_ctx *ctx, struct screen *src, u_int px, - u_int py, u_int nx, u_int ny, bitstr_t *mbs, const struct grid_cell *mgc) -{ - struct screen *s = ctx->s; - struct grid *gd = src->grid; - struct grid_cell gc; - u_int xx, yy, cx, cy, b; - - if (nx == 0 || ny == 0) - return; - - cx = s->cx; - cy = s->cy; - - for (yy = py; yy < py + ny; yy++) { - for (xx = px; xx < px + nx; xx++) { - grid_get_cell(gd, xx, yy, &gc); - if (mbs != NULL) { - b = (yy * screen_size_x(src)) + xx; - if (bit_test(mbs, b)) { - gc.attr = mgc->attr; - gc.fg = mgc->fg; - gc.bg = mgc->bg; - } - } - if (xx + gc.data.width <= px + nx) - screen_write_cell(ctx, &gc); - } - cy++; - screen_write_set_cursor(ctx, cx, cy); - } -} - /* - * Copy from another screen but without the selection stuff. Also assumes the - * target region is already big enough. + * Copy from another screen but without the selection stuff. Assumes the target + * region is already big enough. */ void screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, @@ -382,7 +604,7 @@ screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right) screen_write_set_cursor(ctx, cx, cy); } -/* Draw a horizontal line on screen. */ +/* Draw a vertical line on screen. */ void screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) { @@ -409,21 +631,23 @@ screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) /* Draw a menu on screen. */ void -screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice) +screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, + int choice, const struct grid_cell *choice_gc) { struct screen *s = ctx->s; - struct grid_cell gc; + struct grid_cell default_gc; + const struct grid_cell *gc = &default_gc; u_int cx, cy, i, j; const char *name; cx = s->cx; cy = s->cy; - memcpy(&gc, &grid_default_cell, sizeof gc); + memcpy(&default_gc, &grid_default_cell, sizeof default_gc); screen_write_box(ctx, menu->width + 4, menu->count + 2); screen_write_cursormove(ctx, cx + 2, cy, 0); - format_draw(ctx, &gc, menu->width, menu->title, NULL); + format_draw(ctx, &default_gc, menu->width, menu->title, NULL); for (i = 0; i < menu->count; i++) { name = menu->items[i].name; @@ -432,20 +656,19 @@ screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice) screen_write_hline(ctx, menu->width + 4, 1, 1); } else { if (choice >= 0 && i == (u_int)choice && *name != '-') - gc.attr |= GRID_ATTR_REVERSE; + gc = choice_gc; screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); for (j = 0; j < menu->width; j++) - screen_write_putc(ctx, &gc, ' '); + screen_write_putc(ctx, gc, ' '); screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); if (*name == '-') { name++; - gc.attr |= GRID_ATTR_DIM; - format_draw(ctx, &gc, menu->width, name, NULL); - gc.attr &= ~GRID_ATTR_DIM; + default_gc.attr |= GRID_ATTR_DIM; + format_draw(ctx, gc, menu->width, name, NULL); + default_gc.attr &= ~GRID_ATTR_DIM; } else - format_draw(ctx, &gc, menu->width, name, NULL); - if (choice >= 0 && i == (u_int)choice) - gc.attr &= ~GRID_ATTR_REVERSE; + format_draw(ctx, gc, menu->width, name, NULL); + gc = &default_gc; } } @@ -465,6 +688,7 @@ screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny) memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= GRID_ATTR_CHARSET; + gc.flags |= GRID_FLAG_NOPALETTE; screen_write_putc(ctx, &gc, 'l'); for (i = 1; i < nx - 1; i++) @@ -547,23 +771,6 @@ screen_write_preview(struct screen_write_ctx *ctx, struct screen *src, u_int nx, } } -/* Set up context for TTY command. */ -static void -screen_write_initctx(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) -{ - struct screen *s = ctx->s; - - memset(ttyctx, 0, sizeof *ttyctx); - - ttyctx->wp = ctx->wp; - - ttyctx->ocx = s->cx; - ttyctx->ocy = s->cy; - - ttyctx->orlower = s->rlower; - ttyctx->orupper = s->rupper; -} - /* Set a mode. */ void screen_write_mode_set(struct screen_write_ctx *ctx, int mode) @@ -571,6 +778,9 @@ screen_write_mode_set(struct screen_write_ctx *ctx, int mode) struct screen *s = ctx->s; s->mode |= mode; + + if (log_get_level() != 0) + log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } /* Clear a mode. */ @@ -580,6 +790,9 @@ screen_write_mode_clear(struct screen_write_ctx *ctx, int mode) struct screen *s = ctx->s; s->mode &= ~mode; + + if (log_get_level() != 0) + log_debug("%s: %s", __func__, screen_mode_to_string(mode)); } /* Cursor up by ny. */ @@ -725,7 +938,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); tty_write(tty_cmd_alignmenttest, &ttyctx); @@ -752,12 +965,12 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; grid_view_insert_cells(s->grid, s->cx, s->cy, nx, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = nx; tty_write(tty_cmd_insertcharacter, &ttyctx); } @@ -783,12 +996,12 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; grid_view_delete_cells(s->grid, s->cx, s->cy, nx, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = nx; tty_write(tty_cmd_deletecharacter, &ttyctx); } @@ -814,12 +1027,12 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; grid_view_clear(s->grid, s->cx, s->cy, nx, 1, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = nx; tty_write(tty_cmd_clearcharacter, &ttyctx); } @@ -845,12 +1058,12 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; grid_view_insert_lines(gd, s->cy, ny, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = ny; tty_write(tty_cmd_insertline, &ttyctx); return; @@ -861,7 +1074,7 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; if (s->cy < s->rupper || s->cy > s->rlower) @@ -869,7 +1082,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) else grid_view_insert_lines_region(gd, s->rlower, s->cy, ny, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); + ttyctx.num = ny; tty_write(tty_cmd_insertline, &ttyctx); } @@ -895,12 +1109,12 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; grid_view_delete_lines(gd, s->cy, ny, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = ny; tty_write(tty_cmd_deleteline, &ttyctx); return; @@ -911,7 +1125,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) return; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; if (s->cy < s->rupper || s->cy > s->rlower) @@ -919,7 +1133,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) else grid_view_delete_lines_region(gd, s->rlower, s->cy, ny, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = ny; tty_write(tty_cmd_deleteline, &ttyctx); } @@ -928,10 +1142,10 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) void screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct grid_line *gl; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s); + struct screen *s = ctx->s; + struct grid_line *gl; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item; gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) @@ -940,24 +1154,30 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); - ttyctx.bg = bg; - grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); screen_write_collect_clear(ctx, s->cy, 1); - screen_write_collect_flush(ctx, 0); - tty_write(tty_cmd_clearline, &ttyctx); + ci->x = 0; + ci->used = sx; + ci->type = CLEAR; + ci->bg = bg; + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + ctx->item = screen_write_get_citem(); } /* Clear to end of line from cursor. */ void screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct grid_line *gl; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s); + struct screen *s = ctx->s; + struct grid_line *gl; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item, *before; + + if (s->cx == 0) { + screen_write_clearline(ctx, bg); + return; + } gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) @@ -966,27 +1186,32 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); - ttyctx.bg = bg; - grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); - if (s->cx == 0) - screen_write_collect_clear(ctx, s->cy, 1); - screen_write_collect_flush(ctx, 0); - tty_write(tty_cmd_clearendofline, &ttyctx); + before = screen_write_collect_trim(ctx, s->cy, s->cx, sx - s->cx, NULL); + ci->x = s->cx; + ci->used = sx - s->cx; + ci->type = CLEAR; + ci->bg = bg; + if (before == NULL) + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); } /* Clear to start of line from cursor. */ void screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct tty_ctx ttyctx; - u_int sx = screen_size_x(s); + struct screen *s = ctx->s; + u_int sx = screen_size_x(s); + struct screen_write_citem *ci = ctx->item, *before; - screen_write_initctx(ctx, &ttyctx); - ttyctx.bg = bg; + if (s->cx >= sx - 1) { + screen_write_clearline(ctx, bg); + return; + } if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; @@ -996,10 +1221,16 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) else grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg); - if (s->cx > sx - 1) - screen_write_collect_clear(ctx, s->cy, 1); - screen_write_collect_flush(ctx, 0); - tty_write(tty_cmd_clearstartofline, &ttyctx); + before = screen_write_collect_trim(ctx, s->cy, 0, s->cx + 1, NULL); + ci->x = 0; + ci->used = s->cx + 1; + ci->type = CLEAR; + ci->bg = bg; + if (before == NULL) + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->cy].items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); } /* Move cursor to px,py. */ @@ -1021,6 +1252,7 @@ screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, if (py != -1 && (u_int)py > screen_size_y(s) - 1) py = screen_size_y(s) - 1; + log_debug("%s: from %u,%u to %u,%u", __func__, s->cx, s->cy, px, py); screen_write_set_cursor(ctx, px, py); } @@ -1034,16 +1266,17 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); - ttyctx.bg = bg; - - if (s->cy == s->rupper) + if (s->cy == s->rupper) { grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); - else if (s->cy > 0) + screen_write_collect_flush(ctx, 0, __func__); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = bg; + + tty_write(tty_cmd_reverseindex, &ttyctx); + } else if (s->cy > 0) screen_write_set_cursor(ctx, -1, s->cy - 1); - screen_write_collect_flush(ctx, 0); - tty_write(tty_cmd_reverseindex, &ttyctx); } /* Set scroll region. */ @@ -1060,7 +1293,7 @@ screen_write_scrollregion(struct screen_write_ctx *ctx, u_int rupper, if (rupper >= rlower) /* cannot be one line */ return; - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); /* Cursor moves to top-left. */ screen_write_set_cursor(ctx, 0, 0); @@ -1082,26 +1315,24 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) gl = grid_get_line(gd, gd->hsize + s->cy); if (wrapped) gl->flags |= GRID_LINE_WRAPPED; - else - gl->flags &= ~GRID_LINE_WRAPPED; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, rupper, rlower); if (bg != ctx->bg) { - screen_write_collect_flush(ctx, 1); + screen_write_collect_flush(ctx, 1, __func__); ctx->bg = bg; } - if (s->cy == rlower) { + if (s->cy == s->rlower) { if (rlower == screen_size_y(s) - 1) redraw = image_scroll_up(s, 1); else redraw = image_check_line(s, rupper, rlower - rupper); if (redraw && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - grid_view_scroll_region_up(gd, rupper, rlower, bg); - screen_write_collect_scroll(ctx); + grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); + screen_write_collect_scroll(ctx, bg); ctx->scrolled++; } else if (s->cy < screen_size_y(s) - 1) screen_write_set_cursor(ctx, -1, s->cy + 1); @@ -1121,7 +1352,7 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) lines = s->rlower - s->rupper + 1; if (bg != ctx->bg) { - screen_write_collect_flush(ctx, 1); + screen_write_collect_flush(ctx, 1, __func__); ctx->bg = bg; } @@ -1130,7 +1361,7 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) for (i = 0; i < lines; i++) { grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); - screen_write_collect_scroll(ctx); + screen_write_collect_scroll(ctx, bg); } ctx->scrolled += lines; } @@ -1144,7 +1375,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) struct tty_ctx ttyctx; u_int i; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; if (lines == 0) @@ -1158,7 +1389,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) for (i = 0; i < lines; i++) grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = lines; tty_write(tty_cmd_scrolldown, &ttyctx); } @@ -1182,7 +1413,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled and clearing entire screen. */ @@ -1195,7 +1426,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) } screen_write_collect_clear(ctx, s->cy + 1, sy - (s->cy + 1)); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); tty_write(tty_cmd_clearendofscreen, &ttyctx); } @@ -1210,7 +1441,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; if (s->cy > 0) @@ -1221,7 +1452,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) grid_view_clear(s->grid, 0, s->cy, s->cx + 1, 1, bg); screen_write_collect_clear(ctx, 0, s->cy); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); tty_write(tty_cmd_clearstartofscreen, &ttyctx); } @@ -1236,7 +1467,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; /* Scroll into history if it is enabled. */ @@ -1256,55 +1487,157 @@ screen_write_clearhistory(struct screen_write_ctx *ctx) grid_clear_history(ctx->s->grid); } -/* Clear a collected line. */ +/* Force a full redraw. */ +void +screen_write_fullredraw(struct screen_write_ctx *ctx) +{ + struct tty_ctx ttyctx; + + screen_write_collect_flush(ctx, 0, __func__); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.redraw_cb(&ttyctx); +} + +/* Trim collected items. */ +static struct screen_write_citem * +screen_write_collect_trim(struct screen_write_ctx *ctx, u_int y, u_int x, + u_int used, int *wrapped) +{ + struct screen_write_cline *cl = &ctx->s->write_list[y]; + struct screen_write_citem *ci, *ci2, *tmp, *before = NULL; + u_int sx = x, ex = x + used - 1; + u_int csx, cex; + + if (TAILQ_EMPTY(&cl->items)) + return (NULL); + TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + csx = ci->x; + cex = ci->x + ci->used - 1; + + /* Item is entirely before. */ + if (cex < sx) { + log_debug("%s: %p %u-%u before %u-%u", __func__, ci, + csx, cex, sx, ex); + continue; + } + + /* Item is entirely after. */ + if (csx > ex) { + log_debug("%s: %p %u-%u after %u-%u", __func__, ci, + csx, cex, sx, ex); + before = ci; + break; + } + + /* Item is entirely inside. */ + if (csx >= sx && cex <= ex) { + log_debug("%s: %p %u-%u inside %u-%u", __func__, ci, + csx, cex, sx, ex); + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + if (csx == 0 && ci->wrapped && wrapped != NULL) + *wrapped = 1; + continue; + } + + /* Item under the start. */ + if (csx < sx && cex >= sx && cex <= ex) { + log_debug("%s: %p %u-%u start %u-%u", __func__, ci, + csx, cex, sx, ex); + ci->used = sx - csx; + log_debug("%s: %p now %u-%u", __func__, ci, ci->x, + ci->x + ci->used + 1); + continue; + } + + /* Item covers the end. */ + if (cex > ex && csx >= sx && csx <= ex) { + log_debug("%s: %p %u-%u end %u-%u", __func__, ci, + csx, cex, sx, ex); + ci->x = ex + 1; + ci->used = cex - ex; + log_debug("%s: %p now %u-%u", __func__, ci, ci->x, + ci->x + ci->used + 1); + before = ci; + break; + } + + /* Item must cover both sides. */ + log_debug("%s: %p %u-%u under %u-%u", __func__, ci, + csx, cex, sx, ex); + ci2 = screen_write_get_citem(); + ci2->type = ci->type; + ci2->bg = ci->bg; + memcpy(&ci2->gc, &ci->gc, sizeof ci2->gc); + TAILQ_INSERT_AFTER(&cl->items, ci, ci2, entry); + + ci->used = sx - csx; + ci2->x = ex + 1; + ci2->used = cex - ex; + + log_debug("%s: %p now %u-%u (%p) and %u-%u (%p)", __func__, ci, + ci->x, ci->x + ci->used - 1, ci, ci2->x, + ci2->x + ci2->used - 1, ci2); + before = ci2; + break; + } + return (before); +} + +/* Clear collected lines. */ static void screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n) { - struct screen_write_collect_item *ci, *tmp; - u_int i; - size_t size; + struct screen_write_cline *cl; + u_int i; for (i = y; i < y + n; i++) { - if (TAILQ_EMPTY(&ctx->list[i].items)) - continue; - size = 0; - TAILQ_FOREACH_SAFE(ci, &ctx->list[i].items, entry, tmp) { - size += ci->used; - TAILQ_REMOVE(&ctx->list[i].items, ci, entry); - free(ci); - } - ctx->skipped += size; - log_debug("%s: dropped %zu bytes (line %u)", __func__, size, i); + cl = &ctx->s->write_list[i]; + TAILQ_CONCAT(&screen_write_citem_freelist, &cl->items, entry); } } /* Scroll collected lines up. */ static void -screen_write_collect_scroll(struct screen_write_ctx *ctx) +screen_write_collect_scroll(struct screen_write_ctx *ctx, u_int bg) { - struct screen *s = ctx->s; - struct screen_write_collect_line *cl; - u_int y; + struct screen *s = ctx->s; + struct screen_write_cline *cl; + u_int y; + char *saved; + struct screen_write_citem *ci; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, s->rupper, s->rlower); screen_write_collect_clear(ctx, s->rupper, 1); + saved = ctx->s->write_list[s->rupper].data; for (y = s->rupper; y < s->rlower; y++) { - cl = &ctx->list[y + 1]; - TAILQ_CONCAT(&ctx->list[y].items, &cl->items, entry); + cl = &ctx->s->write_list[y + 1]; + TAILQ_CONCAT(&ctx->s->write_list[y].items, &cl->items, entry); + ctx->s->write_list[y].data = cl->data; } + ctx->s->write_list[s->rlower].data = saved; + + ci = screen_write_get_citem(); + ci->x = 0; + ci->used = screen_size_x(s); + ci->type = CLEAR; + ci->bg = bg; + TAILQ_INSERT_TAIL(&ctx->s->write_list[s->rlower].items, ci, entry); } /* Flush collected lines. */ static void -screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only) +screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only, + const char *from) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci, *tmp; - u_int y, cx, cy, items = 0; - struct tty_ctx ttyctx; - size_t written = 0; + struct screen *s = ctx->s; + struct screen_write_citem *ci, *tmp; + struct screen_write_cline *cl; + u_int y, cx, cy, last, items = 0; + struct tty_ctx ttyctx; if (ctx->scrolled != 0) { log_debug("%s: scrolled %u (region %u-%u)", __func__, @@ -1312,7 +1645,7 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only) if (ctx->scrolled > s->rlower - s->rupper + 1) ctx->scrolled = s->rlower - s->rupper + 1; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 1); ttyctx.num = ctx->scrolled; ttyctx.bg = ctx->bg; tty_write(tty_cmd_scrollup, &ttyctx); @@ -1325,47 +1658,65 @@ screen_write_collect_flush(struct screen_write_ctx *ctx, int scroll_only) cx = s->cx; cy = s->cy; for (y = 0; y < screen_size_y(s); y++) { - TAILQ_FOREACH_SAFE(ci, &ctx->list[y].items, entry, tmp) { + cl = &ctx->s->write_list[y]; + last = UINT_MAX; + TAILQ_FOREACH_SAFE(ci, &cl->items, entry, tmp) { + if (last != UINT_MAX && ci->x <= last) { + fatalx("collect list not in order: %u <= %u", + ci->x, last); + } screen_write_set_cursor(ctx, ci->x, y); - screen_write_initctx(ctx, &ttyctx); - ttyctx.cell = &ci->gc; - ttyctx.wrapped = ci->wrapped; - ttyctx.ptr = ci->data; - ttyctx.num = ci->used; - tty_write(tty_cmd_cells, &ttyctx); - + if (ci->type == CLEAR) { + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.bg = ci->bg; + ttyctx.num = ci->used; + tty_write(tty_cmd_clearcharacter, &ttyctx); + } else { + screen_write_initctx(ctx, &ttyctx, 0); + ttyctx.cell = &ci->gc; + ttyctx.wrapped = ci->wrapped; + ttyctx.ptr = cl->data + ci->x; + ttyctx.num = ci->used; + tty_write(tty_cmd_cells, &ttyctx); + } items++; - written += ci->used; - TAILQ_REMOVE(&ctx->list[y].items, ci, entry); - free(ci); + TAILQ_REMOVE(&cl->items, ci, entry); + screen_write_free_citem(ci); + last = ci->x; } } s->cx = cx; s->cy = cy; - log_debug("%s: flushed %u items (%zu bytes)", __func__, items, written); - ctx->written += written; + log_debug("%s: flushed %u items (%s)", __func__, items, from); } /* Finish and store collected cells. */ void screen_write_collect_end(struct screen_write_ctx *ctx) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci = ctx->item; - struct grid_cell gc; - u_int xx; + struct screen *s = ctx->s; + struct screen_write_citem *ci = ctx->item, *before; + struct screen_write_cline *cl = &s->write_list[s->cy]; + struct grid_cell gc; + u_int xx; + int wrapped = ci->wrapped; if (ci->used == 0) return; - ci->data[ci->used] = '\0'; + before = screen_write_collect_trim(ctx, s->cy, s->cx, ci->used, + &wrapped); ci->x = s->cx; - TAILQ_INSERT_TAIL(&ctx->list[s->cy].items, ci, entry); - ctx->item = xcalloc(1, sizeof *ctx->item); + ci->wrapped = wrapped; + if (before == NULL) + TAILQ_INSERT_TAIL(&cl->items, ci, entry); + else + TAILQ_INSERT_BEFORE(before, ci, entry); + ctx->item = screen_write_get_citem(); - log_debug("%s: %u %s (at %u,%u)", __func__, ci->used, ci->data, s->cx, - s->cy); + log_debug("%s: %u %.*s (at %u,%u)", __func__, ci->used, + (int)ci->used, cl->data + ci->x, s->cx, s->cy); if (s->cx != 0) { for (xx = s->cx; xx > 0; xx--) { @@ -1384,7 +1735,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx) if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; - grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, ci->data, ci->used); + grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, + ci->used); screen_write_set_cursor(ctx, s->cx + ci->used, -1); for (xx = s->cx; xx < screen_size_x(s); xx++) { @@ -1400,10 +1752,10 @@ void screen_write_collect_add(struct screen_write_ctx *ctx, const struct grid_cell *gc) { - struct screen *s = ctx->s; - struct screen_write_collect_item *ci; - u_int sx = screen_size_x(s); - int collect; + struct screen *s = ctx->s; + struct screen_write_citem *ci; + u_int sx = screen_size_x(s); + int collect; /* * Don't need to check that the attributes and whatnot are still the @@ -1424,11 +1776,10 @@ screen_write_collect_add(struct screen_write_ctx *ctx, collect = 0; if (!collect) { screen_write_collect_end(ctx); - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); screen_write_cell(ctx, gc); return; } - ctx->cells++; if (s->cx > sx - 1 || ctx->item->used > sx - 1 - s->cx) screen_write_collect_end(ctx); @@ -1443,9 +1794,9 @@ screen_write_collect_add(struct screen_write_ctx *ctx, if (ci->used == 0) memcpy(&ci->gc, gc, sizeof ci->gc); - ci->data[ci->used++] = gc->data.data[0]; - if (ci->used == (sizeof ci->data) - 1) - screen_write_collect_end(ctx); + if (ctx->s->write_list[s->cy].data == NULL) + ctx->s->write_list[s->cy].data = xmalloc(screen_size_x(ctx->s)); + ctx->s->write_list[s->cy].data[s->cx + ci->used++] = gc->data.data[0]; } /* Write cell data. */ @@ -1454,6 +1805,8 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) { struct screen *s = ctx->s; struct grid *gd = s->grid; + const struct utf8_data *ud = &gc->data; + const struct utf8_data zwj = { "\342\200\215", 0, 3, 0 }; struct grid_line *gl; struct grid_cell_entry *gce; struct grid_cell tmp_gc, now_gc; @@ -1465,15 +1818,36 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; - ctx->cells++; - /* If the width is zero, combine onto the previous character. */ - if (width == 0) { - screen_write_collect_flush(ctx, 0); - if ((gc = screen_write_combine(ctx, &gc->data, &xx)) != 0) { + /* + * If this is a zero width joiner, set the flag so the next character + * will be treated as zero width and appended. Note that we assume a + * ZWJ will not change the width - the width of the first character is + * used. + */ + if (ud->size == 3 && memcmp(ud->data, "\342\200\215", 3) == 0) { + log_debug("zero width joiner at %u,%u", s->cx, s->cy); + ctx->flags |= SCREEN_WRITE_ZWJ; + return; + } + + /* + * If the width is zero, combine onto the previous character. We always + * combine with the cell to the left of the cursor position. In theory, + * the application could have moved the cursor somewhere else, but if + * they are silly enough to do that, who cares? + */ + if (ctx->flags & SCREEN_WRITE_ZWJ) { + screen_write_collect_flush(ctx, 0, __func__); + screen_write_combine(ctx, &zwj, &xx); + } + if (width == 0 || (ctx->flags & SCREEN_WRITE_ZWJ)) { + ctx->flags &= ~SCREEN_WRITE_ZWJ; + screen_write_collect_flush(ctx, 0, __func__); + if ((gc = screen_write_combine(ctx, ud, &xx)) != NULL) { cx = s->cx; cy = s->cy; screen_write_set_cursor(ctx, xx, s->cy); - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = gc; tty_write(tty_cmd_cell, &ttyctx); s->cx = cx; s->cy = cy; @@ -1482,7 +1856,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } /* Flush any existing scrolling. */ - screen_write_collect_flush(ctx, 1); + screen_write_collect_flush(ctx, 1, __func__); /* If this character doesn't fit, ignore it. */ if ((~s->mode & MODE_WRAP) && @@ -1501,13 +1875,13 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) log_debug("%s: wrapped at %u,%u", __func__, s->cx, s->cy); screen_write_linefeed(ctx, 1, 8); screen_write_set_cursor(ctx, 0, -1); - screen_write_collect_flush(ctx, 1); + screen_write_collect_flush(ctx, 1, __func__); } /* Sanity check cursor position. */ if (s->cx > sx - width || s->cy > sy - 1) return; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); /* Handle overwriting of UTF-8 characters. */ gl = grid_get_line(s->grid, s->grid->hsize + s->cy); @@ -1523,7 +1897,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) */ for (xx = s->cx + 1; xx < s->cx + width; xx++) { log_debug("%s: new padding at %u,%u", __func__, xx, s->cy); - grid_view_set_cell(gd, xx, s->cy, &screen_write_pad_cell); + grid_view_set_padding(gd, xx, s->cy); skip = 0; } @@ -1579,7 +1953,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) /* Create space for character in insert mode. */ if (s->mode & MODE_INSERT) { - screen_write_collect_flush(ctx, 0); + screen_write_collect_flush(ctx, 0, __func__); ttyctx.num = width; tty_write(tty_cmd_insertcharacter, &ttyctx); } @@ -1592,9 +1966,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) } else ttyctx.cell = gc; tty_write(tty_cmd_cell, &ttyctx); - ctx->written++; - } else - ctx->skipped++; + } } /* Combine a UTF-8 zero-width character onto the previous. */ @@ -1710,7 +2082,7 @@ screen_write_setselection(struct screen_write_ctx *ctx, u_char *str, u_int len) { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; ttyctx.num = len; @@ -1723,7 +2095,7 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len) { struct tty_ctx ttyctx; - screen_write_initctx(ctx, &ttyctx); + screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; ttyctx.num = len; @@ -1784,3 +2156,39 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, screen_write_cursormove(ctx, 0, cy + y, 0); } + +/* Turn alternate screen on. */ +void +screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, + int cursor) +{ + struct tty_ctx ttyctx; + struct window_pane *wp = ctx->wp; + + if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) + return; + + screen_write_collect_flush(ctx, 0, __func__); + screen_alternate_on(ctx->s, gc, cursor); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.redraw_cb(&ttyctx); +} + +/* Turn alternate screen off. */ +void +screen_write_alternateoff(struct screen_write_ctx *ctx, struct grid_cell *gc, + int cursor) +{ + struct tty_ctx ttyctx; + struct window_pane *wp = ctx->wp; + + if (wp != NULL && !options_get_number(wp->options, "alternate-screen")) + return; + + screen_write_collect_flush(ctx, 0, __func__); + screen_alternate_off(ctx->s, gc, cursor); + + screen_write_initctx(ctx, &ttyctx, 1); + ttyctx.redraw_cb(&ttyctx); +} diff --git a/screen.c b/screen.c index 4ffd2cca..77b511a9 100644 --- a/screen.c +++ b/screen.c @@ -47,9 +47,8 @@ struct screen_title_entry { }; TAILQ_HEAD(screen_titles, screen_title_entry); -static void screen_resize_y(struct screen *, u_int); - -static void screen_reflow(struct screen *, u_int); +static void screen_resize_y(struct screen *, u_int, int, u_int *); +static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int); /* Free titles stack. */ static void @@ -75,15 +74,19 @@ void screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) { s->grid = grid_create(sx, sy, hlimit); + s->saved_grid = NULL; + s->title = xstrdup(""); s->titles = NULL; + s->path = NULL; - s->cstyle = 0; + s->cstyle = SCREEN_CURSOR_DEFAULT; s->ccolour = xstrdup(""); s->tabs = NULL; s->sel = NULL; TAILQ_INIT(&s->images); + s->write_list = NULL; screen_reinit(s); } @@ -98,7 +101,14 @@ screen_reinit(struct screen *s) s->rupper = 0; s->rlower = screen_size_y(s) - 1; - s->mode = MODE_CURSOR | MODE_WRAP; + s->mode = MODE_CURSOR|MODE_WRAP; + if (options_get_number(global_options, "extended-keys") == 2) + s->mode |= MODE_KEXTENDED; + + if (s->saved_grid != NULL) + screen_alternate_off(s, NULL, 0); + s->saved_cx = UINT_MAX; + s->saved_cy = UINT_MAX; screen_reset_tabs(s); @@ -115,9 +125,15 @@ screen_free(struct screen *s) { free(s->sel); free(s->tabs); + free(s->path); free(s->title); free(s->ccolour); + if (s->write_list != NULL) + screen_write_free_list(s); + + if (s->saved_grid != NULL) + grid_destroy(s->saved_grid); grid_destroy(s->grid); screen_free_titles(s); @@ -142,8 +158,36 @@ screen_reset_tabs(struct screen *s) void screen_set_cursor_style(struct screen *s, u_int style) { - if (style <= 6) - s->cstyle = style; + log_debug("%s: new %u, was %u", __func__, style, s->cstyle); + switch (style) { + case 0: + s->cstyle = SCREEN_CURSOR_DEFAULT; + break; + case 1: + s->cstyle = SCREEN_CURSOR_BLOCK; + s->mode |= MODE_CURSOR_BLINKING; + break; + case 2: + s->cstyle = SCREEN_CURSOR_BLOCK; + s->mode &= ~MODE_CURSOR_BLINKING; + break; + case 3: + s->cstyle = SCREEN_CURSOR_UNDERLINE; + s->mode |= MODE_CURSOR_BLINKING; + break; + case 4: + s->cstyle = SCREEN_CURSOR_UNDERLINE; + s->mode &= ~MODE_CURSOR_BLINKING; + break; + case 5: + s->cstyle = SCREEN_CURSOR_BAR; + s->mode |= MODE_CURSOR_BLINKING; + break; + case 6: + s->cstyle = SCREEN_CURSOR_BAR; + s->mode &= ~MODE_CURSOR_BLINKING; + break; + } } /* Set screen cursor colour. */ @@ -210,10 +254,20 @@ screen_pop_title(struct screen *s) } } -/* Resize screen. */ +/* Resize screen with options. */ void -screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) +screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, + int eat_empty, int cursor) { + u_int cx = s->cx, cy = s->grid->hsize + s->cy; + + if (s->write_list != NULL) + screen_write_free_list(s); + + log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)", + __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy, + cx, cy); + if (sx < 1) sx = 1; if (sy < 1) @@ -226,16 +280,37 @@ screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) reflow = 0; if (sy != screen_size_y(s)) - screen_resize_y(s, sy); + screen_resize_y(s, sy, eat_empty, &cy); if (reflow) { image_free_all(s); - screen_reflow(s, sx); + screen_reflow(s, sx, &cx, &cy, cursor); } + + if (cy >= s->grid->hsize) { + s->cx = cx; + s->cy = cy - s->grid->hsize; + } else { + s->cx = 0; + s->cy = 0; + } + + log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx, + s->cy, cx, cy); + + if (s->write_list != NULL) + screen_write_make_list(s); +} + +/* Resize screen. */ +void +screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) +{ + screen_resize_cursor(s, sx, sy, reflow, 1, 1); } static void -screen_resize_y(struct screen *s, u_int sy) +screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy) { struct grid *gd = s->grid; u_int needed, available, oldy, i; @@ -260,14 +335,16 @@ screen_resize_y(struct screen *s, u_int sy) needed = oldy - sy; /* Delete as many lines as possible from the bottom. */ - available = oldy - 1 - s->cy; - if (available > 0) { - if (available > needed) - available = needed; - grid_view_delete_lines(gd, oldy - available, available, - 8); + if (eat_empty) { + available = oldy - 1 - s->cy; + if (available > 0) { + if (available > needed) + available = needed; + grid_view_delete_lines(gd, oldy - available, + available, 8); + } + needed -= available; } - needed -= available; /* * Now just increase the history size, if possible, to take @@ -282,8 +359,8 @@ screen_resize_y(struct screen *s, u_int sy) if (available > needed) available = needed; grid_view_delete_lines(gd, 0, available, 8); + (*cy) -= available; } - s->cy -= needed; } /* Resize line array. */ @@ -303,14 +380,13 @@ screen_resize_y(struct screen *s, u_int sy) available = needed; gd->hscrolled -= available; gd->hsize -= available; - s->cy += available; } else available = 0; needed -= available; /* Then fill the rest in with blanks. */ for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) - memset(grid_get_line(gd, i), 0, sizeof(struct grid_line)); + grid_empty_line(gd, i, 8); } /* Set the new size, and reset the scroll region. */ @@ -478,32 +554,156 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, /* Reflow wrapped lines. */ static void -screen_reflow(struct screen *s, u_int new_x) +screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor) { - u_int cx = s->cx, cy = s->grid->hsize + s->cy, wx, wy; - struct timeval start, tv; + u_int wx, wy; - gettimeofday(&start, NULL); - - grid_wrap_position(s->grid, cx, cy, &wx, &wy); - log_debug("%s: cursor %u,%u is %u,%u", __func__, cx, cy, wx, wy); + if (cursor) { + grid_wrap_position(s->grid, *cx, *cy, &wx, &wy); + log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx, + wy); + } grid_reflow(s->grid, new_x); - grid_unwrap_position(s->grid, &cx, &cy, wx, wy); - log_debug("%s: new cursor is %u,%u", __func__, cx, cy); + if (cursor) { + grid_unwrap_position(s->grid, cx, cy, wx, wy); + log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy); + } + else { + *cx = 0; + *cy = s->grid->hsize; + } +} - if (cy >= s->grid->hsize) { - s->cx = cx; - s->cy = cy - s->grid->hsize; - } else { - s->cx = 0; - s->cy = 0; +/* + * Enter alternative screen mode. A copy of the visible screen is saved and the + * history is not updated. + */ +void +screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) +{ + u_int sx, sy; + + if (s->saved_grid != NULL) + return; + sx = screen_size_x(s); + sy = screen_size_y(s); + + s->saved_grid = grid_create(sx, sy, 0); + grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy); + if (cursor) { + s->saved_cx = s->cx; + s->saved_cy = s->cy; + } + memcpy(&s->saved_cell, gc, sizeof s->saved_cell); + + grid_view_clear(s->grid, 0, 0, sx, sy, 8); + + s->saved_flags = s->grid->flags; + s->grid->flags &= ~GRID_HISTORY; +} + +/* Exit alternate screen mode and restore the copied grid. */ +void +screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) +{ + u_int sx = screen_size_x(s), sy = screen_size_y(s); + + /* + * If the current size is different, temporarily resize to the old size + * before copying back. + */ + if (s->saved_grid != NULL) + screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 1); + + /* + * Restore the cursor position and cell. This happens even if not + * currently in the alternate screen. + */ + if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) { + s->cx = s->saved_cx; + s->cy = s->saved_cy; + if (gc != NULL) + memcpy(gc, &s->saved_cell, sizeof *gc); } - gettimeofday(&tv, NULL); - timersub(&tv, &start, &tv); + /* If not in the alternate screen, do nothing more. */ + if (s->saved_grid == NULL) { + if (s->cx > screen_size_x(s) - 1) + s->cx = screen_size_x(s) - 1; + if (s->cy > screen_size_y(s) - 1) + s->cy = screen_size_y(s) - 1; + return; + } - log_debug("%s: reflow took %llu.%06u seconds", __func__, - (unsigned long long)tv.tv_sec, (u_int)tv.tv_usec); + /* Restore the saved grid. */ + grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, + s->saved_grid->sy); + + /* + * Turn history back on (so resize can use it) and then resize back to + * the current size. + */ + if (s->saved_flags & GRID_HISTORY) + s->grid->flags |= GRID_HISTORY; + screen_resize(s, sx, sy, 1); + + grid_destroy(s->saved_grid); + s->saved_grid = NULL; + + if (s->cx > screen_size_x(s) - 1) + s->cx = screen_size_x(s) - 1; + if (s->cy > screen_size_y(s) - 1) + s->cy = screen_size_y(s) - 1; +} + +/* Get mode as a string. */ +const char * +screen_mode_to_string(int mode) +{ + static char tmp[1024]; + + if (mode == 0) + return "NONE"; + if (mode == ALL_MODES) + return "ALL"; + + *tmp = '\0'; + if (mode & MODE_CURSOR) + strlcat(tmp, "CURSOR,", sizeof tmp); + if (mode & MODE_INSERT) + strlcat(tmp, "INSERT,", sizeof tmp); + if (mode & MODE_KCURSOR) + strlcat(tmp, "KCURSOR,", sizeof tmp); + if (mode & MODE_KKEYPAD) + strlcat(tmp, "KKEYPAD,", sizeof tmp); + if (mode & MODE_WRAP) + strlcat(tmp, "WRAP,", sizeof tmp); + if (mode & MODE_MOUSE_STANDARD) + strlcat(tmp, "MOUSE_STANDARD,", sizeof tmp); + if (mode & MODE_MOUSE_BUTTON) + strlcat(tmp, "MOUSE_BUTTON,", sizeof tmp); + if (mode & MODE_CURSOR_BLINKING) + strlcat(tmp, "CURSOR_BLINKING,", sizeof tmp); + if (mode & MODE_CURSOR_VERY_VISIBLE) + strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp); + if (mode & MODE_MOUSE_UTF8) + strlcat(tmp, "UTF8,", sizeof tmp); + if (mode & MODE_MOUSE_SGR) + strlcat(tmp, "SGR,", sizeof tmp); + if (mode & MODE_BRACKETPASTE) + strlcat(tmp, "BRACKETPASTE,", sizeof tmp); + if (mode & MODE_FOCUSON) + strlcat(tmp, "FOCUSON,", sizeof tmp); + if (mode & MODE_MOUSE_ALL) + strlcat(tmp, "MOUSE_ALL,", sizeof tmp); + if (mode & MODE_ORIGIN) + strlcat(tmp, "ORIGIN,", sizeof tmp); + if (mode & MODE_CRLF) + strlcat(tmp, "CRLF,", sizeof tmp); + if (mode & MODE_KEXTENDED) + strlcat(tmp, "KEXTENDED,", sizeof tmp); + tmp[strlen(tmp) - 1] = '\0'; + return (tmp); } diff --git a/server-client.c b/server-client.c index 497397d3..5c93f8c7 100644 --- a/server-client.c +++ b/server-client.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -31,29 +30,37 @@ #include "tmux.h" static void server_client_free(int, short, void *); -static void server_client_check_focus(struct window_pane *); -static void server_client_check_resize(struct window_pane *); +static void server_client_check_pane_resize(struct window_pane *); +static void server_client_check_pane_buffer(struct window_pane *); +static void server_client_check_window_resize(struct window *); static key_code server_client_check_mouse(struct client *, struct key_event *); static void server_client_repeat_timer(int, short, void *); static void server_client_click_timer(int, short, void *); static void server_client_check_exit(struct client *); static void server_client_check_redraw(struct client *); +static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); static void server_client_reset_state(struct client *); static int server_client_assume_paste(struct session *); -static void server_client_clear_overlay(struct client *); -static void server_client_resize_event(int, short, void *); +static void server_client_update_latest(struct client *); static void server_client_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); static void server_client_dispatch_identify(struct client *, struct imsg *); static void server_client_dispatch_shell(struct client *); -static void server_client_dispatch_write_ready(struct client *, - struct imsg *); -static void server_client_dispatch_read_data(struct client *, - struct imsg *); -static void server_client_dispatch_read_done(struct client *, - struct imsg *); + +/* Compare client windows. */ +static int +server_client_window_cmp(struct client_window *cw1, + struct client_window *cw2) +{ + if (cw1->window < cw2->window) + return (-1); + if (cw1->window > cw2->window) + return (1); + return (0); +} +RB_GENERATE(client_windows, client_window, entry, server_client_window_cmp); /* Number of attached clients. */ u_int @@ -64,7 +71,7 @@ server_client_how_many(void) n = 0; TAILQ_FOREACH(c, &clients, entry) { - if (c->session != NULL && (~c->flags & CLIENT_DETACHING)) + if (c->session != NULL && (~c->flags & CLIENT_UNATTACHEDFLAGS)) n++; } return (n); @@ -79,8 +86,10 @@ server_client_overlay_timer(__unused int fd, __unused short events, void *data) /* Set an overlay on client. */ void -server_client_set_overlay(struct client *c, u_int delay, overlay_draw_cb drawcb, - overlay_key_cb keycb, overlay_free_cb freecb, void *data) +server_client_set_overlay(struct client *c, u_int delay, + overlay_check_cb checkcb, overlay_mode_cb modecb, + overlay_draw_cb drawcb, overlay_key_cb keycb, overlay_free_cb freecb, + overlay_resize_cb resizecb, void *data) { struct timeval tv; @@ -96,17 +105,23 @@ server_client_set_overlay(struct client *c, u_int delay, overlay_draw_cb drawcb, if (delay != 0) evtimer_add(&c->overlay_timer, &tv); + c->overlay_check = checkcb; + c->overlay_mode = modecb; c->overlay_draw = drawcb; c->overlay_key = keycb; c->overlay_free = freecb; + c->overlay_resize = resizecb; c->overlay_data = data; - c->tty.flags |= (TTY_FREEZE|TTY_NOCURSOR); + if (c->overlay_check == NULL) + c->tty.flags |= TTY_FREEZE; + if (c->overlay_mode == NULL) + c->tty.flags |= TTY_NOCURSOR; server_redraw_client(c); } /* Clear overlay mode on client. */ -static void +void server_client_clear_overlay(struct client *c) { if (c->overlay_draw == NULL) @@ -116,10 +131,14 @@ server_client_clear_overlay(struct client *c) evtimer_del(&c->overlay_timer); if (c->overlay_free != NULL) - c->overlay_free(c); + c->overlay_free(c, c->overlay_data); + c->overlay_check = NULL; + c->overlay_mode = NULL; c->overlay_draw = NULL; c->overlay_key = NULL; + c->overlay_free = NULL; + c->overlay_data = NULL; c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); server_redraw_client(c); @@ -197,30 +216,16 @@ server_client_create(int fd) c->environ = environ_create(); c->fd = -1; - c->cwd = NULL; + c->out_fd = -1; - TAILQ_INIT(&c->queue); - - c->tty.fd = -1; - c->title = NULL; - - c->session = NULL; - c->last_session = NULL; + c->queue = cmdq_new(); + RB_INIT(&c->windows); + RB_INIT(&c->files); c->tty.sx = 80; c->tty.sy = 24; status_init(c); - - c->message_string = NULL; - TAILQ_INIT(&c->message_log); - - c->prompt_string = NULL; - c->prompt_buffer = NULL; - c->prompt_index = 0; - - RB_INIT(&c->files); - c->flags |= CLIENT_FOCUSED; c->keytable = key_bindings_get_table("root", 1); @@ -238,11 +243,22 @@ server_client_create(int fd) int server_client_open(struct client *c, char **cause) { + const char *ttynam = _PATH_TTY; + if (c->flags & CLIENT_CONTROL) return (0); - if (strcmp(c->ttyname, "/dev/tty") == 0) { - *cause = xstrdup("can't use /dev/tty"); + if (strcmp(c->ttyname, ttynam) == 0|| + ((isatty(STDIN_FILENO) && + (ttynam = ttyname(STDIN_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0) || + (isatty(STDOUT_FILENO) && + (ttynam = ttyname(STDOUT_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0) || + (isatty(STDERR_FILENO) && + (ttynam = ttyname(STDERR_FILENO)) != NULL && + strcmp(c->ttyname, ttynam) == 0))) { + xasprintf(cause, "can't use %s", c->ttyname); return (-1); } @@ -257,12 +273,78 @@ server_client_open(struct client *c, char **cause) return (0); } +/* Lost an attached client. */ +static void +server_client_attached_lost(struct client *c) +{ + struct session *s; + struct window *w; + struct client *loop; + struct client *found; + + log_debug("lost attached client %p", c); + + /* + * By this point the session in the client has been cleared so walk all + * windows to find any with this client as the latest. + */ + RB_FOREACH(w, windows, &windows) { + if (w->latest != c) + continue; + + found = NULL; + TAILQ_FOREACH(loop, &clients, entry) { + s = loop->session; + if (loop == c || s == NULL || s->curw->window != w) + continue; + if (found == NULL || timercmp(&loop->activity_time, + &found->activity_time, >)) + found = loop; + } + if (found != NULL) + server_client_update_latest(found); + } +} + +/* Set client session. */ +void +server_client_set_session(struct client *c, struct session *s) +{ + struct session *old = c->session; + + if (s != NULL && c->session != NULL && c->session != s) + c->last_session = c->session; + else if (s == NULL) + c->last_session = NULL; + c->session = s; + c->flags |= CLIENT_FOCUSED; + + if (old != NULL && old->curw != NULL) + window_update_focus(old->curw->window); + if (s != NULL) { + recalculate_sizes(); + window_update_focus(s->curw->window); + session_update_activity(s, NULL); + gettimeofday(&s->last_attached_time, NULL); + s->curw->flags &= ~WINLINK_ALERTFLAGS; + s->curw->window->latest = c; + alerts_check_session(s); + tty_update_client_offset(c); + status_timer_start(c); + notify_client("client-session-changed", c); + server_redraw_client(c); + } + + server_check_unattached(); + server_update_socket(); +} + /* Lost a client. */ void server_client_lost(struct client *c) { - struct message_entry *msg, *msg1; - struct client_file *cf; + struct client_file *cf, *cf1; + struct client_window *cw, *cw1; c->flags |= CLIENT_DEAD; @@ -270,22 +352,32 @@ server_client_lost(struct client *c) status_prompt_clear(c); status_message_clear(c); - RB_FOREACH(cf, client_files, &c->files) { + RB_FOREACH_SAFE(cf, client_files, &c->files, cf1) { cf->error = EINTR; file_fire_done(cf); } + RB_FOREACH_SAFE(cw, client_windows, &c->windows, cw1) { + RB_REMOVE(client_windows, &c->windows, cw); + free(cw); + } TAILQ_REMOVE(&clients, c, entry); log_debug("lost client %p", c); - /* - * If CLIENT_TERMINAL hasn't been set, then tty_init hasn't been called - * and tty_free might close an unrelated fd. - */ + if (c->flags & CLIENT_ATTACHED) { + server_client_attached_lost(c); + notify_client("client-detached", c); + } + + if (c->flags & CLIENT_CONTROL) + control_stop(c); if (c->flags & CLIENT_TERMINAL) tty_free(&c->tty); free(c->ttyname); - free(c->term); + + free(c->term_name); + free(c->term_type); + tty_term_free_list(c->term_caps, c->term_ncaps); status_free(c); @@ -300,11 +392,6 @@ server_client_lost(struct client *c) free(c->message_string); if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); - TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { - free(msg->msg); - TAILQ_REMOVE(&c->message_log, msg, entry); - free(msg); - } free(c->prompt_saved); free(c->prompt_string); @@ -316,6 +403,12 @@ server_client_lost(struct client *c) proc_remove_peer(c->peer); c->peer = NULL; + if (c->out_fd != -1) + close(c->out_fd); + if (c->fd != -1) { + close(c->fd); + c->fd = -1; + } server_client_unref(c); server_add_accept(0); /* may be more file descriptors now */ @@ -344,8 +437,7 @@ server_client_free(__unused int fd, __unused short events, void *arg) log_debug("free client %p (%d references)", c, c->references); - if (!TAILQ_EMPTY(&c->queue)) - fatalx("queue not empty"); + cmdq_free(c->queue); if (c->references == 0) { free((void *)c->name); @@ -359,7 +451,7 @@ server_client_suspend(struct client *c) { struct session *s = c->session; - if (s == NULL || (c->flags & CLIENT_DETACHING)) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; tty_stop_tty(&c->tty); @@ -373,12 +465,14 @@ server_client_detach(struct client *c, enum msgtype msgtype) { struct session *s = c->session; - if (s == NULL || (c->flags & CLIENT_DETACHING)) + if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) return; - c->flags |= CLIENT_DETACHING; - notify_client("client-detached", c); - proc_send(c->peer, msgtype, -1, s->name, strlen(s->name) + 1); + c->flags |= CLIENT_EXIT; + + c->exit_type = CLIENT_EXIT_DETACH; + c->exit_msgtype = msgtype; + c->exit_session = xstrdup(s->name); } /* Execute command to replace a client. */ @@ -398,6 +492,8 @@ server_client_exec(struct client *c, const char *cmd) shell = options_get_string(s->options, "default-shell"); else shell = options_get_string(global_s_options, "default-shell"); + if (!checkshell(shell)) + shell = _PATH_BSHELL; shellsize = strlen(shell) + 1; msg = xmalloc(cmdsize + shellsize); @@ -417,7 +513,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) struct winlink *wl; struct window_pane *wp; u_int x, y, b, sx, sy, px, py; - int flag; + int ignore = 0; key_code key; struct timeval tv; struct style_range *sr; @@ -427,6 +523,7 @@ server_client_check_mouse(struct client *c, struct key_event *event) UP, DRAG, WHEEL, + SECOND, DOUBLE, TRIPLE } type = NOTYPE; enum { NOWHERE, @@ -441,7 +538,12 @@ server_client_check_mouse(struct client *c, struct key_event *event) m->x, m->y, m->lx, m->ly, c->tty.mouse_drag_flag); /* What type of event is this? */ - if ((m->sgr_type != ' ' && + if (event->key == KEYC_DOUBLECLICK) { + type = DOUBLE; + x = m->x, y = m->y, b = m->b; + ignore = 1; + log_debug("double-click at %u,%u", x, y); + } else if ((m->sgr_type != ' ' && MOUSE_DRAG(m->sgr_b) && MOUSE_BUTTONS(m->sgr_b) == 3) || (m->sgr_type == ' ' && @@ -475,11 +577,10 @@ server_client_check_mouse(struct client *c, struct key_event *event) evtimer_del(&c->click_timer); c->flags &= ~CLIENT_DOUBLECLICK; if (m->b == c->click_button) { - type = DOUBLE; + type = SECOND; x = m->x, y = m->y, b = m->b; - log_debug("double-click at %u,%u", x, y); - flag = CLIENT_TRIPLECLICK; - goto add_timer; + log_debug("second-click at %u,%u", x, y); + c->flags |= CLIENT_TRIPLECLICK; } } else if (c->flags & CLIENT_TRIPLECLICK) { evtimer_del(&c->click_timer); @@ -490,18 +591,18 @@ server_client_check_mouse(struct client *c, struct key_event *event) log_debug("triple-click at %u,%u", x, y); goto have_event; } + } else { + type = DOWN; + x = m->x, y = m->y, b = m->b; + log_debug("down at %u,%u", x, y); + c->flags |= CLIENT_DOUBLECLICK; } - type = DOWN; - x = m->x, y = m->y, b = m->b; - log_debug("down at %u,%u", x, y); - flag = CLIENT_DOUBLECLICK; - - add_timer: if (KEYC_CLICK_TIMEOUT != 0) { - c->flags |= flag; + memcpy(&c->click_event, m, sizeof c->click_event); c->click_button = m->b; + log_debug("click timer started"); tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000; tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L; evtimer_del(&c->click_timer); @@ -516,6 +617,7 @@ have_event: /* Save the session. */ m->s = s->id; m->w = -1; + m->ignore = ignore; /* Is this on the status line? */ m->statusat = status_at_line(c); @@ -587,10 +689,9 @@ have_event: wp = window_get_active_at(s->curw->window, px, py); if (wp != NULL) where = PANE; + else + return (KEYC_UNKNOWN); } - - if (where == NOWHERE) - return (KEYC_UNKNOWN); if (where == PANE) log_debug("mouse %u,%u on pane %%%u", x, y, wp->id); else if (where == BORDER) @@ -859,6 +960,52 @@ have_event: break; } break; + case SECOND: + switch (MOUSE_BUTTONS(b)) { + case 0: + if (where == PANE) + key = KEYC_SECONDCLICK1_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK1_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK1_BORDER; + break; + case 1: + if (where == PANE) + key = KEYC_SECONDCLICK2_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK2_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK2_BORDER; + break; + case 2: + if (where == PANE) + key = KEYC_SECONDCLICK3_PANE; + if (where == STATUS) + key = KEYC_SECONDCLICK3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_SECONDCLICK3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_SECONDCLICK3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_SECONDCLICK3_STATUS_DEFAULT; + if (where == BORDER) + key = KEYC_SECONDCLICK3_BORDER; + break; + } + break; case DOUBLE: switch (MOUSE_BUTTONS(b)) { case 0: @@ -958,14 +1105,14 @@ have_event: out: /* Apply modifiers if any. */ if (b & MOUSE_MASK_META) - key |= KEYC_ESCAPE; + key |= KEYC_META; if (b & MOUSE_MASK_CTRL) key |= KEYC_CTRL; if (b & MOUSE_MASK_SHIFT) key |= KEYC_SHIFT; if (log_get_level() != 0) - log_debug("mouse key is %s", key_string_lookup_key (key)); + log_debug("mouse key is %s", key_string_lookup_key (key, 1)); return (key); } @@ -1008,7 +1155,9 @@ server_client_update_latest(struct client *c) w->latest = c; if (options_get_number(w->options, "window-size") == WINDOW_SIZE_LATEST) - recalculate_size(w); + recalculate_size(w, 0); + + notify_client("client-active", c); } /* @@ -1018,7 +1167,7 @@ server_client_update_latest(struct client *c) static enum cmd_retval server_client_key_callback(struct cmdq_item *item, void *data) { - struct client *c = item->client; + struct client *c = cmdq_get_client(item); struct key_event *event = data; key_code key = event->key; struct mouse_event *m = &event->m; @@ -1045,7 +1194,7 @@ server_client_key_callback(struct cmdq_item *item, void *data) /* Check for mouse keys. */ m->valid = 0; - if (key == KEYC_MOUSE) { + if (key == KEYC_MOUSE || key == KEYC_DOUBLECLICK) { if (c->flags & CLIENT_READONLY) goto out; key = server_client_check_mouse(c, event); @@ -1063,11 +1212,12 @@ server_client_key_callback(struct cmdq_item *item, void *data) c->tty.mouse_drag_update(c, m); goto out; } + event->key = key; } /* Find affected pane. */ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) - cmd_find_from_session(&fs, s, 0); + cmd_find_from_client(&fs, c, 0); wp = fs.wp; /* Forward mouse keys if disabled. */ @@ -1096,7 +1246,7 @@ table_changed: * The prefix always takes precedence and forces a switch to the prefix * table, unless we are already there. */ - key0 = (key & ~KEYC_XTERM); + key0 = (key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)); if ((key0 == (key_code)options_get_number(s->options, "prefix") || key0 == (key_code)options_get_number(s->options, "prefix2")) && strcmp(table->name, "prefix") != 0) { @@ -1160,7 +1310,7 @@ try_again: server_status_client(c); /* Execute the key binding. */ - key_bindings_dispatch(bd, item, c, m, &fs); + key_bindings_dispatch(bd, item, c, event, &fs); key_bindings_unref_table(table); goto out; } @@ -1207,7 +1357,7 @@ forward_key: window_pane_key(wp, c, s, wl, key, m); out: - if (s != NULL) + if (s != NULL && key != KEYC_FOCUS_OUT) server_client_update_latest(c); free(event); return (CMD_RETURN_NORMAL); @@ -1230,13 +1380,13 @@ server_client_handle_key(struct client *c, struct key_event *event) * immediately rather than queued. */ if (~c->flags & CLIENT_READONLY) { - status_message_clear(c); - if (c->prompt_string != NULL) { - if (status_prompt_key(c, event->key) == 0) + if (c->message_string != NULL) { + if (c->message_ignore_keys) return (0); + status_message_clear(c); } if (c->overlay_key != NULL) { - switch (c->overlay_key(c, event)) { + switch (c->overlay_key(c, c->overlay_data, event)) { case 0: return (0); case 1: @@ -1245,6 +1395,10 @@ server_client_handle_key(struct client *c, struct key_event *event) } } server_client_clear_overlay(c); + if (c->prompt_string != NULL) { + if (status_prompt_key(c, event->key) == 0) + return (0); + } } /* @@ -1263,13 +1417,16 @@ server_client_loop(void) struct client *c; struct window *w; struct window_pane *wp; - struct winlink *wl; - struct session *s; - int focus, attached, resize; + /* Check for window resize. This is done before redrawing. */ + RB_FOREACH(w, windows, &windows) + server_client_check_window_resize(w); + + /* Check clients. */ TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); if (c->session != NULL) { + server_client_check_modes(c); server_client_check_redraw(c); server_client_reset_state(c); } @@ -1277,35 +1434,13 @@ server_client_loop(void) /* * Any windows will have been redrawn as part of clients, so clear - * their flags now. Also check pane focus and resize. - * - * As an optimization, panes in windows that are in an attached session - * but not the current window are not resized (this reduces the amount - * of work needed when, for example, resizing an X terminal a - * lot). Windows in no attached session are resized immediately since - * that is likely to have come from a command like split-window and be - * what the user wanted. + * their flags now. */ - focus = options_get_number(global_options, "focus-events"); RB_FOREACH(w, windows, &windows) { - attached = resize = 0; - TAILQ_FOREACH(wl, &w->winlinks, wentry) { - s = wl->session; - if (s->attached != 0) - attached = 1; - if (s->attached != 0 && s->curw == wl) { - resize = 1; - break; - } - } - if (!attached) - resize = 1; TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->fd != -1) { - if (focus) - server_client_check_focus(wp); - if (resize) - server_client_check_resize(wp); + server_client_check_pane_resize(wp); + server_client_check_pane_buffer(wp); } wp->flags &= ~PANE_REDRAW; } @@ -1313,153 +1448,194 @@ server_client_loop(void) } } -/* Check if we need to force a resize. */ -static int -server_client_resize_force(struct window_pane *wp) +/* Check if window needs to be resized. */ +static void +server_client_check_window_resize(struct window *w) { - struct timeval tv = { .tv_usec = 100000 }; + struct winlink *wl; - /* - * If we are resizing to the same size as when we entered the loop - * (that is, to the same size the application currently thinks it is), - * tmux may have gone through several resizes internally and thrown - * away parts of the screen. So we need the application to actually - * redraw even though its final size has not changed. - */ + if (~w->flags & WINDOW_RESIZE) + return; - if (wp->flags & PANE_RESIZEFORCE) { - wp->flags &= ~PANE_RESIZEFORCE; - return (0); + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session->attached != 0 && wl->session->curw == wl) + break; } + if (wl == NULL) + return; - if (wp->sx != wp->osx || - wp->sy != wp->osy || - wp->sx <= 1 || - wp->sy <= 1) - return (0); - - log_debug("%s: %%%u forcing resize", __func__, wp->id); - window_pane_send_resize(wp, -1); - - evtimer_add(&wp->resize_timer, &tv); - wp->flags |= PANE_RESIZEFORCE; - return (1); -} - -/* Resize a pane. */ -static void -server_client_resize_pane(struct window_pane *wp) -{ - log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); - window_pane_send_resize(wp, 0); - - wp->flags &= ~PANE_RESIZE; - - wp->osx = wp->sx; - wp->osy = wp->sy; -} - -/* Start the resize timer. */ -static void -server_client_start_resize_timer(struct window_pane *wp) -{ - struct timeval tv = { .tv_usec = 250000 }; - - if (!evtimer_pending(&wp->resize_timer, NULL)) - evtimer_add(&wp->resize_timer, &tv); + log_debug("%s: resizing window @%u", __func__, w->id); + resize_window(w, w->new_sx, w->new_sy, w->new_xpixel, w->new_ypixel); } /* Resize timer event. */ static void -server_client_resize_event(__unused int fd, __unused short events, void *data) +server_client_resize_timer(__unused int fd, __unused short events, void *data) { struct window_pane *wp = data; + log_debug("%s: %%%u resize timer expired", __func__, wp->id); evtimer_del(&wp->resize_timer); - - if (~wp->flags & PANE_RESIZE) - return; - log_debug("%s: %%%u timer fired (was%s resized)", __func__, wp->id, - (wp->flags & PANE_RESIZED) ? "" : " not"); - - if (wp->saved_grid == NULL && (wp->flags & PANE_RESIZED)) { - log_debug("%s: %%%u deferring timer", __func__, wp->id); - server_client_start_resize_timer(wp); - } else if (!server_client_resize_force(wp)) { - log_debug("%s: %%%u resizing pane", __func__, wp->id); - server_client_resize_pane(wp); - } - wp->flags &= ~PANE_RESIZED; } /* Check if pane should be resized. */ static void -server_client_check_resize(struct window_pane *wp) +server_client_check_pane_resize(struct window_pane *wp) { - if (~wp->flags & PANE_RESIZE) + struct window_pane_resize *r; + struct window_pane_resize *r1; + struct window_pane_resize *first; + struct window_pane_resize *last; + struct timeval tv = { .tv_usec = 250000 }; + + if (TAILQ_EMPTY(&wp->resize_queue)) return; if (!event_initialized(&wp->resize_timer)) - evtimer_set(&wp->resize_timer, server_client_resize_event, wp); + evtimer_set(&wp->resize_timer, server_client_resize_timer, wp); + if (evtimer_pending(&wp->resize_timer, NULL)) + return; - if (!evtimer_pending(&wp->resize_timer, NULL)) { - log_debug("%s: %%%u starting timer", __func__, wp->id); - server_client_resize_pane(wp); - server_client_start_resize_timer(wp); - } else - log_debug("%s: %%%u timer running", __func__, wp->id); -} - -/* Check whether pane should be focused. */ -static void -server_client_check_focus(struct window_pane *wp) -{ - struct client *c; - int push; - - /* Do we need to push the focus state? */ - push = wp->flags & PANE_FOCUSPUSH; - wp->flags &= ~PANE_FOCUSPUSH; - - /* If we're not the active pane in our window, we're not focused. */ - if (wp->window->active != wp) - goto not_focused; - - /* If we're in a mode, we're not focused. */ - if (wp->screen != &wp->base) - goto not_focused; + log_debug("%s: %%%u needs to be resized", __func__, wp->id); + TAILQ_FOREACH(r, &wp->resize_queue, entry) { + log_debug("queued resize: %ux%u -> %ux%u", r->osx, r->osy, + r->sx, r->sy); + } /* - * If our window is the current window in any focused clients with an - * attached session, we're focused. + * There are three cases that matter: + * + * - Only one resize. It can just be applied. + * + * - Multiple resizes and the ending size is different from the + * starting size. We can discard all resizes except the most recent. + * + * - Multiple resizes and the ending size is the same as the starting + * size. We must resize at least twice to force the application to + * redraw. So apply the first and leave the last on the queue for + * next time. */ + first = TAILQ_FIRST(&wp->resize_queue); + last = TAILQ_LAST(&wp->resize_queue, window_pane_resizes); + if (first == last) { + /* Only one resize. */ + window_pane_send_resize(wp, first->sx, first->sy); + TAILQ_REMOVE(&wp->resize_queue, first, entry); + free(first); + } else if (last->sx != first->osx || last->sy != first->osy) { + /* Multiple resizes ending up with a different size. */ + window_pane_send_resize(wp, last->sx, last->sy); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } + } else { + /* + * Multiple resizes ending up with the same size. There will + * not be more than one to the same size in succession so we + * can just use the last-but-one on the list and leave the last + * for later. We reduce the time until the next check to avoid + * a long delay between the resizes. + */ + r = TAILQ_PREV(last, window_pane_resizes, entry); + window_pane_send_resize(wp, r->sx, r->sy); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + if (r == last) + break; + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } + tv.tv_usec = 10000; + } + evtimer_add(&wp->resize_timer, &tv); +} + +/* Check pane buffer size. */ +static void +server_client_check_pane_buffer(struct window_pane *wp) +{ + struct evbuffer *evb = wp->event->input; + size_t minimum; + struct client *c; + struct window_pane_offset *wpo; + int off = 1, flag; + u_int attached_clients = 0; + size_t new_size; + + /* + * Work out the minimum used size. This is the most that can be removed + * from the buffer. + */ + minimum = wp->offset.used; + if (wp->pipe_fd != -1 && wp->pipe_offset.used < minimum) + minimum = wp->pipe_offset.used; TAILQ_FOREACH(c, &clients, entry) { - if (c->session == NULL || !(c->flags & CLIENT_FOCUSED)) + if (c->session == NULL) continue; - if (c->session->attached == 0) + attached_clients++; + + if (~c->flags & CLIENT_CONTROL) { + off = 0; continue; + } + wpo = control_pane_offset(c, wp, &flag); + if (wpo == NULL) { + off = 0; + continue; + } + if (!flag) + off = 0; - if (c->session->curw->window == wp->window) - goto focused; + window_pane_get_new_data(wp, wpo, &new_size); + log_debug("%s: %s has %zu bytes used and %zu left for %%%u", + __func__, c->name, wpo->used - wp->base_offset, new_size, + wp->id); + if (wpo->used < minimum) + minimum = wpo->used; } + if (attached_clients == 0) + off = 0; + minimum -= wp->base_offset; + if (minimum == 0) + goto out; -not_focused: - if (push || (wp->flags & PANE_FOCUSED)) { - if (wp->base.mode & MODE_FOCUSON) - bufferevent_write(wp->event, "\033[O", 3); - notify_pane("pane-focus-out", wp); - } - wp->flags &= ~PANE_FOCUSED; - return; + /* Drain the buffer. */ + log_debug("%s: %%%u has %zu minimum (of %zu) bytes used", __func__, + wp->id, minimum, EVBUFFER_LENGTH(evb)); + evbuffer_drain(evb, minimum); -focused: - if (push || !(wp->flags & PANE_FOCUSED)) { - if (wp->base.mode & MODE_FOCUSON) - bufferevent_write(wp->event, "\033[I", 3); - notify_pane("pane-focus-in", wp); - session_update_activity(c->session, NULL); - } - wp->flags |= PANE_FOCUSED; + /* + * Adjust the base offset. If it would roll over, all the offsets into + * the buffer need to be adjusted. + */ + if (wp->base_offset > SIZE_MAX - minimum) { + log_debug("%s: %%%u base offset has wrapped", __func__, wp->id); + wp->offset.used -= wp->base_offset; + if (wp->pipe_fd != -1) + wp->pipe_offset.used -= wp->base_offset; + TAILQ_FOREACH(c, &clients, entry) { + if (c->session == NULL || (~c->flags & CLIENT_CONTROL)) + continue; + wpo = control_pane_offset(c, wp, &flag); + if (wpo != NULL && !flag) + wpo->used -= wp->base_offset; + } + wp->base_offset = minimum; + } else + wp->base_offset += minimum; + +out: + /* + * If there is data remaining, and there are no clients able to consume + * it, do not read any more. This is true when there are attached + * clients, all of which are control clients which are not able to + * accept any more data. + */ + log_debug("%s: pane %%%u is %s", __func__, wp->id, off ? "off" : "on"); + if (off) + bufferevent_disable(wp->event, EV_READ); + else + bufferevent_enable(wp->event, EV_READ); } /* @@ -1474,60 +1650,85 @@ focused: static void server_client_reset_state(struct client *c) { + struct tty *tty = &c->tty; struct window *w = c->session->curw->window; - struct window_pane *wp = w->active, *loop; - struct screen *s = wp->screen; + struct window_pane *wp = server_client_get_pane(c), *loop; + struct screen *s = NULL; struct options *oo = c->session->options; - int mode, cursor = 0; + int mode = 0, cursor, flags; u_int cx = 0, cy = 0, ox, oy, sx, sy; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) return; - if (c->overlay_draw != NULL) - return; - mode = s->mode; - tty_region_off(&c->tty); - tty_margin_off(&c->tty); + /* Disable the block flag. */ + flags = (tty->flags & TTY_BLOCK); + tty->flags &= ~TTY_BLOCK; + + /* Get mode from overlay if any, else from screen. */ + if (c->overlay_draw != NULL) { + if (c->overlay_mode != NULL) + s = c->overlay_mode(c, c->overlay_data, &cx, &cy); + } else + s = wp->screen; + if (s != NULL) + mode = s->mode; + if (log_get_level() != 0) { + log_debug("%s: client %s mode %s", __func__, c->name, + screen_mode_to_string(mode)); + } + + /* Reset region and margin. */ + tty_region_off(tty); + tty_margin_off(tty); /* Move cursor to pane cursor and offset. */ - cursor = 0; - tty_window_offset(&c->tty, &ox, &oy, &sx, &sy); - if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && - wp->yoff + s->cy >= oy && wp->yoff + s->cy <= oy + sy) { - cursor = 1; + if (c->overlay_draw == NULL) { + cursor = 0; + tty_window_offset(tty, &ox, &oy, &sx, &sy); + if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && + wp->yoff + s->cy >= oy && wp->yoff + s->cy <= oy + sy) { + cursor = 1; - cx = wp->xoff + s->cx - ox; - cy = wp->yoff + s->cy - oy; + cx = wp->xoff + s->cx - ox; + cy = wp->yoff + s->cy - oy; - if (status_at_line(c) == 0) - cy += status_line_size(c); + if (status_at_line(c) == 0) + cy += status_line_size(c); + } + if (!cursor) + mode &= ~MODE_CURSOR; } - if (!cursor) - mode &= ~MODE_CURSOR; - tty_cursor(&c->tty, cx, cy); + log_debug("%s: cursor to %u,%u", __func__, cx, cy); + tty_cursor(tty, cx, cy); /* * Set mouse mode if requested. To support dragging, always use button * mode. */ if (options_get_number(oo, "mouse")) { - mode &= ~ALL_MOUSE_MODES; - TAILQ_FOREACH(loop, &w->panes, entry) { - if (loop->screen->mode & MODE_MOUSE_ALL) - mode |= MODE_MOUSE_ALL; + if (c->overlay_draw == NULL) { + mode &= ~ALL_MOUSE_MODES; + TAILQ_FOREACH(loop, &w->panes, entry) { + if (loop->screen->mode & MODE_MOUSE_ALL) + mode |= MODE_MOUSE_ALL; + } } if (~mode & MODE_MOUSE_ALL) mode |= MODE_MOUSE_BUTTON; } /* Clear bracketed paste mode if at the prompt. */ - if (c->prompt_string != NULL) + if (c->overlay_draw == NULL && c->prompt_string != NULL) mode &= ~MODE_BRACKETPASTE; /* Set the terminal mode and reset attributes. */ - tty_update_mode(&c->tty, mode, s); - tty_reset(&c->tty); + tty_update_mode(tty, mode, s); + tty_reset(tty); + + /* All writing must be done, send a sync end (if it was started). */ + tty_sync_end(tty); + tty->flags |= flags; } /* Repeat time callback. */ @@ -1547,8 +1748,22 @@ server_client_repeat_timer(__unused int fd, __unused short events, void *data) static void server_client_click_timer(__unused int fd, __unused short events, void *data) { - struct client *c = data; + struct client *c = data; + struct key_event *event; + log_debug("click timer expired"); + + if (c->flags & CLIENT_TRIPLECLICK) { + /* + * Waiting for a third click that hasn't happened, so this must + * have been a double click. + */ + event = xmalloc(sizeof *event); + event->key = KEYC_DOUBLECLICK; + memcpy(&event->m, &c->click_event, sizeof event->m); + if (!server_client_handle_key(c, event)) + free(event); + } c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK); } @@ -1557,21 +1772,49 @@ static void server_client_check_exit(struct client *c) { struct client_file *cf; + const char *name = c->exit_session; + char *data; + size_t size, msize; + if (c->flags & (CLIENT_DEAD|CLIENT_EXITED)) + return; if (~c->flags & CLIENT_EXIT) return; - if (c->flags & CLIENT_EXITED) - return; + if (c->flags & CLIENT_CONTROL) { + control_discard(c); + if (!control_all_done(c)) + return; + } RB_FOREACH(cf, client_files, &c->files) { if (EVBUFFER_LENGTH(cf->buffer) != 0) return; } - - if (c->flags & CLIENT_ATTACHED) - notify_client("client-detached", c); - proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval); c->flags |= CLIENT_EXITED; + + switch (c->exit_type) { + case CLIENT_EXIT_RETURN: + if (c->exit_message != NULL) + msize = strlen(c->exit_message) + 1; + else + msize = 0; + size = (sizeof c->retval) + msize; + data = xmalloc(size); + memcpy(data, &c->retval, sizeof c->retval); + if (c->exit_message != NULL) + memcpy(data + sizeof c->retval, c->exit_message, msize); + proc_send(c->peer, MSG_EXIT, -1, data, size); + free(data); + break; + case CLIENT_EXIT_SHUTDOWN: + proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); + break; + case CLIENT_EXIT_DETACH: + proc_send(c->peer, c->exit_msgtype, -1, name, strlen(name) + 1); + break; + } + free(c->exit_session); + free(c->exit_message); } /* Redraw timer callback. */ @@ -1582,14 +1825,39 @@ server_client_redraw_timer(__unused int fd, __unused short events, log_debug("redraw timer fired"); } +/* + * Check if modes need to be updated. Only modes in the current window are + * updated and it is done when the status line is redrawn. + */ +static void +server_client_check_modes(struct client *c) +{ + struct window *w = c->session->curw->window; + struct window_pane *wp; + struct window_mode_entry *wme; + + if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) + return; + if (~c->flags & CLIENT_REDRAWSTATUS) + return; + TAILQ_FOREACH(wp, &w->panes, entry) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode->update != NULL) + wme->mode->update(wme); + } +} + /* Check for client redraws. */ static void server_client_check_redraw(struct client *c) { struct session *s = c->session; struct tty *tty = &c->tty; + struct window *w = c->session->curw->window; struct window_pane *wp; - int needed, flags; + int needed, flags, mode = tty->mode, new_flags = 0; + int redraw; + u_int bit = 0; struct timeval tv = { .tv_usec = 1000 }; static struct event ev; size_t left; @@ -1597,11 +1865,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_REDRAWPANES) ? " panes" : ""); } /* @@ -1613,12 +1882,14 @@ server_client_check_redraw(struct client *c) if (c->flags & (CLIENT_ALLREDRAWFLAGS & ~CLIENT_REDRAWSTATUS)) needed = 1; else { - TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { + TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->flags & PANE_REDRAW) { needed = 1; break; } } + if (needed) + new_flags |= CLIENT_REDRAWPANES; } if (needed && (left = EVBUFFER_LENGTH(tty->out)) != 0) { log_debug("%s: redraw deferred (%zu left)", c->name, left); @@ -1629,29 +1900,53 @@ server_client_check_redraw(struct client *c) evtimer_add(&ev, &tv); } - /* - * We may have got here for a single pane redraw, but force a - * full redraw next time in case other panes have been updated. - */ - c->flags |= CLIENT_ALLREDRAWFLAGS; + if (~c->flags & CLIENT_REDRAWWINDOW) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_REDRAW) { + log_debug("%s: pane %%%u needs redraw", + c->name, wp->id); + c->redraw_panes |= (1 << bit); + } + if (++bit == 64) { + /* + * If more that 64 panes, give up and + * just redraw the window. + */ + new_flags &= CLIENT_REDRAWPANES; + new_flags |= CLIENT_REDRAWWINDOW; + break; + } + } + if (c->redraw_panes != 0) + c->flags |= CLIENT_REDRAWPANES; + } + c->flags |= new_flags; return; } else if (needed) log_debug("%s: redraw needed", c->name); flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); - tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE)) | TTY_NOCURSOR; + tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE))|TTY_NOCURSOR; if (~c->flags & CLIENT_REDRAWWINDOW) { /* * If not redrawing the entire window, check whether each pane * needs to be redrawn. */ - TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { - if (wp->flags & PANE_REDRAW) { - tty_update_mode(tty, tty->mode, NULL); - screen_redraw_pane(c, wp); - } + TAILQ_FOREACH(wp, &w->panes, entry) { + redraw = 0; + if (wp->flags & PANE_REDRAW) + redraw = 1; + else if (c->flags & CLIENT_REDRAWPANES) + redraw = !!(c->redraw_panes & (1 << bit)); + bit++; + if (!redraw) + continue; + log_debug("%s: redrawing pane %%%u", __func__, wp->id); + screen_redraw_pane(c, wp); } + c->redraw_panes = 0; + c->flags &= ~CLIENT_REDRAWPANES; } if (c->flags & CLIENT_ALLREDRAWFLAGS) { @@ -1660,8 +1955,9 @@ server_client_check_redraw(struct client *c) screen_redraw_screen(c); } - tty->flags = (tty->flags & ~(TTY_FREEZE|TTY_NOCURSOR)) | flags; - tty_update_mode(tty, tty->mode, NULL); + tty->flags = (tty->flags & ~TTY_NOCURSOR)|(flags & TTY_NOCURSOR); + tty_update_mode(tty, mode, NULL); + tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))|flags; c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE); @@ -1720,13 +2016,17 @@ server_client_dispatch(struct imsg *imsg, void *arg) datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { - case MSG_IDENTIFY_FLAGS: - case MSG_IDENTIFY_TERM: - case MSG_IDENTIFY_TTYNAME: - case MSG_IDENTIFY_CWD: - case MSG_IDENTIFY_STDIN: - case MSG_IDENTIFY_ENVIRON: case MSG_IDENTIFY_CLIENTPID: + case MSG_IDENTIFY_CWD: + case MSG_IDENTIFY_ENVIRON: + case MSG_IDENTIFY_FEATURES: + case MSG_IDENTIFY_FLAGS: + case MSG_IDENTIFY_LONGFLAGS: + case MSG_IDENTIFY_STDIN: + case MSG_IDENTIFY_STDOUT: + case MSG_IDENTIFY_TERM: + case MSG_IDENTIFY_TERMINFO: + case MSG_IDENTIFY_TTYNAME: case MSG_IDENTIFY_DONE: server_client_dispatch_identify(c, imsg); break; @@ -1740,9 +2040,12 @@ server_client_dispatch(struct imsg *imsg, void *arg) if (c->flags & CLIENT_CONTROL) break; server_client_update_latest(c); - server_client_clear_overlay(c); tty_resize(&c->tty); recalculate_sizes(); + if (c->overlay_resize == NULL) + server_client_clear_overlay(c); + else + c->overlay_resize(c, c->overlay_data); server_redraw_client(c); if (c->session != NULL) notify_client("client-resized", c); @@ -1750,8 +2053,8 @@ server_client_dispatch(struct imsg *imsg, void *arg) case MSG_EXITING: if (datalen != 0) fatalx("bad MSG_EXITING size"); - - c->session = NULL; + server_client_set_session(c, NULL); + recalculate_sizes(); tty_close(&c->tty); proc_send(c->peer, MSG_EXITED, -1, NULL, 0); break; @@ -1764,7 +2067,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) break; c->flags &= ~CLIENT_SUSPENDED; - if (c->tty.fd == -1) /* exited in the meantime */ + if (c->fd == -1 || c->session == NULL) /* exited already */ break; s = c->session; @@ -1785,13 +2088,13 @@ server_client_dispatch(struct imsg *imsg, void *arg) server_client_dispatch_shell(c); break; case MSG_WRITE_READY: - server_client_dispatch_write_ready(c, imsg); + file_write_ready(&c->files, imsg); break; case MSG_READ: - server_client_dispatch_read_data(c, imsg); + file_read_data(&c->files, imsg); break; case MSG_READ_DONE: - server_client_dispatch_read_done(c, imsg); + file_read_done(&c->files, imsg); break; } } @@ -1800,10 +2103,12 @@ server_client_dispatch(struct imsg *imsg, void *arg) static enum cmd_retval server_client_command_done(struct cmdq_item *item, __unused void *data) { - struct client *c = item->client; + struct client *c = cmdq_get_client(item); if (~c->flags & CLIENT_ATTACHED) c->flags |= CLIENT_EXIT; + else if (~c->flags & CLIENT_EXIT) + tty_send_requests(&c->tty); return (CMD_RETURN_NORMAL); } @@ -1817,6 +2122,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) int argc; char **argv, *cause; struct cmd_parse_result *pr; + struct args_value *values; if (c->flags & CLIENT_EXIT) return; @@ -1842,20 +2148,20 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) *argv = xstrdup("new-session"); } - pr = cmd_parse_from_arguments(argc, argv, NULL); + values = args_from_vector(argc, argv); + pr = cmd_parse_from_arguments(values, argc, NULL); switch (pr->status) { - case CMD_PARSE_EMPTY: - cause = xstrdup("empty command"); - goto error; case CMD_PARSE_ERROR: cause = pr->error; goto error; case CMD_PARSE_SUCCESS: break; } + args_free_values(values, argc); + free(values); cmd_free_argv(argc, argv); - cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL, NULL, 0)); + cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL)); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); cmd_list_free(pr->cmdlist); @@ -1876,7 +2182,8 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) { const char *data, *home; size_t datalen; - int flags; + int flags, feat; + uint64_t longflags; char *name; if (c->flags & CLIENT_IDENTIFIED) @@ -1886,6 +2193,14 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { + case MSG_IDENTIFY_FEATURES: + if (datalen != sizeof feat) + fatalx("bad MSG_IDENTIFY_FEATURES size"); + memcpy(&feat, data, sizeof feat); + c->term_features |= feat; + log_debug("client %p IDENTIFY_FEATURES %s", c, + tty_get_features(feat)); + break; case MSG_IDENTIFY_FLAGS: if (datalen != sizeof flags) fatalx("bad MSG_IDENTIFY_FLAGS size"); @@ -1893,12 +2208,31 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) c->flags |= flags; log_debug("client %p IDENTIFY_FLAGS %#x", c, flags); break; + case MSG_IDENTIFY_LONGFLAGS: + if (datalen != sizeof longflags) + fatalx("bad MSG_IDENTIFY_LONGFLAGS size"); + memcpy(&longflags, data, sizeof longflags); + c->flags |= longflags; + log_debug("client %p IDENTIFY_LONGFLAGS %#llx", c, + (unsigned long long)longflags); + break; case MSG_IDENTIFY_TERM: if (datalen == 0 || data[datalen - 1] != '\0') fatalx("bad MSG_IDENTIFY_TERM string"); - c->term = xstrdup(data); + if (*data == '\0') + c->term_name = xstrdup("unknown"); + else + c->term_name = xstrdup(data); log_debug("client %p IDENTIFY_TERM %s", c, data); break; + case MSG_IDENTIFY_TERMINFO: + if (datalen == 0 || data[datalen - 1] != '\0') + fatalx("bad MSG_IDENTIFY_TERMINFO string"); + c->term_caps = xreallocarray(c->term_caps, c->term_ncaps + 1, + sizeof *c->term_caps); + c->term_caps[c->term_ncaps++] = xstrdup(data); + log_debug("client %p IDENTIFY_TERMINFO %s", c, data); + break; case MSG_IDENTIFY_TTYNAME: if (datalen == 0 || data[datalen - 1] != '\0') fatalx("bad MSG_IDENTIFY_TTYNAME string"); @@ -1922,11 +2256,17 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) c->fd = imsg->fd; log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd); break; + case MSG_IDENTIFY_STDOUT: + if (datalen != 0) + fatalx("bad MSG_IDENTIFY_STDOUT size"); + c->out_fd = imsg->fd; + log_debug("client %p IDENTIFY_STDOUT %d", c, imsg->fd); + break; case MSG_IDENTIFY_ENVIRON: if (datalen == 0 || data[datalen - 1] != '\0') fatalx("bad MSG_IDENTIFY_ENVIRON string"); if (strchr(data, '=') != NULL) - environ_put(c->environ, data); + environ_put(c->environ, data, 0); log_debug("client %p IDENTIFY_ENVIRON %s", c, data); break; case MSG_IDENTIFY_CLIENTPID: @@ -1954,34 +2294,28 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) c->fd = open(c->ttyname, O_RDWR|O_NOCTTY); #endif - if (c->flags & CLIENT_CONTROL) { - close(c->fd); - c->fd = -1; - + if (c->flags & CLIENT_CONTROL) control_start(c); - c->tty.fd = -1; - } else if (c->fd != -1) { - if (tty_init(&c->tty, c, c->fd, c->term) != 0) { + else if (c->fd != -1) { + if (tty_init(&c->tty, c) != 0) { close(c->fd); c->fd = -1; } else { - if (c->flags & CLIENT_UTF8) - c->tty.flags |= TTY_UTF8; - if (c->flags & CLIENT_256COLOURS) - c->tty.term_flags |= TERM_256COLOURS; tty_resize(&c->tty); c->flags |= CLIENT_TERMINAL; } + close(c->out_fd); + c->out_fd = -1; } /* - * If this is the first client that has finished identifying, load - * configuration files. + * If this is the first client, load configuration files. Any later + * clients are allowed to continue with their command even if the + * config has not been loaded - they might have been run from inside it */ if ((~c->flags & CLIENT_EXIT) && - !cfg_finished && - c == TAILQ_FIRST(&clients) && - TAILQ_NEXT(c, entry) == NULL) + !cfg_finished && + c == TAILQ_FIRST(&clients)) start_cfg(); } @@ -1992,109 +2326,13 @@ server_client_dispatch_shell(struct client *c) const char *shell; shell = options_get_string(global_s_options, "default-shell"); - if (*shell == '\0' || areshell(shell)) + if (!checkshell(shell)) shell = _PATH_BSHELL; proc_send(c->peer, MSG_SHELL, -1, shell, strlen(shell) + 1); proc_kill_peer(c->peer); } -/* Handle write ready message. */ -static void -server_client_dispatch_write_ready(struct client *c, struct imsg *imsg) -{ - struct msg_write_ready *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - - if (msglen != sizeof *msg) - fatalx("bad MSG_WRITE_READY size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - if (msg->error != 0) { - cf->error = msg->error; - file_fire_done(cf); - } else - file_push(cf); -} - -/* Handle read data message. */ -static void -server_client_dispatch_read_data(struct client *c, struct imsg *imsg) -{ - struct msg_read_data *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - void *bdata = msg + 1; - size_t bsize = msglen - sizeof *msg; - - if (msglen < sizeof *msg) - fatalx("bad MSG_READ_DATA size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - - log_debug("%s: file %d read %zu bytes", c->name, cf->stream, bsize); - if (cf->error == 0) { - if (evbuffer_add(cf->buffer, bdata, bsize) != 0) { - cf->error = ENOMEM; - file_fire_done(cf); - } else - file_fire_read(cf); - } -} - -/* Handle read done message. */ -static void -server_client_dispatch_read_done(struct client *c, struct imsg *imsg) -{ - struct msg_read_done *msg = imsg->data; - size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; - struct client_file find, *cf; - - if (msglen != sizeof *msg) - fatalx("bad MSG_READ_DONE size"); - find.stream = msg->stream; - if ((cf = RB_FIND(client_files, &c->files, &find)) == NULL) - return; - - log_debug("%s: file %d read done", c->name, cf->stream); - cf->error = msg->error; - file_fire_done(cf); -} - -/* Add to client message log. */ -void -server_client_add_message(struct client *c, const char *fmt, ...) -{ - struct message_entry *msg, *msg1; - char *s; - va_list ap; - u_int limit; - - va_start(ap, fmt); - xvasprintf(&s, fmt, ap); - va_end(ap); - - log_debug("message %s (client %p)", s, c); - - msg = xcalloc(1, sizeof *msg); - msg->msg_time = time(NULL); - msg->msg_num = c->message_next++; - msg->msg = s; - TAILQ_INSERT_TAIL(&c->message_log, msg, entry); - - limit = options_get_number(global_options, "message-limit"); - TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { - if (msg->msg_num + limit >= c->message_next) - break; - free(msg->msg); - TAILQ_REMOVE(&c->message_log, msg, entry); - free(msg); - } -} - /* Get client working directory. */ const char * server_client_get_cwd(struct client *c, struct session *s) @@ -2113,3 +2351,173 @@ server_client_get_cwd(struct client *c, struct session *s) return (home); return ("/"); } + +/* Get control client flags. */ +static uint64_t +server_client_control_flags(struct client *c, const char *next) +{ + if (strcmp(next, "pause-after") == 0) { + c->pause_age = 0; + return (CLIENT_CONTROL_PAUSEAFTER); + } + if (sscanf(next, "pause-after=%u", &c->pause_age) == 1) { + c->pause_age *= 1000; + return (CLIENT_CONTROL_PAUSEAFTER); + } + if (strcmp(next, "no-output") == 0) + return (CLIENT_CONTROL_NOOUTPUT); + if (strcmp(next, "wait-exit") == 0) + return (CLIENT_CONTROL_WAITEXIT); + return (0); +} + +/* Set client flags. */ +void +server_client_set_flags(struct client *c, const char *flags) +{ + char *s, *copy, *next; + uint64_t flag; + int not; + + s = copy = xstrdup(flags); + while ((next = strsep(&s, ",")) != NULL) { + not = (*next == '!'); + if (not) + next++; + + if (c->flags & CLIENT_CONTROL) + flag = server_client_control_flags(c, next); + else + flag = 0; + if (strcmp(next, "read-only") == 0) + flag = CLIENT_READONLY; + else if (strcmp(next, "ignore-size") == 0) + flag = CLIENT_IGNORESIZE; + else if (strcmp(next, "active-pane") == 0) + flag = CLIENT_ACTIVEPANE; + if (flag == 0) + continue; + + log_debug("client %s set flag %s", c->name, next); + if (not) + c->flags &= ~flag; + else + c->flags |= flag; + if (flag == CLIENT_CONTROL_NOOUTPUT) + control_reset_offsets(c); + } + free(copy); + proc_send(c->peer, MSG_FLAGS, -1, &c->flags, sizeof c->flags); +} + +/* Get client flags. This is only flags useful to show to users. */ +const char * +server_client_get_flags(struct client *c) +{ + static char s[256]; + char tmp[32]; + + *s = '\0'; + if (c->flags & CLIENT_ATTACHED) + strlcat(s, "attached,", sizeof s); + if (c->flags & CLIENT_FOCUSED) + strlcat(s, "focused,", sizeof s); + if (c->flags & CLIENT_CONTROL) + strlcat(s, "control-mode,", sizeof s); + if (c->flags & CLIENT_IGNORESIZE) + strlcat(s, "ignore-size,", sizeof s); + if (c->flags & CLIENT_CONTROL_NOOUTPUT) + strlcat(s, "no-output,", sizeof s); + if (c->flags & CLIENT_CONTROL_WAITEXIT) + strlcat(s, "wait-exit,", sizeof s); + if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { + xsnprintf(tmp, sizeof tmp, "pause-after=%u,", + c->pause_age / 1000); + strlcat(s, tmp, sizeof s); + } + if (c->flags & CLIENT_READONLY) + strlcat(s, "read-only,", sizeof s); + if (c->flags & CLIENT_ACTIVEPANE) + strlcat(s, "active-pane,", sizeof s); + if (c->flags & CLIENT_SUSPENDED) + strlcat(s, "suspended,", sizeof s); + if (c->flags & CLIENT_UTF8) + strlcat(s, "UTF-8,", sizeof s); + if (*s != '\0') + s[strlen(s) - 1] = '\0'; + return (s); +} + +/* Get client window. */ +struct client_window * +server_client_get_client_window(struct client *c, u_int id) +{ + struct client_window cw = { .window = id }; + + return (RB_FIND(client_windows, &c->windows, &cw)); +} + +/* Add client window. */ +struct client_window * +server_client_add_client_window(struct client *c, u_int id) +{ + struct client_window *cw; + + cw = server_client_get_client_window(c, id); + if (cw == NULL) { + cw = xcalloc(1, sizeof *cw); + cw->window = id; + RB_INSERT(client_windows, &c->windows, cw); + } + return cw; +} + +/* Get client active pane. */ +struct window_pane * +server_client_get_pane(struct client *c) +{ + struct session *s = c->session; + struct client_window *cw; + + if (s == NULL) + return (NULL); + + if (~c->flags & CLIENT_ACTIVEPANE) + return (s->curw->window->active); + cw = server_client_get_client_window(c, s->curw->window->id); + if (cw == NULL) + return (s->curw->window->active); + return (cw->pane); +} + +/* Set client active pane. */ +void +server_client_set_pane(struct client *c, struct window_pane *wp) +{ + struct session *s = c->session; + struct client_window *cw; + + if (s == NULL) + return; + + cw = server_client_add_client_window(c, s->curw->window->id); + cw->pane = wp; + log_debug("%s pane now %%%u", c->name, wp->id); +} + +/* Remove pane from client lists. */ +void +server_client_remove_pane(struct window_pane *wp) +{ + struct client *c; + struct window *w = wp->window; + struct client_window *cw; + + TAILQ_FOREACH(c, &clients, entry) { + cw = server_client_get_client_window(c, w->id); + if (cw != NULL && cw->pane == wp) { + RB_REMOVE(client_windows, &c->windows, cw); + free(cw); + } + } +} diff --git a/server-fn.c b/server-fn.c index 2247f1c5..92793093 100644 --- a/server-fn.c +++ b/server-fn.c @@ -181,10 +181,11 @@ server_kill_pane(struct window_pane *wp) struct window *w = wp->window; if (window_count_panes(w) == 1) { - server_kill_window(w); + server_kill_window(w, 1); recalculate_sizes(); } else { server_unzoom_window(w); + server_client_remove_pane(wp); layout_close_pane(wp); window_remove_pane(w, wp); server_redraw_window(w); @@ -192,19 +193,15 @@ server_kill_pane(struct window_pane *wp) } void -server_kill_window(struct window *w) +server_kill_window(struct window *w, int renumber) { - struct session *s, *next_s, *target_s; - struct session_group *sg; - struct winlink *wl; - - next_s = RB_MIN(sessions, &sessions); - while (next_s != NULL) { - s = next_s; - next_s = RB_NEXT(sessions, &sessions, s); + struct session *s, *s1; + struct winlink *wl; + RB_FOREACH_SAFE(s, sessions, &sessions, s1) { if (!session_has(s, w)) continue; + server_unzoom_window(w); while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) { if (session_detach(s, wl)) { @@ -214,17 +211,35 @@ server_kill_window(struct window *w) server_redraw_session_group(s); } - if (options_get_number(s->options, "renumber-windows")) { - if ((sg = session_group_contains(s)) != NULL) { - TAILQ_FOREACH(target_s, &sg->sessions, gentry) - session_renumber_windows(target_s); - } else - session_renumber_windows(s); - } + if (renumber) + server_renumber_session(s); } recalculate_sizes(); } +void +server_renumber_session(struct session *s) +{ + struct session_group *sg; + + if (options_get_number(s->options, "renumber-windows")) { + if ((sg = session_group_contains(s)) != NULL) { + TAILQ_FOREACH(s, &sg->sessions, gentry) + session_renumber_windows(s); + } else + session_renumber_windows(s); + } +} + +void +server_renumber_all(void) +{ + struct session *s; + + RB_FOREACH(s, sessions, &sessions) + server_renumber_session(s); +} + int server_link_window(struct session *src, struct winlink *srcwl, struct session *dst, int dstidx, int killflag, int selectflag, @@ -297,6 +312,7 @@ server_destroy_pane(struct window_pane *wp, int notify) struct grid_cell gc; time_t t; char tim[26]; + int remain_on_exit; if (wp->fd != -1) { #ifdef HAVE_UTEMPTER @@ -308,10 +324,17 @@ server_destroy_pane(struct window_pane *wp, int notify) wp->fd = -1; } - if (options_get_number(wp->options, "remain-on-exit")) { - if (~wp->flags & PANE_STATUSREADY) - return; - + remain_on_exit = options_get_number(wp->options, "remain-on-exit"); + if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY)) + return; + switch (remain_on_exit) { + case 0: + break; + case 2: + if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) + break; + /* FALLTHROUGH */ + case 1: if (wp->flags & PANE_STATUSDRAWN) return; wp->flags |= PANE_STATUSDRAWN; @@ -319,7 +342,7 @@ server_destroy_pane(struct window_pane *wp, int notify) if (notify) notify_pane("pane-died", wp); - screen_write_start(&ctx, wp, &wp->base); + screen_write_start_pane(&ctx, wp, &wp->base); screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1); screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1, 0); screen_write_linefeed(&ctx, 1, 8); @@ -327,6 +350,7 @@ server_destroy_pane(struct window_pane *wp, int notify) time(&t); ctime_r(&t, tim); + tim[strcspn(tim, "\n")] = '\0'; if (WIFEXITED(wp->status)) { screen_write_nputs(&ctx, -1, &gc, @@ -335,8 +359,8 @@ server_destroy_pane(struct window_pane *wp, int notify) tim); } else if (WIFSIGNALED(wp->status)) { screen_write_nputs(&ctx, -1, &gc, - "Pane is dead (signal %d, %s)", - WTERMSIG(wp->status), + "Pane is dead (signal %s, %s)", + sig2name(WTERMSIG(wp->status)), tim); } @@ -349,11 +373,12 @@ server_destroy_pane(struct window_pane *wp, int notify) notify_pane("pane-exited", wp); server_unzoom_window(w); + server_client_remove_pane(wp); layout_close_pane(wp); window_remove_pane(w, wp); if (TAILQ_EMPTY(&w->panes)) - server_kill_window(w); + server_kill_window(w, 1); else server_redraw_window(w); } @@ -377,9 +402,8 @@ server_destroy_session_group(struct session *s) static struct session * server_next_session(struct session *s) { - struct session *s_loop, *s_out; + struct session *s_loop, *s_out = NULL; - s_out = NULL; RB_FOREACH(s_loop, sessions, &sessions) { if (s_loop == s) continue; @@ -390,35 +414,41 @@ server_next_session(struct session *s) return (s_out); } +static struct session * +server_next_detached_session(struct session *s) +{ + struct session *s_loop, *s_out = NULL; + + RB_FOREACH(s_loop, sessions, &sessions) { + if (s_loop == s || s_loop->attached) + continue; + if (s_out == NULL || + timercmp(&s_loop->activity_time, &s_out->activity_time, <)) + s_out = s_loop; + } + return (s_out); +} + void server_destroy_session(struct session *s) { struct client *c; struct session *s_new; + int detach_on_destroy; - if (!options_get_number(s->options, "detach-on-destroy")) + detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); + if (detach_on_destroy == 0) s_new = server_next_session(s); + else if (detach_on_destroy == 2) + s_new = server_next_detached_session(s); else s_new = NULL; - TAILQ_FOREACH(c, &clients, entry) { if (c->session != s) continue; - if (s_new == NULL) { - c->session = NULL; + server_client_set_session(c, s_new); + if (s_new == NULL) c->flags |= CLIENT_EXIT; - } else { - c->last_session = NULL; - c->session = s_new; - server_client_set_key_table(c, NULL); - tty_update_client_offset(c); - status_timer_start(c); - notify_client("client-session-changed", c); - session_update_activity(s_new, NULL); - gettimeofday(&s_new->last_attached_time, NULL); - server_redraw_client(c); - alerts_check_session(s_new); - } } recalculate_sizes(); } diff --git a/server.c b/server.c index a5d5e37f..29bcf88a 100644 --- a/server.c +++ b/server.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -44,11 +43,16 @@ struct clients clients; struct tmuxproc *server_proc; static int server_fd = -1; +static uint64_t server_client_flags; static int server_exit; static struct event server_ev_accept; +static struct event server_ev_tidy; struct cmd_find_state marked_pane; +static u_int message_next; +struct message_list message_log; + static int server_loop(void); static void server_send_exit(void); static void server_accept(int, short, void *); @@ -97,7 +101,7 @@ server_check_marked(void) /* Create server socket. */ static int -server_create_socket(char **cause) +server_create_socket(int flags, char **cause) { struct sockaddr_un sa; size_t size; @@ -116,7 +120,10 @@ server_create_socket(char **cause) if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) goto fail; - mask = umask(S_IXUSR|S_IXGRP|S_IRWXO); + if (flags & CLIENT_DEFAULTSOCKET) + mask = umask(S_IXUSR|S_IXGRP|S_IRWXO); + else + mask = umask(S_IXUSR|S_IRWXG|S_IRWXO); if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) { saved_errno = errno; close(fd); @@ -143,38 +150,51 @@ fail: return (-1); } +/* Tidy up every hour. */ +static void +server_tidy_event(__unused int fd, __unused short events, __unused void *data) +{ + struct timeval tv = { .tv_sec = 3600 }; + uint64_t t = get_timer(); + + format_tidy_jobs(); + +#ifdef HAVE_MALLOC_TRIM + malloc_trim(0); +#endif + + log_debug("%s: took %llu milliseconds", __func__, + (unsigned long long)(get_timer() - t)); + evtimer_add(&server_ev_tidy, &tv); +} + /* Fork new server. */ int -server_start(struct tmuxproc *client, struct event_base *base, int lockfd, - char *lockfile) +server_start(struct tmuxproc *client, int flags, struct event_base *base, + int lockfd, char *lockfile) { - int pair[2]; + int fd; sigset_t set, oldset; - struct client *c; + struct client *c = NULL; char *cause = NULL; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - fatal("socketpair failed"); + struct timeval tv = { .tv_sec = 3600 }; sigfillset(&set); sigprocmask(SIG_BLOCK, &set, &oldset); - switch (fork()) { - case -1: - fatal("fork failed"); - case 0: - break; - default: - sigprocmask(SIG_SETMASK, &oldset, NULL); - close(pair[1]); - return (pair[0]); + + if (~flags & CLIENT_NOFORK) { + if (proc_fork_and_daemon(&fd) != 0) { + sigprocmask(SIG_SETMASK, &oldset, NULL); + return (fd); + } } - close(pair[0]); - if (daemon(1, 0) != 0) - fatal("daemon failed"); proc_clear_signals(client, 0); + server_client_flags = flags; + if (event_reinit(base) != 0) fatalx("event_reinit failed"); server_proc = proc_start("server"); + proc_set_signals(server_proc, server_signal); sigprocmask(SIG_SETMASK, &oldset, NULL); @@ -184,18 +204,23 @@ server_start(struct tmuxproc *client, struct event_base *base, int lockfd, "tty ps", NULL) != 0) fatal("pledge failed"); + input_key_build(); RB_INIT(&windows); RB_INIT(&all_window_panes); TAILQ_INIT(&clients); RB_INIT(&sessions); key_bindings_init(); + TAILQ_INIT(&message_log); gettimeofday(&start_time, NULL); - server_fd = server_create_socket(&cause); + server_fd = server_create_socket(flags, &cause); if (server_fd != -1) server_update_socket(); - c = server_client_create(pair[1]); + if (~flags & CLIENT_NOFORK) + c = server_client_create(fd); + else + options_set_number(global_options, "exit-empty", 0); if (lockfd >= 0) { unlink(lockfile); @@ -204,11 +229,16 @@ server_start(struct tmuxproc *client, struct event_base *base, int lockfd, } if (cause != NULL) { - cmdq_append(c, cmdq_get_error(cause)); + if (c != NULL) { + cmdq_append(c, cmdq_get_error(cause)); + c->flags |= CLIENT_EXIT; + } free(cause); - c->flags |= CLIENT_EXIT; } + evtimer_set(&server_ev_tidy, server_tidy_event, NULL); + evtimer_add(&server_ev_tidy, &tv); + server_add_accept(0); proc_loop(server_proc, server_loop); @@ -275,9 +305,8 @@ server_send_exit(void) if (c->flags & CLIENT_SUSPENDED) server_client_lost(c); else { - if (c->flags & CLIENT_ATTACHED) - notify_client("client-detached", c); - proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); + c->flags |= CLIENT_EXIT; + c->exit_type = CLIENT_EXIT_SHUTDOWN; } c->session = NULL; } @@ -386,6 +415,7 @@ server_signal(int sig) log_debug("%s: %s", __func__, strsignal(sig)); switch (sig) { + case SIGINT: case SIGTERM: server_exit = 1; server_send_exit(); @@ -395,7 +425,7 @@ server_signal(int sig) break; case SIGUSR1: event_del(&server_ev_accept); - fd = server_create_socket(NULL); + fd = server_create_socket(server_client_flags, NULL); if (fd != -1) { close(server_fd); server_fd = fd; @@ -475,4 +505,36 @@ server_child_stopped(pid_t pid, int status) } } } + job_check_died(pid, status); +} + +/* Add to message log. */ +void +server_add_message(const char *fmt, ...) +{ + struct message_entry *msg, *msg1; + char *s; + va_list ap; + u_int limit; + + va_start(ap, fmt); + xvasprintf(&s, fmt, ap); + va_end(ap); + + log_debug("message: %s", s); + + msg = xcalloc(1, sizeof *msg); + gettimeofday(&msg->msg_time, NULL); + msg->msg_num = message_next++; + msg->msg = s; + TAILQ_INSERT_TAIL(&message_log, msg, entry); + + limit = options_get_number(global_options, "message-limit"); + TAILQ_FOREACH_SAFE(msg, &message_log, entry, msg1) { + if (msg->msg_num + limit >= message_next) + break; + free(msg->msg); + TAILQ_REMOVE(&message_log, msg, entry); + free(msg); + } } diff --git a/session.c b/session.c index eddafa2c..f30f6a61 100644 --- a/session.c +++ b/session.c @@ -122,7 +122,6 @@ session_create(const char *prefix, const char *name, const char *cwd, s->cwd = xstrdup(cwd); - s->curw = NULL; TAILQ_INIT(&s->lastw); RB_INIT(&s->windows); @@ -141,7 +140,6 @@ session_create(const char *prefix, const char *name, const char *cwd, s->name = xstrdup(name); s->id = next_session_id++; } else { - s->name = NULL; do { s->id = next_session_id++; free(s->name); @@ -205,6 +203,9 @@ session_destroy(struct session *s, int notify, const char *from) struct winlink *wl; log_debug("session %s destroyed (%s)", s->name, from); + + if (s->curw == NULL) + return; s->curw = NULL; RB_REMOVE(sessions, &sessions, s); @@ -231,11 +232,22 @@ session_destroy(struct session *s, int notify, const char *from) session_remove_ref(s, __func__); } -/* Check a session name is valid: not empty and no colons or periods. */ -int +/* Sanitize session name. */ +char * session_check_name(const char *name) { - return (*name != '\0' && name[strcspn(name, ":.")] == '\0'); + char *copy, *cp, *new_name; + + if (*name == '\0') + return (NULL); + copy = xstrdup(name); + for (cp = copy; *cp != '\0'; cp++) { + if (*cp == ':' || *cp == '.') + *cp = '_'; + } + utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); + free(copy); + return (new_name); } /* Lock session if it has timed out. */ @@ -478,6 +490,8 @@ session_last(struct session *s) int session_set_current(struct session *s, struct winlink *wl) { + struct winlink *old = s->curw; + if (wl == NULL) return (-1); if (wl == s->curw) @@ -486,6 +500,10 @@ session_set_current(struct session *s, struct winlink *wl) winlink_stack_remove(&s->lastw, wl); winlink_stack_push(&s->lastw, s->curw); s->curw = wl; + if (options_get_number(global_options, "focus-events")) { + window_update_focus(old->window); + window_update_focus(wl->window); + } winlink_clear_flags(wl); window_update_activity(wl->window); tty_update_window_offset(wl->window); @@ -555,6 +573,7 @@ session_group_remove(struct session *s) TAILQ_REMOVE(&sg->sessions, s, gentry); if (TAILQ_EMPTY(&sg->sessions)) { RB_REMOVE(session_groups, &session_groups, sg); + free((void *)sg->name); free(sg); } } diff --git a/spawn.c b/spawn.c index d49d3adb..c8ac1bd8 100644 --- a/spawn.c +++ b/spawn.c @@ -54,10 +54,10 @@ spawn_log(const char *from, struct spawn_context *sc) struct session *s = sc->s; struct winlink *wl = sc->wl; struct window_pane *wp0 = sc->wp0; + const char *name = cmdq_get_name(sc->item); char tmp[128]; - const char *name; - log_debug("%s: %s, flags=%#x", from, sc->item->name, sc->flags); + log_debug("%s: %s, flags=%#x", from, name, sc->flags); if (wl != NULL && wp0 != NULL) xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id); @@ -68,18 +68,14 @@ spawn_log(const char *from, struct spawn_context *sc) else xsnprintf(tmp, sizeof tmp, "wl=none wp0=none"); log_debug("%s: s=$%u %s idx=%d", from, s->id, tmp, sc->idx); - - name = sc->name; - if (name == NULL) - name = "none"; - log_debug("%s: name=%s", from, name); + log_debug("%s: name=%s", from, sc->name == NULL ? "none" : sc->name); } struct winlink * spawn_window(struct spawn_context *sc, char **cause) { struct cmdq_item *item = sc->item; - struct client *c = item->client; + struct client *c = cmdq_get_client(item); struct session *s = sc->s; struct window *w; struct window_pane *wp; @@ -155,7 +151,7 @@ spawn_window(struct spawn_context *sc, char **cause) xasprintf(cause, "couldn't add window %d", idx); return (NULL); } - default_window_size(sc->c, s, NULL, &sx, &sy, &xpixel, &ypixel, + default_window_size(sc->tc, s, NULL, &sx, &sy, &xpixel, &ypixel, -1); if ((w = window_create(sx, sy, xpixel, ypixel)) == NULL) { winlink_remove(&s->windows, sc->wl); @@ -165,7 +161,7 @@ spawn_window(struct spawn_context *sc, char **cause) if (s->curw == NULL) s->curw = sc->wl; sc->wl->session = s; - w->latest = sc->c; + w->latest = sc->tc; winlink_set_window(sc->wl, w); } else w = NULL; @@ -181,12 +177,13 @@ spawn_window(struct spawn_context *sc, char **cause) /* Set the name of the new window. */ if (~sc->flags & SPAWN_RESPAWN) { + free(w->name); if (sc->name != NULL) { w->name = format_single(item, sc->name, c, s, NULL, NULL); options_set_number(w->options, "automatic-rename", 0); } else - w->name = xstrdup(default_window_name(w)); + w->name = default_window_name(w); } /* Switch to the new window if required. */ @@ -205,7 +202,8 @@ struct window_pane * spawn_pane(struct spawn_context *sc, char **cause) { struct cmdq_item *item = sc->item; - struct client *c = item->client; + struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); struct session *s = sc->s; struct window *w = sc->wl->window; struct window_pane *new_wp; @@ -228,9 +226,9 @@ spawn_pane(struct spawn_context *sc, char **cause) * the pane's stored one unless specified. */ if (sc->cwd != NULL) - cwd = format_single(item, sc->cwd, c, s, NULL, NULL); + cwd = format_single(item, sc->cwd, c, target->s, NULL, NULL); else if (~sc->flags & SPAWN_RESPAWN) - cwd = xstrdup(server_client_get_cwd(c, s)); + cwd = xstrdup(server_client_get_cwd(c, target->s)); else cwd = NULL; @@ -253,7 +251,8 @@ spawn_pane(struct spawn_context *sc, char **cause) } window_pane_reset_mode_all(sc->wp0); screen_reinit(&sc->wp0->base); - input_init(sc->wp0); + input_free(sc->wp0->ictx); + sc->wp0->ictx = NULL; new_wp = sc->wp0; new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); } else if (sc->lc == NULL) { @@ -261,12 +260,16 @@ spawn_pane(struct spawn_context *sc, char **cause) layout_init(w, new_wp); } else { new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags); - layout_assign_pane(sc->lc, new_wp); + if (sc->flags & SPAWN_ZOOM) + layout_assign_pane(sc->lc, new_wp, 1); + else + layout_assign_pane(sc->lc, new_wp, 0); } /* - * Now we have a pane with nothing running in it ready for the new process. - * Work out the command and arguments and store the working directory. + * Now we have a pane with nothing running in it ready for the new + * process. Work out the command and arguments and store the working + * directory. */ if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) { cmd = options_get_string(s->options, "default-command"); @@ -300,7 +303,7 @@ spawn_pane(struct spawn_context *sc, char **cause) child = environ_for_session(s, 0); if (sc->environ != NULL) environ_copy(sc->environ, child); - environ_set(child, "TMUX_PANE", "%%%u", new_wp->id); + environ_set(child, "TMUX_PANE", 0, "%%%u", new_wp->id); /* * Then the PATH environment variable. The session one is replaced from @@ -310,20 +313,20 @@ spawn_pane(struct spawn_context *sc, char **cause) if (c != NULL && c->session == NULL) { /* only unattached clients */ ee = environ_find(c->environ, "PATH"); if (ee != NULL) - environ_set(child, "PATH", "%s", ee->value); + environ_set(child, "PATH", 0, "%s", ee->value); } if (environ_find(child, "PATH") == NULL) - environ_set(child, "%s", _PATH_DEFPATH); + environ_set(child, "PATH", 0, "%s", _PATH_DEFPATH); /* Then the shell. If respawning, use the old one. */ if (~sc->flags & SPAWN_RESPAWN) { tmp = options_get_string(s->options, "default-shell"); - if (*tmp == '\0' || areshell(tmp)) + if (!checkshell(tmp)) tmp = _PATH_BSHELL; free(new_wp->shell); new_wp->shell = xstrdup(tmp); } - environ_set(child, "SHELL", "%s", new_wp->shell); + environ_set(child, "SHELL", 0, "%s", new_wp->shell); /* Log the arguments we are going to use. */ log_debug("%s: shell=%s", __func__, new_wp->shell); @@ -362,10 +365,12 @@ spawn_pane(struct spawn_context *sc, char **cause) xasprintf(cause, "fork failed: %s", strerror(errno)); new_wp->fd = -1; if (~sc->flags & SPAWN_RESPAWN) { + server_client_remove_pane(new_wp); layout_close_pane(new_wp); window_remove_pane(w, new_wp); } sigprocmask(SIG_SETMASK, &oldset, NULL); + environ_free(child); return (NULL); } @@ -377,10 +382,10 @@ spawn_pane(struct spawn_context *sc, char **cause) * Child process. Change to the working directory or home if that * fails. */ - if (chdir(new_wp->cwd) != 0) { - if ((tmp = find_home()) == NULL || chdir(tmp) != 0) - chdir("/"); - } + if (chdir(new_wp->cwd) != 0 && + ((tmp = find_home()) == NULL || chdir(tmp) != 0) && + chdir("/") != 0) + fatal("chdir failed"); /* * Update terminal escape characters from the session if available and @@ -395,6 +400,9 @@ spawn_pane(struct spawn_context *sc, char **cause) now.c_cc[VERASE] = '\177'; else now.c_cc[VERASE] = key; +#ifdef IUTF8 + now.c_iflag |= IUTF8; +#endif if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0) _exit(1); @@ -446,12 +454,13 @@ complete: } #endif - new_wp->pipe_off = 0; new_wp->flags &= ~PANE_EXITED; sigprocmask(SIG_SETMASK, &oldset, NULL); window_pane_set_event(new_wp); + environ_free(child); + if (sc->flags & SPAWN_RESPAWN) return (new_wp); if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { diff --git a/status.c b/status.c index 33f6c47a..b442e85d 100644 --- a/status.c +++ b/status.c @@ -33,18 +33,32 @@ static void status_message_callback(int, short, void *); static void status_timer_callback(int, short, void *); static char *status_prompt_find_history_file(void); -static const char *status_prompt_up_history(u_int *); -static const char *status_prompt_down_history(u_int *); -static void status_prompt_add_history(const char *); +static const char *status_prompt_up_history(u_int *, u_int); +static const char *status_prompt_down_history(u_int *, u_int); +static void status_prompt_add_history(const char *, u_int); -static char **status_prompt_complete_list(u_int *, const char *); -static char *status_prompt_complete_prefix(char **, u_int); -static char *status_prompt_complete(struct session *, const char *); +static char *status_prompt_complete(struct client *, const char *, u_int); +static char *status_prompt_complete_window_menu(struct client *, + struct session *, const char *, u_int, char); + +struct status_prompt_menu { + struct client *c; + u_int start; + u_int size; + char **list; + char flag; +}; + +static const char *prompt_type_strings[] = { + "command", + "search", + "target", + "window-target" +}; /* Status prompt history. */ -#define PROMPT_HISTORY 100 -static char **status_prompt_hlist; -static u_int status_prompt_hsize; +char **status_prompt_hlist[PROMPT_NTYPES]; +u_int status_prompt_hsize[PROMPT_NTYPES]; /* Find the history file to load/save from/to. */ static char * @@ -67,6 +81,28 @@ status_prompt_find_history_file(void) return (path); } +/* Add loaded history item to the appropriate list. */ +static void +status_prompt_add_typed_history(char *line) +{ + char *typestr; + enum prompt_type type = PROMPT_TYPE_INVALID; + + typestr = strsep(&line, ":"); + if (line != NULL) + type = status_prompt_type(typestr); + if (type == PROMPT_TYPE_INVALID) { + /* + * Invalid types are not expected, but this provides backward + * compatibility with old history files. + */ + if (line != NULL) + *(--line) = ':'; + status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND); + } else + status_prompt_add_history(line, type); +} + /* Load status prompt history from file. */ void status_prompt_load_history(void) @@ -94,12 +130,12 @@ status_prompt_load_history(void) if (length > 0) { if (line[length - 1] == '\n') { line[length - 1] = '\0'; - status_prompt_add_history(line); + status_prompt_add_typed_history(line); } else { tmp = xmalloc(length + 1); memcpy(tmp, line, length); tmp[length] = '\0'; - status_prompt_add_history(tmp); + status_prompt_add_typed_history(tmp); free(tmp); } } @@ -112,7 +148,7 @@ void status_prompt_save_history(void) { FILE *f; - u_int i; + u_int i, type; char *history_file; if ((history_file = status_prompt_find_history_file()) == NULL) @@ -127,9 +163,13 @@ status_prompt_save_history(void) } free(history_file); - for (i = 0; i < status_prompt_hsize; i++) { - fputs(status_prompt_hlist[i], f); - fputc('\n', f); + for (type = 0; type < PROMPT_NTYPES; type++) { + for (i = 0; i < status_prompt_hsize[type]; i++) { + fputs(prompt_type_strings[type], f); + fputc(':', f); + fputs(status_prompt_hlist[type][i], f); + fputc('\n', f); + } } fclose(f); @@ -218,6 +258,8 @@ status_line_size(struct client *c) if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) return (0); + if (s == NULL) + return (options_get_number(global_s_options, "status")); return (s->statuslines); } @@ -321,7 +363,7 @@ status_redraw(struct client *c) struct screen_write_ctx ctx; struct grid_cell gc; u_int lines, i, n, width = c->tty.sx; - int flags, force = 0, changed = 0; + int flags, force = 0, changed = 0, fg, bg; struct options_entry *o; union options_value *ov; struct format_tree *ft; @@ -338,8 +380,21 @@ status_redraw(struct client *c) if (c->tty.sy == 0 || lines == 0) return (1); + /* Create format tree. */ + flags = FORMAT_STATUS; + if (c->flags & CLIENT_STATUSFORCE) + flags |= FORMAT_FORCE; + ft = format_create(c, NULL, FORMAT_NONE, flags); + format_defaults(ft, c, NULL, NULL, NULL); + /* Set up default colour. */ - style_apply(&gc, s->options, "status-style"); + style_apply(&gc, s->options, "status-style", ft); + fg = options_get_number(s->options, "status-fg"); + if (!COLOUR_DEFAULT(fg)) + gc.fg = fg; + bg = options_get_number(s->options, "status-bg"); + if (!COLOUR_DEFAULT(bg)) + gc.bg = bg; if (!grid_cells_equal(&gc, &sl->style)) { force = 1; memcpy(&sl->style, &gc, sizeof sl->style); @@ -351,14 +406,7 @@ status_redraw(struct client *c) screen_resize(&sl->screen, width, lines, 0); changed = force = 1; } - screen_write_start(&ctx, NULL, &sl->screen); - - /* Create format tree. */ - flags = FORMAT_STATUS; - if (c->flags & CLIENT_STATUSFORCE) - flags |= FORMAT_FORCE; - ft = format_create(c, NULL, FORMAT_NONE, flags); - format_defaults(ft, c, NULL, NULL, NULL); + screen_write_start(&ctx, &sl->screen); /* Write the status lines. */ o = options_get(s->options, "status-format"); @@ -409,11 +457,11 @@ status_redraw(struct client *c) /* Set a status line message. */ void -status_message_set(struct client *c, const char *fmt, ...) +status_message_set(struct client *c, int delay, int ignore_styles, + int ignore_keys, const char *fmt, ...) { struct timeval tv; va_list ap; - int delay; status_message_clear(c); status_push_screen(c); @@ -422,9 +470,14 @@ status_message_set(struct client *c, const char *fmt, ...) xvasprintf(&c->message_string, fmt, ap); va_end(ap); - server_client_add_message(c, "%s", c->message_string); + server_add_message("%s message: %s", c->name, c->message_string); - delay = options_get_number(c->session->options, "display-time"); + /* + * With delay -1, the display-time option is used; zero means wait for + * key press; more than zero is the actual delay time in milliseconds. + */ + if (delay == -1) + delay = options_get_number(c->session->options, "display-time"); if (delay > 0) { tv.tv_sec = delay / 1000; tv.tv_usec = (delay % 1000) * 1000L; @@ -432,9 +485,14 @@ status_message_set(struct client *c, const char *fmt, ...) if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); evtimer_set(&c->message_timer, status_message_callback, c); + evtimer_add(&c->message_timer, &tv); } + if (delay != 0) + c->message_ignore_keys = ignore_keys; + c->message_ignore_styles = ignore_styles; + c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_REDRAWSTATUS; } @@ -476,6 +534,7 @@ status_message_redraw(struct client *c) size_t len; u_int lines, offset; struct grid_cell gc; + struct format_tree *ft; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -490,15 +549,20 @@ status_message_redraw(struct client *c) if (len > c->tty.sx) len = c->tty.sx; - style_apply(&gc, s->options, "message-style"); + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); + style_apply(&gc, s->options, "message-style", ft); + format_free(ft); - screen_write_start(&ctx, NULL, sl->active); + screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); screen_write_cursormove(&ctx, 0, lines - 1, 0); for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, lines - 1, 0); - screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); + if (c->message_ignore_styles) + screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); + else + format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL); screen_write_stop(&ctx); if (grid_compare(sl->active->grid, old_screen.grid) == 0) { @@ -511,14 +575,17 @@ status_message_redraw(struct client *c) /* Enable status line prompt. */ void -status_prompt_set(struct client *c, const char *msg, const char *input, - prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) +status_prompt_set(struct client *c, struct cmd_find_state *fs, + const char *msg, const char *input, prompt_input_cb inputcb, + prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) { struct format_tree *ft; - char *tmp, *cp; + char *tmp; - ft = format_create(c, NULL, FORMAT_NONE, 0); - format_defaults(ft, c, NULL, NULL, NULL); + if (fs != NULL) + ft = format_create_from_state(NULL, c, fs); + else + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); if (input == NULL) input = ""; @@ -533,27 +600,31 @@ status_prompt_set(struct client *c, const char *msg, const char *input, c->prompt_string = format_expand_time(ft, msg); - c->prompt_buffer = utf8_fromcstr(tmp); + if (flags & PROMPT_INCREMENTAL) { + c->prompt_last = xstrdup(tmp); + c->prompt_buffer = utf8_fromcstr(""); + } else { + c->prompt_last = NULL; + c->prompt_buffer = utf8_fromcstr(tmp); + } c->prompt_index = utf8_strlen(c->prompt_buffer); c->prompt_inputcb = inputcb; c->prompt_freecb = freecb; c->prompt_data = data; - c->prompt_hindex = 0; + memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); c->prompt_flags = flags; + c->prompt_type = prompt_type; c->prompt_mode = PROMPT_ENTRY; if (~flags & PROMPT_INCREMENTAL) c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_REDRAWSTATUS; - if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { - xasprintf(&cp, "=%s", tmp); - c->prompt_inputcb(c, c->prompt_data, cp, 0); - free(cp); - } + if (flags & PROMPT_INCREMENTAL) + c->prompt_inputcb(c, c->prompt_data, "=", 0); free(tmp); format_free(ft); @@ -569,6 +640,9 @@ status_prompt_clear(struct client *c) if (c->prompt_freecb != NULL && c->prompt_data != NULL) c->prompt_freecb(c->prompt_data); + free(c->prompt_last); + c->prompt_last = NULL; + free(c->prompt_string); c->prompt_string = NULL; @@ -603,7 +677,7 @@ status_prompt_update(struct client *c, const char *msg, const char *input) c->prompt_buffer = utf8_fromcstr(tmp); c->prompt_index = utf8_strlen(c->prompt_buffer); - c->prompt_hindex = 0; + memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); c->flags |= CLIENT_REDRAWSTATUS; @@ -622,6 +696,7 @@ status_prompt_redraw(struct client *c) u_int i, lines, offset, left, start, width; u_int pcursor, pwidth; struct grid_cell gc, cursorgc; + struct format_tree *ft; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); @@ -632,10 +707,12 @@ status_prompt_redraw(struct client *c) lines = 1; screen_init(sl->active, c->tty.sx, lines, 0); + ft = format_create_defaults(NULL, c, NULL, NULL, NULL); if (c->prompt_mode == PROMPT_COMMAND) - style_apply(&gc, s->options, "message-command-style"); + style_apply(&gc, s->options, "message-command-style", ft); else - style_apply(&gc, s->options, "message-style"); + style_apply(&gc, s->options, "message-style", ft); + format_free(ft); memcpy(&cursorgc, &gc, sizeof cursorgc); cursorgc.attr ^= GRID_ATTR_REVERSE; @@ -644,7 +721,7 @@ status_prompt_redraw(struct client *c) if (start > c->tty.sx) start = c->tty.sx; - screen_write_start(&ctx, NULL, sl->active); + screen_write_start(&ctx, sl->active); screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); screen_write_cursormove(&ctx, 0, lines - 1, 0); for (offset = 0; offset < c->tty.sx; offset++) @@ -724,7 +801,7 @@ status_prompt_space(const struct utf8_data *ud) } /* - * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key + * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key * as an emacs key; return 2 to append to the buffer. */ static int @@ -733,6 +810,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) if (c->prompt_mode == PROMPT_ENTRY) { switch (key) { case '\003': /* C-c */ + case '\007': /* C-g */ case '\010': /* C-h */ case '\011': /* Tab */ case '\025': /* C-u */ @@ -798,21 +876,32 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) *new_key = KEYC_BSPACE; return (1); case 'b': + *new_key = 'b'|KEYC_META; + return (1); case 'B': - *new_key = 'b'|KEYC_ESCAPE; + *new_key = 'B'|KEYC_VI; return (1); case 'd': *new_key = '\025'; return (1); case 'e': + *new_key = 'e'|KEYC_VI; + return (1); case 'E': + *new_key = 'E'|KEYC_VI; + return (1); case 'w': + *new_key = 'w'|KEYC_VI; + return (1); case 'W': - *new_key = 'f'|KEYC_ESCAPE; + *new_key = 'W'|KEYC_VI; return (1); case 'p': *new_key = '\031'; /* C-y */ return (1); + case 'q': + *new_key = '\003'; /* C-c */ + return (1); case 's': case KEYC_DC: case 'x': @@ -909,19 +998,199 @@ status_prompt_paste(struct client *c) return (1); } +/* Finish completion. */ +static int +status_prompt_replace_complete(struct client *c, const char *s) +{ + char word[64], *allocated = NULL; + size_t size, n, off, idx, used; + struct utf8_data *first, *last, *ud; + + /* Work out where the cursor currently is. */ + idx = c->prompt_index; + if (idx != 0) + idx--; + size = utf8_strlen(c->prompt_buffer); + + /* Find the word we are in. */ + first = &c->prompt_buffer[idx]; + while (first > c->prompt_buffer && !status_prompt_space(first)) + first--; + while (first->size != 0 && status_prompt_space(first)) + first++; + last = &c->prompt_buffer[idx]; + while (last->size != 0 && !status_prompt_space(last)) + last++; + while (last > c->prompt_buffer && status_prompt_space(last)) + last--; + if (last->size != 0) + last++; + if (last < first) + return (0); + if (s == NULL) { + used = 0; + for (ud = first; ud < last; ud++) { + if (used + ud->size >= sizeof word) + break; + memcpy(word + used, ud->data, ud->size); + used += ud->size; + } + if (ud != last) + return (0); + word[used] = '\0'; + } + + /* Try to complete it. */ + if (s == NULL) { + allocated = status_prompt_complete(c, word, + first - c->prompt_buffer); + if (allocated == NULL) + return (0); + s = allocated; + } + + /* Trim out word. */ + n = size - (last - c->prompt_buffer) + 1; /* with \0 */ + memmove(first, last, n * sizeof *c->prompt_buffer); + size -= last - first; + + /* Insert the new word. */ + size += strlen(s); + off = first - c->prompt_buffer; + c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, + sizeof *c->prompt_buffer); + first = c->prompt_buffer + off; + memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); + for (idx = 0; idx < strlen(s); idx++) + utf8_set(&first[idx], s[idx]); + c->prompt_index = (first - c->prompt_buffer) + strlen(s); + + free(allocated); + return (1); +} + +/* Prompt forward to the next beginning of a word. */ +static void +status_prompt_forward_word(struct client *c, size_t size, int vi, + const char *separators) +{ + size_t idx = c->prompt_index; + int word_is_separators; + + /* In emacs mode, skip until the first non-whitespace character. */ + if (!vi) + while (idx != size && + status_prompt_space(&c->prompt_buffer[idx])) + idx++; + + /* Can't move forward if we're already at the end. */ + if (idx == size) { + c->prompt_index = idx; + return; + } + + /* Determine the current character class (separators or not). */ + word_is_separators = status_prompt_in_list(separators, + &c->prompt_buffer[idx]) && + !status_prompt_space(&c->prompt_buffer[idx]); + + /* Skip ahead until the first space or opposite character class. */ + do { + idx++; + if (status_prompt_space(&c->prompt_buffer[idx])) { + /* In vi mode, go to the start of the next word. */ + if (vi) + while (idx != size && + status_prompt_space(&c->prompt_buffer[idx])) + idx++; + break; + } + } while (idx != size && word_is_separators == status_prompt_in_list( + separators, &c->prompt_buffer[idx])); + + c->prompt_index = idx; +} + +/* Prompt forward to the next end of a word. */ +static void +status_prompt_end_word(struct client *c, size_t size, const char *separators) +{ + size_t idx = c->prompt_index; + int word_is_separators; + + /* Can't move forward if we're already at the end. */ + if (idx == size) + return; + + /* Find the next word. */ + do { + idx++; + if (idx == size) { + c->prompt_index = idx; + return; + } + } while (status_prompt_space(&c->prompt_buffer[idx])); + + /* Determine the character class (separators or not). */ + word_is_separators = status_prompt_in_list(separators, + &c->prompt_buffer[idx]); + + /* Skip ahead until the next space or opposite character class. */ + do { + idx++; + if (idx == size) + break; + } while (!status_prompt_space(&c->prompt_buffer[idx]) && + word_is_separators == status_prompt_in_list(separators, + &c->prompt_buffer[idx])); + + /* Back up to the previous character to stop at the end of the word. */ + c->prompt_index = idx - 1; +} + +/* Prompt backward to the previous beginning of a word. */ +static void +status_prompt_backward_word(struct client *c, const char *separators) +{ + size_t idx = c->prompt_index; + int word_is_separators; + + /* Find non-whitespace. */ + while (idx != 0) { + --idx; + if (!status_prompt_space(&c->prompt_buffer[idx])) + break; + } + word_is_separators = status_prompt_in_list(separators, + &c->prompt_buffer[idx]); + + /* Find the character before the beginning of the word. */ + while (idx != 0) { + --idx; + if (status_prompt_space(&c->prompt_buffer[idx]) || + word_is_separators != status_prompt_in_list(separators, + &c->prompt_buffer[idx])) { + /* Go back to the word. */ + idx++; + break; + } + } + c->prompt_index = idx; +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; - char *s, *cp, word[64], prefix = '='; - const char *histstr, *ws = NULL, *keystring; - size_t size, n, off, idx, used; - struct utf8_data tmp, *first, *last, *ud; - int keys; + char *s, *cp, prefix = '='; + const char *histstr, *separators = NULL, *keystring; + size_t size, idx; + struct utf8_data tmp; + int keys, word_is_separators; if (c->prompt_flags & PROMPT_KEY) { - keystring = key_string_lookup_key(key); + keystring = key_string_lookup_key(key, 0); c->prompt_inputcb(c, c->prompt_data, keystring, 1); status_prompt_clear(c); return (0); @@ -937,7 +1206,7 @@ status_prompt_key(struct client *c, key_code key) free(s); return (1); } - key &= ~KEYC_XTERM; + key &= ~KEYC_MASK_FLAGS; keys = options_get_number(c->session->options, "status-keys"); if (keys == MODEKEY_VI) { @@ -982,63 +1251,9 @@ process_key: } break; case '\011': /* Tab */ - if (c->prompt_buffer[0].size == 0) - break; - - idx = c->prompt_index; - if (idx != 0) - idx--; - - /* Find the word we are in. */ - first = &c->prompt_buffer[idx]; - while (first > c->prompt_buffer && !status_prompt_space(first)) - first--; - while (first->size != 0 && status_prompt_space(first)) - first++; - last = &c->prompt_buffer[idx]; - while (last->size != 0 && !status_prompt_space(last)) - last++; - while (last > c->prompt_buffer && status_prompt_space(last)) - last--; - if (last->size != 0) - last++; - if (last <= first) - break; - - used = 0; - for (ud = first; ud < last; ud++) { - if (used + ud->size >= sizeof word) - break; - memcpy(word + used, ud->data, ud->size); - used += ud->size; - } - if (ud != last) - break; - word[used] = '\0'; - - /* And try to complete it. */ - if ((s = status_prompt_complete(c->session, word)) == NULL) - break; - - /* Trim out word. */ - n = size - (last - c->prompt_buffer) + 1; /* with \0 */ - memmove(first, last, n * sizeof *c->prompt_buffer); - size -= last - first; - - /* Insert the new word. */ - size += strlen(s); - off = first - c->prompt_buffer; - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, - sizeof *c->prompt_buffer); - first = c->prompt_buffer + off; - memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); - for (idx = 0; idx < strlen(s); idx++) - utf8_set(&first[idx], s[idx]); - - c->prompt_index = (first - c->prompt_buffer) + strlen(s); - free(s); - - goto changed; + if (status_prompt_replace_complete(c, NULL)) + goto changed; + break; case KEYC_BSPACE: case '\010': /* C-h */ if (c->prompt_index != 0) { @@ -1075,20 +1290,24 @@ process_key: } break; case '\027': /* C-w */ - ws = options_get_string(oo, "word-separators"); + separators = options_get_string(oo, "word-separators"); idx = c->prompt_index; - /* Find a non-separator. */ + /* Find non-whitespace. */ while (idx != 0) { idx--; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) + if (!status_prompt_space(&c->prompt_buffer[idx])) break; } + word_is_separators = status_prompt_in_list(separators, + &c->prompt_buffer[idx]); - /* Find the separator at the beginning of the word. */ + /* Find the character before the beginning of the word. */ while (idx != 0) { idx--; - if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { + if (status_prompt_space(&c->prompt_buffer[idx]) || + word_is_separators != status_prompt_in_list( + separators, &c->prompt_buffer[idx])) { /* Go back to the word. */ idx++; break; @@ -1110,54 +1329,37 @@ process_key: c->prompt_index = idx; goto changed; - case 'f'|KEYC_ESCAPE: case KEYC_RIGHT|KEYC_CTRL: - ws = options_get_string(oo, "word-separators"); - - /* Find a word. */ - while (c->prompt_index != size) { - idx = ++c->prompt_index; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Find the separator at the end of the word. */ - while (c->prompt_index != size) { - idx = ++c->prompt_index; - if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Back up to the end-of-word like vi. */ - if (options_get_number(oo, "status-keys") == MODEKEY_VI && - c->prompt_index != 0) - c->prompt_index--; - + case 'f'|KEYC_META: + separators = options_get_string(oo, "word-separators"); + status_prompt_forward_word(c, size, 0, separators); + goto changed; + case 'E'|KEYC_VI: + status_prompt_end_word(c, size, ""); + goto changed; + case 'e'|KEYC_VI: + separators = options_get_string(oo, "word-separators"); + status_prompt_end_word(c, size, separators); + goto changed; + case 'W'|KEYC_VI: + status_prompt_forward_word(c, size, 1, ""); + goto changed; + case 'w'|KEYC_VI: + separators = options_get_string(oo, "word-separators"); + status_prompt_forward_word(c, size, 1, separators); + goto changed; + case 'B'|KEYC_VI: + status_prompt_backward_word(c, ""); goto changed; - case 'b'|KEYC_ESCAPE: case KEYC_LEFT|KEYC_CTRL: - ws = options_get_string(oo, "word-separators"); - - /* Find a non-separator. */ - while (c->prompt_index != 0) { - idx = --c->prompt_index; - if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) - break; - } - - /* Find the separator at the beginning of the word. */ - while (c->prompt_index != 0) { - idx = --c->prompt_index; - if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { - /* Go back to the word. */ - c->prompt_index++; - break; - } - } + case 'b'|KEYC_META: + separators = options_get_string(oo, "word-separators"); + status_prompt_backward_word(c, separators); goto changed; case KEYC_UP: case '\020': /* C-p */ - histstr = status_prompt_up_history(&c->prompt_hindex); + histstr = status_prompt_up_history(c->prompt_hindex, + c->prompt_type); if (histstr == NULL) break; free(c->prompt_buffer); @@ -1166,7 +1368,8 @@ process_key: goto changed; case KEYC_DOWN: case '\016': /* C-n */ - histstr = status_prompt_down_history(&c->prompt_hindex); + histstr = status_prompt_down_history(c->prompt_hindex, + c->prompt_type); if (histstr == NULL) break; free(c->prompt_buffer); @@ -1194,7 +1397,7 @@ process_key: case '\n': s = utf8_tocstr(c->prompt_buffer); if (*s != '\0') - status_prompt_add_history(s); + status_prompt_add_history(s, c->prompt_type); if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) status_prompt_clear(c); free(s); @@ -1206,17 +1409,27 @@ process_key: status_prompt_clear(c); break; case '\022': /* C-r */ - if (c->prompt_flags & PROMPT_INCREMENTAL) { + if (~c->prompt_flags & PROMPT_INCREMENTAL) + break; + if (c->prompt_buffer[0].size == 0) { + prefix = '='; + free(c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(c->prompt_last); + c->prompt_index = utf8_strlen(c->prompt_buffer); + } else prefix = '-'; - goto changed; - } - break; + goto changed; case '\023': /* C-s */ - if (c->prompt_flags & PROMPT_INCREMENTAL) { + if (~c->prompt_flags & PROMPT_INCREMENTAL) + break; + if (c->prompt_buffer[0].size == 0) { + prefix = '='; + free(c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(c->prompt_last); + c->prompt_index = utf8_strlen(c->prompt_buffer); + } else prefix = '+'; - goto changed; - } - break; + goto changed; default: goto append_key; } @@ -1225,9 +1438,13 @@ process_key: return (0); append_key: - if (key <= 0x1f || key >= KEYC_BASE) + if (key <= 0x1f || (key >= KEYC_BASE && key < KEYC_BASE_END)) return (0); - if (utf8_split(key, &tmp) != UTF8_DONE) + if (key <= 0x7f) + utf8_set(&tmp, key); + else if (KEYC_IS_UNICODE(key)) + utf8_to_data(key, &tmp); + else return (0); c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, @@ -1247,12 +1464,14 @@ append_key: } if (c->prompt_flags & PROMPT_SINGLE) { - s = utf8_tocstr(c->prompt_buffer); - if (strlen(s) != 1) + if (utf8_strlen(c->prompt_buffer) != 1) status_prompt_clear(c); - else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) - status_prompt_clear(c); - free(s); + else { + s = utf8_tocstr(c->prompt_buffer); + if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) + status_prompt_clear(c); + free(s); + } } changed: @@ -1269,65 +1488,88 @@ changed: /* Get previous line from the history. */ static const char * -status_prompt_up_history(u_int *idx) +status_prompt_up_history(u_int *idx, u_int type) { /* * History runs from 0 to size - 1. Index is from 0 to size. Zero is * empty. */ - if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) + if (status_prompt_hsize[type] == 0 || + idx[type] == status_prompt_hsize[type]) return (NULL); - (*idx)++; - return (status_prompt_hlist[status_prompt_hsize - *idx]); + idx[type]++; + return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); } /* Get next line from the history. */ static const char * -status_prompt_down_history(u_int *idx) +status_prompt_down_history(u_int *idx, u_int type) { - if (status_prompt_hsize == 0 || *idx == 0) + if (status_prompt_hsize[type] == 0 || idx[type] == 0) return (""); - (*idx)--; - if (*idx == 0) + idx[type]--; + if (idx[type] == 0) return (""); - return (status_prompt_hlist[status_prompt_hsize - *idx]); + return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); } /* Add line to the history. */ static void -status_prompt_add_history(const char *line) +status_prompt_add_history(const char *line, u_int type) { - size_t size; + u_int i, oldsize, newsize, freecount, hlimit, new = 1; + size_t movesize; - if (status_prompt_hsize > 0 && - strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) - return; + oldsize = status_prompt_hsize[type]; + if (oldsize > 0 && + strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0) + new = 0; - if (status_prompt_hsize == PROMPT_HISTORY) { - free(status_prompt_hlist[0]); - - size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; - memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); - - status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); - return; + hlimit = options_get_number(global_options, "prompt-history-limit"); + if (hlimit > oldsize) { + if (new == 0) + return; + newsize = oldsize + new; + } else { + newsize = hlimit; + freecount = oldsize + new - newsize; + if (freecount > oldsize) + freecount = oldsize; + if (freecount == 0) + return; + for (i = 0; i < freecount; i++) + free(status_prompt_hlist[type][i]); + movesize = (oldsize - freecount) * + sizeof *status_prompt_hlist[type]; + if (movesize > 0) { + memmove(&status_prompt_hlist[type][0], + &status_prompt_hlist[type][freecount], movesize); + } } - status_prompt_hlist = xreallocarray(status_prompt_hlist, - status_prompt_hsize + 1, sizeof *status_prompt_hlist); - status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); + if (newsize == 0) { + free(status_prompt_hlist[type]); + status_prompt_hlist[type] = NULL; + } else if (newsize != oldsize) { + status_prompt_hlist[type] = + xreallocarray(status_prompt_hlist[type], newsize, + sizeof *status_prompt_hlist[type]); + } + + if (new == 1 && newsize > 0) + status_prompt_hlist[type][newsize - 1] = xstrdup(line); + status_prompt_hsize[type] = newsize; } /* Build completion list. */ -char ** -status_prompt_complete_list(u_int *size, const char *s) +static char ** +status_prompt_complete_list(u_int *size, const char *s, int at_start) { char **list = NULL; const char **layout, *value, *cp; const struct cmd_entry **cmdent; const struct options_table_entry *oe; - u_int idx; size_t slen = strlen(s), valuelen; struct options_entry *o; struct options_array_item *a; @@ -1342,17 +1584,10 @@ status_prompt_complete_list(u_int *size, const char *s) list = xreallocarray(list, (*size) + 1, sizeof *list); list[(*size)++] = xstrdup((*cmdent)->name); } - } - for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, slen) == 0) { + if ((*cmdent)->alias != NULL && + strncmp((*cmdent)->alias, s, slen) == 0) { list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(oe->name); - } - } - for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, slen) == 0) { - list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = xstrdup(*layout); + list[(*size)++] = xstrdup((*cmdent)->alias); } } o = options_get_only(global_options, "command-alias"); @@ -1373,8 +1608,21 @@ status_prompt_complete_list(u_int *size, const char *s) a = options_array_next(a); } } - for (idx = 0; idx < (*size); idx++) - log_debug("complete %u: %s", idx, list[idx]); + if (at_start) + return (list); + + for (oe = options_table; oe->name != NULL; oe++) { + if (strncmp(oe->name, s, slen) == 0) { + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrdup(oe->name); + } + } + for (layout = layouts; *layout != NULL; layout++) { + if (strncmp(*layout, s, slen) == 0) { + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrdup(*layout); + } + } return (list); } @@ -1386,6 +1634,8 @@ status_prompt_complete_prefix(char **list, u_int size) u_int i; size_t j; + if (list == NULL || size == 0) + return (NULL); out = xstrdup(list[0]); for (i = 1; i < size; i++) { j = strlen(list[i]); @@ -1399,124 +1649,336 @@ status_prompt_complete_prefix(char **list, u_int size) return (out); } +/* Complete word menu callback. */ +static void +status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, + void *data) +{ + struct status_prompt_menu *spm = data; + struct client *c = spm->c; + u_int i; + char *s; + + if (key != KEYC_NONE) { + idx += spm->start; + if (spm->flag == '\0') + s = xstrdup(spm->list[idx]); + else + xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { + free(c->prompt_buffer); + c->prompt_buffer = utf8_fromcstr(s); + c->prompt_index = utf8_strlen(c->prompt_buffer); + c->flags |= CLIENT_REDRAWSTATUS; + } else if (status_prompt_replace_complete(c, s)) + c->flags |= CLIENT_REDRAWSTATUS; + free(s); + } + + for (i = 0; i < spm->size; i++) + free(spm->list[i]); + free(spm->list); +} + +/* Show complete word menu. */ +static int +status_prompt_complete_list_menu(struct client *c, char **list, u_int size, + u_int offset, char flag) +{ + struct menu *menu; + struct menu_item item; + struct status_prompt_menu *spm; + u_int lines = status_line_size(c), height, i; + u_int py; + + if (size <= 1) + return (0); + if (c->tty.sy - lines < 3) + return (0); + + spm = xmalloc(sizeof *spm); + spm->c = c; + spm->size = size; + spm->list = list; + spm->flag = flag; + + height = c->tty.sy - lines - 2; + if (height > 10) + height = 10; + if (height > size) + height = size; + spm->start = size - height; + + menu = menu_create(""); + for (i = spm->start; i < size; i++) { + item.name = list[i]; + item.key = '0' + (i - spm->start); + item.command = NULL; + menu_add_item(menu, &item, NULL, NULL, NULL); + } + + if (options_get_number(c->session->options, "status-position") == 0) + py = lines; + else + py = c->tty.sy - 3 - height; + offset += utf8_cstrwidth(c->prompt_string); + if (offset > 2) + offset -= 2; + else + offset = 0; + + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + py, c, NULL, status_prompt_menu_callback, spm) != 0) { + menu_free(menu); + free(spm); + return (0); + } + return (1); +} + +/* Show complete word menu. */ +static char * +status_prompt_complete_window_menu(struct client *c, struct session *s, + const char *word, u_int offset, char flag) +{ + struct menu *menu; + struct menu_item item; + struct status_prompt_menu *spm; + struct winlink *wl; + char **list = NULL, *tmp; + u_int lines = status_line_size(c), height; + u_int py, size = 0; + + if (c->tty.sy - lines < 3) + return (NULL); + + spm = xmalloc(sizeof *spm); + spm->c = c; + spm->flag = flag; + + height = c->tty.sy - lines - 2; + if (height > 10) + height = 10; + spm->start = 0; + + menu = menu_create(""); + RB_FOREACH(wl, winlinks, &s->windows) { + if (word != NULL && *word != '\0') { + xasprintf(&tmp, "%d", wl->idx); + if (strncmp(tmp, word, strlen(word)) != 0) { + free(tmp); + continue; + } + free(tmp); + } + + list = xreallocarray(list, size + 1, sizeof *list); + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { + xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); + xasprintf(&list[size++], "%d", wl->idx); + } else { + xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, + wl->window->name); + xasprintf(&list[size++], "%s:%d", s->name, wl->idx); + } + item.name = tmp; + item.key = '0' + size - 1; + item.command = NULL; + menu_add_item(menu, &item, NULL, NULL, NULL); + free(tmp); + + if (size == height) + break; + } + if (size == 0) { + menu_free(menu); + return (NULL); + } + if (size == 1) { + menu_free(menu); + if (flag != '\0') { + xasprintf(&tmp, "-%c%s", flag, list[0]); + free(list[0]); + } else + tmp = list[0]; + free(list); + return (tmp); + } + if (height > size) + height = size; + + spm->size = size; + spm->list = list; + + if (options_get_number(c->session->options, "status-position") == 0) + py = lines; + else + py = c->tty.sy - 3 - height; + offset += utf8_cstrwidth(c->prompt_string); + if (offset > 2) + offset -= 2; + else + offset = 0; + + if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, + py, c, NULL, status_prompt_menu_callback, spm) != 0) { + menu_free(menu); + free(spm); + return (NULL); + } + return (NULL); +} + +/* Sort complete list. */ +static int +status_prompt_complete_sort(const void *a, const void *b) +{ + const char **aa = (const char **)a, **bb = (const char **)b; + + return (strcmp(*aa, *bb)); +} + +/* Complete a session. */ +static char * +status_prompt_complete_session(char ***list, u_int *size, const char *s, + char flag) +{ + struct session *loop; + char *out, *tmp, n[11]; + + RB_FOREACH(loop, sessions, &sessions) { + if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) { + *list = xreallocarray(*list, (*size) + 2, + sizeof **list); + xasprintf(&(*list)[(*size)++], "%s:", loop->name); + } else if (*s == '$') { + xsnprintf(n, sizeof n, "%u", loop->id); + if (s[1] == '\0' || + strncmp(n, s + 1, strlen(s) - 1) == 0) { + *list = xreallocarray(*list, (*size) + 2, + sizeof **list); + xasprintf(&(*list)[(*size)++], "$%s:", n); + } + } + } + out = status_prompt_complete_prefix(*list, *size); + if (out != NULL && flag != '\0') { + xasprintf(&tmp, "-%c%s", flag, out); + free(out); + out = tmp; + } + return (out); +} + /* Complete word. */ static char * -status_prompt_complete(struct session *session, const char *s) +status_prompt_complete(struct client *c, const char *word, u_int offset) { - char **list = NULL; - const char *colon; + struct session *session; + const char *s, *colon; + char **list = NULL, *copy = NULL, *out = NULL; + char flag = '\0'; u_int size = 0, i; - struct session *s_loop; - struct winlink *wl; - struct window *w; - char *copy, *out, *tmp; - if (*s == '\0') + if (*word == '\0' && + c->prompt_type != PROMPT_TYPE_TARGET && + c->prompt_type != PROMPT_TYPE_WINDOW_TARGET) return (NULL); - out = NULL; - if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { - list = status_prompt_complete_list(&size, s); + if (c->prompt_type != PROMPT_TYPE_TARGET && + c->prompt_type != PROMPT_TYPE_WINDOW_TARGET && + strncmp(word, "-t", 2) != 0 && + strncmp(word, "-s", 2) != 0) { + list = status_prompt_complete_list(&size, word, offset == 0); if (size == 0) out = NULL; else if (size == 1) xasprintf(&out, "%s ", list[0]); else out = status_prompt_complete_prefix(list, size); - for (i = 0; i < size; i++) - free(list[i]); - free(list); - return (out); - } - copy = xstrdup(s); - - colon = ":"; - if (copy[strlen(copy) - 1] == ':') - copy[strlen(copy) - 1] = '\0'; - else - colon = ""; - s = copy + 2; - - RB_FOREACH(s_loop, sessions, &sessions) { - if (strncmp(s_loop->name, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 2, sizeof *list); - list[size++] = s_loop->name; - } - } - if (size == 1) { - out = xstrdup(list[0]); - if (session_find(list[0]) != NULL) - colon = ":"; - } else if (size != 0) - out = status_prompt_complete_prefix(list, size); - if (out != NULL) { - xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); - free(out); - out = tmp; goto found; } - colon = ""; - if (*s == ':') { - RB_FOREACH(wl, winlinks, &session->windows) { - xasprintf(&tmp, ":%s", wl->window->name); - if (strncmp(tmp, s, strlen(s)) == 0){ - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); - - xasprintf(&tmp, ":%d", wl->idx); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); - } + if (c->prompt_type == PROMPT_TYPE_TARGET || + c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { + s = word; + flag = '\0'; } else { - RB_FOREACH(s_loop, sessions, &sessions) { - RB_FOREACH(wl, winlinks, &s_loop->windows) { - w = wl->window; + s = word + 2; + flag = word[1]; + offset += 2; + } - xasprintf(&tmp, "%s:%s", s_loop->name, w->name); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); + /* If this is a window completion, open the window menu. */ + if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { + out = status_prompt_complete_window_menu(c, c->session, s, + offset, '\0'); + goto found; + } + colon = strchr(s, ':'); - xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); - if (strncmp(tmp, s, strlen(s)) == 0) { - list = xreallocarray(list, size + 1, - sizeof *list); - list[size++] = tmp; - continue; - } - free(tmp); - } + /* If there is no colon, complete as a session. */ + if (colon == NULL) { + out = status_prompt_complete_session(&list, &size, s, flag); + goto found; + } + + /* If there is a colon but no period, find session and show a menu. */ + if (strchr(colon + 1, '.') == NULL) { + if (*s == ':') + session = c->session; + else { + copy = xstrdup(s); + *strchr(copy, ':') = '\0'; + session = session_find(copy); + free(copy); + if (session == NULL) + goto found; } + out = status_prompt_complete_window_menu(c, session, colon + 1, + offset, flag); + if (out == NULL) + return (NULL); } - if (size == 1) { - out = xstrdup(list[0]); - colon = " "; - } else if (size != 0) - out = status_prompt_complete_prefix(list, size); - if (out != NULL) { - xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); - out = tmp; - } - - for (i = 0; i < size; i++) - free((void *)list[i]); found: - free(copy); - free(list); + if (size != 0) { + qsort(list, size, sizeof *list, status_prompt_complete_sort); + for (i = 0; i < size; i++) + log_debug("complete %u: %s", i, list[i]); + } + + if (out != NULL && strcmp(word, out) == 0) { + free(out); + out = NULL; + } + if (out != NULL || + !status_prompt_complete_list_menu(c, list, size, offset, flag)) { + for (i = 0; i < size; i++) + free(list[i]); + free(list); + } return (out); } + +/* Return the type of the prompt as an enum. */ +enum prompt_type +status_prompt_type(const char *type) +{ + u_int i; + + for (i = 0; i < PROMPT_NTYPES; i++) { + if (strcmp(type, status_prompt_type_string(i)) == 0) + return (i); + } + return (PROMPT_TYPE_INVALID); +} + +/* Accessor for prompt_type_strings. */ +const char * +status_prompt_type_string(u_int type) +{ + if (type >= PROMPT_NTYPES) + return ("invalid"); + return (prompt_type_strings[type]); +} diff --git a/style.c b/style.c index 6ba4c524..89a4e63a 100644 --- a/style.c +++ b/style.c @@ -26,11 +26,12 @@ #include "tmux.h" /* Mask for bits not included in style. */ -#define STYLE_ATTR_MASK (~GRID_ATTR_CHARSET) +#define STYLE_ATTR_MASK (~0) /* Default style. */ static struct style style_default = { { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + 0, 8, STYLE_ALIGN_DEFAULT, @@ -50,7 +51,7 @@ int style_parse(struct style *sy, const struct grid_cell *base, const char *in) { struct style saved; - const char delimiters[] = " ,", *cp; + const char delimiters[] = " ,\n", *cp; char tmp[256], *found; int value; size_t end; @@ -59,6 +60,7 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) return (0); style_copy(&saved, sy); + log_debug("%s: %s", __func__, in); do { while (*in != '\0' && strchr(delimiters, *in) != NULL) in++; @@ -71,12 +73,17 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) memcpy(tmp, in, end); tmp[end] = '\0'; + log_debug("%s: %s", __func__, tmp); if (strcasecmp(tmp, "default") == 0) { sy->gc.fg = base->fg; sy->gc.bg = base->bg; sy->gc.attr = base->attr; sy->gc.flags = base->flags; - } else if (strcasecmp(tmp, "push-default") == 0) + } else if (strcasecmp(tmp, "ignore") == 0) + sy->ignore = 1; + else if (strcasecmp(tmp, "noignore") == 0) + sy->ignore = 0; + else if (strcasecmp(tmp, "push-default") == 0) sy->default_type = STYLE_DEFAULT_PUSH; else if (strcasecmp(tmp, "pop-default") == 0) sy->default_type = STYLE_DEFAULT_POP; @@ -132,6 +139,8 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->align = STYLE_ALIGN_CENTRE; else if (strcasecmp(tmp + 6, "right") == 0) sy->align = STYLE_ALIGN_RIGHT; + else if (strcasecmp(tmp + 6, "absolute-centre") == 0) + sy->align = STYLE_ALIGN_ABSOLUTE_CENTRE; else goto error; } else if (end > 5 && strncasecmp(tmp, "fill=", 5) == 0) { @@ -220,6 +229,8 @@ style_tostring(struct style *sy) tmp = "centre"; else if (sy->align == STYLE_ALIGN_RIGHT) tmp = "right"; + else if (sy->align == STYLE_ALIGN_ABSOLUTE_CENTRE) + tmp = "absolute-centre"; off += xsnprintf(s + off, sizeof s - off, "%salign=%s", comma, tmp); comma = ","; @@ -247,7 +258,7 @@ style_tostring(struct style *sy) colour_tostring(gc->bg)); comma = ","; } - if (gc->attr != 0 && gc->attr != GRID_ATTR_CHARSET) { + if (gc->attr != 0) { xsnprintf(s + off, sizeof s - off, "%s%s", comma, attributes_tostring(gc->attr)); comma = ","; @@ -258,17 +269,37 @@ style_tostring(struct style *sy) return (s); } -/* Apply a style. */ +/* Apply a style on top of the given style. */ void -style_apply(struct grid_cell *gc, struct options *oo, const char *name) +style_add(struct grid_cell *gc, struct options *oo, const char *name, + struct format_tree *ft) { - struct style *sy; + struct style *sy; + struct format_tree *ft0 = NULL; - memcpy(gc, &grid_default_cell, sizeof *gc); - sy = options_get_style(oo, name); - gc->fg = sy->gc.fg; - gc->bg = sy->gc.bg; + if (ft == NULL) + ft = ft0 = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + + sy = options_string_to_style(oo, name, ft); + if (sy == NULL) + sy = &style_default; + if (sy->gc.fg != 8) + gc->fg = sy->gc.fg; + if (sy->gc.bg != 8) + gc->bg = sy->gc.bg; gc->attr |= sy->gc.attr; + + if (ft0 != NULL) + format_free(ft0); +} + +/* Apply a style on top of the default style. */ +void +style_apply(struct grid_cell *gc, struct options *oo, const char *name, + struct format_tree *ft) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + style_add(gc, oo, name, ft); } /* Initialize style from cell. */ @@ -285,30 +316,3 @@ style_copy(struct style *dst, struct style *src) { memcpy(dst, src, sizeof *dst); } - -/* Check if two styles are (visibly) the same. */ -int -style_equal(struct style *sy1, struct style *sy2) -{ - struct grid_cell *gc1 = &sy1->gc; - struct grid_cell *gc2 = &sy2->gc; - - if (gc1->fg != gc2->fg) - return (0); - if (gc1->bg != gc2->bg) - return (0); - if ((gc1->attr & STYLE_ATTR_MASK) != (gc2->attr & STYLE_ATTR_MASK)) - return (0); - if (sy1->fill != sy2->fill) - return (0); - if (sy1->align != sy2->align) - return (0); - return (1); -} - -/* Is this style default? */ -int -style_is_default(struct style *sy) -{ - return (style_equal(sy, &style_default)); -} diff --git a/tmux-protocol.h b/tmux-protocol.h new file mode 100644 index 00000000..08422291 --- /dev/null +++ b/tmux-protocol.h @@ -0,0 +1,114 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 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. + */ + +#ifndef TMUX_PROTOCOL_H +#define TMUX_PROTOCOL_H + +/* Protocol version. */ +#define PROTOCOL_VERSION 8 + +/* Message types. */ +enum msgtype { + MSG_VERSION = 12, + + MSG_IDENTIFY_FLAGS = 100, + MSG_IDENTIFY_TERM, + MSG_IDENTIFY_TTYNAME, + MSG_IDENTIFY_OLDCWD, /* unused */ + MSG_IDENTIFY_STDIN, + MSG_IDENTIFY_ENVIRON, + MSG_IDENTIFY_DONE, + MSG_IDENTIFY_CLIENTPID, + MSG_IDENTIFY_CWD, + MSG_IDENTIFY_FEATURES, + MSG_IDENTIFY_STDOUT, + MSG_IDENTIFY_LONGFLAGS, + MSG_IDENTIFY_TERMINFO, + + MSG_COMMAND = 200, + MSG_DETACH, + MSG_DETACHKILL, + MSG_EXIT, + MSG_EXITED, + MSG_EXITING, + MSG_LOCK, + MSG_READY, + MSG_RESIZE, + MSG_SHELL, + MSG_SHUTDOWN, + MSG_OLDSTDERR, /* unused */ + MSG_OLDSTDIN, /* unused */ + MSG_OLDSTDOUT, /* unused */ + MSG_SUSPEND, + MSG_UNLOCK, + MSG_WAKEUP, + MSG_EXEC, + MSG_FLAGS, + + MSG_READ_OPEN = 300, + MSG_READ, + MSG_READ_DONE, + MSG_WRITE_OPEN, + MSG_WRITE, + MSG_WRITE_READY, + MSG_WRITE_CLOSE +}; + +/* + * Message data. + * + * Don't forget to bump PROTOCOL_VERSION if any of these change! + */ +struct msg_command { + int argc; +}; /* followed by packed argv */ + +struct msg_read_open { + int stream; + int fd; +}; /* followed by path */ + +struct msg_read_data { + int stream; +}; + +struct msg_read_done { + int stream; + int error; +}; + +struct msg_write_open { + int stream; + int fd; + int flags; +}; /* followed by path */ + +struct msg_write_data { + int stream; +}; /* followed by data */ + +struct msg_write_ready { + int stream; + int error; +}; + +struct msg_write_close { + int stream; +}; + +#endif /* TMUX_PROTOCOL_H */ diff --git a/tmux.1 b/tmux.1 index 4f7b693c..80fde996 100644 --- a/tmux.1 +++ b/tmux.1 @@ -23,11 +23,12 @@ .Sh SYNOPSIS .Nm tmux .Bk -words -.Op Fl 2CluvV +.Op Fl 2CDluvV .Op Fl c Ar shell-command .Op Fl f Ar file .Op Fl L Ar socket-name .Op Fl S Ar socket-path +.Op Fl T Ar features .Op Ar command Op Ar flags .Ek .Sh DESCRIPTION @@ -98,6 +99,8 @@ The options are as follows: Force .Nm to assume the terminal supports 256 colours. +This is equivalent to +.Fl T Ar 256 . .It Fl C Start in control mode (see the .Sx CONTROL MODE @@ -119,6 +122,17 @@ This option is for compatibility with when .Nm is used as a login shell. +.It Fl D +Do not start the +.Nm +server as a daemon. +This also turns the +.Ic exit-empty +option off. +With +.Fl D , +.Ar command +may not be specified. .It Fl f Ar file Specify an alternative configuration file. By default, @@ -155,8 +169,17 @@ independent servers to be run. Unlike .Fl S -a full path is not necessary: the sockets are all created in the same -directory. +a full path is not necessary: the sockets are all created in a directory +.Pa tmux-UID +under the directory given by +.Ev TMUX_TMPDIR +or in +.Pa /tmp . +The +.Pa tmux-UID +directory is created by +.Nm +and must not be world readable, writable or executable. .Pp If the socket is accidentally removed, the .Dv SIGUSR1 @@ -168,6 +191,11 @@ directories are missing). Behave as a login shell. This flag currently has no effect and is for compatibility with other shells when using tmux as a login shell. +.It Fl N +Do not start the server even if the command would normally do so (for example +.Ic new-session +or +.Ic start-server ) . .It Fl S Ar socket-path Specify a full alternative path to the server socket. If @@ -186,6 +214,14 @@ that is set does not contain .Qq UTF-8 or .Qq UTF8 . +This is equivalent to +.Fl T Ar UTF-8 . +.It Fl T Ar features +Set terminal features for the client. +This is a comma-separated list of features. +See the +.Ic terminal-features +option. .It Fl v Request verbose logging. Log messages will be saved into @@ -486,6 +522,67 @@ Commands separated by semicolons together form a - if a command in the sequence encounters an error, no subsequent commands are executed. .Pp +It is recommended that a semicolon used as a command separator should be +written as an individual token, for example from +.Xr sh 1 : +.Bd -literal -offset indent +$ tmux neww \\; splitw +.Ed +.Pp +Or: +.Bd -literal -offset indent +$ tmux neww ';' splitw +.Ed +.Pp +Or from the tmux command prompt: +.Bd -literal -offset indent +neww ; splitw +.Ed +.Pp +However, a trailing semicolon is also interpreted as a command separator, +for example in these +.Xr sh 1 +commands: +.Bd -literal -offset indent +$ tmux neww\e\e; splitw +.Ed +.Pp +Or: +.Bd -literal -offset indent +$ tmux 'neww;' splitw +.Ed +.Pp +As in these examples, when running tmux from the shell extra care must be taken +to properly quote semicolons: +.Bl -enum -offset Ds +.It +Semicolons that should be interpreted as a command separator +should be escaped according to the shell conventions. +For +.Xr sh 1 +this typically means quoted (such as +.Ql neww ';' splitw ) +or escaped (such as +.Ql neww \e\e\e\e; splitw ) . +.It +Individual semicolons or trailing semicolons that should be interpreted as +arguments should be escaped twice: once according to the shell conventions and +a second time for +.Nm ; +for example: +.Bd -literal -offset indent +$ tmux neww 'foo\e\e;' bar +$ tmux neww foo\e\e\e\e; bar +.Ed +.It +Semicolons that are not individual tokens or trailing another token should only +be escaped once according to shell conventions; for example: +.Bd -literal -offset indent +$ tmux neww 'foo-;-bar' +$ tmux neww foo-\e\e;-bar +.Ed +.El +.Pp Comments are marked by the unquoted # character - any remaining text after a comment is ignored until the end of the line. .Pp @@ -530,17 +627,14 @@ is removed) and are not treated as having any special meaning - so for example variable. .El .Pp -Braces are similar to single quotes in that the text inside is taken literally -without any replacements but this also includes line continuation. -Braces can span multiple lines in which case a literal newline is included in the -string. -They are designed to avoid the need for additional escaping when passing a group -of +Braces are parsed as a configuration file (so conditions such as +.Ql %if +are processed) and then converted into a string. +They are designed to avoid the need for additional escaping when passing a +group of .Nm -or shell commands as an argument (for example to -.Ic if-shell -or -.Ic pipe-pane ) . +commands as an argument (for example to +.Ic if-shell ) . These two examples produce an identical command - note that no escaping is needed when using {}: .Bd -literal -offset indent @@ -548,7 +642,7 @@ if-shell true { display -p 'brace-dollar-foo: }$foo' } -if-shell true "\en display -p 'brace-dollar-foo: }\e$foo'\en" +if-shell true "display -p 'brace-dollar-foo: }\e$foo'" .Ed .Pp Braces may be enclosed inside braces, for example: @@ -565,6 +659,18 @@ Environment variables may be set by using the syntax for example .Ql HOME=/home/user . Variables set during parsing are added to the global environment. +A hidden variable may be set with +.Ql %hidden , +for example: +.Bd -literal -offset indent +%hidden MYVAR=42 +.Ed +.Pp +Hidden variables are not passed to the environment of processes created +by tmux. +See the +.Sx GLOBAL AND SESSION ENVIRONMENT +section. .Pp Commands may be parsed conditionally by surrounding them with .Ql %if , @@ -827,12 +933,12 @@ arguments are commands. This may be a single argument passed to the shell, for example: .Bd -literal -offset indent -new-window 'vi /etc/passwd' +new-window 'vi ~/.tmux.conf' .Ed .Pp Will run: .Bd -literal -offset indent -/bin/sh -c 'vi /etc/passwd' +/bin/sh -c 'vi ~/.tmux.conf' .Ed .Pp Additionally, the @@ -849,7 +955,7 @@ to be given as multiple arguments and executed directly (without This can avoid issues with shell quoting. For example: .Bd -literal -offset indent -$ tmux new-window vi /etc/passwd +$ tmux new-window vi ~/.tmux.conf .Ed .Pp Will run @@ -895,7 +1001,7 @@ $ tmux kill-window -t :1 $ tmux new-window \e; split-window -d -$ tmux new-session -d 'vi /etc/passwd' \e; split-window -d \e; attach +$ tmux new-session -d 'vi ~/.tmux.conf' \e; split-window -d \e; attach .Ed .Sh CLIENTS AND SESSIONS The @@ -924,6 +1030,7 @@ The following commands are available to manage clients and sessions: .It Xo Ic attach-session .Op Fl dErx .Op Fl c Ar working-directory +.Op Fl f Ar flags .Op Fl t Ar target-session .Xc .D1 (alias: Ic attach ) @@ -941,12 +1048,45 @@ is given, send .Dv SIGHUP to the parent process of the client as well as detaching the client, typically causing it to exit. +.Fl f +sets a comma-separated list of client flags. +The flags are: +.Bl -tag -width Ds +.It active-pane +the client has an independent active pane +.It ignore-size +the client does not affect the size of other clients +.It no-output +the client does not receive pane output in control mode +.It pause-after=seconds +output is paused once the pane is +.Ar seconds +behind in control mode +.It read-only +the client is read-only +.It wait-exit +wait for an empty line input before exiting in control mode +.El +.Pp +A leading +.Ql \&! +turns a flag off if the client is already attached. .Fl r -signifies the client is read-only (only keys bound to the +is an alias for +.Fl f +.Ar read-only,ignore-size . +When a client is read-only, only keys bound to the .Ic detach-client or .Ic switch-client -commands have any effect) +commands have any effect. +A client with the +.Ar active-pane +flag allows the active pane to be selected independently of the window's active +pane used by clients without the flag. +This only affects the cursor position and commands issued from the client; +other features such as hooks and styles continue to use the window's active +pane. .Pp If no server is started, .Ic attach-session @@ -1036,16 +1176,25 @@ If is specified, list only clients connected to that session. .It Xo Ic list-commands .Op Fl F Ar format +.Op Ar command .Xc .D1 (alias: Ic lscm ) -List the syntax of all commands supported by +List the syntax of +.Ar command +or - if omitted - of all commands supported by .Nm . -.It Ic list-sessions Op Fl F Ar format +.It Xo Ic list-sessions +.Op Fl F Ar format +.Op Fl f Ar filter +.Xc .D1 (alias: Ic ls ) List all sessions managed by the server. -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only sessions for which the filter is true are shown. +See the .Sx FORMATS section. .It Ic lock-client Op Fl t Ar target-client @@ -1062,6 +1211,8 @@ Lock all clients attached to .It Xo Ic new-session .Op Fl AdDEPX .Op Fl c Ar start-directory +.Op Fl e Ar environment +.Op Fl f Ar flags .Op Fl F Ar format .Op Fl n Ar window-name .Op Fl s Ar session-name @@ -1099,6 +1250,9 @@ or is given, the .Ic default-size option is set for the session. +.Fl f +sets a comma-separated list of client flags (see +.Ic attach-session ) . .Pp If run from a terminal, any .Xr termios 4 @@ -1168,10 +1322,17 @@ If is used, the .Ic update-environment option will not be applied. +.Fl e +takes the form +.Ql VARIABLE=value +and sets an environment variable for the newly created session; it may be +specified multiple times. .It Xo Ic refresh-client .Op Fl cDlLRSU -.Op Fl C Ar XxY -.Op Fl F Ar flags +.Op Fl A Ar pane:state +.Op Fl B Ar name:what:format +.Op Fl C Ar size +.Op Fl f Ar flags .Op Fl t Ar target-client .Op Ar adjustment .Xc @@ -1214,12 +1375,73 @@ window, changing the current window in the attached session will reset it. .Pp .Fl C -sets the width and height of a control client and -.Fl F -sets a comma-separated list of flags. -Currently the only flag available is -.Ql no-output -to disable receiving pane output. +sets the width and height of a control mode client or of a window for a +control mode client, +.Ar size +must be one of +.Ql widthxheight +or +.Ql window ID:widthxheight , +for example +.Ql 80x24 +or +.Ql @0:80x24 . +.Fl A +allows a control mode client to trigger actions on a pane. +The argument is a pane ID (with leading +.Ql % ) , +a colon, then one of +.Ql on , +.Ql off , +.Ql continue +or +.Ql pause . +If +.Ql off , +.Nm +will not send output from the pane to the client and if all clients have turned +the pane off, will stop reading from the pane. +If +.Ql continue , +.Nm +will return to sending output to the pane if it was paused (manually or with the +.Ar pause-after +flag). +If +.Ql pause , +.Nm +will pause the pane. +.Fl A +may be given multiple times for different panes. +.Pp +.Fl B +sets a subscription to a format for a control mode client. +The argument is split into three items by colons: +.Ar name +is a name for the subscription; +.Ar what +is a type of item to subscribe to; +.Ar format +is the format. +After a subscription is added, changes to the format are reported with the +.Ic %subscription-changed +notification, at most once a second. +If only the name is given, the subscription is removed. +.Ar what +may be empty to check the format only for the attached session, or one of: +a pane ID such as +.Ql %0 ; +.Ql %* +for all panes in the attached session; +a window ID such as +.Ql @0 ; +or +.Ql @* +for all windows in the attached session. +.Pp +.Fl f +sets a comma-separated list of client flags, see +.Ic attach-session . .Pp .Fl l requests the clipboard from the client using the @@ -1252,21 +1474,16 @@ Rename the session to .Op Fl t Ar target-client .Xc .D1 (alias: Ic showmsgs ) -Show client messages or server information. -Any messages displayed on the status line are saved in a per-client message -log, up to a maximum of the limit set by the +Show server messages or information. +Messages are stored, up to a maximum of the limit set by the .Ar message-limit server option. -With -.Fl t , -display the log for -.Ar target-client . .Fl J and .Fl T show debugging information about jobs and terminals. .It Xo Ic source-file -.Op Fl nqv +.Op Fl Fnqv .Ar path .Ar ... .Xc @@ -1277,6 +1494,11 @@ Execute commands from one or more files specified by .Xr glob 7 patterns). If +.Fl F +is present, then +.Ar path +is expanded as a format. +If .Fl q is given, no error will be returned if .Ar path @@ -1339,7 +1561,11 @@ or is used, the client is moved to the last, next or previous session respectively. .Fl r -toggles whether a client is read-only (see the +toggles the client +.Ic read-only +and +.Ic ignore-size +flags (see the .Ic attach-session command). .Pp @@ -1410,6 +1636,10 @@ This mode is entered with the command, bound to .Ql \&[ by default. +Copied text can be pasted with the +.Ic paste-buffer +command, bound to +.Ql \&] . .It View mode, which is like copy mode but is entered when a command that produces output, such as @@ -1456,11 +1686,17 @@ The following commands are supported in copy mode: .It Li "bottom-line" Ta "L" Ta "" .It Li "cancel" Ta "q" Ta "Escape" .It Li "clear-selection" Ta "Escape" Ta "C-g" -.It Li "copy-end-of-line []" Ta "D" Ta "C-k" +.It Li "copy-end-of-line []" Ta "" Ta "" +.It Li "copy-end-of-line-and-cancel []" Ta "" Ta "" +.It Li "copy-pipe-end-of-line [] []" Ta "" Ta "" +.It Li "copy-pipe-end-of-line-and-cancel [] []" Ta "D" Ta "C-k" .It Li "copy-line []" Ta "" Ta "" -.It Li "copy-pipe []" Ta "" Ta "" -.It Li "copy-pipe-no-clear []" Ta "" Ta "" -.It Li "copy-pipe-and-cancel []" Ta "" Ta "" +.It Li "copy-line-and-cancel []" Ta "" Ta "" +.It Li "copy-pipe-line [] []" Ta "" Ta "" +.It Li "copy-pipe-line-and-cancel [] []" Ta "" Ta "" +.It Li "copy-pipe [] []" Ta "" Ta "" +.It Li "copy-pipe-no-clear [] []" Ta "" Ta "" +.It Li "copy-pipe-and-cancel [] []" Ta "" Ta "" .It Li "copy-selection []" Ta "" Ta "" .It Li "copy-selection-no-clear []" Ta "" Ta "" .It Li "copy-selection-and-cancel []" Ta "Enter" Ta "M-w" @@ -1482,6 +1718,7 @@ The following commands are supported in copy mode: .It Li "jump-reverse" Ta "," Ta "," .It Li "jump-to-backward " Ta "T" Ta "" .It Li "jump-to-forward " Ta "t" Ta "" +.It Li "jump-to-mark" Ta "M-x" Ta "M-x" .It Li "middle-line" Ta "M" Ta "M-r" .It Li "next-matching-bracket" Ta "%" Ta "M-C-f" .It Li "next-paragraph" Ta "}" Ta "M-}" @@ -1493,27 +1730,56 @@ The following commands are supported in copy mode: .It Li "page-down" Ta "C-f" Ta "PageDown" .It Li "page-down-and-cancel" Ta "" Ta "" .It Li "page-up" Ta "C-b" Ta "PageUp" +.It Li "pipe [] []" Ta "" Ta "" +.It Li "pipe-no-clear [] []" Ta "" Ta "" +.It Li "pipe-and-cancel [] []" Ta "" Ta "" .It Li "previous-matching-bracket" Ta "" Ta "M-C-b" .It Li "previous-paragraph" Ta "{" Ta "M-{" .It Li "previous-space" Ta "B" Ta "" .It Li "previous-word" Ta "b" Ta "M-b" +.It Li "rectangle-on" Ta "" Ta "" +.It Li "rectangle-off" Ta "" Ta "" .It Li "rectangle-toggle" Ta "v" Ta "R" +.It Li "refresh-from-pane" Ta "r" Ta "r" .It Li "scroll-down" Ta "C-e" Ta "C-Down" .It Li "scroll-down-and-cancel" Ta "" Ta "" .It Li "scroll-up" Ta "C-y" Ta "C-Up" .It Li "search-again" Ta "n" Ta "n" .It Li "search-backward " Ta "?" Ta "" -.It Li "search-forward " Ta "/" Ta "" .It Li "search-backward-incremental " Ta "" Ta "C-r" +.It Li "search-backward-text " Ta "" Ta "" +.It Li "search-forward " Ta "/" Ta "" .It Li "search-forward-incremental " Ta "" Ta "C-s" +.It Li "search-forward-text " Ta "" Ta "" .It Li "search-reverse" Ta "N" Ta "N" .It Li "select-line" Ta "V" Ta "" .It Li "select-word" Ta "" Ta "" +.It Li "set-mark" Ta "X" Ta "X" .It Li "start-of-line" Ta "0" Ta "C-a" .It Li "stop-selection" Ta "" Ta "" .It Li "top-line" Ta "H" Ta "M-R" .El .Pp +The search commands come in several varieties: +.Ql search-forward +and +.Ql search-backward +search for a regular expression; +the +.Ql -text +variants search for a plain text string rather than a regular expression; +.Ql -incremental +perform an incremental search and expect to be used with the +.Fl i +flag to the +.Ic command-prompt +command. +.Ql search-again +repeats the last search and +.Ql search-reverse +does the same but reverses the direction (forward becomes backward and backward +becomes forward). +.Pp Copy commands may take an optional buffer prefix argument which is used to generate the buffer name (the default is .Ql buffer @@ -1522,7 +1788,9 @@ so buffers are named .Ql buffer1 and so on). Pipe commands take a command argument which is the command to which the -copied text is piped. +selected text is piped. +.Ql copy-pipe +variants also copy the selection. The .Ql -and-cancel variants of some commands exit copy mode after they have completed (for copy @@ -1530,19 +1798,18 @@ commands) or when the cursor reaches the bottom (for scrolling commands). .Ql -no-clear variants do not clear the selection. .Pp -The next and previous word keys use space and the -.Ql - , -.Ql _ -and -.Ql @ -characters as word delimiters by default, but this can be adjusted by -setting the +The next and previous word keys skip over whitespace and treat consecutive +runs of either word separators or other letters as words. +Word separators can be customized with the .Em word-separators session option. Next word moves to the start of the next word, next word end to the end of the next word and previous word to the start of the previous word. The three next and previous space keys work similarly but use a space alone as the word separator. +Setting +.Em word-separators +to the empty string makes next/previous word equivalent to next/previous space. .Pp The jump commands enable quick movement within a line. For instance, typing @@ -1565,7 +1832,8 @@ The synopsis for the command is: .Bl -tag -width Ds .It Xo Ic copy-mode -.Op Fl Meu +.Op Fl eHMqu +.Op Fl s Ar src-pane .Op Fl t Ar target-pane .Xc Enter copy mode. @@ -1575,6 +1843,16 @@ option scrolls one page up. .Fl M begins a mouse drag (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . +.Fl H +hides the position indicator in the top right. +.Fl q +cancels copy mode and any other modes. +.Fl s +copies from +.Ar src-pane +instead of +.Ar target-pane . +.Pp .Fl e specifies that scrolling to the bottom of the history (to the visible screen) should exit copy mode. @@ -1644,7 +1922,7 @@ from which the layout was originally defined. Commands related to windows and panes are as follows: .Bl -tag -width Ds .It Xo Ic break-pane -.Op Fl dP +.Op Fl abdP .Op Fl F Ar format .Op Fl n Ar window-name .Op Fl s Ar src-pane @@ -1655,6 +1933,12 @@ Break .Ar src-pane off from its containing window to make it the only pane in .Ar dst-window . +With +.Fl a +or +.Fl b , +the window is moved to the next index after or before (existing windows are +moved if necessary). If .Fl d is given, the new window does not become the current window. @@ -1662,7 +1946,7 @@ The .Fl P option prints information about the new window after it has been created. By default, it uses the format -.Ql #{session_name}:#{window_index} +.Ql #{session_name}:#{window_index}.#{pane_index} but a different format may be specified with .Fl F . .It Xo Ic capture-pane @@ -1716,12 +2000,17 @@ The default is to capture only the visible contents of the pane. .Op Fl NrZ .Op Fl F Ar format .Op Fl f Ar filter +.Op Fl K Ar key-format .Op Fl O Ar sort-order .Op Fl t Ar target-pane .Op Ar template .Xc Put a pane into client mode, allowing a client to be selected interactively from a list. +Each client is shown on one line. +A shortcut key is shown on the left in brackets allowing for immediate choice, +or the list may be navigated and an item chosen or otherwise manipulated using +the keys below. .Fl Z zooms the pane. The following keys may be used in client mode: @@ -1771,7 +2060,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero, the item in the list is not shown, otherwise it is shown. If a filter would lead to an empty list, it is ignored. .Fl F -specifies the format for each item in the list. +specifies the format for each item in the list and +.Fl K +a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview. This command works only if at least one client is attached. @@ -1780,12 +2071,17 @@ This command works only if at least one client is attached. .Op Fl GNrswZ .Op Fl F Ar format .Op Fl f Ar filter +.Op Fl K Ar key-format .Op Fl O Ar sort-order .Op Fl t Ar target-pane .Op Ar template .Xc Put a pane into tree mode, where a session, window or pane may be chosen -interactively from a list. +interactively from a tree. +Each session, window or pane is shown on one line. +A shortcut key is shown on the left in brackets allowing for immediate choice, +or the tree may be navigated and an item chosen or otherwise manipulated using +the keys below. .Fl s starts with sessions collapsed and .Fl w @@ -1798,26 +2094,35 @@ The following keys may be used in tree mode: .It Li "Enter" Ta "Choose selected item" .It Li "Up" Ta "Select previous item" .It Li "Down" Ta "Select next item" +.It Li "+" Ta "Expand selected item" +.It Li "-" Ta "Collapse selected item" +.It Li "M-+" Ta "Expand all items" +.It Li "M--" Ta "Collapse all items" .It Li "x" Ta "Kill selected item" .It Li "X" Ta "Kill tagged items" .It Li "<" Ta "Scroll list of previews left" .It Li ">" Ta "Scroll list of previews right" .It Li "C-s" Ta "Search by name" +.It Li "m" Ta "Set the marked pane" +.It Li "M" Ta "Clear the marked pane" .It Li "n" Ta "Repeat last search" .It Li "t" Ta "Toggle if item is tagged" .It Li "T" Ta "Tag no items" .It Li "C-t" Ta "Tag all items" .It Li "\&:" Ta "Run a command for each tagged item" .It Li "f" Ta "Enter a format to filter items" +.It Li "H" Ta "Jump to the starting pane" .It Li "O" Ta "Change sort field" .It Li "r" Ta "Reverse sort order" .It Li "v" Ta "Toggle preview" .It Li "q" Ta "Exit mode" .El .Pp -After a session, window or pane is chosen, +After a session, window or pane is chosen, the first instance of .Ql %% -is replaced by the target in +and all instances of +.Ql %1 +are replaced by the target in .Ar template and the result executed as a command. If @@ -1837,7 +2142,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero, the item in the list is not shown, otherwise it is shown. If a filter would lead to an empty list, it is ignored. .Fl F -specifies the format for each item in the tree. +specifies the format for each item in the tree and +.Fl K +a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview. .Fl G @@ -1845,8 +2152,57 @@ includes all sessions in any session groups in the tree rather than only the first. This command works only if at least one client is attached. .It Xo +.Ic customize-mode +.Op Fl NZ +.Op Fl F Ar format +.Op Fl f Ar filter +.Op Fl t Ar target-pane +.Op Ar template +.Xc +Put a pane into customize mode, where options and key bindings may be browsed +and modified from a list. +Option values in the list are shown for the active pane in the current window. +.Fl Z +zooms the pane. +The following keys may be used in customize mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Set pane, window, session or global option value" +.It Li "Up" Ta "Select previous item" +.It Li "Down" Ta "Select next item" +.It Li "+" Ta "Expand selected item" +.It Li "-" Ta "Collapse selected item" +.It Li "M-+" Ta "Expand all items" +.It Li "M--" Ta "Collapse all items" +.It Li "s" Ta "Set option value or key attribute" +.It Li "S" Ta "Set global option value" +.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 "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" +.It Li "n" Ta "Repeat last search" +.It Li "t" Ta "Toggle if item is tagged" +.It Li "T" Ta "Tag no items" +.It Li "C-t" Ta "Tag all items" +.It Li "f" Ta "Enter a format to filter items" +.It Li "v" Ta "Toggle option information" +.It Li "q" Ta "Exit mode" +.El +.Pp +.Fl f +specifies an initial filter: the filter is a format - if it evaluates to zero, +the item in the list is not shown, otherwise it is shown. +If a filter would lead to an empty list, it is ignored. +.Fl F +specifies the format for each item in the tree. +.Fl N +starts without the option information. +This command works only if at least one client is attached. +.It Xo .Ic display-panes -.Op Fl b +.Op Fl bN .Op Fl d Ar duration .Op Fl t Ar target-client .Op Ar template @@ -1859,7 +2215,9 @@ See the and .Ic display-panes-active-colour session options. -The indicator is closed when a key is pressed or +The indicator is closed when a key is pressed (unless +.Fl N +is given) or .Ar duration milliseconds have passed. If @@ -1884,7 +2242,7 @@ With .Fl b , other commands are not blocked from running until the indicator is closed. .It Xo Ic find-window -.Op Fl rCNTZ +.Op Fl iCNrTZ .Op Fl t Ar target-pane .Ar match-string .Xc @@ -1903,6 +2261,8 @@ matches only visible window contents, matches only the window name and .Fl T matches only the window title. +.Fl i +makes the search ignore case. The default is .Fl CNT . .Fl Z @@ -1980,7 +2340,7 @@ If no .Ar target-session is specified, select the last window of the current session. .It Xo Ic link-window -.Op Fl adk +.Op Fl abdk .Op Fl s Ar src-window .Op Fl t Ar dst-window .Xc @@ -1995,9 +2355,12 @@ is specified and no such window exists, the .Ar src-window is linked there. With -.Fl a , -the window is moved to the next index up (following windows -are moved if necessary). +.Fl a +or +.Fl b +the window is moved to the next index after or before +.Ar dst-window +(existing windows are moved if necessary). If .Fl k is given and @@ -2009,6 +2372,7 @@ is given, the newly linked window is not selected. .It Xo Ic list-panes .Op Fl as .Op Fl F Ar format +.Op Fl f Ar filter .Op Fl t Ar target .Xc .D1 (alias: Ic lsp ) @@ -2025,14 +2389,18 @@ is a session (or the current session). If neither is given, .Ar target is a window (or the current window). -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only panes for which the filter is true are shown. +See the .Sx FORMATS section. .It Xo Ic list-windows .Op Fl a .Op Fl F Ar format +.Op Fl f Ar filter .Op Fl t Ar target-session .Xc .D1 (alias: Ic lsw ) @@ -2041,27 +2409,25 @@ If is given, list all windows on the server. Otherwise, list windows in the current session or in .Ar target-session . -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only windows for which the filter is true are shown. +See the .Sx FORMATS section. .It Xo Ic move-pane -.Op Fl bdhv +.Op Fl bdfhv .Op Fl l Ar size .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc .D1 (alias: Ic movep ) -Like -.Ic join-pane , -but -.Ar src-pane -and -.Ar dst-pane -may belong to the same window. +Does the same as +.Ic join-pane . .It Xo Ic move-window -.Op Fl ardk +.Op Fl abrdk .Op Fl s Ar src-window .Op Fl t Ar dst-window .Xc @@ -2079,7 +2445,7 @@ the .Ic base-index option. .It Xo Ic new-window -.Op Fl adkP +.Op Fl abdkPS .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -2090,10 +2456,12 @@ option. .D1 (alias: Ic neww ) Create a new window. With -.Fl a , -the new window is inserted at the next index up from the specified +.Fl a +or +.Fl b , +the new window is inserted at the next index after or before the specified .Ar target-window , -moving windows up if necessary, +moving windows up if necessary; otherwise .Ar target-window is the new window location. @@ -2106,6 +2474,14 @@ represents the window to be created; if the target already exists an error is shown, unless the .Fl k flag is used, in which case it is destroyed. +If +.Fl S +is given and a window named +.Ar window-name +already exists, it is selected (unless +.Fl d +is also given in which case the command does nothing). +.Pp .Ar shell-command is the command to execute. If @@ -2234,7 +2610,7 @@ Rename the current window, or the window at if specified, to .Ar new-name . .It Xo Ic resize-pane -.Op Fl DLMRUZ +.Op Fl DLMRTUZ .Op Fl t Ar target-pane .Op Fl x Ar width .Op Fl y Ar height @@ -2273,6 +2649,10 @@ and unzoomed (its normal position in the layout). .Fl M begins mouse resizing (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . +.Pp +.Fl T +trims all lines below the current cursor position and moves lines out of the +history to replace them. .It Xo Ic resize-window .Op Fl aADLRU .Op Fl t Ar target-window @@ -2318,7 +2698,8 @@ Reactivate a pane in which the command has exited (see the window option). If .Ar shell-command -is not given, the command used when the pane was created is executed. +is not given, the command used when the pane was created or last respawned is +executed. The pane must be already inactive, unless .Fl k is given, in which case any existing command is killed. @@ -2342,7 +2723,8 @@ Reactivate a window in which the command has exited (see the window option). If .Ar shell-command -is not given, the command used when the window was created is executed. +is not given, the command used when the window was created or last respawned is +executed. The window must be already inactive, unless .Fl k is given, in which case any existing command is killed. @@ -2394,8 +2776,7 @@ spreads the current pane and any panes next to it out evenly. .D1 (alias: Ic selectp ) Make pane .Ar target-pane -the active pane in window -.Ar target-window . +the active pane in its window. If one of .Fl D , .Fl L , @@ -2427,6 +2808,7 @@ The marked pane is the default target for .Fl s to .Ic join-pane , +.Ic move-pane , .Ic swap-pane and .Ic swap-window . @@ -2453,7 +2835,7 @@ is given and the selected window is already the current window, the command behaves like .Ic last-window . .It Xo Ic split-window -.Op Fl bdfhIvP +.Op Fl bdfhIvPZ .Op Fl c Ar start-directory .Op Fl e Ar environment .Op Fl l Ar size @@ -2489,6 +2871,8 @@ option creates a new pane spanning the full window height (with or full window width (with .Fl v ) , instead of splitting the active pane. +.Fl Z +zooms if the window is not zoomed, or keeps it zoomed if already zoomed. .Pp An empty .Ar shell-command @@ -2586,6 +2970,8 @@ Ctrl keys may be prefixed with .Ql C- or .Ql ^ , +Shift keys with +.Ql S- and Alt (meta) with .Ql M- . In addition, the following special key names are accepted: @@ -2631,7 +3017,7 @@ Commands related to key bindings are as follows: .Op Fl nr .Op Fl N Ar note .Op Fl T Ar key-table -.Ar key Ar command Op Ar arguments +.Ar key command Op Ar arguments .Xc .D1 (alias: Ic bind ) Bind key @@ -2688,7 +3074,7 @@ command. .It Xo Ic list-keys .Op Fl 1aN .Op Fl P Ar prefix-string Fl T Ar key-table -.Op key +.Op Ar key .Xc .D1 (alias: Ic lsk ) List key bindings. @@ -2719,7 +3105,7 @@ specifies a prefix to print before each key and .Fl 1 lists only the first matching key. .Fl a -lists the command for keys that do have a note rather than skipping them. +lists the command for keys that do not have a note rather than skipping them. .It Xo Ic send-keys .Op Fl FHlMRX .Op Fl N Ar repeat-count @@ -2737,6 +3123,7 @@ or to send; if the string is not recognised as a key, it is sent as a series of characters. All arguments are sent sequentially from first to last. +If no keys are given and the command is bound to a key, then that key is used. .Pp The .Fl l @@ -2771,7 +3158,7 @@ Send the prefix key, or with .Fl 2 the secondary prefix key, to a window as if it was pressed. .It Xo Ic unbind-key -.Op Fl an +.Op Fl anq .Op Fl T Ar key-table .Ar key .Xc @@ -2786,6 +3173,9 @@ are the same as for If .Fl a is present, all key bindings are removed. +The +.Fl q +option prevents errors being returned. .El .Sh OPTIONS The appearance and behaviour of @@ -2793,8 +3183,8 @@ The appearance and behaviour of may be modified by changing the value of various options. There are four types of option: .Em server options , -.Em session options -.Em window options +.Em session options , +.Em window options , and .Em pane options . .Pp @@ -2855,15 +3245,15 @@ User options may have any name, so long as they are prefixed with and be set to any string. For example: .Bd -literal -offset indent -$ tmux setw -q @foo "abc123" -$ tmux showw -v @foo +$ tmux set -wq @foo "abc123" +$ tmux show -wv @foo abc123 .Ed .Pp Commands which set options are as follows: .Bl -tag -width Ds .It Xo Ic set-option -.Op Fl aFgopqsuw +.Op Fl aFgopqsuUw .Op Fl t Ar target-pane .Ar option Ar value .Xc @@ -2896,6 +3286,14 @@ flag unsets an option, so a session inherits the option from the global options (or with .Fl g , restores a global option to the default). +.Fl U +unsets an option (like +.Fl u ) +but if the option is a pane option also unsets the option on any panes in the +window. +.Ar value +depends on the option and may be a number, a string, or a flag (on, off, or +omitted to toggle). .Pp The .Fl o @@ -2967,9 +3365,6 @@ includes hooks (omitted by default). .Fl A includes options inherited from a parent set of options, such options are marked with an asterisk. -.Ar value -depends on the option and may be a number, a string, or a flag (on, off, or -omitted to toggle). .El .Pp Available server options are: @@ -3019,12 +3414,20 @@ be set to .Ql screen , .Ql tmux or a derivative of them. +.It Ic copy-command Ar shell-command +Give the command to pipe to if the +.Ic copy-pipe +copy mode command is used without arguments. .It Ic escape-time Ar time Set the time in milliseconds for which .Nm waits after an escape is input to determine if it is part of a function or meta key sequences. The default is 500 milliseconds. +.It Ic editor Ar shell-command +Set the command used when +.Nm +runs an editor. .It Xo Ic exit-empty .Op Ic on | off .Xc @@ -3034,6 +3437,25 @@ sessions. .Op Ic on | off .Xc If enabled, the server will exit when there are no attached clients. +.It Xo Ic extended-keys +.Op Ic on | off | always +.Xc +When +.Ic on +or +.Ic always , +the escape sequence to enable extended keys is sent to the terminal, if +.Nm +knows that it is supported. +.Nm +always recognises extended keys itself. +If this option is +.Ic on , +.Nm +will only forward extended keys to applications when they request them; if +.Ic always , +.Nm +will always forward the keys. .It Xo Ic focus-events .Op Ic on | off .Xc @@ -3049,7 +3471,9 @@ will write command prompt history on exit and load it from on start. .It Ic message-limit Ar number Set the number of error or information messages to save in the message log for each client. -The default is 100. +.It Ic prompt-history-limit Ar number +Set the number of history items to save in the history file for each type of +command prompt. .It Xo Ic set-clipboard .Op Ic on | external | off .Xc @@ -3091,6 +3515,71 @@ disallowedWindowOps: 20,21,SetXprop Or changing this property from the .Xr xterm 1 interactive menu when required. +.It Ic terminal-features[] Ar string +Set terminal features for terminal types read from +.Xr terminfo 5 . +.Nm +has a set of named terminal features. +Each will apply appropriate changes to the +.Xr terminfo 5 +entry in use. +.Pp +.Nm +can detect features for a few common terminals; this option can be used to +easily tell tmux about features supported by terminals it cannot detect. +The +.Ic terminal-overrides +option allows individual +.Xr terminfo 5 +capabilities to be set instead, +.Ic terminal-features +is intended for classes of functionality supported in a standard way but not +reported by +.Xr terminfo 5 . +Care must be taken to configure this only with features the terminal actually +supports. +.Pp +This is an array option where each entry is a colon-separated string made up +of a terminal type pattern (matched using +.Xr fnmatch 3 ) +followed by a list of terminal features. +The available features are: +.Bl -tag -width Ds +.It 256 +Supports 256 colours with the SGR escape sequences. +.It clipboard +Allows setting the system clipboard. +.It ccolour +Allows setting the cursor colour. +.It cstyle +Allows setting the cursor style. +.It extkeys +Supports extended keys. +.It focus +Supports focus reporting. +.It margins +Supports DECSLRM margins. +.It mouse +Supports +.Xr xterm 1 +mouse sequences. +.It overline +Supports the overline SGR attribute. +.It rectfill +Supports the DECFRA rectangle fill escape sequence. +.It RGB +Supports RGB colour with the SGR escape sequences. +.It strikethrough +Supports the strikethrough SGR escape sequence. +.It sync +Supports synchronized updates. +.It title +Supports +.Xr xterm 1 +title setting. +.It usstyle +Allows underscore style and colour to be set. +.El .It Ic terminal-overrides[] Ar string Allow terminal descriptions read using .Xr terminfo 5 @@ -3213,12 +3702,16 @@ The default is 80x24. If enabled and the session is no longer attached to any clients, it is destroyed. .It Xo Ic detach-on-destroy -.Op Ic on | off +.Op Ic off | on | no-detached .Xc If on (the default), the client is detached when the session it is attached to is destroyed. If off, the client is switched to the most recently active of the remaining sessions. +If +.Ic no-detached , +the client is detached only if there are no detached sessions; if detached +sessions exist, the client is switched to the most recently active. .It Ic display-panes-active-colour Ar colour Set the colour used by the .Ic display-panes @@ -3261,6 +3754,9 @@ with .Fl np . .It Ic message-command-style Ar style Set status line message command style. +This is used for the command prompt with +.Xr vi 1 +keys when in command mode. For how to specify .Ar style , see the @@ -3268,6 +3764,7 @@ see the section. .It Ic message-style Ar style Set status line message style. +This is used for messages and for the command prompt. For how to specify .Ar style , see the @@ -3371,10 +3868,11 @@ seconds. By default, updates will occur every 15 seconds. A setting of zero disables redrawing at interval. .It Xo Ic status-justify -.Op Ic left | centre | right +.Op Ic left | centre | right | absolute-centre .Xc -Set the position of the window list component of the status line: left, centre -or right justified. +Set the position of the window list in the status line: left, centre or right. +centre puts the window list in the relative centre of the available free space; +absolute-centre uses the centre of the entire horizontal space. .It Xo Ic status-keys .Op Ic vi | emacs .Xc @@ -3498,8 +3996,6 @@ If set to both, a bell and a message are produced. Sets the session's conception of what characters are considered word separators, for the purposes of the next and previous word commands in copy mode. -The default is -.Ql \ -_@ . .El .Pp Available window options are: @@ -3564,6 +4060,33 @@ Set the width or height of the main (left or top) pane in the or .Ic main-vertical layouts. +If suffixed by +.Ql % , +this is a percentage of the window size. +.Pp +.It Ic copy-mode-match-style Ar style +Set the style of search matches in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic copy-mode-mark-style Ar style +Set the style of the line containing the mark in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic copy-mode-current-match-style Ar style +Set the style of the current search match in copy mode. +For how to specify +.Ar style , +see the +.Sx STYLES +section. .Pp .It Xo Ic mode-keys .Op Ic vi | emacs @@ -3617,6 +4140,9 @@ and .Ic other-pane-height options are set, the main pane will grow taller to make the other panes the specified height, but will never shrink to do so. +If suffixed by +.Ql % , +this is a percentage of the window size. .Pp .It Ic other-pane-width Ar width Like @@ -3633,7 +4159,6 @@ see the .Sx STYLES section. Attributes are ignored. -.Pp .It Ic pane-base-index Ar index Like .Ic base-index , @@ -3642,6 +4167,28 @@ but set the starting index for pane numbers. .It Ic pane-border-format Ar format Set the text shown in pane border status lines. .Pp +.It Ic pane-border-lines Ar type +Set the type of characters used for drawing pane borders. +.Ar type +may be one of: +.Bl -tag -width Ds +.It single +single lines using ACS or UTF-8 characters +.It double +double lines using UTF-8 characters +.It heavy +heavy lines using UTF-8 characters +.It simple +simple ASCII characters +.It number +the pane number +.El +.Pp +.Ql double +and +.Ql heavy +will fall back to standard ACS line drawing when UTF-8 is not supported. +.Pp .It Xo Ic pane-border-status .Op Ic off | top | bottom .Xc @@ -3656,12 +4203,6 @@ see the section. Attributes are ignored. .Pp -.It Xo Ic synchronize-panes -.Op Ic on | off -.Xc -Duplicate input to any pane to all other panes in the same window (only -for panes that are not in any special mode). -.Pp .It Ic window-status-activity-style Ar style Set status line style for windows with an activity alert. For how to specify @@ -3750,16 +4291,6 @@ option. .Xc If this option is set, searches will wrap around the end of the pane contents. The default is on. -.Pp -.It Xo Ic xterm-keys -.Op Ic on | off -.Xc -If this option is set, -.Nm -will generate -.Xr xterm 1 -style -function key sequences; these have a number included to indicate modifiers such -as Shift, Alt or Ctrl. .El .Pp Available pane options are: @@ -3785,15 +4316,31 @@ The alternate screen feature preserves the contents of the window when an interactive application starts and restores it on exit, so that any output visible before the application starts reappears unchanged after it exits. .Pp +.It Ic pane-colours[] Ar colour +The default colour palette. +Each entry in the array defines the colour +.Nm +uses when the colour with that index is requested. +The index may be from zero to 255. +.Pp .It Xo Ic remain-on-exit -.Op Ic on | off +.Op Ic on | off | failed .Xc A pane with this flag set is not destroyed when the program running in it exits. +If set to +.Ic failed , +then only when the program exit status is not zero. The pane may be reactivated with the .Ic respawn-pane command. .Pp +.It Xo Ic synchronize-panes +.Op Ic on | off +.Xc +Duplicate input to all other panes in the same window where this option is also +on (only for panes that are not in any mode). +.Pp .It Ic window-active-style Ar style Set the pane style when it is the active pane. For how to specify @@ -3810,6 +4357,7 @@ see the .Sx STYLES section. .El +.El .Sh HOOKS .Nm allows commands to run on various triggers, called @@ -3822,6 +4370,7 @@ hook and there are a number of hooks not associated with commands. .Pp Hooks are stored as array options, members of the array are executed in order when the hook is triggered. +Like options different hooks may be global or belong to a session, window or pane. Hooks may be configured with the .Ic set-hook or @@ -3871,10 +4420,16 @@ See Run when a window has been silent. See .Ic monitor-silence . +.It client-active +Run when a client becomes the latest active client of its session. .It client-attached Run when a client is attached. .It client-detached Run when a client is detached +.It client-focus-in +Run when focus enters a client +.It client-focus-out +Run when focus exits a client .It client-resized Run when a client is resized. .It client-session-changed @@ -3914,8 +4469,8 @@ Run when a window is unlinked from a session. Hooks are managed with these commands: .Bl -tag -width Ds .It Xo Ic set-hook -.Op Fl agRu -.Op Fl t Ar target-session +.Op Fl agpRuw +.Op Fl t Ar target-pane .Ar hook-name .Ar command .Xc @@ -3927,18 +4482,8 @@ unsets) hook .Ar hook-name to .Ar command . -If -.Fl g -is given, -.Em hook-name -is added to the global list of hooks, otherwise it is added to the session -hooks (for -.Ar target-session -with -.Fl t ) . -.Fl a -appends to a hook. -Like options, session hooks inherit from the global ones. +The flags are the same as for +.Ic set-option . .Pp With .Fl R , @@ -3946,12 +4491,12 @@ run .Ar hook-name immediately. .It Xo Ic show-hooks -.Op Fl g -.Op Fl t Ar target-session +.Op Fl gpw +.Op Fl t Ar target-pane .Xc -Shows the global list of hooks with -.Fl g , -otherwise the session hooks. +Shows hooks. +The flags are the same as for +.Ic show-options . .El .Sh MOUSE SUPPORT If the @@ -3977,10 +4522,19 @@ The following mouse events are available: .It Li "MouseDown1" Ta "MouseUp1" Ta "MouseDrag1" Ta "MouseDragEnd1" .It Li "MouseDown2" Ta "MouseUp2" Ta "MouseDrag2" Ta "MouseDragEnd2" .It Li "MouseDown3" Ta "MouseUp3" Ta "MouseDrag3" Ta "MouseDragEnd3" +.It Li "SecondClick1" Ta "SecondClick2" Ta "SecondClick3" .It Li "DoubleClick1" Ta "DoubleClick2" Ta "DoubleClick3" .It Li "TripleClick1" Ta "TripleClick2" Ta "TripleClick3" .El .Pp +The +.Ql SecondClick +events are fired for the second click of a double click, even if there may be a +third click which will fire +.Ql TripleClick +instead of +.Ql DoubleClick . +.Pp Each should be suffixed with a location, for example .Ql MouseDown1Status . .Pp @@ -4107,7 +4661,7 @@ specifies an .Xr fnmatch 3 or regular expression comparison. The first argument is the pattern and the second the string to compare. -An optional third argument specifies flags: +An optional argument specifies flags: .Ql r means the pattern is a regular expression instead of the default .Xr fnmatch 3 @@ -4134,6 +4688,51 @@ ignores case. For example: .Ql #{C/r:^Start} .Pp +Numeric operators may be performed by prefixing two comma-separated alternatives with an +.Ql e +and an operator. +An optional +.Ql f +flag may be given after the operator to use floating point numbers, otherwise integers are used. +This may be followed by a number giving the number of decimal places to use for the result. +The available operators are: +addition +.Ql + , +subtraction +.Ql - , +multiplication +.Ql * , +division +.Ql / , +modulus +.Ql m +or +.Ql % +(note that +.Ql % +must be escaped as +.Ql %% +in formats which are also expanded by +.Xr strftime 3 ) +and numeric comparison operators +.Ql == , +.Ql != , +.Ql < , +.Ql <= , +.Ql > +and +.Ql >= . +For example, +.Ql #{e|*|f|4:5.5,3} +multiplies 5.5 by 3 for a result with four decimal places and +.Ql #{e|%%:7,3} +returns the modulus of 7 and 3. +.Ql a +replaces a numeric argument by its ASCII equivalent, so +.Ql #{a:98} +results in +.Ql b . +.Pp A limit may be placed on the length of the resultant string by prefixing it by an .Ql = , @@ -4156,6 +4755,11 @@ pads the string to a given width, for example .Ql #{p10:pane_title} will result in a width of at least 10 characters. A positive width pads on the left, a negative on the right. +.Ql n +expands to the length of the variable and +.Ql w +to its width when displayed, for example +.Ql #{n:window_name} . .Pp Prefixing a time variable with .Ql t:\& @@ -4166,6 +4770,25 @@ gives .Ql #{t:window_activity} gives .Ql Sun Oct 25 09:25:02 2015 . +Adding +.Ql p ( +.Ql `t/p` ) +will use shorter but less accurate time format for times in the past. +A custom format may be given using an +.Ql f +suffix (note that +.Ql % +must be escaped as +.Ql %% +if the format is separately being passed through +.Xr strftime 3 , +for example in the +.Ic status-left +option): +.Ql #{t/f/%%H#:%%M:window_activity} , +see +.Xr strftime 3 . +.Pp The .Ql b:\& and @@ -4178,7 +4801,12 @@ of the variable respectively. .Ql q:\& will escape .Xr sh 1 -special characters. +special characters or with a +.Ql h +suffix, escape hash characters (so +.Ql # +becomes +.Ql ## ) . .Ql E:\& will expand the format twice, for example .Ql #{E:status-left} @@ -4204,6 +4832,17 @@ For example, to get a list of windows formatted like the status line: #{W:#{E:window-status-format} ,#{E:window-status-current-format} } .Ed .Pp +.Ql N:\& +checks if a window (without any suffix or with the +.Ql w +suffix) or a session (with the +.Ql s +suffix) name exists, for example +.Ql `N/w:foo` +is replaced with 1 if a window named +.Ql foo +exists. +.Pp A prefix of the form .Ql s/foo/bar/:\& will substitute @@ -4250,6 +4889,7 @@ will be replaced by The following variables are available, where appropriate: .Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX" .It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with" +.It Li "active_window_index" Ta "" Ta "Index of active window in session" .It Li "alternate_on" Ta "" Ta "1 if pane is in alternate screen" .It Li "alternate_saved_x" Ta "" Ta "Saved cursor X in alternate screen" .It Li "alternate_saved_y" Ta "" Ta "Saved cursor Y in alternate screen" @@ -4263,6 +4903,7 @@ The following variables are available, where appropriate: .It Li "client_control_mode" Ta "" Ta "1 if client is in control mode" .It Li "client_created" Ta "" Ta "Time client created" .It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" +.It Li "client_flags" Ta "" Ta "List of client flags" .It Li "client_height" Ta "" Ta "Height of client" .It Li "client_key_table" Ta "" Ta "Current key table" .It Li "client_last_session" Ta "" Ta "Name of the client's last session" @@ -4271,7 +4912,9 @@ The following variables are available, where appropriate: .It Li "client_prefix" Ta "" Ta "1 if prefix key has been pressed" .It Li "client_readonly" Ta "" Ta "1 if client is readonly" .It Li "client_session" Ta "" Ta "Name of the client's session" +.It Li "client_termfeatures" Ta "" Ta "Terminal features of client, if any" .It Li "client_termname" Ta "" Ta "Terminal name of client" +.It Li "client_termtype" Ta "" Ta "Terminal type of client, if available" .It Li "client_tty" Ta "" Ta "Pseudo terminal of client" .It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8" .It Li "client_width" Ta "" Ta "Width of client" @@ -4280,10 +4923,12 @@ The following variables are available, where appropriate: .It Li "command_list_alias" Ta "" Ta "Command alias if listing commands" .It Li "command_list_name" Ta "" Ta "Command name if listing commands" .It Li "command_list_usage" Ta "" Ta "Command usage if listing commands" +.It Li "config_files" Ta "" Ta "List of configuration files loaded" .It Li "copy_cursor_line" Ta "" Ta "Line the cursor is on in copy mode" .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 "current_file" Ta "" Ta "Current configuration file" .It Li "cursor_character" Ta "" Ta "Character at cursor in pane" .It Li "cursor_flag" Ta "" Ta "Pane cursor flag" .It Li "cursor_x" Ta "" Ta "Cursor X position in pane" @@ -4292,6 +4937,7 @@ The following variables are available, where appropriate: .It Li "history_limit" Ta "" Ta "Maximum window history lines" .It Li "history_size" Ta "" Ta "Size of history in lines" .It Li "hook" Ta "" Ta "Name of running hook, if any" +.It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any" .It Li "hook_pane" Ta "" Ta "ID of pane where hook was run, if any" .It Li "hook_session" Ta "" Ta "ID of session where hook was run, if any" .It Li "hook_session_name" Ta "" Ta "Name of session where hook was run, if any" @@ -4302,6 +4948,7 @@ The following variables are available, where appropriate: .It Li "insert_flag" Ta "" Ta "Pane insert flag" .It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag" .It Li "keypad_flag" Ta "" Ta "Pane keypad flag" +.It Li "last_window_index" Ta "" Ta "Index of last window in session" .It Li "line" Ta "" Ta "Line number in the list" .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" @@ -4319,22 +4966,25 @@ The following variables are available, where appropriate: .It Li "pane_at_left" Ta "" Ta "1 if pane is at the left of window" .It Li "pane_at_right" Ta "" Ta "1 if pane is at the right of window" .It Li "pane_at_top" Ta "" Ta "1 if pane is at the top of window" +.It Li "pane_bg" Ta "" Ta "Pane background colour" .It Li "pane_bottom" Ta "" Ta "Bottom of pane" .It Li "pane_current_command" Ta "" Ta "Current command if available" .It Li "pane_current_path" Ta "" Ta "Current path if available" .It Li "pane_dead" Ta "" Ta "1 if pane is dead" .It Li "pane_dead_status" Ta "" Ta "Exit status of process in dead pane" +.It Li "pane_fg" Ta "" Ta "Pane foreground colour" .It Li "pane_format" Ta "" Ta "1 if format is for a pane" .It Li "pane_height" Ta "" Ta "Height of pane" .It Li "pane_id" Ta "#D" Ta "Unique pane ID" .It Li "pane_in_mode" Ta "" Ta "1 if pane is in a mode" .It Li "pane_index" Ta "#P" Ta "Index of pane" .It Li "pane_input_off" Ta "" Ta "1 if input to pane is disabled" +.It Li "pane_last" Ta "" Ta "1 if last pane" .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" .It Li "pane_mode" Ta "" Ta "Name of pane mode, if any" -.It Li "pane_path" Ta "#T" Ta "Path of pane (can be set by application)" +.It Li "pane_path" Ta "" Ta "Path of pane (can be set by application)" .It Li "pane_pid" Ta "" Ta "PID of first process in pane" .It Li "pane_pipe" Ta "" Ta "1 if pane is being piped" .It Li "pane_right" Ta "" Ta "Right of pane" @@ -4346,11 +4996,13 @@ The following variables are available, where appropriate: .It Li "pane_top" Ta "" Ta "Top of pane" .It Li "pane_tty" Ta "" Ta "Pseudo terminal of pane" .It Li "pane_width" Ta "" Ta "Width of pane" -.It Li "pid" Ta "" Ta "Server PID" +.It Li "pid" Ta "" Ta "Server PID" .It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated" .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" +.It Li "search_match" Ta "" Ta "Search match if any" +.It Li "search_present" Ta "" Ta "1 if search started in copy mode" .It Li "selection_active" Ta "" Ta "1 if selection started and changes with the cursor in copy mode" .It Li "selection_end_x" Ta "" Ta "X position of the end of the selection" .It Li "selection_end_y" Ta "" Ta "Y position of the end of the selection" @@ -4373,7 +5025,9 @@ The following variables are available, where appropriate: .It Li "session_id" Ta "" Ta "Unique session ID" .It Li "session_last_attached" Ta "" Ta "Time session last attached" .It Li "session_many_attached" Ta "" Ta "1 if multiple clients attached" +.It Li "session_marked" Ta "" Ta "1 if this session contains the marked pane" .It Li "session_name" Ta "#S" Ta "Name of session" +.It Li "session_path" Ta "" Ta "Working directory of session" .It Li "session_stack" Ta "" Ta "Window indexes in most recent order" .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" @@ -4391,7 +5045,8 @@ The following variables are available, where appropriate: .It Li "window_cell_height" Ta "" Ta "Height of each cell in pixels" .It Li "window_cell_width" Ta "" Ta "Width of each cell in pixels" .It Li "window_end_flag" Ta "" Ta "1 if window has the highest index" -.It Li "window_flags" Ta "#F" Ta "Window flags" +.It Li "window_flags" Ta "#F" Ta "Window flags with # escaped as ##" +.It Li "window_raw_flags" Ta "" Ta "Window flags with nothing escaped" .It Li "window_format" Ta "" Ta "1 if format is for a window" .It Li "window_height" Ta "" Ta "Height of window" .It Li "window_id" Ta "" Ta "Unique window ID" @@ -4462,7 +5117,8 @@ for the terminal default colour; or a hexadecimal RGB string such as Set the background colour. .It Ic none Set no attributes (turn off any active attributes). -.It Xo Ic bright +.It Xo Ic acs , +.Ic bright (or .Ic bold ) , .Ic dim , @@ -4482,6 +5138,8 @@ Set an attribute. Any of the attributes may be prefixed with .Ql no to unset. +.Ic acs +is the terminal alternate character set. .It Xo Ic align=left (or .Ic noalign ) , @@ -4637,10 +5295,16 @@ from inside, and the variable with the correct terminal setting of .Ql screen . .Pp +Variables in both session and global environments may be marked as hidden. +Hidden variables are not passed into the environment of new processes and +instead can only be used by tmux itself (for example in formats, see the +.Sx FORMATS +section). +.Pp Commands to alter and view the environment are: .Bl -tag -width Ds .It Xo Ic set-environment -.Op Fl gru +.Op Fl Fhgru .Op Fl t Ar target-session .Ar name Op Ar value .Xc @@ -4651,14 +5315,21 @@ If is used, the change is made in the global environment; otherwise, it is applied to the session environment for .Ar target-session . +If +.Fl F +is present, then +.Ar value +is expanded as a format. The .Fl u flag unsets a variable. .Fl r indicates the variable is to be removed from the environment before starting a new process. +.Fl h +marks the variable as hidden. .It Xo Ic show-environment -.Op Fl gs +.Op Fl hgs .Op Fl t Ar target-session .Op Ar variable .Xc @@ -4675,6 +5346,8 @@ Variables removed from the environment are prefixed with If .Fl s is used, the output is formatted as a set of Bourne shell commands. +.Fl h +shows hidden variables (omitted by default). .El .Sh STATUS LINE .Nm @@ -4739,11 +5412,25 @@ session option. .Pp Commands related to the status line are as follows: .Bl -tag -width Ds +.It Xo Ic clear-prompt-history +.Op Fl T Ar prompt-type +.Xc +.D1 (alias: Ic clrphist) +Clear status prompt history for prompt type +.Ar prompt-type . +If +.Fl T +is omitted, then clear history for all types. +See +.Ic command-prompt +for possible values for +.Ar prompt-type . .It Xo Ic command-prompt -.Op Fl 1ikN +.Op Fl 1bFikN .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client +.Op Fl T Ar prompt-type .Op Ar template .Xc Open the command prompt in a client. @@ -4754,6 +5441,11 @@ to execute commands interactively. If .Ar template is specified, it is used as the command. +With +.Fl F , +.Ar template +is expanded as a format. +.Pp If present, .Fl I is a comma-separated list of the initial text for each prompt. @@ -4800,13 +5492,27 @@ makes the prompt only accept numeric key presses. executes the command every time the prompt input changes instead of when the user exits the command prompt. .Pp +.Fl T +tells +.Nm +the prompt type. +This affects what completions are offered when +.Em Tab +is pressed. +Available types are: +.Ql command , +.Ql search , +.Ql target +and +.Ql window-target . +.Pp The following keys have a special meaning in the command prompt, depending on the value of the .Ic status-keys option: .Bl -column "FunctionXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXX" "emacsX" -offset indent .It Sy "Function" Ta Sy "vi" Ta Sy "emacs" -.It Li "Cancel command prompt" Ta "Escape" Ta "Escape" +.It Li "Cancel command prompt" Ta "q" Ta "Escape" .It Li "Delete from cursor to start of word" Ta "" Ta "C-w" .It Li "Delete entire command" Ta "d" Ta "C-u" .It Li "Delete from cursor to end" Ta "D" Ta "C-k" @@ -4823,7 +5529,13 @@ option: .It Li "Move cursor to start" Ta "0" Ta "C-a" .It Li "Transpose characters" Ta "" Ta "C-t" .El +.Pp +With +.Fl b , +the prompt is shown in the background and the invoking client does not exit +until it is dismissed. .It Xo Ic confirm-before +.Op Fl b .Op Fl p Ar prompt .Op Fl t Ar target-client .Ar command @@ -4840,10 +5552,12 @@ is the prompt to display; otherwise a prompt is constructed from It may contain the special character sequences supported by the .Ic status-left option. -.Pp -This command works only from inside -.Nm . +With +.Fl b , +the prompt is shown in the background and the invoking client does not exit +until it is dismissed. .It Xo Ic display-menu +.Op Fl O .Op Fl c Ar target-client .Op Fl t Ar target-pane .Op Fl T Ar title @@ -4884,18 +5598,47 @@ give the position of the menu. Both may be a row or column number, or one of the following special values: .Bl -column "XXXXX" "XXXX" -offset indent .It Sy "Value" Ta Sy "Flag" Ta Sy "Meaning" +.It Li "C" Ta "Both" Ta "The centre of the terminal" .It Li "R" Ta Fl x Ta "The right side of the terminal" .It Li "P" Ta "Both" Ta "The bottom left of the pane" .It Li "M" Ta "Both" Ta "The mouse position" -.It Li "W" Ta Fl x Ta "The window position on the status line" +.It Li "W" Ta "Both" Ta "The window position on the status line" .It Li "S" Ta Fl y Ta "The line above or below the status line" .El .Pp +Or a format, which is expanded including the following additional variables: +.Bl -column "XXXXXXXXXXXXXXXXXXXXXXXXXX" -offset indent +.It Sy "Variable name" Ta Sy "Replaced with" +.It Li "popup_centre_x" Ta "Centered in the client" +.It Li "popup_centre_y" Ta "Centered in the client" +.It Li "popup_height" Ta "Height of menu or popup" +.It Li "popup_mouse_bottom" Ta "Bottom of at the mouse" +.It Li "popup_mouse_centre_x" Ta "Horizontal centre at the mouse" +.It Li "popup_mouse_centre_y" Ta "Vertical centre at the mouse" +.It Li "popup_mouse_top" Ta "Top at the mouse" +.It Li "popup_mouse_x" Ta "Mouse X position" +.It Li "popup_mouse_y" Ta "Mouse Y position" +.It Li "popup_pane_bottom" Ta "Bottom of the pane" +.It Li "popup_pane_left" Ta "Left of the pane" +.It Li "popup_pane_right" Ta "Right of the pane" +.It Li "popup_pane_top" Ta "Top of the pane" +.It Li "popup_status_line_y" Ta "Above or below the status line" +.It Li "popup_width" Ta "Width of menu or popup" +.It Li "popup_window_status_line_x" Ta "At the window position in status line" +.It Li "popup_window_status_line_y" Ta "At the status line showing the window" +.El +.Pp Each menu consists of items followed by a key shortcut shown in brackets. If the menu is too large to fit on the terminal, it is not displayed. Pressing the key shortcut chooses the corresponding item. -If the mouse is enabled and the menu is opened from a mouse key binding, releasing -the mouse button with an item selected will choose that item. +If the mouse is enabled and the menu is opened from a mouse key binding, +releasing the mouse button with an item selected chooses that item and +releasing the mouse button without an item selected closes the menu. +.Fl O +changes this behaviour so that the menu does not close when the mouse button is +released without an item selected the menu is not closed and a mouse button +must be clicked to choose an item. +.Pp The following keys are also available: .Bl -column "Key" "Function" -offset indent .It Sy "Key" Ta Sy "Function" @@ -4905,8 +5648,9 @@ The following keys are also available: .It Li "q" Ta "Exit menu" .El .It Xo Ic display-message -.Op Fl aIpv +.Op Fl aINpv .Op Fl c Ar target-client +.Op Fl d Ar delay .Op Fl t Ar target-pane .Op Ar message .Xc @@ -4916,7 +5660,16 @@ If .Fl p is given, the output is printed to stdout, otherwise it is displayed in the .Ar target-client -status line. +status line for up to +.Ar delay +milliseconds. +If +.Ar delay +is not given, the +.Ic message-time +option is used; a delay of zero waits for a key press. +.Ql N +ignores key presses and closes only after the delay expires. The format of .Ar message is described in the @@ -4935,6 +5688,66 @@ lists the format variables and their values. .Fl I forwards any input read from stdin to the empty pane given by .Ar target-pane . +.It Xo Ic display-popup +.Op Fl BCE +.Op Fl c Ar target-client +.Op Fl d Ar start-directory +.Op Fl h Ar height +.Op Fl t Ar target-pane +.Op Fl w Ar width +.Op Fl x Ar position +.Op Fl y Ar position +.Op Ar shell-command +.Xc +.D1 (alias: Ic popup ) +Display a popup running +.Ar shell-command +on +.Ar target-client . +A popup is a rectangular box drawn over the top of any panes. +Panes are not updated while a popup is present. +.Pp +.Fl E +closes the popup automatically when +.Ar shell-command +exits. +Two +.Fl E +closes the popup only if +.Ar shell-command +exited with success. +.Pp +.Fl x +and +.Fl y +give the position of the popup, they have the same meaning as for the +.Ic display-menu +command. +.Fl w +and +.Fl h +give the width and height - both may be a percentage (followed by +.Ql % ) . +If omitted, half of the terminal size is used. +.Fl B +does not surround the popup by a border. +.Pp +The +.Fl C +flag closes any popup on the client. +.It Xo Ic show-prompt-history +.Op Fl T Ar prompt-type +.Xc +.D1 (alias: Ic showphist) +Display status prompt history for prompt type +.Ar prompt-type . +If +.Fl T +is omitted, then show history for all types. +See +.Ic command-prompt +for possible values for +.Ar prompt-type . .El .Sh BUFFERS .Nm @@ -4987,12 +5800,17 @@ The buffer commands are as follows: .Op Fl NZr .Op Fl F Ar format .Op Fl f Ar filter +.Op Fl K Ar key-format .Op Fl O Ar sort-order .Op Fl t Ar target-pane .Op Ar template .Xc Put a pane into buffer mode, where a buffer may be chosen interactively from a list. +Each buffer is shown on one line. +A shortcut key is shown on the left in brackets allowing for immediate choice, +or the list may be navigated and an item chosen or otherwise manipulated using +the keys below. .Fl Z zooms the pane. The following keys may be used in buffer mode: @@ -5010,6 +5828,7 @@ The following keys may be used in buffer mode: .It Li "P" Ta "Paste tagged buffers" .It Li "d" Ta "Delete selected buffer" .It Li "D" Ta "Delete tagged buffers" +.It Li "e" Ta "Open the buffer in an editor" .It Li "f" Ta "Enter a format to filter items" .It Li "O" Ta "Change sort field" .It Li "r" Ta "Reverse sort order" @@ -5039,7 +5858,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero, the item in the list is not shown, otherwise it is shown. If a filter would lead to an empty list, it is ignored. .Fl F -specifies the format for each item in the list. +specifies the format for each item in the list and +.Fl K +a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview. This command works only if at least one client is attached. @@ -5053,21 +5874,34 @@ Delete the buffer named or the most recently added automatically named buffer if not specified. .It Xo Ic list-buffers .Op Fl F Ar format +.Op Fl f Ar filter .Xc .D1 (alias: Ic lsb ) List the global buffers. -For the meaning of the .Fl F -flag, see the +specifies the format of each line and +.Fl f +a filter. +Only buffers for which the filter is true are shown. +See the .Sx FORMATS section. .It Xo Ic load-buffer +.Op Fl w .Op Fl b Ar buffer-name +.Op Fl t Ar target-client .Ar path .Xc .D1 (alias: Ic loadb ) Load the contents of the specified paste buffer from .Ar path . +If +.Fl w +is given, the buffer is also sent to the clipboard for +.Ar target-client +using the +.Xr xterm 1 +escape sequence, if possible. .It Xo Ic paste-buffer .Op Fl dpr .Op Fl b Ar buffer-name @@ -5104,14 +5938,22 @@ The .Fl a option appends to rather than overwriting the file. .It Xo Ic set-buffer -.Op Fl a +.Op Fl aw .Op Fl b Ar buffer-name +.Op Fl t Ar target-client .Op Fl n Ar new-buffer-name .Ar data .Xc .D1 (alias: Ic setb ) Set the contents of the specified buffer to .Ar data . +If +.Fl w +is given, the buffer is also sent to the clipboard for +.Ar target-client +using the +.Xr xterm 1 +escape sequence, if possible. The .Fl a option appends to rather than overwriting the buffer. @@ -5167,26 +6009,38 @@ Lock each client individually by running the command specified by the .Ic lock-command option. .It Xo Ic run-shell -.Op Fl b +.Op Fl bC +.Op Fl d Ar delay .Op Fl t Ar target-pane -.Ar shell-command +.Op Ar shell-command .Xc .D1 (alias: Ic run ) Execute .Ar shell-command -in the background without creating a window. -Before being executed, shell-command is expanded using the rules specified in -the +or (with +.Fl C ) +a +.Nm +command in the background without creating a window. +Before being executed, +.Ar shell-command +is expanded using the rules specified in the .Sx FORMATS section. With .Fl b , the command is run in the background. -After it finishes, any output to stdout is displayed in copy mode (in the pane +.Fl d +waits for +.Ar delay +seconds before starting the command. +If +.Fl C +is not given, any output to stdout is displayed in view mode (in the pane specified by .Fl t -or the current pane if omitted). -If the command doesn't return success, the exit status is also displayed. +or the current pane if omitted) after the command finishes. +If the command fails, the exit status is also displayed. .It Xo Ic wait-for .Op Fl L | S | U .Ar channel @@ -5209,37 +6063,51 @@ When a client detaches, it prints a message. This may be one of: .Bl -tag -width Ds -.It [detached (from session ...)] +.It detached (from session ...) The client was detached normally. -.It [detached and SIGHUP] +.It detached and SIGHUP The client was detached and its parent sent the .Dv SIGHUP signal (for example with .Ic detach-client .Fl P ) . -.It [lost tty] +.It lost tty The client's .Xr tty 4 or .Xr pty 4 was unexpectedly destroyed. -.It [terminated] +.It terminated The client was killed with .Dv SIGTERM . -.It [exited] +.It too far behind +The client is in control mode and became unable to keep up with the data from +.Nm . +.It exited The server exited when it had no sessions. -.It [server exited] +.It server exited The server exited when it received .Dv SIGTERM . -.It [server exited unexpectedly] +.It server exited unexpectedly The server crashed or otherwise exited without telling the client the reason. .El .Sh TERMINFO EXTENSIONS .Nm understands some unofficial extensions to -.Xr terminfo 5 : +.Xr terminfo 5 . +It is not normally necessary to set these manually, instead the +.Ic terminal-features +option should be used. .Bl -tag -width Ds -.It Em Cs , Cr +.It Em \&AX +An existing extension that tells +.Nm +the terminal supports default colours. +.It Em \&Bidi +Tell +.Nm +that the terminal supports the VTE bidirectional text extensions. +.It Em \&Cs , Cr Set the cursor colour. The first takes a single string argument and is used to set the colour; the second takes no arguments and restores the default cursor colour. @@ -5249,35 +6117,38 @@ to change the cursor colour from inside .Bd -literal -offset indent $ printf '\e033]12;red\e033\e\e' .Ed +.It Em \&Cmg, \&Clmg, \&Dsmg , \&Enmg +Set, clear, disable or enable DECSLRM margins. +These are set automatically if the terminal reports it is +.Em VT420 +compatible. +.It Em \&Dsbp , \&Enbp +Disable and enable bracketed paste. +These are set automatically if the +.Em XT +capability is present. +.It Em \&Dseks , \&Eneks +Disable and enable extended keys. +.It Em \&Dsfcs , \&Enfcs +Disable and enable focus reporting. +These are set automatically if the +.Em XT +capability is present. +.It Em \&Rect +Tell +.Nm +that the terminal supports rectangle operations. .It Em \&Smol Enable the overline attribute. -The capability is usually SGR 53 and can be added to -.Ic terminal-overrides -as: -.Bd -literal -offset indent -Smol=\eE[53m -.Ed .It Em \&Smulx Set a styled underscore. The single parameter is one of: 0 for no underscore, 1 for normal underscore, 2 for double underscore, 3 for curly underscore, 4 for dotted underscore and 5 for dashed underscore. -The capability can typically be added to -.Ic terminal-overrides -as: -.Bd -literal -offset indent -Smulx=\eE[4::%p1%dm -.Ed -.It Em \&Setulc -Set the underscore colour. +.It Em \&Setulc , \&ol +Set the underscore colour or reset to the default. The argument is (red * 65536) + (green * 256) + blue where each is between 0 and 255. -The capability can typically be added to -.Ic terminal-overrides -as: -.Bd -literal -offset indent -Setulc=\eE[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m -.Ed .It Em \&Ss , Se Set or reset the cursor style. If set, a sequence such as this may be used @@ -5291,6 +6162,8 @@ If is not set, \&Ss with argument 0 will be used to reset the cursor style instead. .It Em \&Sxl Indicates that the terminal supports SIXEL. +.It Em \&Sync +Start (parameter is 1) or end (parameter is 2) a synchronized update. .It Em \&Tc Indicate that the terminal supports the .Ql direct colour @@ -5305,6 +6178,11 @@ capabilities to the .Nm .Xr terminfo 5 entry). +.Pp +This is equivalent to the +.Em RGB +.Xr terminfo 5 +capability. .It Em \&Ms Store the current buffer in the host terminal's selection (clipboard). See the @@ -5312,6 +6190,11 @@ See the option above and the .Xr xterm 1 man page. +.It Em \&XT +This is an existing extension capability that tmux uses to mean that the +terminal supports the +.Xr xterm 1 +title set sequences and to automatically set some of the capabilities above. .El .Sh CONTROL MODE .Nm @@ -5337,12 +6220,13 @@ and matching .Em %end or .Em %error -have two arguments: an integer time (as seconds from epoch) and command number. +have three arguments: an integer time (as seconds from epoch), command number and +flags (currently not used). For example: .Bd -literal -offset indent -%begin 1363006971 2 +%begin 1363006971 2 1 0: ksh* (1 panes) [80x24] [layout b25f,80x24,0,0,2] @2 (active) -%end 1363006971 2 +%end 1363006971 2 1 .Ed .Pp The @@ -5357,11 +6241,19 @@ A notification will never occur inside an output block. .Pp The following notifications are defined: .Bl -tag -width Ds -.It Ic %client-session-changed Ar client Ar session-id Ar name +.It Ic %client-detached Ar client +The client has detached. +.It Ic %client-session-changed Ar client session-id name The client is now attached to the session with ID .Ar session-id , which is named .Ar name . +.It Ic %continue Ar pane-id +The pane has been continued after being paused (if the +.Ar pause-after +flag is set, see +.Ic refresh-client +.Fl A ) . .It Ic %exit Op Ar reason The .Nm @@ -5370,6 +6262,17 @@ or an error occurred. If present, .Ar reason describes why the client exited. +.It Ic %extended-output Ar pane-id Ar age Ar ... \& : Ar value +New form of +.Ic %output +sent when the +.Ar pause-after +flag is set. +.Ar age +is the time in milliseconds for which tmux had buffered the output before it was sent. +Any subsequent arguments up until a single +.Ql \&: +are for future use and should be ignored. .It Ic %layout-change Ar window-id Ar window-layout Ar window-visible-layout Ar window-flags The layout of a window with ID .Ar window-id @@ -5388,6 +6291,10 @@ escapes non-printable characters and backslash as octal \\xxx. The pane with ID .Ar pane-id has changed mode. +.It Ic %pause Ar pane-id +The pane has been paused (if the +.Ar pause-after +flag is set). .It Ic %session-changed Ar session-id Ar name The client is now attached to the session with ID .Ar session-id , @@ -5403,6 +6310,26 @@ changed its active window to the window with ID .Ar window-id . .It Ic %sessions-changed A session was created or destroyed. +.It Xo Ic %subscription-changed +.Ar name +.Ar session-id +.Ar window-id +.Ar window-index +.Ar pane-id ... \& : +.Ar value +.Xc +The value of the format associated with subscription +.Ar name +has changed to +.Ar value . +See +.Ic refresh-client +.Fl B . +Any arguments after +.Ar pane-id +up until a single +.Ql \&: +are for future use and should be ignored. .It Ic %unlinked-window-add Ar window-id The window with ID .Ar window-id diff --git a/tmux.c b/tmux.c index 3c1feccc..11c368ff 100644 --- a/tmux.c +++ b/tmux.c @@ -21,11 +21,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -46,15 +46,15 @@ const char *shell_command; static __dead void usage(void); static char *make_label(const char *, char **); +static int areshell(const char *); static const char *getshell(void); -static int checkshell(const char *); static __dead void usage(void) { fprintf(stderr, - "usage: %s [-2CluvV] [-c shell-command] [-f file] [-L socket-name]\n" - " [-S socket-path] [command [flags]]\n", + "usage: %s [-2CDlNuvV] [-c shell-command] [-f file] [-L socket-name]\n" + " [-S socket-path] [-T features] [command [flags]]\n", getprogname()); exit(1); } @@ -76,7 +76,7 @@ getshell(void) return (_PATH_BSHELL); } -static int +int checkshell(const char *shell) { if (shell == NULL || *shell != '/') @@ -88,7 +88,7 @@ checkshell(const char *shell) return (1); } -int +static int areshell(const char *shell) { const char *progname, *ptr; @@ -105,48 +105,133 @@ areshell(const char *shell) return (0); } +static char * +expand_path(const char *path, const char *home) +{ + char *expanded, *name; + const char *end; + struct environ_entry *value; + + if (strncmp(path, "~/", 2) == 0) { + if (home == NULL) + return (NULL); + xasprintf(&expanded, "%s%s", home, path + 1); + return (expanded); + } + + if (*path == '$') { + end = strchr(path, '/'); + if (end == NULL) + name = xstrdup(path + 1); + else + name = xstrndup(path + 1, end - path - 1); + value = environ_find(global_environ, name); + free(name); + if (value == NULL) + return (NULL); + if (end == NULL) + end = ""; + xasprintf(&expanded, "%s%s", value->value, end); + return (expanded); + } + + return (xstrdup(path)); +} + +static void +expand_paths(const char *s, char ***paths, u_int *n, int ignore_errors) +{ + const char *home = find_home(); + char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; + char *path; + u_int i; + + *paths = NULL; + *n = 0; + + copy = tmp = xstrdup(s); + while ((next = strsep(&tmp, ":")) != NULL) { + expanded = expand_path(next, home); + if (expanded == NULL) { + log_debug("%s: invalid path: %s", __func__, next); + continue; + } + if (realpath(expanded, resolved) == NULL) { + log_debug("%s: realpath(\"%s\") failed: %s", __func__, + expanded, strerror(errno)); + if (ignore_errors) { + free(expanded); + continue; + } + path = expanded; + } else { + path = xstrdup(resolved); + free(expanded); + } + for (i = 0; i < *n; i++) { + if (strcmp(path, (*paths)[i]) == 0) + break; + } + if (i != *n) { + log_debug("%s: duplicate path: %s", __func__, path); + free(path); + continue; + } + *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); + (*paths)[(*n)++] = path; + } + free(copy); +} + static char * make_label(const char *label, char **cause) { - char *base, resolved[PATH_MAX], *path, *s; - struct stat sb; - uid_t uid; + char **paths, *path, *base; + u_int i, n; + struct stat sb; + uid_t uid; *cause = NULL; - if (label == NULL) label = "default"; uid = getuid(); - if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0') - xasprintf(&base, "%s/tmux-%ld", s, (long)uid); - else - xasprintf(&base, "%s/tmux-%ld", _PATH_TMP, (long)uid); - if (realpath(base, resolved) == NULL && - strlcpy(resolved, base, sizeof resolved) >= sizeof resolved) { - errno = ERANGE; - free(base); + expand_paths(TMUX_SOCK, &paths, &n, 1); + if (n == 0) { + xasprintf(cause, "no suitable socket path"); + return (NULL); + } + path = paths[0]; /* can only have one socket! */ + for (i = 1; i < n; i++) + free(paths[i]); + free(paths); + + xasprintf(&base, "%s/tmux-%ld", path, (long)uid); + free(path); + if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) { + xasprintf(cause, "couldn't create directory %s (%s)", base, + strerror(errno)); goto fail; } - free(base); - - if (mkdir(resolved, S_IRWXU) != 0 && errno != EEXIST) - goto fail; - if (lstat(resolved, &sb) != 0) + if (lstat(base, &sb) != 0) { + xasprintf(cause, "couldn't read directory %s (%s)", base, + strerror(errno)); goto fail; + } if (!S_ISDIR(sb.st_mode)) { - errno = ENOTDIR; + xasprintf(cause, "%s is not a directory", base); goto fail; } if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) { - errno = EACCES; + xasprintf(cause, "directory %s has unsafe permissions", base); goto fail; } - xasprintf(&path, "%s/%s", resolved, label); + xasprintf(&path, "%s/%s", base, label); + free(base); return (path); fail: - xasprintf(cause, "error creating %s (%s)", resolved, strerror(errno)); + free(base); return (NULL); } @@ -164,6 +249,33 @@ setblocking(int fd, int state) } } +uint64_t +get_timer(void) +{ + struct timespec ts; + + /* + * We want a timestamp in milliseconds suitable for time measurement, + * so prefer the monotonic clock. + */ + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) + clock_gettime(CLOCK_REALTIME, &ts); + return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL)); +} + +const char * +sig2name(int signo) +{ + static char s[11]; + +#ifdef HAVE_SYS_SIGNAME + if (signo > 0 && signo < NSIG) + return (sys_signame[signo]); +#endif + xsnprintf(s, sizeof s, "%d", signo); + return (s); +} + const char * find_cwd(void) { @@ -219,10 +331,13 @@ getversion(void) int main(int argc, char **argv) { - char *path, *label, *cause, **var; - const char *s, *shell, *cwd; - int opt, flags, keys; + char *path = NULL, *label = NULL; + char *cause, **var; + const char *s, *cwd; + int opt, keys, feat = 0, fflag = 0; + uint64_t flags = 0; const struct options_table_entry *oe; + u_int i; if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && setlocale(LC_CTYPE, "C.UTF-8") == NULL) { @@ -238,18 +353,25 @@ main(int argc, char **argv) if (**argv == '-') flags = CLIENT_LOGIN; - else - flags = 0; - label = path = NULL; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUvV")) != -1) { + global_environ = environ_create(); + for (var = environ; *var != NULL; var++) + environ_put(global_environ, *var, 0); + if ((cwd = find_cwd()) != NULL) + environ_set(global_environ, "PWD", 0, "%s", cwd); + expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); + + while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) { switch (opt) { case '2': - flags |= CLIENT_256COLOURS; + tty_add_features(&feat, "256", ":,"); break; case 'c': shell_command = optarg; break; + case 'D': + flags |= CLIENT_NOFORK; + break; case 'C': if (flags & CLIENT_CONTROL) flags |= CLIENT_CONTROLCONTROL; @@ -257,7 +379,16 @@ main(int argc, char **argv) flags |= CLIENT_CONTROL; break; case 'f': - set_cfg_file(optarg); + if (!fflag) { + fflag = 1; + for (i = 0; i < cfg_nfiles; i++) + free(cfg_files[i]); + cfg_nfiles = 0; + } + cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1, + sizeof *cfg_files); + cfg_files[cfg_nfiles++] = xstrdup(optarg); + cfg_quiet = 0; break; case 'V': printf("%s %s\n", getprogname(), getversion()); @@ -269,12 +400,18 @@ main(int argc, char **argv) free(label); label = xstrdup(optarg); break; + case 'N': + flags |= CLIENT_NOSTARTSERVER; + break; case 'q': break; case 'S': free(path); path = xstrdup(optarg); break; + case 'T': + tty_add_features(&feat, optarg, ":,"); + break; case 'u': flags |= CLIENT_UTF8; break; @@ -290,6 +427,8 @@ main(int argc, char **argv) if (shell_command != NULL && argc != 0) usage(); + if ((flags & CLIENT_NOFORK) && argc != 0) + usage(); if ((ptm_fd = getptmfd()) == -1) err(1, "getptmfd"); @@ -319,12 +458,6 @@ main(int argc, char **argv) flags |= CLIENT_UTF8; } - global_environ = environ_create(); - for (var = environ; *var != NULL; var++) - environ_put(global_environ, *var); - if ((cwd = find_cwd()) != NULL) - environ_set(global_environ, "PWD", "%s", cwd); - global_options = options_create(NULL); global_s_options = options_create(NULL); global_w_options = options_create(NULL); @@ -341,11 +474,12 @@ main(int argc, char **argv) * The default shell comes from SHELL or from the user's passwd entry * if available. */ - shell = getshell(); - options_set_string(global_s_options, "default-shell", 0, "%s", shell); + options_set_string(global_s_options, "default-shell", 0, "%s", + getshell()); /* Override keys to vi if VISUAL or EDITOR are set. */ if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { + options_set_string(global_options, "editor", 0, "%s", s); if (strrchr(s, '/') != NULL) s = strrchr(s, '/') + 1; if (strstr(s, "vi") != NULL) @@ -368,16 +502,19 @@ main(int argc, char **argv) path[strcspn(path, ",")] = '\0'; } } - if (path == NULL && (path = make_label(label, &cause)) == NULL) { - if (cause != NULL) { - fprintf(stderr, "%s\n", cause); - free(cause); + if (path == NULL) { + if ((path = make_label(label, &cause)) == NULL) { + if (cause != NULL) { + fprintf(stderr, "%s\n", cause); + free(cause); + } + exit(1); } - exit(1); + flags |= CLIENT_DEFAULTSOCKET; } socket_path = path; free(label); /* Pass control to the client. */ - exit(client_main(osdep_event_init(), argc, argv, flags)); + exit(client_main(osdep_event_init(), argc, argv, flags, feat)); } diff --git a/tmux.h b/tmux.h index 1418d0bd..4182ce07 100644 --- a/tmux.h +++ b/tmux.h @@ -22,51 +22,64 @@ #include #include -#include #include #include #include #include -#include #ifdef HAVE_UTEMPTER #include #endif #include "compat.h" +#include "tmux-protocol.h" #include "xmalloc.h" extern char **environ; struct args; -struct args_value; +struct args_command_state; struct client; +struct cmd; struct cmd_find_state; struct cmdq_item; struct cmdq_list; +struct cmdq_state; +struct cmds; +struct control_state; struct environ; struct format_job_tree; struct format_tree; struct input_ctx; struct job; +struct menu_data; struct mode_tree_data; struct mouse_event; struct options; -struct options_entry; struct options_array_item; +struct options_entry; +struct screen_write_citem; +struct screen_write_cline; +struct screen_write_ctx; struct session; struct sixel_image; +struct tty_ctx; +struct tty_code; +struct tty_key; struct tmuxpeer; struct tmuxproc; struct winlink; -/* Client-server protocol version. */ -#define PROTOCOL_VERSION 8 - -/* Default configuration files. */ +/* Default configuration files and socket paths. */ #ifndef TMUX_CONF #define TMUX_CONF "/etc/tmux.conf:~/.tmux.conf" #endif +#ifndef TMUX_SOCK +#define TMUX_SOCK "$TMUX_TMPDIR:" _PATH_TMP +#endif +#ifndef TMUX_TERM +#define TMUX_TERM "screen" +#endif /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 @@ -78,9 +91,6 @@ struct winlink; /* Automatic name refresh interval, in microseconds. Must be < 1 second. */ #define NAME_INTERVAL 500000 -/* Maximum size of data to hold from a pane. */ -#define READ_SIZE 4096 - /* Default pixel cell sizes. */ #define DEFAULT_XPIXEL 16 #define DEFAULT_YPIXEL 32 @@ -104,29 +114,48 @@ struct winlink; #define VISUAL_ON 1 #define VISUAL_BOTH 2 -/* Special key codes. */ -#define KEYC_NONE 0xffff00000000ULL -#define KEYC_UNKNOWN 0xfffe00000000ULL -#define KEYC_BASE 0x000010000000ULL -#define KEYC_USER 0x000020000000ULL +/* No key or unknown key. */ +#define KEYC_NONE 0x000ff000000000ULL +#define KEYC_UNKNOWN 0x000fe000000000ULL + +/* + * Base for special (that is, not Unicode) keys. An enum must be at most a + * signed int, so these are based in the highest Unicode PUA. + */ +#define KEYC_BASE 0x0000000010e000ULL +#define KEYC_USER 0x0000000010f000ULL + +/* Key modifier bits. */ +#define KEYC_META 0x00100000000000ULL +#define KEYC_CTRL 0x00200000000000ULL +#define KEYC_SHIFT 0x00400000000000ULL + +/* Key flag bits. */ +#define KEYC_LITERAL 0x01000000000000ULL +#define KEYC_KEYPAD 0x02000000000000ULL +#define KEYC_CURSOR 0x04000000000000ULL +#define KEYC_IMPLIED_META 0x08000000000000ULL +#define KEYC_BUILD_MODIFIERS 0x10000000000000ULL +#define KEYC_VI 0x20000000000000ULL + +/* Masks for key bits. */ +#define KEYC_MASK_MODIFIERS 0x00f00000000000ULL +#define KEYC_MASK_FLAGS 0xff000000000000ULL +#define KEYC_MASK_KEY 0x000fffffffffffULL /* Available user keys. */ #define KEYC_NUSER 1000 -/* Key modifier bits. */ -#define KEYC_ESCAPE 0x200000000000ULL -#define KEYC_CTRL 0x400000000000ULL -#define KEYC_SHIFT 0x800000000000ULL -#define KEYC_XTERM 0x1000000000000ULL -#define KEYC_LITERAL 0x2000000000000ULL - -/* Mask to obtain key w/o modifiers. */ -#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT|KEYC_XTERM|KEYC_LITERAL) -#define KEYC_MASK_KEY (~KEYC_MASK_MOD) - /* Is this a mouse key? */ -#define KEYC_IS_MOUSE(key) (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \ - ((key) & KEYC_MASK_KEY) < KEYC_BSPACE) +#define KEYC_IS_MOUSE(key) \ + (((key) & KEYC_MASK_KEY) >= KEYC_MOUSE && \ + ((key) & KEYC_MASK_KEY) < KEYC_BSPACE) + +/* Is this a Unicode key? */ +#define KEYC_IS_UNICODE(key) \ + (((key) & KEYC_MASK_KEY) > 0x7f && \ + (((key) & KEYC_MASK_KEY) < KEYC_BASE || \ + ((key) & KEYC_MASK_KEY) >= KEYC_BASE_END)) /* Multiple click timeout. */ #define KEYC_CLICK_TIMEOUT 300 @@ -148,8 +177,8 @@ struct winlink; { #s "Border", KEYC_ ## name ## _BORDER } /* - * A single key. This can be ASCII or Unicode or one of the keys starting at - * KEYC_BASE. + * A single key. This can be ASCII or Unicode or one of the keys between + * KEYC_BASE and KEYC_BASE_END. */ typedef unsigned long long key_code; @@ -169,6 +198,7 @@ enum { /* Mouse keys. */ KEYC_MOUSE, /* unclassified mouse event */ KEYC_DRAGGING, /* dragging in progress */ + KEYC_DOUBLECLICK, /* double click complete */ KEYC_MOUSE_KEY(MOUSEMOVE), KEYC_MOUSE_KEY(MOUSEDOWN1), KEYC_MOUSE_KEY(MOUSEDOWN2), @@ -184,6 +214,9 @@ enum { KEYC_MOUSE_KEY(MOUSEDRAGEND3), KEYC_MOUSE_KEY(WHEELUP), KEYC_MOUSE_KEY(WHEELDOWN), + KEYC_MOUSE_KEY(SECONDCLICK1), + KEYC_MOUSE_KEY(SECONDCLICK2), + KEYC_MOUSE_KEY(SECONDCLICK3), KEYC_MOUSE_KEY(DOUBLECLICK1), KEYC_MOUSE_KEY(DOUBLECLICK2), KEYC_MOUSE_KEY(DOUBLECLICK3), @@ -238,18 +271,25 @@ enum { KEYC_KP_ENTER, KEYC_KP_ZERO, KEYC_KP_PERIOD, + + /* End of special keys. */ + KEYC_BASE_END }; /* Termcap codes. */ enum tty_code_code { TTYC_ACSC, + TTYC_AM, TTYC_AX, TTYC_BCE, TTYC_BEL, + TTYC_BIDI, TTYC_BLINK, TTYC_BOLD, TTYC_CIVIS, TTYC_CLEAR, + TTYC_CLMG, + TTYC_CMG, TTYC_CNORM, TTYC_COLORS, TTYC_CR, @@ -270,12 +310,20 @@ enum tty_code_code { TTYC_DIM, TTYC_DL, TTYC_DL1, + TTYC_DSBP, + TTYC_DSEKS, + TTYC_DSFCS, + TTYC_DSMG, TTYC_E3, TTYC_ECH, TTYC_ED, TTYC_EL, TTYC_EL1, TTYC_ENACS, + TTYC_ENBP, + TTYC_ENEKS, + TTYC_ENFCS, + TTYC_ENMG, TTYC_FSL, TTYC_HOME, TTYC_HPA, @@ -423,7 +471,9 @@ enum tty_code_code { TTYC_KUP6, TTYC_KUP7, TTYC_MS, + TTYC_OL, TTYC_OP, + TTYC_RECT, TTYC_REV, TTYC_RGB, TTYC_RI, @@ -434,6 +484,7 @@ enum tty_code_code { TTYC_SE, TTYC_SETAB, TTYC_SETAF, + TTYC_SETAL, TTYC_SETRGBB, TTYC_SETRGBF, TTYC_SETULC, @@ -441,105 +492,24 @@ enum tty_code_code { TTYC_SITM, TTYC_SMACS, TTYC_SMCUP, - TTYC_SMOL, TTYC_SMKX, + TTYC_SMOL, TTYC_SMSO, - TTYC_SMULX, TTYC_SMUL, + TTYC_SMULX, TTYC_SMXX, TTYC_SXL, TTYC_SS, + TTYC_SYNC, TTYC_TC, TTYC_TSL, TTYC_U8, TTYC_VPA, - TTYC_XENL, - TTYC_XT, + TTYC_XT }; -/* Message codes. */ -enum msgtype { - MSG_VERSION = 12, - - MSG_IDENTIFY_FLAGS = 100, - MSG_IDENTIFY_TERM, - MSG_IDENTIFY_TTYNAME, - MSG_IDENTIFY_OLDCWD, /* unused */ - MSG_IDENTIFY_STDIN, - MSG_IDENTIFY_ENVIRON, - MSG_IDENTIFY_DONE, - MSG_IDENTIFY_CLIENTPID, - MSG_IDENTIFY_CWD, - - MSG_COMMAND = 200, - MSG_DETACH, - MSG_DETACHKILL, - MSG_EXIT, - MSG_EXITED, - MSG_EXITING, - MSG_LOCK, - MSG_READY, - MSG_RESIZE, - MSG_SHELL, - MSG_SHUTDOWN, - MSG_OLDSTDERR, /* unused */ - MSG_OLDSTDIN, /* unused */ - MSG_OLDSTDOUT, /* unused */ - MSG_SUSPEND, - MSG_UNLOCK, - MSG_WAKEUP, - MSG_EXEC, - - MSG_READ_OPEN = 300, - MSG_READ, - MSG_READ_DONE, - MSG_WRITE_OPEN, - MSG_WRITE, - MSG_WRITE_READY, - MSG_WRITE_CLOSE -}; - -/* - * Message data. - * - * Don't forget to bump PROTOCOL_VERSION if any of these change! - */ -struct msg_command { - int argc; -}; /* followed by packed argv */ - -struct msg_read_open { - int stream; - int fd; -}; /* followed by path */ - -struct msg_read_data { - int stream; -}; - -struct msg_read_done { - int stream; - int error; -}; - -struct msg_write_open { - int stream; - int fd; - int flags; -}; /* followed by path */ - -struct msg_write_data { - int stream; -}; /* followed by data */ - -struct msg_write_ready { - int stream; - int error; -}; - -struct msg_write_close { - int stream; -}; +/* Character classes. */ +#define WHITESPACE " " /* Mode keys. */ #define MODEKEY_EMACS 0 @@ -549,11 +519,11 @@ struct msg_write_close { #define MODE_CURSOR 0x1 #define MODE_INSERT 0x2 #define MODE_KCURSOR 0x4 -#define MODE_KKEYPAD 0x8 /* set = application, clear = number */ -#define MODE_WRAP 0x10 /* whether lines wrap */ +#define MODE_KKEYPAD 0x8 +#define MODE_WRAP 0x10 #define MODE_MOUSE_STANDARD 0x20 #define MODE_MOUSE_BUTTON 0x40 -#define MODE_BLINKING 0x80 +#define MODE_CURSOR_BLINKING 0x80 #define MODE_MOUSE_UTF8 0x100 #define MODE_MOUSE_SGR 0x200 #define MODE_BRACKETPASTE 0x400 @@ -561,16 +531,23 @@ struct msg_write_close { #define MODE_MOUSE_ALL 0x1000 #define MODE_ORIGIN 0x2000 #define MODE_CRLF 0x4000 +#define MODE_KEXTENDED 0x8000 +#define MODE_CURSOR_VERY_VISIBLE 0x10000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) +#define MOTION_MOUSE_MODES (MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) +#define CURSOR_MODES (MODE_CURSOR|MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE) + +/* A single UTF-8 character. */ +typedef u_int utf8_char; /* - * A single UTF-8 character. UTF8_SIZE must be big enough to hold - * combining characters as well, currently at most five (of three - * bytes) are supported. -*/ -#define UTF8_SIZE 18 + * An expanded UTF-8 character. UTF8_SIZE must be big enough to hold combining + * characters as well. It can't be more than 32 bytes without changes to how + * characters are stored. + */ +#define UTF8_SIZE 21 struct utf8_data { u_char data[UTF8_SIZE]; @@ -578,7 +555,7 @@ struct utf8_data { u_char size; u_char width; /* 0xff if invalid */ -} __packed; +}; enum utf8_state { UTF8_MORE, UTF8_DONE, @@ -592,6 +569,15 @@ enum utf8_state { /* Special colours. */ #define COLOUR_DEFAULT(c) ((c) == 8 || (c) == 9) +/* Replacement palette. */ +struct colour_palette { + int fg; + int bg; + + int *palette; + int *default_palette; +}; + /* Grid attributes. Anything above 0xff is stored in an extended cell. */ #define GRID_ATTR_BRIGHT 0x1 #define GRID_ATTR_DIM 0x2 @@ -632,13 +618,25 @@ enum utf8_state { /* Grid cell data. */ struct grid_cell { - struct utf8_data data; /* 21 bytes */ + struct utf8_data data; + u_short attr; + u_char flags; + int fg; + int bg; + int us; +}; + +/* Grid extended cell entry. */ +struct grid_extd_entry { + utf8_char data; u_short attr; u_char flags; int fg; int bg; int us; } __packed; + +/* Grid cell entry. */ struct grid_cell_entry { u_char flags; union { @@ -659,7 +657,7 @@ struct grid_line { struct grid_cell_entry *celldata; u_int extdsize; - struct grid_cell *extddata; + struct grid_extd_entry *extddata; int flags; } __packed; @@ -679,12 +677,20 @@ struct grid { struct grid_line *linedata; }; +/* Virtual cursor in a grid. */ +struct grid_reader { + struct grid *gd; + u_int cx; + u_int cy; +}; + /* Style alignment. */ enum style_align { STYLE_ALIGN_DEFAULT, STYLE_ALIGN_LEFT, STYLE_ALIGN_CENTRE, - STYLE_ALIGN_RIGHT + STYLE_ALIGN_RIGHT, + STYLE_ALIGN_ABSOLUTE_CENTRE }; /* Style list. */ @@ -724,6 +730,7 @@ enum style_default_type { /* Style option. */ struct style { struct grid_cell gc; + int ignore; int fill; enum style_align align; @@ -750,47 +757,64 @@ struct image { }; TAILQ_HEAD(images, image); +/* Cursor style. */ +enum screen_cursor_style { + SCREEN_CURSOR_DEFAULT, + SCREEN_CURSOR_BLOCK, + SCREEN_CURSOR_UNDERLINE, + SCREEN_CURSOR_BAR +}; + /* Virtual screen. */ struct screen_sel; struct screen_titles; struct screen { - char *title; - char *path; - struct screen_titles *titles; + char *title; + char *path; + struct screen_titles *titles; - struct grid *grid; /* grid data */ + struct grid *grid; /* grid data */ - u_int cx; /* cursor x */ - u_int cy; /* cursor y */ + u_int cx; /* cursor x */ + u_int cy; /* cursor y */ - u_int cstyle; /* cursor style */ - char *ccolour; /* cursor colour string */ + enum screen_cursor_style cstyle; /* cursor style */ + char *ccolour; /* cursor colour */ - u_int rupper; /* scroll region top */ - u_int rlower; /* scroll region bottom */ + u_int rupper; /* scroll region top */ + u_int rlower; /* scroll region bottom */ - int mode; - bitstr_t *tabs; - struct screen_sel *sel; + u_int saved_cx; + u_int saved_cy; + struct grid *saved_grid; + struct grid_cell saved_cell; + int saved_flags; - struct images images; + int mode; + bitstr_t *tabs; + struct screen_sel *sel; + struct images images; + + struct screen_write_cline *write_list; }; /* Screen write context. */ -struct screen_write_collect_item; -struct screen_write_collect_line; +typedef void (*screen_write_init_ctx_cb)(struct screen_write_ctx *, + struct tty_ctx *); struct screen_write_ctx { - struct window_pane *wp; - struct screen *s; + struct window_pane *wp; + struct screen *s; - struct screen_write_collect_item *item; - struct screen_write_collect_line *list; - u_int scrolled; - u_int bg; + int flags; +#define SCREEN_WRITE_SYNC 0x1 +#define SCREEN_WRITE_ZWJ 0x2 - u_int cells; - u_int written; - u_int skipped; + screen_write_init_ctx_cb init_ctx_cb; + void *arg; + + struct screen_write_citem *item; + u_int scrolled; + u_int bg; }; /* Screen redraw context. */ @@ -801,6 +825,10 @@ struct screen_redraw_ctx { int statustop; int pane_status; + int pane_lines; + + struct grid_cell no_pane_gc; + int no_pane_gc_set; u_int sx; u_int sy; @@ -827,7 +855,6 @@ struct menu { u_int width; }; typedef void (*menu_choice_cb)(struct menu *, u_int, key_code, void *); -#define MENU_NOMOUSE 0x1 /* * Window mode. Windows can be in several modes and this is used to call the @@ -842,6 +869,7 @@ struct window_mode { 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 *); void (*key)(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -853,11 +881,11 @@ struct window_mode { void (*formats)(struct window_mode_entry *, struct format_tree *); }; -#define WINDOW_MODE_TIMEOUT 180 /* Active window mode. */ struct window_mode_entry { struct window_pane *wp; + struct window_pane *swp; const struct window_mode *mode; void *data; @@ -865,9 +893,26 @@ struct window_mode_entry { struct screen *screen; u_int prefix; - TAILQ_ENTRY (window_mode_entry) entry; + TAILQ_ENTRY(window_mode_entry) entry; }; +/* Offsets into pane buffer. */ +struct window_pane_offset { + size_t used; +}; + +/* Queued pane resize. */ +struct window_pane_resize { + u_int sx; + u_int sy; + + u_int osx; + u_int osy; + + TAILQ_ENTRY(window_pane_resize) entry; +}; +TAILQ_HEAD(window_pane_resizes, window_pane_resize); + /* Child window structure. */ struct window_pane { u_int id; @@ -882,9 +927,6 @@ struct window_pane { u_int sx; u_int sy; - u_int osx; - u_int osy; - u_int xoff; u_int yoff; @@ -892,9 +934,9 @@ struct window_pane { #define PANE_REDRAW 0x1 #define PANE_DROP 0x2 #define PANE_FOCUSED 0x4 -#define PANE_RESIZE 0x8 -#define PANE_RESIZEFORCE 0x10 -#define PANE_FOCUSPUSH 0x20 +/* 0x8 unused */ +/* 0x10 unused */ +/* 0x20 unused */ #define PANE_INPUTOFF 0x40 #define PANE_CHANGED 0x80 #define PANE_EXITED 0x100 @@ -902,7 +944,6 @@ struct window_pane { #define PANE_STATUSDRAWN 0x400 #define PANE_EMPTY 0x800 #define PANE_STYLECHANGED 0x1000 -#define PANE_RESIZED 0x2000 int argc; char **argv; @@ -915,19 +956,22 @@ struct window_pane { int fd; struct bufferevent *event; - u_int disabled; + struct window_pane_offset offset; + size_t base_offset; + + struct window_pane_resizes resize_queue; struct event resize_timer; struct input_ctx *ictx; - struct style cached_style; - struct style cached_active_style; - int *palette; + struct grid_cell cached_gc; + struct grid_cell cached_active_gc; + struct colour_palette palette; int pipe_fd; struct bufferevent *pipe_event; - size_t pipe_off; + struct window_pane_offset pipe_offset; struct screen *screen; struct screen base; @@ -935,16 +979,13 @@ struct window_pane { struct screen status_screen; size_t status_size; - /* Saved in alternative screen mode. */ - u_int saved_cx; - u_int saved_cy; - struct grid *saved_grid; - struct grid_cell saved_cell; + TAILQ_HEAD(, window_mode_entry) modes; - TAILQ_HEAD (, window_mode_entry) modes; - struct event modetimer; - time_t modelast; char *searchstr; + int searchregex; + + int border_gc_set; + struct grid_cell border_gc; TAILQ_ENTRY(window_pane) entry; RB_ENTRY(window_pane) tree_entry; @@ -977,15 +1018,23 @@ struct window { u_int sx; u_int sy; + u_int manual_sx; + u_int manual_sy; u_int xpixel; u_int ypixel; + u_int new_sx; + u_int new_sy; + u_int new_xpixel; + u_int new_ypixel; + int flags; #define WINDOW_BELL 0x1 #define WINDOW_ACTIVITY 0x2 #define WINDOW_SILENCE 0x4 #define WINDOW_ZOOMED 0x8 #define WINDOW_WASZOOMED 0x10 +#define WINDOW_RESIZE 0x20 #define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE) int alerts_queued; @@ -1030,6 +1079,13 @@ TAILQ_HEAD(winlink_stack, winlink); #define PANE_STATUS_TOP 1 #define PANE_STATUS_BOTTOM 2 +/* Pane border lines option. */ +#define PANE_LINES_SINGLE 0 +#define PANE_LINES_DOUBLE 1 +#define PANE_LINES_HEAVY 2 +#define PANE_LINES_SIMPLE 3 +#define PANE_LINES_NUMBER 4 + /* Layout direction. */ enum layout_type { LAYOUT_LEFTRIGHT, @@ -1063,6 +1119,9 @@ struct environ_entry { char *name; char *value; + int flags; +#define ENVIRON_HIDDEN 0x1 + RB_ENTRY(environ_entry) entry; }; @@ -1121,6 +1180,7 @@ RB_HEAD(sessions, session); #define MOUSE_MASK_CTRL 16 #define MOUSE_MASK_DRAG 32 #define MOUSE_MASK_WHEEL 64 +#define MOUSE_MASK_MODIFIERS (MOUSE_MASK_SHIFT|MOUSE_MASK_META|MOUSE_MASK_CTRL) /* Mouse wheel states. */ #define MOUSE_WHEEL_UP 0 @@ -1135,6 +1195,7 @@ RB_HEAD(sessions, session); /* Mouse input. */ struct mouse_event { int valid; + int ignore; key_code key; @@ -1166,38 +1227,30 @@ struct key_event { struct mouse_event m; }; -/* TTY information. */ -struct tty_key { - char ch; - key_code key; - - struct tty_key *left; - struct tty_key *right; - - struct tty_key *next; -}; - -struct tty_code; +/* Terminal definition. */ struct tty_term { char *name; - u_int references; + struct tty *tty; + int features; char acs[UCHAR_MAX + 1][2]; struct tty_code *codes; #define TERM_256COLOURS 0x1 -#define TERM_NOXENL 0x2 +#define TERM_NOAM 0x2 #define TERM_DECSLRM 0x4 #define TERM_DECFRA 0x8 #define TERM_RGBCOLOURS 0x10 -#define TERM_SIXEL 0x20 +#define TERM_VT100LIKE 0x20 +#define TERM_SIXEL 0x40 int flags; LIST_ENTRY(tty_term) entry; }; LIST_HEAD(tty_terms, tty_term); +/* Client terminal. */ struct tty { struct client *client; struct event start_timer; @@ -1209,7 +1262,7 @@ struct tty { u_int cx; u_int cy; - u_int cstyle; + enum screen_cursor_style cstyle; char *ccolour; int oflag; @@ -1226,7 +1279,6 @@ struct tty { u_int rleft; u_int rright; - int fd; struct event event_in; struct evbuffer *in; struct event event_out; @@ -1237,26 +1289,23 @@ struct tty { struct termios tio; struct grid_cell cell; - - int last_wp; struct grid_cell last_cell; #define TTY_NOCURSOR 0x1 #define TTY_FREEZE 0x2 #define TTY_TIMER 0x4 -#define TTY_UTF8 0x8 +/* 0x8 unused */ #define TTY_STARTED 0x10 #define TTY_OPENED 0x20 -#define TTY_FOCUS 0x40 +/* 0x40 unused */ #define TTY_BLOCK 0x80 #define TTY_HAVEDA 0x100 -#define TTY_HAVEDSR 0x200 -#define TTY_NOBLOCK 0x400 +#define TTY_HAVEXDA 0x200 +#define TTY_SYNCING 0x400 +#define TTY_NOBLOCK 0x800 int flags; struct tty_term *term; - char *term_name; - int term_flags; u_int mouse_last_x; u_int mouse_last_y; @@ -1271,9 +1320,15 @@ struct tty { struct tty_key *key_tree; }; -/* TTY command context. */ +/* Terminal command context. */ +typedef void (*tty_ctx_redraw_cb)(const struct tty_ctx *); +typedef int (*tty_ctx_set_client_cb)(struct tty_ctx *, struct client *); struct tty_ctx { - struct window_pane *wp; + struct screen *s; + + tty_ctx_redraw_cb redraw_cb; + tty_ctx_set_client_cb set_client_cb; + void *arg; const struct grid_cell *cell; int wrapped; @@ -1293,36 +1348,76 @@ struct tty_ctx { u_int orupper; u_int orlower; - /* Pane offset. */ + /* Target region (usually pane) offset and size. */ u_int xoff; u_int yoff; + u_int rxoff; + u_int ryoff; + u_int sx; + u_int sy; /* The background colour used for clearing (erasing). */ u_int bg; - /* Window offset and size. */ + /* The default colours and palette. */ + struct grid_cell defaults; + struct colour_palette *palette; + + /* Containing region (usually window) offset and size. */ int bigger; - u_int ox; - u_int oy; - u_int sx; - u_int sy; + u_int wox; + u_int woy; + u_int wsx; + u_int wsy; }; /* Saved message entry. */ struct message_entry { - char *msg; - u_int msg_num; - time_t msg_time; - TAILQ_ENTRY(message_entry) entry; + char *msg; + u_int msg_num; + struct timeval msg_time; + + TAILQ_ENTRY(message_entry) entry; +}; +TAILQ_HEAD(message_list, message_entry); + +/* Argument type. */ +enum args_type { + ARGS_NONE, + ARGS_STRING, + ARGS_COMMANDS }; -/* Parsed arguments structures. */ +/* Argument value. */ +struct args_value { + enum args_type type; + union { + char *string; + struct cmd_list *cmdlist; + }; + char *cached; + TAILQ_ENTRY(args_value) entry; +}; + +/* Arguments set. */ struct args_entry; RB_HEAD(args_tree, args_entry); -struct args { - struct args_tree tree; - int argc; - char **argv; + +/* Arguments parsing type. */ +enum args_parse_type { + ARGS_PARSE_INVALID, + ARGS_PARSE_STRING, + ARGS_PARSE_COMMANDS_OR_STRING, + ARGS_PARSE_COMMANDS +}; + +/* Arguments parsing state. */ +typedef enum args_parse_type (*args_parse_cb)(struct args *, u_int, char **); +struct args_parse { + const char *template; + int lower; + int upper; + args_parse_cb cb; }; /* Command find structures. */ @@ -1351,27 +1446,11 @@ struct cmd_find_state { #define CMD_FIND_EXACT_WINDOW 0x20 #define CMD_FIND_CANFAIL 0x40 -/* Command and list of commands. */ -struct cmd { - const struct cmd_entry *entry; - struct args *args; - u_int group; - - char *file; - u_int line; - - char *alias; - int argc; - char **argv; - - TAILQ_ENTRY(cmd) qentry; -}; -TAILQ_HEAD(cmds, cmd); - +/* List of commands. */ struct cmd_list { - int references; - u_int group; - struct cmds list; + int references; + u_int group; + struct cmds *list; }; /* Command return values. */ @@ -1384,7 +1463,6 @@ enum cmd_retval { /* Command parse result. */ enum cmd_parse_status { - CMD_PARSE_EMPTY, CMD_PARSE_ERROR, CMD_PARSE_SUCCESS }; @@ -1399,6 +1477,7 @@ struct cmd_parse_input { #define CMD_PARSE_PARSEONLY 0x2 #define CMD_PARSE_NOALIAS 0x4 #define CMD_PARSE_VERBOSE 0x8 +#define CMD_PARSE_ONEGROUP 0x10 const char *file; u_int line; @@ -1408,59 +1487,13 @@ struct cmd_parse_input { struct cmd_find_state fs; }; -/* Command queue item type. */ -enum cmdq_type { - CMDQ_COMMAND, - CMDQ_CALLBACK, -}; +/* Command queue flags. */ +#define CMDQ_STATE_REPEAT 0x1 +#define CMDQ_STATE_CONTROL 0x2 +#define CMDQ_STATE_NOHOOKS 0x4 -/* Command queue item shared state. */ -struct cmdq_shared { - int references; - - int flags; -#define CMDQ_SHARED_REPEAT 0x1 -#define CMDQ_SHARED_CONTROL 0x2 - - struct format_tree *formats; - - struct mouse_event mouse; - struct cmd_find_state current; -}; - -/* Command queue item. */ +/* Command queue callback. */ typedef enum cmd_retval (*cmdq_cb) (struct cmdq_item *, void *); -struct cmdq_item { - char *name; - struct cmdq_list *queue; - struct cmdq_item *next; - - struct client *client; - - enum cmdq_type type; - u_int group; - - u_int number; - time_t time; - - int flags; -#define CMDQ_FIRED 0x1 -#define CMDQ_WAITING 0x2 -#define CMDQ_NOHOOKS 0x4 - - struct cmdq_shared *shared; - struct cmd_find_state source; - struct cmd_find_state target; - - struct cmd_list *cmdlist; - struct cmd *cmd; - - cmdq_cb cb; - void *data; - - TAILQ_ENTRY(cmdq_item) entry; -}; -TAILQ_HEAD(cmdq_list, cmdq_item); /* Command definition flag. */ struct cmd_entry_flag { @@ -1474,11 +1507,7 @@ struct cmd_entry { const char *name; const char *alias; - struct { - const char *template; - int lower; - int upper; - } args; + struct args_parse args; const char *usage; struct cmd_entry_flag source; @@ -1487,6 +1516,9 @@ struct cmd_entry { #define CMD_STARTSERVER 0x1 #define CMD_READONLY 0x2 #define CMD_AFTERHOOK 0x4 +#define CMD_CLIENT_CFLAG 0x8 +#define CMD_CLIENT_TFLAG 0x10 +#define CMD_CLIENT_CANFAIL 0x20 int flags; enum cmd_retval (*exec)(struct cmd *, struct cmdq_item *); @@ -1509,11 +1541,23 @@ struct status_line { struct status_line_entry entries[STATUS_LINES_LIMIT]; }; +/* Prompt type. */ +#define PROMPT_NTYPES 4 +enum prompt_type { + PROMPT_TYPE_COMMAND, + PROMPT_TYPE_SEARCH, + PROMPT_TYPE_TARGET, + PROMPT_TYPE_WINDOW_TARGET, + PROMPT_TYPE_INVALID = 0xff +}; + /* File in client. */ typedef void (*client_file_cb) (struct client *, const char *, int, int, struct evbuffer *, void *); struct client_file { struct client *c; + struct tmuxpeer *peer; + struct client_files *tree; int references; int stream; @@ -1528,23 +1572,46 @@ struct client_file { client_file_cb cb; void *data; - RB_ENTRY (client_file) entry; + RB_ENTRY(client_file) entry; }; RB_HEAD(client_files, client_file); +/* Client window. */ +struct client_window { + u_int window; + struct window_pane *pane; + + u_int sx; + u_int sy; + + RB_ENTRY(client_window) entry; +}; +RB_HEAD(client_windows, client_window); + /* Client connection. */ typedef int (*prompt_input_cb)(struct client *, void *, const char *, int); typedef void (*prompt_free_cb)(void *); -typedef void (*overlay_draw_cb)(struct client *, struct screen_redraw_ctx *); -typedef int (*overlay_key_cb)(struct client *, struct key_event *); -typedef void (*overlay_free_cb)(struct client *); +typedef int (*overlay_check_cb)(struct client *, void *, u_int, u_int); +typedef struct screen *(*overlay_mode_cb)(struct client *, void *, u_int *, + u_int *); +typedef void (*overlay_draw_cb)(struct client *, void *, + struct screen_redraw_ctx *); +typedef int (*overlay_key_cb)(struct client *, void *, struct key_event *); +typedef void (*overlay_free_cb)(struct client *, void *); +typedef void (*overlay_resize_cb)(struct client *, void *); struct client { const char *name; struct tmuxpeer *peer; - struct cmdq_list queue; + struct cmdq_list *queue; + + struct client_windows windows; + + struct control_state *control_state; + u_int pause_age; pid_t pid; int fd; + int out_fd; struct event event; int retval; @@ -1557,7 +1624,12 @@ struct client { char *title; const char *cwd; - char *term; + char *term_name; + int term_features; + char *term_type; + char **term_caps; + u_int term_ncaps; + char *ttyname; struct tty tty; @@ -1569,6 +1641,7 @@ struct client { struct event click_timer; u_int click_button; + struct mouse_event click_event; struct status_line status; @@ -1584,12 +1657,12 @@ struct client { #define CLIENT_DEAD 0x200 #define CLIENT_REDRAWBORDERS 0x400 #define CLIENT_READONLY 0x800 -#define CLIENT_DETACHING 0x1000 +#define CLIENT_NOSTARTSERVER 0x1000 #define CLIENT_CONTROL 0x2000 #define CLIENT_CONTROLCONTROL 0x4000 #define CLIENT_FOCUSED 0x8000 #define CLIENT_UTF8 0x10000 -#define CLIENT_256COLOURS 0x20000 +#define CLIENT_IGNORESIZE 0x20000 #define CLIENT_IDENTIFIED 0x40000 #define CLIENT_STATUSFORCE 0x80000 #define CLIENT_DOUBLECLICK 0x100000 @@ -1599,44 +1672,66 @@ struct client { #define CLIENT_REDRAWSTATUSALWAYS 0x1000000 #define CLIENT_REDRAWOVERLAY 0x2000000 #define CLIENT_CONTROL_NOOUTPUT 0x4000000 +#define CLIENT_DEFAULTSOCKET 0x8000000 +#define CLIENT_STARTSERVER 0x10000000 +#define CLIENT_REDRAWPANES 0x20000000 +#define CLIENT_NOFORK 0x40000000 +#define CLIENT_ACTIVEPANE 0x80000000ULL +#define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL +#define CLIENT_CONTROL_WAITEXIT 0x200000000ULL +#define CLIENT_WINDOWSIZECHANGED 0x400000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ - CLIENT_REDRAWOVERLAY) + CLIENT_REDRAWOVERLAY| \ + CLIENT_REDRAWPANES) #define CLIENT_UNATTACHEDFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING) + CLIENT_EXIT) #define CLIENT_NOSIZEFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ - CLIENT_DETACHING) - int flags; + CLIENT_EXIT) + uint64_t flags; + + enum { + CLIENT_EXIT_RETURN, + CLIENT_EXIT_SHUTDOWN, + CLIENT_EXIT_DETACH + } exit_type; + enum msgtype exit_msgtype; + char *exit_session; + char *exit_message; + struct key_table *keytable; + uint64_t redraw_panes; + + int message_ignore_keys; + int message_ignore_styles; char *message_string; struct event message_timer; - u_int message_next; - TAILQ_HEAD(, message_entry) message_log; char *prompt_string; struct utf8_data *prompt_buffer; + char *prompt_last; size_t prompt_index; prompt_input_cb prompt_inputcb; prompt_free_cb prompt_freecb; void *prompt_data; - u_int prompt_hindex; + u_int prompt_hindex[PROMPT_NTYPES]; enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode; struct utf8_data *prompt_saved; - #define PROMPT_SINGLE 0x1 #define PROMPT_NUMERIC 0x2 #define PROMPT_INCREMENTAL 0x4 #define PROMPT_NOFORMAT 0x8 #define PROMPT_KEY 0x10 int prompt_flags; + enum prompt_type prompt_type; struct session *session; struct session *last_session; @@ -1647,9 +1742,12 @@ struct client { u_int pan_ox; u_int pan_oy; + overlay_check_cb overlay_check; + overlay_mode_cb overlay_mode; overlay_draw_cb overlay_draw; overlay_key_cb overlay_key; overlay_free_cb overlay_free; + overlay_resize_cb overlay_resize; void *overlay_data; struct event overlay_timer; @@ -1659,6 +1757,15 @@ struct client { }; TAILQ_HEAD(clients, client); +/* Control mode subscription type. */ +enum control_sub_type { + CONTROL_SUB_SESSION, + CONTROL_SUB_PANE, + CONTROL_SUB_ALL_PANES, + CONTROL_SUB_WINDOW, + CONTROL_SUB_ALL_WINDOWS +}; + /* Key binding and key table. */ struct key_binding { key_code key; @@ -1675,6 +1782,7 @@ RB_HEAD(key_bindings, key_binding); struct key_table { const char *name; struct key_bindings key_bindings; + struct key_bindings default_key_bindings; u_int references; @@ -1685,11 +1793,11 @@ RB_HEAD(key_tables, key_table); /* Option data. */ RB_HEAD(options_array, options_array_item); union options_value { - char *string; - long long number; - struct style style; - struct options_array array; - struct cmd_list *cmdlist; + char *string; + long long number; + struct style style; + struct options_array array; + struct cmd_list *cmdlist; }; /* Option table entries. */ @@ -1700,7 +1808,6 @@ enum options_table_type { OPTIONS_TABLE_COLOUR, OPTIONS_TABLE_FLAG, OPTIONS_TABLE_CHOICE, - OPTIONS_TABLE_STYLE, OPTIONS_TABLE_COMMAND }; @@ -1712,12 +1819,14 @@ enum options_table_type { #define OPTIONS_TABLE_IS_ARRAY 0x1 #define OPTIONS_TABLE_IS_HOOK 0x2 +#define OPTIONS_TABLE_IS_STYLE 0x4 struct options_table_entry { const char *name; + const char *alternative_name; enum options_table_type type; int scope; - int flags; + int flags; u_int minimum; u_int maximum; @@ -1729,6 +1838,14 @@ struct options_table_entry { const char *separator; const char *pattern; + + const char *text; + const char *unit; +}; + +struct options_name_map { + const char *from; + const char *to; }; /* Common command usages. */ @@ -1748,7 +1865,7 @@ struct spawn_context { struct session *s; struct winlink *wl; - struct client *c; + struct client *tc; struct window_pane *wp0; struct layout_cell *lc; @@ -1756,7 +1873,7 @@ struct spawn_context { const char *name; char **argv; int argc; - struct environ *environ; + struct environ *environ; int idx; const char *cwd; @@ -1769,6 +1886,7 @@ struct spawn_context { #define SPAWN_NONOTIFY 0x10 #define SPAWN_FULLSIZE 0x20 #define SPAWN_EMPTY 0x40 +#define SPAWN_ZOOM 0x80 }; /* Mode tree sort order. */ @@ -1787,8 +1905,10 @@ extern const char *socket_path; extern const char *shell_command; extern int ptm_fd; extern const char *shell_command; -int areshell(const char *); +int checkshell(const char *); void setblocking(int, int); +uint64_t get_timer(void); +const char *sig2name(int); const char *find_cwd(void); const char *find_home(void); const char *getversion(void); @@ -1806,16 +1926,19 @@ struct tmuxpeer *proc_add_peer(struct tmuxproc *, int, void proc_remove_peer(struct tmuxpeer *); void proc_kill_peer(struct tmuxpeer *); void proc_toggle_log(struct tmuxproc *); +pid_t proc_fork_and_daemon(int *); /* cfg.c */ extern int cfg_finished; extern struct client *cfg_client; +extern char **cfg_files; +extern u_int cfg_nfiles; +extern int cfg_quiet; void start_cfg(void); int load_cfg(const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); int load_cfg_from_buffer(const void *, size_t, const char *, struct client *, struct cmdq_item *, int, struct cmdq_item **); -void set_cfg_file(const char *); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); void cfg_show_causes(struct session *); @@ -1833,6 +1956,7 @@ void paste_free(struct paste_buffer *); void paste_add(const char *, char *, size_t); int paste_rename(const char *, const char *, char **); int paste_set(char *, size_t, const char *, char **); +void paste_replace(struct paste_buffer *, char *, size_t); char *paste_make_sample(struct paste_buffer *); /* format.c */ @@ -1844,13 +1968,22 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_PANE 0x80000000U #define FORMAT_WINDOW 0x40000000U struct format_tree; +struct format_modifier; +typedef void *(*format_cb)(struct format_tree *); +void format_tidy_jobs(void); const char *format_skip(const char *, const char *); int format_true(const char *); struct format_tree *format_create(struct client *, struct cmdq_item *, int, int); void format_free(struct format_tree *); +void format_merge(struct format_tree *, struct format_tree *); +struct window_pane *format_get_pane(struct format_tree *); void printflike(3, 4) format_add(struct format_tree *, const char *, const char *, ...); +void format_add_tv(struct format_tree *, const char *, + struct timeval *); +void format_add_cb(struct format_tree *, const char *, format_cb); +void format_log_debug(struct format_tree *, const char *); void format_each(struct format_tree *, void (*)(const char *, const char *, void *), void *); char *format_expand_time(struct format_tree *, const char *); @@ -1858,6 +1991,14 @@ char *format_expand(struct format_tree *, const char *); char *format_single(struct cmdq_item *, const char *, struct client *, struct session *, struct winlink *, struct window_pane *); +char *format_single_from_state(struct cmdq_item *, const char *, + struct client *, struct cmd_find_state *); +char *format_single_from_target(struct cmdq_item *, const char *); +struct format_tree *format_create_defaults(struct cmdq_item *, struct client *, + struct session *, struct winlink *, struct window_pane *); +struct format_tree *format_create_from_state(struct cmdq_item *, + struct client *, struct cmd_find_state *); +struct format_tree *format_create_from_target(struct cmdq_item *); void format_defaults(struct format_tree *, struct client *, struct session *, struct winlink *, struct window_pane *); void format_defaults_window(struct format_tree *, struct window *); @@ -1879,7 +2020,6 @@ char *format_trim_right(const char *, u_int); /* notify.c */ void notify_hook(struct cmdq_item *, const char *); -void notify_input(struct window_pane *, const u_char *, size_t); void notify_client(const char *, struct client *); void notify_session(const char *, struct session *); void notify_winlink(const char *, struct winlink *); @@ -1890,6 +2030,7 @@ void notify_pane(const char *, struct window_pane *); /* options.c */ struct options *options_create(struct options *); void options_free(struct options *); +struct options *options_get_parent(struct options *); void options_set_parent(struct options *, struct options *); struct options_entry *options_first(struct options *); struct options_entry *options_next(struct options_entry *); @@ -1897,11 +2038,12 @@ struct options_entry *options_empty(struct options *, const struct options_table_entry *); struct options_entry *options_default(struct options *, const struct options_table_entry *); +char *options_default_to_string(const struct options_table_entry *); const char *options_name(struct options_entry *); +struct options *options_owner(struct options_entry *); const struct options_table_entry *options_table_entry(struct options_entry *); struct options_entry *options_get_only(struct options *, const char *); struct options_entry *options_get(struct options *, const char *); -void options_remove(struct options_entry *); void options_array_clear(struct options_entry *); union options_value *options_array_get(struct options_entry *, u_int); int options_array_set(struct options_entry *, u_int, const char *, @@ -1912,9 +2054,9 @@ struct options_array_item *options_array_first(struct options_entry *); struct options_array_item *options_array_next(struct options_array_item *); u_int options_array_item_index(struct options_array_item *); union options_value *options_array_item_value(struct options_array_item *); -int options_isarray(struct options_entry *); -int options_isstring(struct options_entry *); -char *options_tostring(struct options_entry *, int, int); +int options_is_array(struct options_entry *); +int options_is_string(struct options_entry *); +char *options_to_string(struct options_entry *, int, int); char *options_parse(const char *, int *); struct options_entry *options_parse_get(struct options *, const char *, int *, int); @@ -1923,30 +2065,41 @@ struct options_entry *options_match_get(struct options *, const char *, int *, int, int *); const char *options_get_string(struct options *, const char *); long long options_get_number(struct options *, const char *); -struct style *options_get_style(struct options *, const char *); struct options_entry * printflike(4, 5) options_set_string(struct options *, const char *, int, const char *, ...); struct options_entry *options_set_number(struct options *, const char *, long long); -struct options_entry *options_set_style(struct options *, const char *, int, - const char *); int options_scope_from_name(struct args *, int, const char *, struct cmd_find_state *, struct options **, char **); int options_scope_from_flags(struct args *, int, struct cmd_find_state *, struct options **, char **); +struct style *options_string_to_style(struct options *, const char *, + struct format_tree *); +int options_from_string(struct options *, + const struct options_table_entry *, const char *, + const char *, int, char **); +void options_push_changes(const char *); +int options_remove_or_default(struct options_entry *, int, + char **); /* options-table.c */ -extern const struct options_table_entry options_table[]; +extern const struct options_table_entry options_table[]; +extern const struct options_name_map options_other_names[]; /* job.c */ typedef void (*job_update_cb) (struct job *); typedef void (*job_complete_cb) (struct job *); typedef void (*job_free_cb) (void *); #define JOB_NOWAIT 0x1 -struct job *job_run(const char *, struct session *, const char *, - job_update_cb, job_complete_cb, job_free_cb, void *, int); +#define JOB_KEEPWRITE 0x2 +#define JOB_PTY 0x4 +struct job *job_run(const char *, int, char **, struct session *, + const char *, job_update_cb, job_complete_cb, job_free_cb, + void *, int, int, int); void job_free(struct job *); +int job_transfer(struct job *, pid_t *, char *, size_t); +void job_resize(struct job *, u_int, u_int); void job_check_died(pid_t, int); int job_get_status(struct job *); void *job_get_data(struct job *); @@ -1962,10 +2115,10 @@ struct environ_entry *environ_first(struct environ *); struct environ_entry *environ_next(struct environ_entry *); void environ_copy(struct environ *, struct environ *); struct environ_entry *environ_find(struct environ *, const char *); -void printflike(3, 4) environ_set(struct environ *, const char *, const char *, - ...); +void printflike(4, 5) environ_set(struct environ *, const char *, int, + const char *, ...); void environ_clear(struct environ *, const char *); -void environ_put(struct environ *, const char *); +void environ_put(struct environ *, const char *, int); void environ_unset(struct environ *, const char *); void environ_update(struct options *, struct environ *, struct environ *); void environ_push(struct environ *); @@ -1980,7 +2133,7 @@ void tty_update_window_offset(struct window *); void tty_update_client_offset(struct client *); void tty_raw(struct tty *, const char *); void tty_attributes(struct tty *, const struct grid_cell *, - struct window_pane *); + const struct grid_cell *, struct colour_palette *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); @@ -1995,20 +2148,26 @@ void tty_putcode_ptr2(struct tty *, enum tty_code_code, const void *, void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); -int tty_init(struct tty *, struct client *, int, char *); +void tty_cell(struct tty *, const struct grid_cell *, + const struct grid_cell *, struct colour_palette *); +int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); void tty_start_tty(struct tty *); +void tty_send_requests(struct tty *); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); -void tty_draw_line(struct tty *, struct window_pane *, struct screen *, - u_int, u_int, u_int, u_int, u_int); +void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, + u_int, u_int, const struct grid_cell *, struct colour_palette *); void tty_draw_images(struct tty *, struct window_pane *, struct screen *); +void tty_sync_start(struct tty *); +void tty_sync_end(struct tty *); int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); -void tty_set_flags(struct tty *, int); +void tty_update_features(struct tty *); +void tty_set_selection(struct tty *, const char *, size_t); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -2033,12 +2192,20 @@ void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); +void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); +void tty_default_colours(struct grid_cell *, struct window_pane *); /* tty-term.c */ extern struct tty_terms tty_terms; u_int tty_term_ncodes(void); -struct tty_term *tty_term_find(char *, int, char **); +void tty_term_apply(struct tty_term *, const char *, int); +void tty_term_apply_overrides(struct tty_term *); +struct tty_term *tty_term_create(struct tty *, char *, char **, u_int, int *, + char **); void tty_term_free(struct tty_term *); +int tty_term_read_list(const char *, int, char ***, u_int *, + char **); +void tty_term_free_list(char **, u_int); int tty_term_has(struct tty_term *, enum tty_code_code); const char *tty_term_string(struct tty_term *, enum tty_code_code); const char *tty_term_string1(struct tty_term *, enum tty_code_code, int); @@ -2054,9 +2221,16 @@ int tty_term_number(struct tty_term *, enum tty_code_code); int tty_term_flag(struct tty_term *, enum tty_code_code); const char *tty_term_describe(struct tty_term *, enum tty_code_code); +/* tty-features.c */ +void tty_add_features(int *, const char *, const char *); +const char *tty_get_features(int); +int tty_apply_features(struct tty_term *, int); +void tty_default_features(int *, const char *, u_int); + /* tty-acs.c */ int tty_acs_needed(struct tty *); const char *tty_acs_get(struct tty *, u_char); +int tty_acs_reverse_get(struct tty *, const char *, size_t); /* tty-keys.c */ void tty_keys_build(struct tty *); @@ -2064,17 +2238,42 @@ void tty_keys_free(struct tty *); int tty_keys_next(struct tty *); /* arguments.c */ -void args_set(struct args *, u_char, const char *); -struct args *args_parse(const char *, int, char **); +void args_set(struct args *, u_char, struct args_value *); +struct args *args_create(void); +struct args *args_parse(const struct args_parse *, struct args_value *, + u_int, char **); +struct args *args_copy(struct args *, int, char **); +void args_to_vector(struct args *, int *, char ***); +struct args_value *args_from_vector(int, char **); +void args_free_value(struct args_value *); +void args_free_values(struct args_value *, u_int); void args_free(struct args *); char *args_print(struct args *); char *args_escape(const char *); int args_has(struct args *, u_char); const char *args_get(struct args *, u_char); -const char *args_first_value(struct args *, u_char, struct args_value **); -const char *args_next_value(struct args_value **); +u_char args_first(struct args *, struct args_entry **); +u_char args_next(struct args_entry **); +u_int args_count(struct args *); +struct args_value *args_values(struct args *); +struct args_value *args_value(struct args *, u_int); +const char *args_string(struct args *, u_int); +struct cmd_list *args_make_commands_now(struct cmd *, struct cmdq_item *, + u_int, int); +struct args_command_state *args_make_commands_prepare(struct cmd *, + struct cmdq_item *, u_int, const char *, int, int); +struct cmd_list *args_make_commands(struct args_command_state *, int, char **, + char **); +void args_make_commands_free(struct args_command_state *); +char *args_make_commands_get_command(struct args_command_state *); +struct args_value *args_first_value(struct args *, u_char); +struct args_value *args_next_value(struct args_value *); long long args_strtonum(struct args *, u_char, long long, long long, char **); +long long args_percentage(struct args *, u_char, long long, + long long, long long, char **); +long long args_string_percentage(const char *, long long, long long, + long long, char **); /* cmd-find.c */ int cmd_find_target(struct cmd_find_state *, struct cmdq_item *, @@ -2105,61 +2304,95 @@ int cmd_find_from_mouse(struct cmd_find_state *, int cmd_find_from_nothing(struct cmd_find_state *, int); /* cmd.c */ +extern const struct cmd_entry *cmd_table[]; void printflike(3, 4) cmd_log_argv(int, char **, const char *, ...); -void cmd_prepend_argv(int *, char ***, char *); -void cmd_append_argv(int *, char ***, char *); +void cmd_prepend_argv(int *, char ***, const char *); +void cmd_append_argv(int *, char ***, const char *); int cmd_pack_argv(int, char **, char *, size_t); int cmd_unpack_argv(char *, size_t, int, char ***); char **cmd_copy_argv(int, char **); void cmd_free_argv(int, char **); char *cmd_stringify_argv(int, char **); char *cmd_get_alias(const char *); -struct cmd *cmd_parse(int, char **, const char *, u_int, char **); +const struct cmd_entry *cmd_get_entry(struct cmd *); +struct args *cmd_get_args(struct cmd *); +u_int cmd_get_group(struct cmd *); +void cmd_get_source(struct cmd *, const char **, u_int *); +struct cmd *cmd_parse(struct args_value *, u_int, const char *, u_int, + char **); +struct cmd *cmd_copy(struct cmd *, int, char **); void cmd_free(struct cmd *); char *cmd_print(struct cmd *); +struct cmd_list *cmd_list_new(void); +struct cmd_list *cmd_list_copy(struct cmd_list *, int, char **); +void cmd_list_append(struct cmd_list *, struct cmd *); +void cmd_list_append_all(struct cmd_list *, struct cmd_list *); +void cmd_list_move(struct cmd_list *, struct cmd_list *); +void cmd_list_free(struct cmd_list *); +char *cmd_list_print(struct cmd_list *, int); +struct cmd *cmd_list_first(struct cmd_list *); +struct cmd *cmd_list_next(struct cmd *); +int cmd_list_all_have(struct cmd_list *, int); +int cmd_list_any_have(struct cmd_list *, int); int cmd_mouse_at(struct window_pane *, struct mouse_event *, u_int *, u_int *, int); struct winlink *cmd_mouse_window(struct mouse_event *, struct session **); struct window_pane *cmd_mouse_pane(struct mouse_event *, struct session **, struct winlink **); char *cmd_template_replace(const char *, const char *, int); -extern const struct cmd_entry *cmd_table[]; /* cmd-attach-session.c */ enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int, - int, const char *, int); + int, const char *, int, const char *); /* cmd-parse.c */ -void cmd_parse_empty(struct cmd_parse_input *); +void cmd_parse_empty(struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_file(FILE *, struct cmd_parse_input *); struct cmd_parse_result *cmd_parse_from_string(const char *, struct cmd_parse_input *); +enum cmd_parse_status cmd_parse_and_insert(const char *, + struct cmd_parse_input *, struct cmdq_item *, + struct cmdq_state *, char **); +enum cmd_parse_status cmd_parse_and_append(const char *, + struct cmd_parse_input *, struct client *, + struct cmdq_state *, char **); struct cmd_parse_result *cmd_parse_from_buffer(const void *, size_t, struct cmd_parse_input *); -struct cmd_parse_result *cmd_parse_from_arguments(int, char **, +struct cmd_parse_result *cmd_parse_from_arguments(struct args_value *, u_int, struct cmd_parse_input *); -/* cmd-list.c */ -struct cmd_list *cmd_list_new(void); -void cmd_list_append(struct cmd_list *, struct cmd *); -void cmd_list_move(struct cmd_list *, struct cmd_list *); -void cmd_list_free(struct cmd_list *); -char *cmd_list_print(struct cmd_list *, int); - /* cmd-queue.c */ -struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmd_find_state *, - struct mouse_event *, int); +struct cmdq_state *cmdq_new_state(struct cmd_find_state *, struct key_event *, + int); +struct cmdq_state *cmdq_link_state(struct cmdq_state *); +struct cmdq_state *cmdq_copy_state(struct cmdq_state *); +void cmdq_free_state(struct cmdq_state *); +void printflike(3, 4) cmdq_add_format(struct cmdq_state *, const char *, + const char *, ...); +void cmdq_add_formats(struct cmdq_state *, struct format_tree *); +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 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 *); +struct cmd_find_state *cmdq_get_target(struct cmdq_item *); +struct cmd_find_state *cmdq_get_source(struct cmdq_item *); +struct key_event *cmdq_get_event(struct cmdq_item *); +struct cmd_find_state *cmdq_get_current(struct cmdq_item *); +int cmdq_get_flags(struct cmdq_item *); +struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmdq_state *); #define cmdq_get_callback(cb, data) cmdq_get_callback1(#cb, cb, data) struct cmdq_item *cmdq_get_callback1(const char *, cmdq_cb, void *); struct cmdq_item *cmdq_get_error(const char *); struct cmdq_item *cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); struct cmdq_item *cmdq_append(struct client *, struct cmdq_item *); -void cmdq_insert_hook(struct session *, struct cmdq_item *, +void printflike(4, 5) cmdq_insert_hook(struct session *, struct cmdq_item *, struct cmd_find_state *, const char *, ...); void cmdq_continue(struct cmdq_item *); -void printflike(3, 4) cmdq_format(struct cmdq_item *, const char *, - const char *, ...); u_int cmdq_next(struct client *); +struct cmdq_item *cmdq_running(struct client *); void cmdq_guard(struct cmdq_item *, const char *, int); void printflike(2, 3) cmdq_print(struct cmdq_item *, const char *, ...); void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...); @@ -2168,7 +2401,7 @@ void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...); void cmd_wait_for_flush(void); /* client.c */ -int client_main(struct event_base *, int, char **, int); +int client_main(struct event_base *, int, char **, uint64_t, int); /* key-bindings.c */ struct key_table *key_bindings_get_table(const char *, int); @@ -2176,20 +2409,23 @@ struct key_table *key_bindings_first_table(void); struct key_table *key_bindings_next_table(struct key_table *); void key_bindings_unref_table(struct key_table *); struct key_binding *key_bindings_get(struct key_table *, key_code); +struct key_binding *key_bindings_get_default(struct key_table *, key_code); struct key_binding *key_bindings_first(struct key_table *); struct key_binding *key_bindings_next(struct key_table *, struct key_binding *); void key_bindings_add(const char *, key_code, const char *, int, struct cmd_list *); void key_bindings_remove(const char *, key_code); +void key_bindings_reset(const char *, key_code); void key_bindings_remove_table(const char *); +void key_bindings_reset_table(const char *); void key_bindings_init(void); struct cmdq_item *key_bindings_dispatch(struct key_binding *, - struct cmdq_item *, struct client *, struct mouse_event *, + struct cmdq_item *, struct client *, struct key_event *, struct cmd_find_state *); /* key-string.c */ key_code key_string_lookup_string(const char *); -const char *key_string_lookup_key(key_code); +const char *key_string_lookup_key(key_code, int); /* alerts.c */ void alerts_reset_all(void); @@ -2199,38 +2435,56 @@ void alerts_check_session(struct session *); /* file.c */ int file_cmp(struct client_file *, struct client_file *); RB_PROTOTYPE(client_files, client_file, entry, file_cmp); -struct client_file *file_create(struct client *, int, client_file_cb, void *); +struct client_file *file_create_with_peer(struct tmuxpeer *, + struct client_files *, int, client_file_cb, void *); +struct client_file *file_create_with_client(struct client *, int, + client_file_cb, void *); void file_free(struct client_file *); void file_fire_done(struct client_file *); void file_fire_read(struct client_file *); int file_can_print(struct client *); void printflike(2, 3) file_print(struct client *, const char *, ...); -void file_vprint(struct client *, const char *, va_list); -void file_print_buffer(struct client *, void *, size_t); +void printflike(2, 0) file_vprint(struct client *, const char *, va_list); +void file_print_buffer(struct client *, void *, size_t); void printflike(2, 3) file_error(struct client *, const char *, ...); void file_write(struct client *, const char *, int, const void *, size_t, client_file_cb, void *); void file_read(struct client *, const char *, client_file_cb, void *); void file_push(struct client_file *); +int file_write_left(struct client_files *); +void file_write_open(struct client_files *, struct tmuxpeer *, + struct imsg *, int, int, client_file_cb, void *); +void file_write_data(struct client_files *, struct imsg *); +void file_write_close(struct client_files *, struct imsg *); +void file_read_open(struct client_files *, struct tmuxpeer *, struct imsg *, + int, int, client_file_cb, void *); +void file_write_ready(struct client_files *, struct imsg *); +void file_read_data(struct client_files *, struct imsg *); +void file_read_done(struct client_files *, struct imsg *); /* server.c */ extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; +extern struct message_list message_log; void server_set_marked(struct session *, struct winlink *, struct window_pane *); void server_clear_marked(void); int server_is_marked(struct session *, struct winlink *, struct window_pane *); int server_check_marked(void); -int server_start(struct tmuxproc *, struct event_base *, int, char *); +int server_start(struct tmuxproc *, int, struct event_base *, int, char *); void server_update_socket(void); void server_add_accept(int); +void printflike(1, 2) server_add_message(const char *, ...); /* server-client.c */ +RB_PROTOTYPE(client_windows, client_window, entry, server_client_window_cmp); u_int server_client_how_many(void); -void server_client_set_overlay(struct client *, u_int, overlay_draw_cb, - overlay_key_cb, overlay_free_cb, void *); +void server_client_set_overlay(struct client *, u_int, overlay_check_cb, + overlay_mode_cb, overlay_draw_cb, overlay_key_cb, + overlay_free_cb, overlay_resize_cb, void *); +void server_client_clear_overlay(struct client *); void server_client_set_key_table(struct client *, const char *); const char *server_client_get_key_table(struct client *); int server_client_check_nested(struct client *); @@ -2238,6 +2492,7 @@ int server_client_handle_key(struct client *, struct key_event *); struct client *server_client_create(int); int server_client_open(struct client *, char **); void server_client_unref(struct client *); +void server_client_set_session(struct client *, struct session *); void server_client_lost(struct client *); void server_client_suspend(struct client *); void server_client_detach(struct client *, enum msgtype); @@ -2245,9 +2500,14 @@ void server_client_exec(struct client *, const char *); void server_client_loop(void); void server_client_push_stdout(struct client *); void server_client_push_stderr(struct client *); -void printflike(2, 3) server_client_add_message(struct client *, const char *, - ...); const char *server_client_get_cwd(struct client *, struct session *); +void server_client_set_flags(struct client *, const char *); +const char *server_client_get_flags(struct client *); +struct client_window *server_client_get_client_window(struct client *, u_int); +struct client_window *server_client_add_client_window(struct client *, u_int); +struct window_pane *server_client_get_pane(struct client *); +void server_client_set_pane(struct client *, struct window_pane *); +void server_client_remove_pane(struct window_pane *); /* server-fn.c */ void server_redraw_client(struct client *); @@ -2263,7 +2523,9 @@ void server_lock(void); void server_lock_session(struct session *); void server_lock_client(struct client *); void server_kill_pane(struct window_pane *); -void server_kill_window(struct window *); +void server_kill_window(struct window *, int); +void server_renumber_session(struct session *); +void server_renumber_all(void); int server_link_window(struct session *, struct winlink *, struct session *, int, int, int, char **); void server_unlink_window(struct session *, struct winlink *); @@ -2273,6 +2535,8 @@ void server_check_unattached(void); void server_unzoom_window(struct window *); /* status.c */ +extern char **status_prompt_hlist[]; +extern u_int status_prompt_hsize[]; void status_timer_start(struct client *); void status_timer_start_all(void); void status_update_cache(struct session *); @@ -2282,39 +2546,47 @@ struct style_range *status_get_range(struct client *, u_int, u_int); void status_init(struct client *); void status_free(struct client *); int status_redraw(struct client *); -void printflike(2, 3) status_message_set(struct client *, const char *, ...); +void printflike(5, 6) status_message_set(struct client *, int, int, int, + const char *, ...); void status_message_clear(struct client *); int status_message_redraw(struct client *); -void status_prompt_set(struct client *, const char *, const char *, - prompt_input_cb, prompt_free_cb, void *, int); +void status_prompt_set(struct client *, struct cmd_find_state *, + const char *, const char *, prompt_input_cb, prompt_free_cb, + void *, int, enum prompt_type); void status_prompt_clear(struct client *); int status_prompt_redraw(struct client *); int status_prompt_key(struct client *, key_code); void status_prompt_update(struct client *, const char *, const char *); void status_prompt_load_history(void); void status_prompt_save_history(void); +const char *status_prompt_type_string(u_int); +enum prompt_type status_prompt_type(const char *type); /* resize.c */ void resize_window(struct window *, u_int, u_int, int, int); void default_window_size(struct client *, struct session *, struct window *, u_int *, u_int *, u_int *, u_int *, int); -void recalculate_size(struct window *); +void recalculate_size(struct window *, int); void recalculate_sizes(void); +void recalculate_sizes_now(int); /* input.c */ -void input_init(struct window_pane *); -void input_free(struct window_pane *); -void input_reset(struct window_pane *, int); -struct evbuffer *input_pending(struct window_pane *); -void input_parse(struct window_pane *); +struct input_ctx *input_init(struct window_pane *, struct bufferevent *, + struct colour_palette *); +void input_free(struct input_ctx *); +void input_reset(struct input_ctx *, int); +struct evbuffer *input_pending(struct input_ctx *); +void input_parse_pane(struct window_pane *); void input_parse_buffer(struct window_pane *, u_char *, size_t); +void input_parse_screen(struct input_ctx *, struct screen *, + screen_write_init_ctx_cb, void *, u_char *, size_t); /* input-key.c */ -int input_key(struct window_pane *, key_code, struct mouse_event *); - -/* xterm-keys.c */ -char *xterm_keys_lookup(key_code); -int xterm_keys_find(const char *, size_t, size_t *, key_code *); +void input_key_build(void); +int input_key_pane(struct window_pane *, key_code, struct mouse_event *); +int input_key(struct screen *, struct bufferevent *, key_code); +int input_key_get_mouse(struct screen *, struct mouse_event *, u_int, + u_int, const char **, size_t *); /* colour.c */ int colour_find_rgb(u_char, u_char, u_char); @@ -2324,6 +2596,13 @@ const char *colour_tostring(int); int colour_fromstring(const char *s); int colour_256toRGB(int); int colour_256to16(int); +int colour_byname(const char *); +void colour_palette_init(struct colour_palette *); +void colour_palette_clear(struct colour_palette *); +void colour_palette_free(struct colour_palette *); +int colour_palette_get(struct colour_palette *, int); +int colour_palette_set(struct colour_palette *, int, int); +void colour_palette_from_option(struct colour_palette *, struct options *); /* attributes.c */ const char *attributes_tostring(int); @@ -2331,17 +2610,22 @@ int attributes_fromstring(const char *); /* grid.c */ extern const struct grid_cell grid_default_cell; +void grid_empty_line(struct grid *, u_int, u_int); int grid_cells_equal(const struct grid_cell *, const struct grid_cell *); +int grid_cells_look_equal(const struct grid_cell *, + const struct grid_cell *); struct grid *grid_create(u_int, u_int, u_int); void grid_destroy(struct grid *); int grid_compare(struct grid *, struct grid *); void grid_collect_history(struct grid *); +void grid_remove_history(struct grid *, u_int ); void grid_scroll_history(struct grid *, u_int); void grid_scroll_history_region(struct grid *, u_int, u_int, u_int); void grid_clear_history(struct grid *); const struct grid_line *grid_peek_line(struct grid *, u_int); void grid_get_cell(struct grid *, u_int, u_int, struct grid_cell *); void grid_set_cell(struct grid *, u_int, u_int, const struct grid_cell *); +void grid_set_padding(struct grid *, u_int, u_int); void grid_set_cells(struct grid *, u_int, u_int, const struct grid_cell *, const char *, size_t); struct grid_line *grid_get_line(struct grid *, u_int); @@ -2359,10 +2643,32 @@ void grid_wrap_position(struct grid *, u_int, u_int, u_int *, u_int *); void grid_unwrap_position(struct grid *, u_int *, u_int *, u_int, u_int); u_int grid_line_length(struct grid *, u_int); +/* grid-reader.c */ +void grid_reader_start(struct grid_reader *, struct grid *, u_int, u_int); +void grid_reader_get_cursor(struct grid_reader *, u_int *, u_int *); +u_int grid_reader_line_length(struct grid_reader *); +int grid_reader_in_set(struct grid_reader *, const char *); +void grid_reader_cursor_right(struct grid_reader *, int, int); +void grid_reader_cursor_left(struct grid_reader *, int); +void grid_reader_cursor_down(struct grid_reader *); +void grid_reader_cursor_up(struct grid_reader *); +void grid_reader_cursor_start_of_line(struct grid_reader *, int); +void grid_reader_cursor_end_of_line(struct grid_reader *, int, int); +void grid_reader_cursor_next_word(struct grid_reader *, const char *); +void grid_reader_cursor_next_word_end(struct grid_reader *, const char *); +void grid_reader_cursor_previous_word(struct grid_reader *, const char *, + int, int); +int grid_reader_cursor_jump(struct grid_reader *, + const struct utf8_data *); +int grid_reader_cursor_jump_back(struct grid_reader *, + const struct utf8_data *); +void grid_reader_cursor_back_to_indentation(struct grid_reader *); + /* grid-view.c */ void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *); void grid_view_set_cell(struct grid *, u_int, u_int, const struct grid_cell *); +void grid_view_set_padding(struct grid *, u_int, u_int); void grid_view_set_cells(struct grid *, u_int, u_int, const struct grid_cell *, const char *, size_t); void grid_view_clear_history(struct grid *, u_int); @@ -2380,26 +2686,32 @@ void grid_view_delete_cells(struct grid *, u_int, u_int, u_int, u_int); char *grid_view_string_cells(struct grid *, u_int, u_int, u_int); /* screen-write.c */ -void screen_write_start(struct screen_write_ctx *, struct window_pane *, - struct screen *); +void screen_write_make_list(struct screen *); +void screen_write_free_list(struct screen *); +void screen_write_start_pane(struct screen_write_ctx *, + struct window_pane *, struct screen *); +void screen_write_start(struct screen_write_ctx *, struct screen *); +void screen_write_start_callback(struct screen_write_ctx *, struct screen *, + screen_write_init_ctx_cb, void *); void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); size_t printflike(1, 2) screen_write_strlen(const char *, ...); +int printflike(7, 8) screen_write_text(struct screen_write_ctx *, u_int, u_int, + u_int, int, const struct grid_cell *, const char *, ...); void printflike(3, 4) screen_write_puts(struct screen_write_ctx *, const struct grid_cell *, const char *, ...); void printflike(4, 5) screen_write_nputs(struct screen_write_ctx *, ssize_t, const struct grid_cell *, const char *, ...); -void screen_write_vnputs(struct screen_write_ctx *, ssize_t, +void printflike(4, 0) screen_write_vnputs(struct screen_write_ctx *, ssize_t, const struct grid_cell *, const char *, va_list); void screen_write_putc(struct screen_write_ctx *, const struct grid_cell *, u_char); -void screen_write_copy(struct screen_write_ctx *, struct screen *, u_int, - u_int, u_int, u_int, bitstr_t *, const struct grid_cell *); void screen_write_fast_copy(struct screen_write_ctx *, struct screen *, u_int, u_int, u_int, u_int); void screen_write_hline(struct screen_write_ctx *, u_int, int, int); void screen_write_vline(struct screen_write_ctx *, u_int, int, int); -void screen_write_menu(struct screen_write_ctx *, struct menu *, int); +void screen_write_menu(struct screen_write_ctx *, struct menu *, int, + const struct grid_cell *); void screen_write_box(struct screen_write_ctx *, u_int, u_int); void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, u_int); @@ -2430,6 +2742,7 @@ void screen_write_clearendofscreen(struct screen_write_ctx *, u_int); void screen_write_clearstartofscreen(struct screen_write_ctx *, u_int); void screen_write_clearscreen(struct screen_write_ctx *, u_int); void screen_write_clearhistory(struct screen_write_ctx *); +void screen_write_fullredraw(struct screen_write_ctx *); void screen_write_collect_end(struct screen_write_ctx *); void screen_write_collect_add(struct screen_write_ctx *, const struct grid_cell *); @@ -2438,6 +2751,10 @@ void screen_write_setselection(struct screen_write_ctx *, u_char *, u_int); void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int); void screen_write_sixelimage(struct screen_write_ctx *, struct sixel_image *, u_int); +void screen_write_alternateon(struct screen_write_ctx *, + struct grid_cell *, int); +void screen_write_alternateoff(struct screen_write_ctx *, + struct grid_cell *, int); /* screen-redraw.c */ void screen_redraw_screen(struct client *); @@ -2455,6 +2772,7 @@ void screen_set_path(struct screen *, const char *); void screen_push_title(struct screen *); void screen_pop_title(struct screen *); void screen_resize(struct screen *, u_int, u_int, int); +void screen_resize_cursor(struct screen *, u_int, u_int, int, int, int); void screen_set_selection(struct screen *, u_int, u_int, u_int, u_int, u_int, int, struct grid_cell *); void screen_clear_selection(struct screen *); @@ -2462,11 +2780,13 @@ void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); void screen_select_cell(struct screen *, struct grid_cell *, const struct grid_cell *); +void screen_alternate_on(struct screen *, struct grid_cell *, int); +void screen_alternate_off(struct screen *, struct grid_cell *, int); +const char *screen_mode_to_string(int); /* window.c */ extern struct windows windows; extern struct window_pane_tree all_window_panes; -extern const struct window_mode *all_window_modes[]; int window_cmp(struct window *, struct window *); RB_PROTOTYPE(windows, window, entry, window_cmp); int winlink_cmp(struct winlink *, struct winlink *); @@ -2498,15 +2818,17 @@ struct window_pane *window_find_string(struct window *, const char *); int window_has_pane(struct window *, struct window_pane *); int window_set_active_pane(struct window *, struct window_pane *, int); +void window_update_focus(struct window *); +void window_pane_update_focus(struct window_pane *); void window_redraw_active_switch(struct window *, struct window_pane *); struct window_pane *window_add_pane(struct window *, struct window_pane *, u_int, int); void window_resize(struct window *, u_int, u_int, int, int); -void window_pane_send_resize(struct window_pane *, int); +void window_pane_send_resize(struct window_pane *, u_int, u_int); int window_zoom(struct window_pane *); int window_unzoom(struct window *); -int window_push_zoom(struct window *, int); +int window_push_zoom(struct window *, int, int); int window_pop_zoom(struct window *); void window_lost_pane(struct window *, struct window_pane *); void window_remove_pane(struct window *, struct window_pane *); @@ -2522,17 +2844,9 @@ struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); void window_pane_resize(struct window_pane *, u_int, u_int); -void window_pane_alternate_on(struct window_pane *, - struct grid_cell *, int); -void window_pane_alternate_off(struct window_pane *, - struct grid_cell *, int); -void window_pane_set_palette(struct window_pane *, u_int, int); -void window_pane_unset_palette(struct window_pane *, u_int); -void window_pane_reset_palette(struct window_pane *); -int window_pane_get_palette(struct window_pane *, int); int window_pane_set_mode(struct window_pane *, - const struct window_mode *, struct cmd_find_state *, - struct args *); + struct window_pane *, const struct window_mode *, + 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 *, @@ -2541,7 +2855,7 @@ int window_pane_key(struct window_pane *, struct client *, int window_pane_visible(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, int); -const char *window_printable_flags(struct winlink *); +const char *window_printable_flags(struct winlink *, int); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); @@ -2550,9 +2864,13 @@ void window_set_name(struct window *, const char *); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); void winlink_clear_flags(struct winlink *); -int winlink_shuffle_up(struct session *, struct winlink *); +int winlink_shuffle_up(struct session *, struct winlink *, int); int window_pane_start_input(struct window_pane *, struct cmdq_item *, char **); +void *window_pane_get_new_data(struct window_pane *, + struct window_pane_offset *, size_t *); +void window_pane_update_used_data(struct window_pane *, + struct window_pane_offset *, size_t); /* layout.c */ u_int layout_count_cells(struct layout_cell *); @@ -2569,7 +2887,7 @@ void layout_set_size(struct layout_cell *, u_int, u_int, u_int, void layout_make_leaf(struct layout_cell *, struct window_pane *); void layout_make_node(struct layout_cell *, enum layout_type); void layout_fix_offsets(struct window *); -void layout_fix_panes(struct window *); +void layout_fix_panes(struct window *, struct window_pane *); void layout_resize_adjust(struct window *, struct layout_cell *, enum layout_type, int); void layout_init(struct window *, struct window_pane *); @@ -2579,7 +2897,8 @@ void layout_resize_pane(struct window_pane *, enum layout_type, int, int); void layout_resize_pane_to(struct window_pane *, enum layout_type, u_int); -void layout_assign_pane(struct layout_cell *, struct window_pane *); +void layout_assign_pane(struct layout_cell *, struct window_pane *, + int); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, int, int); void layout_close_pane(struct window_pane *); @@ -2603,18 +2922,24 @@ typedef void (*mode_tree_draw_cb)(void *, void *, struct screen_write_ctx *, u_int, u_int); typedef int (*mode_tree_search_cb)(void *, void *, const char *); typedef void (*mode_tree_menu_cb)(void *, struct client *, key_code); +typedef u_int (*mode_tree_height_cb)(void *, u_int); +typedef key_code (*mode_tree_key_cb)(void *, void *, u_int); typedef void (*mode_tree_each_cb)(void *, void *, struct client *, key_code); u_int mode_tree_count_tagged(struct mode_tree_data *); void *mode_tree_get_current(struct mode_tree_data *); +const char *mode_tree_get_current_name(struct mode_tree_data *); void mode_tree_expand_current(struct mode_tree_data *); -void mode_tree_set_current(struct mode_tree_data *, uint64_t); +void mode_tree_collapse_current(struct mode_tree_data *); +void mode_tree_expand(struct mode_tree_data *, uint64_t); +int mode_tree_set_current(struct mode_tree_data *, uint64_t); void mode_tree_each_tagged(struct mode_tree_data *, mode_tree_each_cb, struct client *, key_code, int); +void mode_tree_up(struct mode_tree_data *, int); void mode_tree_down(struct mode_tree_data *, int); struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *, mode_tree_build_cb, mode_tree_draw_cb, mode_tree_search_cb, - mode_tree_menu_cb, void *, const struct menu_item *, const char **, - u_int, struct screen **); + mode_tree_menu_cb, mode_tree_height_cb, mode_tree_key_cb, void *, + const struct menu_item *, const char **, u_int, struct screen **); void mode_tree_zoom(struct mode_tree_data *, struct args *); void mode_tree_build(struct mode_tree_data *); void mode_tree_free(struct mode_tree_data *); @@ -2622,6 +2947,8 @@ void mode_tree_resize(struct mode_tree_data *, u_int, u_int); struct mode_tree_item *mode_tree_add(struct mode_tree_data *, struct mode_tree_item *, void *, uint64_t, const char *, const char *, int); +void mode_tree_draw_as_parent(struct mode_tree_item *); +void mode_tree_no_tag(struct mode_tree_item *); void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); void mode_tree_draw(struct mode_tree_data *); int mode_tree_key(struct mode_tree_data *, struct client *, key_code *, @@ -2646,9 +2973,15 @@ extern const struct window_mode window_client_mode; extern const struct window_mode window_copy_mode; extern const struct window_mode window_view_mode; void printflike(2, 3) window_copy_add(struct window_pane *, const char *, ...); -void window_copy_vadd(struct window_pane *, const char *, va_list); +void printflike(2, 0) window_copy_vadd(struct window_pane *, const char *, + va_list); void window_copy_pageup(struct window_pane *, int); void window_copy_start_drag(struct client *, struct mouse_event *); +char *window_copy_get_word(struct window_pane *, u_int, u_int); +char *window_copy_get_line(struct window_pane *, u_int); + +/* window-option.c */ +extern const struct window_mode window_customize_mode; /* names.c */ void check_window_name(struct window *); @@ -2656,8 +2989,22 @@ char *default_window_name(struct window *); char *parse_window_name(const char *); /* control.c */ +void control_discard(struct client *); void control_start(struct client *); +void control_stop(struct client *); +void control_set_pane_on(struct client *, struct window_pane *); +void control_set_pane_off(struct client *, struct window_pane *); +void control_continue_pane(struct client *, struct window_pane *); +void control_pause_pane(struct client *, struct window_pane *); +struct window_pane_offset *control_pane_offset(struct client *, + struct window_pane *, int *); +void control_reset_offsets(struct client *); void printflike(2, 3) control_write(struct client *, const char *, ...); +void control_write_output(struct client *, struct window_pane *); +int control_all_done(struct client *); +void control_add_sub(struct client *, const char *, enum control_sub_type, + int, const char *); +void control_remove_sub(struct client *, const char *); /* control-notify.c */ void control_notify_input(struct client *, struct window_pane *, @@ -2669,6 +3016,7 @@ void control_notify_window_unlinked(struct session *, struct window *); void control_notify_window_linked(struct session *, struct window *); void control_notify_window_renamed(struct window *); void control_notify_client_session_changed(struct client *); +void control_notify_client_detached(struct client *); void control_notify_session_renamed(struct session *); void control_notify_session_created(struct session *); void control_notify_session_closed(struct session *); @@ -2684,10 +3032,10 @@ struct session *session_find_by_id_str(const char *); struct session *session_find_by_id(u_int); struct session *session_create(const char *, const char *, const char *, struct environ *, struct options *, struct termios *); -void session_destroy(struct session *, int, const char *); +void session_destroy(struct session *, int, const char *); void session_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); -int session_check_name(const char *); +char *session_check_name(const char *); void session_update_activity(struct session *, struct timeval *); struct session *session_next_session(struct session *); struct session *session_previous_session(struct session *); @@ -2714,15 +3062,17 @@ u_int session_group_attached_count(struct session_group *); void session_renumber_windows(struct session *); /* utf8.c */ +utf8_char utf8_build_one(u_char); +enum utf8_state utf8_from_data(const struct utf8_data *, utf8_char *); +void utf8_to_data(utf8_char, struct utf8_data *); void utf8_set(struct utf8_data *, u_char); void utf8_copy(struct utf8_data *, const struct utf8_data *); enum utf8_state utf8_open(struct utf8_data *, u_char); enum utf8_state utf8_append(struct utf8_data *, u_char); -enum utf8_state utf8_combine(const struct utf8_data *, wchar_t *); -enum utf8_state utf8_split(wchar_t, struct utf8_data *); int utf8_isvalid(const char *); int utf8_strvis(char *, const char *, size_t, int); int utf8_stravis(char **, const char *, int); +int utf8_stravisx(char **, const char *, size_t, int); char *utf8_sanitize(const char *); size_t utf8_strlen(const struct utf8_data *); u_int utf8_strwidth(const struct utf8_data *, ssize_t); @@ -2749,29 +3099,53 @@ __dead void printflike(1, 2) fatal(const char *, ...); __dead void printflike(1, 2) fatalx(const char *, ...); /* menu.c */ +#define MENU_NOMOUSE 0x1 +#define MENU_TAB 0x2 +#define MENU_STAYOPEN 0x4 struct menu *menu_create(const char *); void menu_add_items(struct menu *, const struct menu_item *, struct cmdq_item *, struct client *, struct cmd_find_state *); -void menu_add_item(struct menu *, const struct menu_item *, +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, struct cmdq_item *, u_int, + u_int, struct client *, struct cmd_find_state *, + menu_choice_cb, void *); int menu_display(struct menu *, int, struct cmdq_item *, u_int, u_int, struct client *, struct cmd_find_state *, menu_choice_cb, void *); +struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); +int menu_check_cb(struct client *, void *, u_int, u_int); +void menu_draw_cb(struct client *, void *, + struct screen_redraw_ctx *); +void menu_free_cb(struct client *, void *); +int menu_key_cb(struct client *, void *, struct key_event *); + +/* popup.c */ +#define POPUP_CLOSEEXIT 0x1 +#define POPUP_CLOSEEXITZERO 0x2 +#define POPUP_NOBORDER 0x4 +#define POPUP_INTERNAL 0x8 +typedef void (*popup_close_cb)(int, void *); +typedef void (*popup_finish_edit_cb)(char *, size_t, void *); +int popup_display(int, struct cmdq_item *, u_int, u_int, u_int, + u_int, const char *, int, char **, const char *, + struct client *, struct session *, popup_close_cb, void *); +int popup_editor(struct client *, const char *, size_t, + popup_finish_edit_cb, void *); /* style.c */ int style_parse(struct style *,const struct grid_cell *, const char *); const char *style_tostring(struct style *); +void style_add(struct grid_cell *, struct options *, + const char *, struct format_tree *); void style_apply(struct grid_cell *, struct options *, - const char *); -int style_equal(struct style *, struct style *); + const char *, struct format_tree *); void style_set(struct style *, const struct grid_cell *); void style_copy(struct style *, struct style *); -int style_is_default(struct style *); /* spawn.c */ struct winlink *spawn_window(struct spawn_context *, char **); diff --git a/tools/UTF-8-demo.txt b/tools/UTF-8-demo.txt index 4363f27b..ff915b26 100644 --- a/tools/UTF-8-demo.txt +++ b/tools/UTF-8-demo.txt @@ -2,7 +2,7 @@ UTF-8 encoded sample plain-text file ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ -Markus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 +Markus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 CC BY The ASCII compatible UTF-8 encoding used in this plain-text file diff --git a/tools/cmp-cvs.sh b/tools/cmp-cvs.sh index b17d6f82..048ad15c 100644 --- a/tools/cmp-cvs.sh +++ b/tools/cmp-cvs.sh @@ -4,7 +4,7 @@ rm diff.out touch diff.out for i in *.[ch]; do - diff -u -I'\$OpenBSD' $i ../../OpenBSD/tmux/$i >diff.tmp + diff -u -I'\$OpenBSD' $i /usr/src/usr.bin/tmux/$i >diff.tmp set -- `wc -l diff.tmp` [ $1 -eq 8 ] && continue echo $i diff --git a/tty-acs.c b/tty-acs.c index 14634120..63eccb93 100644 --- a/tty-acs.c +++ b/tty-acs.c @@ -19,11 +19,10 @@ #include #include +#include #include "tmux.h" -static int tty_acs_cmp(const void *, const void *); - /* Table mapping ACS entries to UTF-8. */ struct tty_acs_entry { u_char key; @@ -68,14 +67,65 @@ static const struct tty_acs_entry tty_acs_table[] = { { '~', "\302\267" } /* bullet */ }; +/* Table mapping UTF-8 to ACS entries. */ +struct tty_acs_reverse_entry { + const char *string; + u_char key; +}; +static const struct tty_acs_reverse_entry tty_acs_reverse2[] = { + { "\302\267", '~' } +}; +static const struct tty_acs_reverse_entry tty_acs_reverse3[] = { + { "\342\224\200", 'q' }, + { "\342\224\201", 'q' }, + { "\342\224\202", 'x' }, + { "\342\224\203", 'x' }, + { "\342\224\214", 'l' }, + { "\342\224\217", 'k' }, + { "\342\224\220", 'k' }, + { "\342\224\223", 'l' }, + { "\342\224\224", 'm' }, + { "\342\224\227", 'm' }, + { "\342\224\230", 'j' }, + { "\342\224\233", 'j' }, + { "\342\224\234", 't' }, + { "\342\224\243", 't' }, + { "\342\224\244", 'u' }, + { "\342\224\253", 'u' }, + { "\342\224\263", 'w' }, + { "\342\224\264", 'v' }, + { "\342\224\273", 'v' }, + { "\342\224\274", 'n' }, + { "\342\225\213", 'n' }, + { "\342\225\220", 'q' }, + { "\342\225\221", 'x' }, + { "\342\225\224", 'l' }, + { "\342\225\227", 'k' }, + { "\342\225\232", 'm' }, + { "\342\225\235", 'j' }, + { "\342\225\240", 't' }, + { "\342\225\243", 'u' }, + { "\342\225\246", 'w' }, + { "\342\225\251", 'v' }, + { "\342\225\254", 'n' }, +}; + static int tty_acs_cmp(const void *key, const void *value) { const struct tty_acs_entry *entry = value; - u_char ch; + int test = *(u_char *)key; - ch = *(u_char *) key; - return (ch - entry->key); + return (test - entry->key); +} + +static int +tty_acs_reverse_cmp(const void *key, const void *value) +{ + const struct tty_acs_reverse_entry *entry = value; + const char *test = key; + + return (strcmp(test, entry->string)); } /* Should this terminal use ACS instead of UTF-8 line drawing? */ @@ -99,16 +149,16 @@ tty_acs_needed(struct tty *tty) tty_term_number(tty->term, TTYC_U8) == 0) return (1); - if (tty->flags & TTY_UTF8) + if (tty->client->flags & CLIENT_UTF8) return (0); return (1); } -/* Retrieve ACS to output as a string. */ +/* Retrieve ACS to output as UTF-8. */ const char * tty_acs_get(struct tty *tty, u_char ch) { - struct tty_acs_entry *entry; + const struct tty_acs_entry *entry; /* Use the ACS set instead of UTF-8 if needed. */ if (tty_acs_needed(tty)) { @@ -124,3 +174,24 @@ tty_acs_get(struct tty *tty, u_char ch) return (NULL); return (entry->string); } + +/* Reverse UTF-8 into ACS. */ +int +tty_acs_reverse_get(__unused struct tty *tty, const char *s, size_t slen) +{ + const struct tty_acs_reverse_entry *table, *entry; + u_int items; + + if (slen == 2) { + table = tty_acs_reverse2; + items = nitems(tty_acs_reverse2); + } else if (slen == 3) { + table = tty_acs_reverse3; + items = nitems(tty_acs_reverse3); + } else + return (-1); + entry = bsearch(s, table, items, sizeof table[0], tty_acs_reverse_cmp); + if (entry == NULL) + return (-1); + return (entry->key); +} diff --git a/tty-features.c b/tty-features.c new file mode 100644 index 00000000..48ac51be --- /dev/null +++ b/tty-features.c @@ -0,0 +1,387 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 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" + +/* + * Still hardcoded: + * - default colours (under AX or op capabilities); + * - AIX colours (under colors >= 16); + * - alternate escape (if terminal is VT100-like). + * + * Also: + * - DECFRA uses a flag instead of capabilities; + * - UTF-8 is a separate flag on the client; needed for unattached clients. + */ + +/* A named terminal feature. */ +struct tty_feature { + const char *name; + const char **capabilities; + int flags; +}; + +/* Terminal has xterm(1) title setting. */ +static const char *tty_feature_title_capabilities[] = { + "tsl=\\E]0;", /* should be using TS really */ + "fsl=\\a", + NULL +}; +static const struct tty_feature tty_feature_title = { + "title", + tty_feature_title_capabilities, + 0 +}; + +/* Terminal has mouse support. */ +static const char *tty_feature_mouse_capabilities[] = { + "kmous=\\E[M", + NULL +}; +static const struct tty_feature tty_feature_mouse = { + "mouse", + tty_feature_mouse_capabilities, + 0 +}; + +/* Terminal can set the clipboard with OSC 52. */ +static const char *tty_feature_clipboard_capabilities[] = { + "Ms=\\E]52;%p1%s;%p2%s\\a", + NULL +}; +static const struct tty_feature tty_feature_clipboard = { + "clipboard", + tty_feature_clipboard_capabilities, + 0 +}; + +/* + * Terminal supports RGB colour. This replaces setab and setaf also since + * terminals with RGB have versions that do not allow setting colours from the + * 256 palette. + */ +static const char *tty_feature_rgb_capabilities[] = { + "AX", + "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm", + "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm", + "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + NULL +}; +static const struct tty_feature tty_feature_rgb = { + "RGB", + tty_feature_rgb_capabilities, + TERM_256COLOURS|TERM_RGBCOLOURS +}; + +/* Terminal supports 256 colours. */ +static const char *tty_feature_256_capabilities[] = { + "AX", + "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", + "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", + NULL +}; +static const struct tty_feature tty_feature_256 = { + "256", + tty_feature_256_capabilities, + TERM_256COLOURS +}; + +/* Terminal supports overline. */ +static const char *tty_feature_overline_capabilities[] = { + "Smol=\\E[53m", + NULL +}; +static const struct tty_feature tty_feature_overline = { + "overline", + tty_feature_overline_capabilities, + 0 +}; + +/* Terminal supports underscore styles. */ +static const char *tty_feature_usstyle_capabilities[] = { + "Smulx=\\E[4::%p1%dm", + "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m", + "ol=\\E[59m", + NULL +}; +static const struct tty_feature tty_feature_usstyle = { + "usstyle", + tty_feature_usstyle_capabilities, + 0 +}; + +/* Terminal supports bracketed paste. */ +static const char *tty_feature_bpaste_capabilities[] = { + "Enbp=\\E[?2004h", + "Dsbp=\\E[?2004l", + NULL +}; +static const struct tty_feature tty_feature_bpaste = { + "bpaste", + tty_feature_bpaste_capabilities, + 0 +}; + +/* Terminal supports focus reporting. */ +static const char *tty_feature_focus_capabilities[] = { + "Enfcs=\\E[?1004h", + "Dsfcs=\\E[?1004l", + NULL +}; +static const struct tty_feature tty_feature_focus = { + "focus", + tty_feature_focus_capabilities, + 0 +}; + +/* Terminal supports cursor styles. */ +static const char *tty_feature_cstyle_capabilities[] = { + "Ss=\\E[%p1%d q", + "Se=\\E[2 q", + NULL +}; +static const struct tty_feature tty_feature_cstyle = { + "cstyle", + tty_feature_cstyle_capabilities, + 0 +}; + +/* Terminal supports cursor colours. */ +static const char *tty_feature_ccolour_capabilities[] = { + "Cs=\\E]12;%p1%s\\a", + "Cr=\\E]112\\a", + NULL +}; +static const struct tty_feature tty_feature_ccolour = { + "ccolour", + tty_feature_ccolour_capabilities, + 0 +}; + +/* Terminal supports strikethrough. */ +static const char *tty_feature_strikethrough_capabilities[] = { + "smxx=\\E[9m", + NULL +}; +static const struct tty_feature tty_feature_strikethrough = { + "strikethrough", + tty_feature_strikethrough_capabilities, + 0 +}; + +/* Terminal supports synchronized updates. */ +static const char *tty_feature_sync_capabilities[] = { + "Sync=\\EP=%p1%ds\\E\\\\", + NULL +}; +static const struct tty_feature tty_feature_sync = { + "sync", + tty_feature_sync_capabilities, + 0 +}; + +/* Terminal supports extended keys. */ +static const char *tty_feature_extkeys_capabilities[] = { + "Eneks=\\E[>4;1m", + "Dseks=\\E[>4m", + NULL +}; +static const struct tty_feature tty_feature_extkeys = { + "extkeys", + tty_feature_extkeys_capabilities, + 0 +}; + +/* Terminal supports DECSLRM margins. */ +static const char *tty_feature_margins_capabilities[] = { + "Enmg=\\E[?69h", + "Dsmg=\\E[?69l", + "Clmg=\\E[s", + "Cmg=\\E[%i%p1%d;%p2%ds", + NULL +}; +static const struct tty_feature tty_feature_margins = { + "margins", + tty_feature_margins_capabilities, + TERM_DECSLRM +}; + +/* Terminal supports DECFRA rectangle fill. */ +static const char *tty_feature_rectfill_capabilities[] = { + "Rect", + NULL +}; +static const struct tty_feature tty_feature_rectfill = { + "rectfill", + tty_feature_rectfill_capabilities, + TERM_DECFRA +}; + +/* Available terminal features. */ +static const struct tty_feature *tty_features[] = { + &tty_feature_256, + &tty_feature_bpaste, + &tty_feature_ccolour, + &tty_feature_clipboard, + &tty_feature_cstyle, + &tty_feature_extkeys, + &tty_feature_focus, + &tty_feature_margins, + &tty_feature_mouse, + &tty_feature_overline, + &tty_feature_rectfill, + &tty_feature_rgb, + &tty_feature_strikethrough, + &tty_feature_sync, + &tty_feature_title, + &tty_feature_usstyle +}; + +void +tty_add_features(int *feat, const char *s, const char *separators) +{ + const struct tty_feature *tf; + char *next, *loop, *copy; + u_int i; + + log_debug("adding terminal features %s", s); + + loop = copy = xstrdup(s); + while ((next = strsep(&loop, separators)) != NULL) { + for (i = 0; i < nitems(tty_features); i++) { + tf = tty_features[i]; + if (strcasecmp(tf->name, next) == 0) + break; + } + if (i == nitems(tty_features)) { + log_debug("unknown terminal feature: %s", next); + break; + } + if (~(*feat) & (1 << i)) { + log_debug("adding terminal feature: %s", tf->name); + (*feat) |= (1 << i); + } + } + free(copy); +} + +const char * +tty_get_features(int feat) +{ + const struct tty_feature *tf; + static char s[512]; + u_int i; + + *s = '\0'; + for (i = 0; i < nitems(tty_features); i++) { + if (~feat & (1 << i)) + continue; + tf = tty_features[i]; + + strlcat(s, tf->name, sizeof s); + strlcat(s, ",", sizeof s); + } + if (*s != '\0') + s[strlen(s) - 1] = '\0'; + return (s); +} + +int +tty_apply_features(struct tty_term *term, int feat) +{ + const struct tty_feature *tf; + const char **capability; + u_int i; + + if (feat == 0) + return (0); + log_debug("applying terminal features: %s", tty_get_features(feat)); + + for (i = 0; i < nitems(tty_features); i++) { + if ((term->features & (1 << i)) || (~feat & (1 << i))) + continue; + tf = tty_features[i]; + + log_debug("applying terminal feature: %s", tf->name); + if (tf->capabilities != NULL) { + capability = tf->capabilities; + while (*capability != NULL) { + log_debug("adding capability: %s", *capability); + tty_term_apply(term, *capability, 1); + capability++; + } + } + term->flags |= tf->flags; + } + if ((term->features | feat) == term->features) + return (0); + term->features |= feat; + return (1); +} + +void +tty_default_features(int *feat, const char *name, u_int version) +{ + static struct { + const char *name; + u_int version; + const char *features; + } table[] = { +#define TTY_FEATURES_BASE_MODERN_XTERM \ + "256,RGB,bpaste,clipboard,mouse,strikethrough,title" + { .name = "mintty", + .features = TTY_FEATURES_BASE_MODERN_XTERM + ",ccolour,cstyle,extkeys,margins,overline,usstyle" + }, + { .name = "tmux", + .features = TTY_FEATURES_BASE_MODERN_XTERM + ",ccolour,cstyle,focus,overline,usstyle" + }, + { .name = "rxvt-unicode", + .features = "256,bpaste,ccolour,cstyle,mouse,title" + }, + { .name = "iTerm2", + .features = TTY_FEATURES_BASE_MODERN_XTERM + ",cstyle,extkeys,margins,sync" + }, + { .name = "XTerm", + /* + * xterm also supports DECSLRM and DECFRA, but they can be + * disabled so not set it here - they will be added if + * secondary DA shows VT420. + */ + .features = TTY_FEATURES_BASE_MODERN_XTERM + ",ccolour,cstyle,extkeys,focus" + } + }; + u_int i; + + for (i = 0; i < nitems(table); i++) { + if (strcmp(table[i].name, name) != 0) + continue; + if (version != 0 && version < table[i].version) + continue; + tty_add_features(feat, table[i].features, ","); + } +} diff --git a/tty-keys.c b/tty-keys.c index 8cc90a08..edc1e558 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -46,61 +47,98 @@ static struct tty_key *tty_keys_find(struct tty *, const char *, size_t, static int tty_keys_next1(struct tty *, const char *, size_t, key_code *, size_t *, int); static void tty_keys_callback(int, short, void *); +static int tty_keys_extended_key(struct tty *, const char *, size_t, + size_t *, key_code *); static int tty_keys_mouse(struct tty *, const char *, size_t, size_t *, struct mouse_event *); static int tty_keys_clipboard(struct tty *, const char *, size_t, size_t *); static int tty_keys_device_attributes(struct tty *, const char *, size_t, size_t *); -static int tty_keys_device_status_report(struct tty *, const char *, +static int tty_keys_extended_device_attributes(struct tty *, const char *, size_t, size_t *); +/* A key tree entry. */ +struct tty_key { + char ch; + key_code key; + + struct tty_key *left; + struct tty_key *right; + + struct tty_key *next; +}; + /* Default raw keys. */ struct tty_default_key_raw { const char *string; - key_code key; + key_code key; }; static const struct tty_default_key_raw tty_default_raw_keys[] = { + /* Application escape. */ + { "\033O[", '\033' }, + /* * Numeric keypad. Just use the vt100 escape sequences here and always * put the terminal into keypad_xmit mode. Translation of numbers * mode/applications mode is done in input-keys.c. */ - { "\033Oo", KEYC_KP_SLASH }, - { "\033Oj", KEYC_KP_STAR }, - { "\033Om", KEYC_KP_MINUS }, - { "\033Ow", KEYC_KP_SEVEN }, - { "\033Ox", KEYC_KP_EIGHT }, - { "\033Oy", KEYC_KP_NINE }, - { "\033Ok", KEYC_KP_PLUS }, - { "\033Ot", KEYC_KP_FOUR }, - { "\033Ou", KEYC_KP_FIVE }, - { "\033Ov", KEYC_KP_SIX }, - { "\033Oq", KEYC_KP_ONE }, - { "\033Or", KEYC_KP_TWO }, - { "\033Os", KEYC_KP_THREE }, - { "\033OM", KEYC_KP_ENTER }, - { "\033Op", KEYC_KP_ZERO }, - { "\033On", KEYC_KP_PERIOD }, + { "\033Oo", KEYC_KP_SLASH|KEYC_KEYPAD }, + { "\033Oj", KEYC_KP_STAR|KEYC_KEYPAD }, + { "\033Om", KEYC_KP_MINUS|KEYC_KEYPAD }, + { "\033Ow", KEYC_KP_SEVEN|KEYC_KEYPAD }, + { "\033Ox", KEYC_KP_EIGHT|KEYC_KEYPAD }, + { "\033Oy", KEYC_KP_NINE|KEYC_KEYPAD }, + { "\033Ok", KEYC_KP_PLUS|KEYC_KEYPAD }, + { "\033Ot", KEYC_KP_FOUR|KEYC_KEYPAD }, + { "\033Ou", KEYC_KP_FIVE|KEYC_KEYPAD }, + { "\033Ov", KEYC_KP_SIX|KEYC_KEYPAD }, + { "\033Oq", KEYC_KP_ONE|KEYC_KEYPAD }, + { "\033Or", KEYC_KP_TWO|KEYC_KEYPAD }, + { "\033Os", KEYC_KP_THREE|KEYC_KEYPAD }, + { "\033OM", KEYC_KP_ENTER|KEYC_KEYPAD }, + { "\033Op", KEYC_KP_ZERO|KEYC_KEYPAD }, + { "\033On", KEYC_KP_PERIOD|KEYC_KEYPAD }, /* Arrow keys. */ - { "\033OA", KEYC_UP }, - { "\033OB", KEYC_DOWN }, - { "\033OC", KEYC_RIGHT }, - { "\033OD", KEYC_LEFT }, + { "\033OA", KEYC_UP|KEYC_CURSOR }, + { "\033OB", KEYC_DOWN|KEYC_CURSOR }, + { "\033OC", KEYC_RIGHT|KEYC_CURSOR }, + { "\033OD", KEYC_LEFT|KEYC_CURSOR }, - { "\033[A", KEYC_UP }, - { "\033[B", KEYC_DOWN }, - { "\033[C", KEYC_RIGHT }, - { "\033[D", KEYC_LEFT }, + { "\033[A", KEYC_UP|KEYC_CURSOR }, + { "\033[B", KEYC_DOWN|KEYC_CURSOR }, + { "\033[C", KEYC_RIGHT|KEYC_CURSOR }, + { "\033[D", KEYC_LEFT|KEYC_CURSOR }, + + /* + * Meta arrow keys. These do not get the IMPLIED_META flag so they + * don't match the xterm-style meta keys in the output tree - Escape+Up + * should stay as Escape+Up and not become M-Up. + */ + { "\033\033OA", KEYC_UP|KEYC_CURSOR|KEYC_META }, + { "\033\033OB", KEYC_DOWN|KEYC_CURSOR|KEYC_META }, + { "\033\033OC", KEYC_RIGHT|KEYC_CURSOR|KEYC_META }, + { "\033\033OD", KEYC_LEFT|KEYC_CURSOR|KEYC_META }, + + { "\033\033[A", KEYC_UP|KEYC_CURSOR|KEYC_META }, + { "\033\033[B", KEYC_DOWN|KEYC_CURSOR|KEYC_META }, + { "\033\033[C", KEYC_RIGHT|KEYC_CURSOR|KEYC_META }, + { "\033\033[D", KEYC_LEFT|KEYC_CURSOR|KEYC_META }, /* Other (xterm) "cursor" keys. */ { "\033OH", KEYC_HOME }, { "\033OF", KEYC_END }, + { "\033\033OH", KEYC_HOME|KEYC_META|KEYC_IMPLIED_META }, + { "\033\033OF", KEYC_END|KEYC_META|KEYC_IMPLIED_META }, + { "\033[H", KEYC_HOME }, { "\033[F", KEYC_END }, + { "\033\033[H", KEYC_HOME|KEYC_META|KEYC_IMPLIED_META }, + { "\033\033[F", KEYC_END|KEYC_META|KEYC_IMPLIED_META }, + /* rxvt-style arrow + modifier keys. */ { "\033Oa", KEYC_UP|KEYC_CTRL }, { "\033Ob", KEYC_DOWN|KEYC_CTRL }, @@ -179,15 +217,64 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[201~", KEYC_PASTE_END }, }; +/* Default xterm keys. */ +struct tty_default_key_xterm { + const char *template; + key_code key; +}; +static const struct tty_default_key_xterm tty_default_xterm_keys[] = { + { "\033[1;_P", KEYC_F1 }, + { "\033O1;_P", KEYC_F1 }, + { "\033O_P", KEYC_F1 }, + { "\033[1;_Q", KEYC_F2 }, + { "\033O1;_Q", KEYC_F2 }, + { "\033O_Q", KEYC_F2 }, + { "\033[1;_R", KEYC_F3 }, + { "\033O1;_R", KEYC_F3 }, + { "\033O_R", KEYC_F3 }, + { "\033[1;_S", KEYC_F4 }, + { "\033O1;_S", KEYC_F4 }, + { "\033O_S", KEYC_F4 }, + { "\033[15;_~", KEYC_F5 }, + { "\033[17;_~", KEYC_F6 }, + { "\033[18;_~", KEYC_F7 }, + { "\033[19;_~", KEYC_F8 }, + { "\033[20;_~", KEYC_F9 }, + { "\033[21;_~", KEYC_F10 }, + { "\033[23;_~", KEYC_F11 }, + { "\033[24;_~", KEYC_F12 }, + { "\033[1;_A", KEYC_UP }, + { "\033[1;_B", KEYC_DOWN }, + { "\033[1;_C", KEYC_RIGHT }, + { "\033[1;_D", KEYC_LEFT }, + { "\033[1;_H", KEYC_HOME }, + { "\033[1;_F", KEYC_END }, + { "\033[5;_~", KEYC_PPAGE }, + { "\033[6;_~", KEYC_NPAGE }, + { "\033[2;_~", KEYC_IC }, + { "\033[3;_~", KEYC_DC }, +}; +static const key_code tty_default_xterm_modifiers[] = { + 0, + 0, + KEYC_SHIFT, + KEYC_META|KEYC_IMPLIED_META, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META, + KEYC_CTRL, + KEYC_SHIFT|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL, + KEYC_META|KEYC_IMPLIED_META +}; + /* - * Default terminfo(5) keys. Any keys that have builtin modifiers - * (that is, where the key itself contains the modifiers) has the - * KEYC_XTERM flag set so a leading escape is not treated as meta (and - * probably removed). + * Default terminfo(5) keys. Any keys that have builtin modifiers (that is, + * where the key itself contains the modifiers) has the KEYC_XTERM flag set so + * a leading escape is not treated as meta (and probably removed). */ struct tty_default_key_code { enum tty_code_code code; - key_code key; + key_code key; }; static const struct tty_default_key_code tty_default_code_keys[] = { /* Function keys. */ @@ -204,61 +291,61 @@ static const struct tty_default_key_code tty_default_code_keys[] = { { TTYC_KF11, KEYC_F11 }, { TTYC_KF12, KEYC_F12 }, - { TTYC_KF13, KEYC_F1|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF14, KEYC_F2|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF15, KEYC_F3|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF16, KEYC_F4|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF17, KEYC_F5|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF18, KEYC_F6|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF19, KEYC_F7|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF20, KEYC_F8|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF21, KEYC_F9|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF22, KEYC_F10|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF23, KEYC_F11|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF24, KEYC_F12|KEYC_SHIFT|KEYC_XTERM }, + { TTYC_KF13, KEYC_F1|KEYC_SHIFT }, + { TTYC_KF14, KEYC_F2|KEYC_SHIFT }, + { TTYC_KF15, KEYC_F3|KEYC_SHIFT }, + { TTYC_KF16, KEYC_F4|KEYC_SHIFT }, + { TTYC_KF17, KEYC_F5|KEYC_SHIFT }, + { TTYC_KF18, KEYC_F6|KEYC_SHIFT }, + { TTYC_KF19, KEYC_F7|KEYC_SHIFT }, + { TTYC_KF20, KEYC_F8|KEYC_SHIFT }, + { TTYC_KF21, KEYC_F9|KEYC_SHIFT }, + { TTYC_KF22, KEYC_F10|KEYC_SHIFT }, + { TTYC_KF23, KEYC_F11|KEYC_SHIFT }, + { TTYC_KF24, KEYC_F12|KEYC_SHIFT }, - { TTYC_KF25, KEYC_F1|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF26, KEYC_F2|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF27, KEYC_F3|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF28, KEYC_F4|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF29, KEYC_F5|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF30, KEYC_F6|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF31, KEYC_F7|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF32, KEYC_F8|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF33, KEYC_F9|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF34, KEYC_F10|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF35, KEYC_F11|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF36, KEYC_F12|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KF25, KEYC_F1|KEYC_CTRL }, + { TTYC_KF26, KEYC_F2|KEYC_CTRL }, + { TTYC_KF27, KEYC_F3|KEYC_CTRL }, + { TTYC_KF28, KEYC_F4|KEYC_CTRL }, + { TTYC_KF29, KEYC_F5|KEYC_CTRL }, + { TTYC_KF30, KEYC_F6|KEYC_CTRL }, + { TTYC_KF31, KEYC_F7|KEYC_CTRL }, + { TTYC_KF32, KEYC_F8|KEYC_CTRL }, + { TTYC_KF33, KEYC_F9|KEYC_CTRL }, + { TTYC_KF34, KEYC_F10|KEYC_CTRL }, + { TTYC_KF35, KEYC_F11|KEYC_CTRL }, + { TTYC_KF36, KEYC_F12|KEYC_CTRL }, - { TTYC_KF37, KEYC_F1|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF38, KEYC_F2|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF39, KEYC_F3|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF40, KEYC_F4|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF41, KEYC_F5|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF42, KEYC_F6|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF43, KEYC_F7|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF44, KEYC_F8|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF45, KEYC_F9|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF46, KEYC_F10|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF47, KEYC_F11|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KF48, KEYC_F12|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KF37, KEYC_F1|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF38, KEYC_F2|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF39, KEYC_F3|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF40, KEYC_F4|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF41, KEYC_F5|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF42, KEYC_F6|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF43, KEYC_F7|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF44, KEYC_F8|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF45, KEYC_F9|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF46, KEYC_F10|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF47, KEYC_F11|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KF48, KEYC_F12|KEYC_SHIFT|KEYC_CTRL }, - { TTYC_KF49, KEYC_F1|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF50, KEYC_F2|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF51, KEYC_F3|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF52, KEYC_F4|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF53, KEYC_F5|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF54, KEYC_F6|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF55, KEYC_F7|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF56, KEYC_F8|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF57, KEYC_F9|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF58, KEYC_F10|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF59, KEYC_F11|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KF60, KEYC_F12|KEYC_ESCAPE|KEYC_XTERM }, + { TTYC_KF49, KEYC_F1|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF50, KEYC_F2|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF51, KEYC_F3|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF52, KEYC_F4|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF53, KEYC_F5|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF54, KEYC_F6|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF55, KEYC_F7|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF56, KEYC_F8|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF57, KEYC_F9|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF58, KEYC_F10|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF59, KEYC_F11|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KF60, KEYC_F12|KEYC_META|KEYC_IMPLIED_META }, - { TTYC_KF61, KEYC_F1|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF62, KEYC_F2|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KF63, KEYC_F3|KEYC_ESCAPE|KEYC_SHIFT|KEYC_XTERM }, + { TTYC_KF61, KEYC_F1|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, + { TTYC_KF62, KEYC_F2|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, + { TTYC_KF63, KEYC_F3|KEYC_META|KEYC_IMPLIED_META|KEYC_SHIFT }, { TTYC_KICH1, KEYC_IC }, { TTYC_KDCH1, KEYC_DC }, @@ -269,74 +356,74 @@ static const struct tty_default_key_code tty_default_code_keys[] = { { TTYC_KCBT, KEYC_BTAB }, /* Arrow keys from terminfo. */ - { TTYC_KCUU1, KEYC_UP }, - { TTYC_KCUD1, KEYC_DOWN }, - { TTYC_KCUB1, KEYC_LEFT }, - { TTYC_KCUF1, KEYC_RIGHT }, + { TTYC_KCUU1, KEYC_UP|KEYC_CURSOR }, + { TTYC_KCUD1, KEYC_DOWN|KEYC_CURSOR }, + { TTYC_KCUB1, KEYC_LEFT|KEYC_CURSOR }, + { TTYC_KCUF1, KEYC_RIGHT|KEYC_CURSOR }, /* Key and modifier capabilities. */ - { TTYC_KDC2, KEYC_DC|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDC3, KEYC_DC|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDC5, KEYC_DC|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDC7, KEYC_DC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIND, KEYC_DOWN|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KDN3, KEYC_DOWN|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KDN5, KEYC_DOWN|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KDN7, KEYC_DOWN|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND2, KEYC_END|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KEND3, KEYC_END|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KEND5, KEYC_END|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KEND7, KEYC_END|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KHOM3, KEYC_HOME|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KHOM5, KEYC_HOME|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KHOM7, KEYC_HOME|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC2, KEYC_IC|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KIC3, KEYC_IC|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KIC5, KEYC_IC|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KIC7, KEYC_IC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KLFT3, KEYC_LEFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KLFT7, KEYC_LEFT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KNXT3, KEYC_NPAGE|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KNXT7, KEYC_NPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KPRV3, KEYC_PPAGE|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KPRV7, KEYC_PPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KRIT3, KEYC_RIGHT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRIT7, KEYC_RIGHT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KRI, KEYC_UP|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KUP2, KEYC_UP|KEYC_SHIFT|KEYC_XTERM }, - { TTYC_KUP3, KEYC_UP|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM }, - { TTYC_KUP5, KEYC_UP|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM }, - { TTYC_KUP7, KEYC_UP|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM }, + { TTYC_KDC2, KEYC_DC|KEYC_SHIFT }, + { TTYC_KDC3, KEYC_DC|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDC5, KEYC_DC|KEYC_CTRL }, + { TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KDC7, KEYC_DC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KIND, KEYC_DOWN|KEYC_SHIFT }, + { TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT }, + { TTYC_KDN3, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KDN5, KEYC_DOWN|KEYC_CTRL }, + { TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KDN7, KEYC_DOWN|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KEND2, KEYC_END|KEYC_SHIFT }, + { TTYC_KEND3, KEYC_END|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KEND5, KEYC_END|KEYC_CTRL }, + { TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KEND7, KEYC_END|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT }, + { TTYC_KHOM3, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KHOM5, KEYC_HOME|KEYC_CTRL }, + { TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KHOM7, KEYC_HOME|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KIC2, KEYC_IC|KEYC_SHIFT }, + { TTYC_KIC3, KEYC_IC|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KIC5, KEYC_IC|KEYC_CTRL }, + { TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KIC7, KEYC_IC|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT }, + { TTYC_KLFT3, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL }, + { TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KLFT7, KEYC_LEFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT }, + { TTYC_KNXT3, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL }, + { TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KNXT7, KEYC_NPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT }, + { TTYC_KPRV3, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL }, + { TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KPRV7, KEYC_PPAGE|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT }, + { TTYC_KRIT3, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL }, + { TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KRIT7, KEYC_RIGHT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, + { TTYC_KRI, KEYC_UP|KEYC_SHIFT }, + { TTYC_KUP2, KEYC_UP|KEYC_SHIFT }, + { TTYC_KUP3, KEYC_UP|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META }, + { TTYC_KUP5, KEYC_UP|KEYC_CTRL }, + { TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL }, + { TTYC_KUP7, KEYC_UP|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL }, }; /* Add key to tree. */ @@ -345,9 +432,9 @@ tty_keys_add(struct tty *tty, const char *s, key_code key) { struct tty_key *tk; size_t size; - const char *keystr; + const char *keystr; - keystr = key_string_lookup_key(key); + keystr = key_string_lookup_key(key, 1); if ((tk = tty_keys_find(tty, s, strlen(s), &size)) == NULL) { log_debug("new key %s: 0x%llx (%s)", s, key, keystr); tty_keys_add1(&tty->key_tree, s, key); @@ -400,17 +487,30 @@ void tty_keys_build(struct tty *tty) { const struct tty_default_key_raw *tdkr; + const struct tty_default_key_xterm *tdkx; const struct tty_default_key_code *tdkc; - u_int i; + u_int i, j; const char *s; struct options_entry *o; struct options_array_item *a; union options_value *ov; + char copy[16]; + key_code key; if (tty->key_tree != NULL) tty_keys_free(tty); tty->key_tree = NULL; + for (i = 0; i < nitems(tty_default_xterm_keys); i++) { + tdkx = &tty_default_xterm_keys[i]; + for (j = 2; j < nitems(tty_default_xterm_modifiers); j++) { + strlcpy(copy, tdkx->template, sizeof copy); + copy[strcspn(copy, "_")] = '0' + j; + + key = tdkx->key|tty_default_xterm_modifiers[j]; + tty_keys_add(tty, copy, key); + } + } for (i = 0; i < nitems(tty_default_raw_keys); i++) { tdkr = &tty_default_raw_keys[i]; @@ -511,9 +611,8 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, struct tty_key *tk, *tk1; struct utf8_data ud; enum utf8_state more; + utf8_char uc; u_int i; - wchar_t wc; - int n; log_debug("%s: next key is %zu (%.*s) (expired=%d)", c->name, len, (int)len, buf, expired); @@ -531,13 +630,6 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, return (0); } - /* Is this an an xterm(1) key? */ - n = xterm_keys_find(buf, len, size, key); - if (n == 0) - return (0); - if (n == 1 && !expired) - return (1); - /* Is this valid UTF-8? */ more = utf8_open(&ud, (u_char)*buf); if (more == UTF8_MORE) { @@ -552,12 +644,12 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, if (more != UTF8_DONE) return (-1); - if (utf8_combine(&ud, &wc) != UTF8_DONE) + if (utf8_from_data(&ud, &uc) != UTF8_DONE) return (-1); - *key = wc; + *key = uc; log_debug("%s: UTF-8 key %.*s %#llx", c->name, (int)ud.size, - buf, *key); + ud.data, *key); return (0); } @@ -578,8 +670,6 @@ tty_keys_next(struct tty *tty) struct mouse_event m = { 0 }; struct key_event *event; - gettimeofday(&tv, NULL); - /* Get key buffer. */ buf = EVBUFFER_DATA(tty->in); len = EVBUFFER_LENGTH(tty->in); @@ -609,8 +699,8 @@ tty_keys_next(struct tty *tty) goto partial_key; } - /* Is this a device status report response? */ - switch (tty_keys_device_status_report(tty, buf, len, &size)) { + /* Is this an extended device attributes response? */ + switch (tty_keys_extended_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; goto complete_key; @@ -634,6 +724,16 @@ tty_keys_next(struct tty *tty) goto partial_key; } + /* Is this an extended key press? */ + switch (tty_keys_extended_key(tty, buf, len, &size, &key)) { + case 0: /* yes */ + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + first_key: /* Try to lookup complete key. */ n = tty_keys_next1(tty, buf, len, &key, &size, expired); @@ -650,7 +750,7 @@ first_key: /* Look for a key without the escape. */ n = tty_keys_next1(tty, buf + 1, len - 1, &key, &size, expired); if (n == 0) { /* found */ - if (key & KEYC_XTERM) { + if (key & KEYC_IMPLIED_META) { /* * We want the escape key as well as the xterm * key, because the xterm sequence implicitly @@ -662,7 +762,7 @@ first_key: size = 1; goto complete_key; } - key |= KEYC_ESCAPE; + key |= KEYC_META; size++; goto complete_key; } @@ -675,7 +775,7 @@ first_key: * escape). So pass it through even if the timer has not expired. */ if (*buf == '\033' && len >= 2) { - key = (u_char)buf[1] | KEYC_ESCAPE; + key = (u_char)buf[1] | KEYC_META; size = 2; } else { key = (u_char)buf[0]; @@ -720,7 +820,7 @@ complete_key: */ bspace = tty->tio.c_cc[VERASE]; if (bspace != _POSIX_VDISABLE && (key & KEYC_MASK_KEY) == bspace) - key = (key & KEYC_MASK_MOD) | KEYC_BSPACE; + key = (key & KEYC_MASK_MODIFIERS)|KEYC_BSPACE; /* Remove data from buffer. */ evbuffer_drain(tty->in, size); @@ -732,11 +832,13 @@ complete_key: /* Check for focus events. */ if (key == KEYC_FOCUS_OUT) { - tty->client->flags &= ~CLIENT_FOCUSED; - return (1); + c->flags &= ~CLIENT_FOCUSED; + window_update_focus(c->session->curw->window); + notify_client("client-focus-out", c); } else if (key == KEYC_FOCUS_IN) { - tty->client->flags |= CLIENT_FOCUSED; - return (1); + c->flags |= CLIENT_FOCUSED; + notify_client("client-focus-in", c); + window_update_focus(c->session->curw->window); } /* Fire the key. */ @@ -771,6 +873,134 @@ tty_keys_callback(__unused int fd, __unused short events, void *data) } } +/* + * Handle extended key input. This has two forms: \033[27;m;k~ and \033[k;mu, + * where k is key as a number and m is a modifier. Returns 0 for success, -1 + * for failure, 1 for partial; + */ +static int +tty_keys_extended_key(struct tty *tty, const char *buf, size_t len, + size_t *size, key_code *key) +{ + struct client *c = tty->client; + size_t end; + u_int number, modifiers; + char tmp[64]; + cc_t bspace; + key_code nkey; + key_code onlykey; + + *size = 0; + + /* First two bytes are always \033[. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != '[') + return (-1); + if (len == 2) + return (1); + + /* + * Look for a terminator. Stop at either '~' or anything that isn't a + * number or ';'. + */ + for (end = 2; end < len && end != sizeof tmp; end++) { + if (buf[end] == '~') + break; + if (!isdigit((u_char)buf[end]) && buf[end] != ';') + break; + } + if (end == len) + return (1); + if (end == sizeof tmp || (buf[end] != '~' && buf[end] != 'u')) + return (-1); + + /* Copy to the buffer. */ + memcpy(tmp, buf + 2, end); + tmp[end] = '\0'; + + /* Try to parse either form of key. */ + if (buf[end] == '~') { + if (sscanf(tmp, "27;%u;%u", &modifiers, &number) != 2) + return (-1); + } else { + if (sscanf(tmp ,"%u;%u", &number, &modifiers) != 2) + return (-1); + } + *size = end + 1; + + /* Store the key. */ + bspace = tty->tio.c_cc[VERASE]; + if (bspace != _POSIX_VDISABLE && number == bspace) + nkey = KEYC_BSPACE; + else + nkey = number; + + /* Update the modifiers. */ + switch (modifiers) { + case 2: + nkey |= KEYC_SHIFT; + break; + case 3: + nkey |= (KEYC_META|KEYC_IMPLIED_META); + break; + case 4: + nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META); + break; + case 5: + nkey |= KEYC_CTRL; + break; + case 6: + nkey |= (KEYC_SHIFT|KEYC_CTRL); + break; + case 7: + nkey |= (KEYC_META|KEYC_CTRL); + break; + case 8: + nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL); + break; + case 9: + nkey |= (KEYC_META|KEYC_IMPLIED_META); + break; + default: + *key = KEYC_NONE; + break; + } + + /* + * Don't allow both KEYC_CTRL and as an implied modifier. Also convert + * C-X into C-x and so on. + */ + if (nkey & KEYC_CTRL) { + onlykey = (nkey & KEYC_MASK_KEY); + if (onlykey < 32 && + onlykey != 9 && + onlykey != 13 && + onlykey != 27) + /* nothing */; + else if (onlykey >= 97 && onlykey <= 122) + onlykey -= 96; + else if (onlykey >= 64 && onlykey <= 95) + onlykey -= 64; + else if (onlykey == 32) + onlykey = 0; + else if (onlykey == 63) + onlykey = 127; + else + onlykey |= KEYC_CTRL; + nkey = onlykey|((nkey & KEYC_MASK_MODIFIERS) & ~KEYC_CTRL); + } + + if (log_get_level() != 0) { + log_debug("%s: extended key %.*s is %llx (%s)", c->name, + (int)*size, buf, nkey, key_string_lookup_key(nkey, 1)); + } + *key = nkey; + return (0); +} + /* * Handle mouse key input. Returns 0 for success, -1 for failure, 1 for partial * (probably a mouse sequence but need more data). @@ -933,7 +1163,7 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, *size = 0; - /* First three bytes are always \033]52;. */ + /* First five bytes are always \033]52;. */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -1006,9 +1236,16 @@ tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, return (0); } +//XXX primary DA +// for (i = 1; i < n; i++) { +// log_debug("%s: DA feature: %d", c->name, p[i]); +// if (p[i] == 4) +// flags |= TERM_SIXEL; +// } + /* - * Handle device attributes input. Returns 0 for success, -1 for failure, 1 for - * partial. + * Handle secondary device attributes input. Returns 0 for success, -1 for + * failure, 1 for partial. */ static int tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, @@ -1017,13 +1254,15 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, struct client *c = tty->client; u_int i, n = 0; char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; - int flags = 0; *size = 0; if (tty->flags & TTY_HAVEDA) return (-1); - /* First three bytes are always \033[?. */ + /* + * First three bytes are always \033[>. Some older Terminal.app + * versions respond as for DA (\033[?) so accept and ignore that. + */ if (buf[0] != '\033') return (-1); if (len == 1) @@ -1032,15 +1271,17 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, return (-1); if (len == 2) return (1); - if (buf[2] != '?') + if (buf[2] != '>' && buf[2] != '?') return (-1); if (len == 3) return (1); /* Copy the rest up to a 'c'. */ - for (i = 0; i < (sizeof tmp) - 1 && buf[3 + i] != 'c'; i++) { + for (i = 0; i < (sizeof tmp) - 1; i++) { if (3 + i == len) return (1); + if (buf[3 + i] == 'c') + break; tmp[i] = buf[3 + i]; } if (i == (sizeof tmp) - 1) @@ -1048,7 +1289,11 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, tmp[i] = '\0'; *size = 4 + i; - /* Convert version numbers. */ + /* Ignore DA response. */ + if (buf[2] == '?') + return (0); + + /* Convert all arguments to numbers. */ cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); @@ -1057,75 +1302,92 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, n++; } - /* Set terminal flags. */ + /* Add terminal features. */ switch (p[0]) { - case 64: /* VT420 */ - flags |= (TERM_DECFRA|TERM_DECSLRM); + case 41: /* VT420 */ + tty_add_features(&c->term_features, "margins,rectfill", ","); + break; + case 'M': /* mintty */ + tty_default_features(&c->term_features, "mintty", 0); + break; + case 'T': /* tmux */ + tty_default_features(&c->term_features, "tmux", 0); + break; + case 'U': /* rxvt-unicode */ + tty_default_features(&c->term_features, "rxvt-unicode", 0); break; } - for (i = 1; i < n; i++) { - log_debug("%s: DA feature: %d", c->name, p[i]); - if (p[i] == 4) - flags |= TERM_SIXEL; - } + log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf); - tty_set_flags(tty, flags); + tty_update_features(tty); tty->flags |= TTY_HAVEDA; return (0); } /* - * Handle device status report input. Returns 0 for success, -1 for failure, 1 - * for partial. + * Handle extended device attributes input. Returns 0 for success, -1 for + * failure, 1 for partial. */ static int -tty_keys_device_status_report(struct tty *tty, const char *buf, size_t len, - size_t *size) +tty_keys_extended_device_attributes(struct tty *tty, const char *buf, + size_t len, size_t *size) { struct client *c = tty->client; u_int i; - char tmp[64]; - int flags = 0; + char tmp[128]; *size = 0; - if (tty->flags & TTY_HAVEDSR) + if (tty->flags & TTY_HAVEXDA) return (-1); - /* First three bytes are always \033[. */ + /* First four bytes are always \033P>|. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); - if (buf[1] != '[') + if (buf[1] != 'P') return (-1); if (len == 2) return (1); - if (buf[2] != 'I' && buf[2] != 'T') + if (buf[2] != '>') return (-1); if (len == 3) return (1); + if (buf[3] != '|') + return (-1); + if (len == 4) + return (1); - /* Copy the rest up to a 'n'. */ - for (i = 0; i < (sizeof tmp) - 1 && buf[2 + i] != 'n'; i++) { - if (2 + i == len) + /* Copy the rest up to a '\033\\'. */ + for (i = 0; i < (sizeof tmp) - 1; i++) { + if (4 + i == len) return (1); - tmp[i] = buf[2 + i]; + if (buf[4 + i - 1] == '\033' && buf[4 + i] == '\\') + break; + tmp[i] = buf[4 + i]; } if (i == (sizeof tmp) - 1) return (-1); - tmp[i] = '\0'; - *size = 3 + i; + tmp[i - 1] = '\0'; + *size = 5 + i; - /* Set terminal flags. */ - if (strncmp(tmp, "ITERM2 ", 7) == 0) - flags |= (TERM_DECSLRM|TERM_256COLOURS|TERM_RGBCOLOURS); - if (strncmp(tmp, "TMUX ", 5) == 0) - flags |= (TERM_256COLOURS|TERM_RGBCOLOURS); - log_debug("%s: received DSR %.*s", c->name, (int)*size, buf); + /* Add terminal features. */ + if (strncmp(tmp, "iTerm2 ", 7) == 0) + tty_default_features(&c->term_features, "iTerm2", 0); + else if (strncmp(tmp, "tmux ", 5) == 0) + tty_default_features(&c->term_features, "tmux", 0); + else if (strncmp(tmp, "XTerm(", 6) == 0) + tty_default_features(&c->term_features, "XTerm", 0); + else if (strncmp(tmp, "mintty ", 7) == 0) + tty_default_features(&c->term_features, "mintty", 0); + log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf); - tty_set_flags(tty, flags); - tty->flags |= TTY_HAVEDSR; + free(c->term_type); + c->term_type = xstrdup(tmp); + + tty_update_features(tty); + tty->flags |= TTY_HAVEXDA; return (0); } diff --git a/tty-term.c b/tty-term.c index 4bf58a97..d1060d65 100644 --- a/tty-term.c +++ b/tty-term.c @@ -30,7 +30,6 @@ #include "tmux.h" -static void tty_term_override(struct tty_term *, const char *); static char *tty_term_strip(const char *); struct tty_terms tty_terms = LIST_HEAD_INITIALIZER(tty_terms); @@ -58,13 +57,17 @@ struct tty_term_code_entry { static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_ACSC] = { TTYCODE_STRING, "acsc" }, + [TTYC_AM] = { TTYCODE_FLAG, "am" }, [TTYC_AX] = { TTYCODE_FLAG, "AX" }, [TTYC_BCE] = { TTYCODE_FLAG, "bce" }, [TTYC_BEL] = { TTYCODE_STRING, "bel" }, + [TTYC_BIDI] = { TTYCODE_STRING, "Bidi" }, [TTYC_BLINK] = { TTYCODE_STRING, "blink" }, [TTYC_BOLD] = { TTYCODE_STRING, "bold" }, [TTYC_CIVIS] = { TTYCODE_STRING, "civis" }, [TTYC_CLEAR] = { TTYCODE_STRING, "clear" }, + [TTYC_CLMG] = { TTYCODE_STRING, "Clmg" }, + [TTYC_CMG] = { TTYCODE_STRING, "Cmg" }, [TTYC_CNORM] = { TTYCODE_STRING, "cnorm" }, [TTYC_COLORS] = { TTYCODE_NUMBER, "colors" }, [TTYC_CR] = { TTYCODE_STRING, "Cr" }, @@ -85,12 +88,20 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_DIM] = { TTYCODE_STRING, "dim" }, [TTYC_DL1] = { TTYCODE_STRING, "dl1" }, [TTYC_DL] = { TTYCODE_STRING, "dl" }, + [TTYC_DSEKS] = { TTYCODE_STRING, "Dseks" }, + [TTYC_DSFCS] = { TTYCODE_STRING, "Dsfcs" }, + [TTYC_DSBP] = { TTYCODE_STRING, "Dsbp" }, + [TTYC_DSMG] = { TTYCODE_STRING, "Dsmg" }, [TTYC_E3] = { TTYCODE_STRING, "E3" }, [TTYC_ECH] = { TTYCODE_STRING, "ech" }, [TTYC_ED] = { TTYCODE_STRING, "ed" }, [TTYC_EL1] = { TTYCODE_STRING, "el1" }, [TTYC_EL] = { TTYCODE_STRING, "el" }, [TTYC_ENACS] = { TTYCODE_STRING, "enacs" }, + [TTYC_ENBP] = { TTYCODE_STRING, "Enbp" }, + [TTYC_ENEKS] = { TTYCODE_STRING, "Eneks" }, + [TTYC_ENFCS] = { TTYCODE_STRING, "Enfcs" }, + [TTYC_ENMG] = { TTYCODE_STRING, "Enmg" }, [TTYC_FSL] = { TTYCODE_STRING, "fsl" }, [TTYC_HOME] = { TTYCODE_STRING, "home" }, [TTYC_HPA] = { TTYCODE_STRING, "hpa" }, @@ -238,16 +249,19 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_KUP6] = { TTYCODE_STRING, "kUP6" }, [TTYC_KUP7] = { TTYCODE_STRING, "kUP7" }, [TTYC_MS] = { TTYCODE_STRING, "Ms" }, + [TTYC_OL] = { TTYCODE_STRING, "ol" }, [TTYC_OP] = { TTYCODE_STRING, "op" }, + [TTYC_RECT] = { TTYCODE_STRING, "Rect" }, [TTYC_REV] = { TTYCODE_STRING, "rev" }, [TTYC_RGB] = { TTYCODE_FLAG, "RGB" }, - [TTYC_RI] = { TTYCODE_STRING, "ri" }, [TTYC_RIN] = { TTYCODE_STRING, "rin" }, + [TTYC_RI] = { TTYCODE_STRING, "ri" }, [TTYC_RMACS] = { TTYCODE_STRING, "rmacs" }, [TTYC_RMCUP] = { TTYCODE_STRING, "rmcup" }, [TTYC_RMKX] = { TTYCODE_STRING, "rmkx" }, [TTYC_SETAB] = { TTYCODE_STRING, "setab" }, [TTYC_SETAF] = { TTYCODE_STRING, "setaf" }, + [TTYC_SETAL] = { TTYCODE_STRING, "setal" }, [TTYC_SETRGBB] = { TTYCODE_STRING, "setrgbb" }, [TTYC_SETRGBF] = { TTYCODE_STRING, "setrgbf" }, [TTYC_SETULC] = { TTYCODE_STRING, "Setulc" }, @@ -264,12 +278,12 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SMUL] = { TTYCODE_STRING, "smul" }, [TTYC_SMXX] = { TTYCODE_STRING, "smxx" }, [TTYC_SS] = { TTYCODE_STRING, "Ss" }, + [TTYC_SYNC] = { TTYCODE_STRING, "Sync" }, [TTYC_TC] = { TTYCODE_FLAG, "Tc" }, [TTYC_TSL] = { TTYCODE_STRING, "tsl" }, [TTYC_U8] = { TTYCODE_NUMBER, "U8" }, [TTYC_VPA] = { TTYCODE_STRING, "vpa" }, - [TTYC_XENL] = { TTYCODE_FLAG, "xenl" }, - [TTYC_XT] = { TTYCODE_FLAG, "XT" }, + [TTYC_XT] = { TTYCODE_FLAG, "XT" } }; u_int @@ -296,6 +310,8 @@ tty_term_strip(const char *s) ptr++; if (*ptr == '>') ptr++; + if (*ptr == '\0') + break; } buf[len++] = *ptr; @@ -338,22 +354,18 @@ tty_term_override_next(const char *s, size_t *offset) return (value); } -static void -tty_term_override(struct tty_term *term, const char *override) +void +tty_term_apply(struct tty_term *term, const char *capabilities, int quiet) { const struct tty_term_code_entry *ent; struct tty_code *code; size_t offset = 0; char *cp, *value, *s; - const char *errstr; + const char *errstr, *name = term->name; u_int i; int n, remove; - s = tty_term_override_next(override, &offset); - if (s == NULL || fnmatch(s, term->name, 0) != 0) - return; - - while ((s = tty_term_override_next(override, &offset)) != NULL) { + while ((s = tty_term_override_next(capabilities, &offset)) != NULL) { if (*s == '\0') continue; value = NULL; @@ -372,12 +384,14 @@ tty_term_override(struct tty_term *term, const char *override) } else value = xstrdup(""); - if (remove) - log_debug("%s override: %s@", term->name, s); - else if (*value == '\0') - log_debug("%s override: %s", term->name, s); - else - log_debug("%s override: %s=%s", term->name, s, value); + if (!quiet) { + if (remove) + log_debug("%s override: %s@", name, s); + else if (*value == '\0') + log_debug("%s override: %s", name, s); + else + log_debug("%s override: %s=%s", name, s, value); + } for (i = 0; i < tty_term_ncodes(); i++) { ent = &tty_term_codes[i]; @@ -416,8 +430,92 @@ tty_term_override(struct tty_term *term, const char *override) } } +void +tty_term_apply_overrides(struct tty_term *term) +{ + struct options_entry *o; + struct options_array_item *a; + union options_value *ov; + const char *s, *acs; + size_t offset; + char *first; + + /* Update capabilities from the option. */ + o = options_get_only(global_options, "terminal-overrides"); + a = options_array_first(o); + while (a != NULL) { + ov = options_array_item_value(a); + s = ov->string; + + offset = 0; + first = tty_term_override_next(s, &offset); + if (first != NULL && fnmatch(first, term->name, 0) == 0) + tty_term_apply(term, s + offset, 0); + a = options_array_next(a); + } + + /* Update the RGB flag if the terminal has RGB colours. */ + if (tty_term_has(term, TTYC_SETRGBF) && + tty_term_has(term, TTYC_SETRGBB)) + term->flags |= TERM_RGBCOLOURS; + else + term->flags &= ~TERM_RGBCOLOURS; + log_debug("RGBCOLOURS flag is %d", !!(term->flags & TERM_RGBCOLOURS)); + + /* + * Set or clear the DECSLRM flag if the terminal has the margin + * capabilities. + */ + if (tty_term_has(term, TTYC_CMG) && tty_term_has(term, TTYC_CLMG)) + term->flags |= TERM_DECSLRM; + else + term->flags &= ~TERM_DECSLRM; + log_debug("DECSLRM flag is %d", !!(term->flags & TERM_DECSLRM)); + + /* + * Set or clear the DECFRA flag if the terminal has the rectangle + * capability. + */ + if (tty_term_has(term, TTYC_RECT)) + term->flags |= TERM_DECFRA; + else + term->flags &= ~TERM_DECFRA; + log_debug("DECFRA flag is %d", !!(term->flags & TERM_DECFRA)); + + /* + * Terminals without am (auto right margin) wrap at at $COLUMNS - 1 + * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). + * + * Terminals without xenl (eat newline glitch) ignore a newline beyond + * the right edge of the terminal, but tmux doesn't care about this - + * it always uses absolute only moves the cursor with a newline when + * also sending a linefeed. + * + * This is irritating, most notably because it is painful to write to + * the very bottom-right of the screen without scrolling. + * + * Flag the terminal here and apply some workarounds in other places to + * do the best possible. + */ + if (!tty_term_flag(term, TTYC_AM)) + term->flags |= TERM_NOAM; + else + term->flags &= ~TERM_NOAM; + log_debug("NOAM flag is %d", !!(term->flags & TERM_NOAM)); + + /* Generate ACS table. If none is present, use nearest ASCII. */ + memset(term->acs, 0, sizeof term->acs); + if (tty_term_has(term, TTYC_ACSC)) + acs = tty_term_string(term, TTYC_ACSC); + else + acs = "a#j+k+l+m+n+o-p-q-r-s-t+u+v+w+x|y~."; + for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2) + term->acs[(u_char) acs[0]][0] = acs[1]; +} + struct tty_term * -tty_term_find(char *name, int fd, char **cause) +tty_term_create(struct tty *tty, char *name, char **caps, u_int ncaps, + int *feat, char **cause) { struct tty_term *term; const struct tty_term_code_entry *ent; @@ -425,85 +523,65 @@ tty_term_find(char *name, int fd, char **cause) struct options_entry *o; struct options_array_item *a; union options_value *ov; - u_int i; - int n, error; - const char *s, *acs; + u_int i, j; + const char *s, *value; + size_t offset, namelen; + char *first; - LIST_FOREACH(term, &tty_terms, entry) { - if (strcmp(term->name, name) == 0) { - term->references++; - return (term); - } - } - log_debug("new term: %s", name); + log_debug("adding term %s", name); - term = xmalloc(sizeof *term); + term = xcalloc(1, sizeof *term); + term->tty = tty; term->name = xstrdup(name); - term->references = 1; - term->flags = 0; term->codes = xcalloc(tty_term_ncodes(), sizeof *term->codes); LIST_INSERT_HEAD(&tty_terms, term, entry); - /* Set up curses terminal. */ - if (setupterm(name, fd, &error) != OK) { - switch (error) { - case 1: - xasprintf(cause, "can't use hardcopy terminal: %s", - name); - break; - case 0: - xasprintf(cause, "missing or unsuitable terminal: %s", - name); - break; - case -1: - xasprintf(cause, "can't find terminfo database"); - break; - default: - xasprintf(cause, "unknown error"); - break; - } - goto error; - } - /* Fill in codes. */ - for (i = 0; i < tty_term_ncodes(); i++) { - ent = &tty_term_codes[i]; + for (i = 0; i < ncaps; i++) { + namelen = strcspn(caps[i], "="); + if (namelen == 0) + continue; + value = caps[i] + namelen + 1; - code = &term->codes[i]; - code->type = TTYCODE_NONE; - switch (ent->type) { - case TTYCODE_NONE: - break; - case TTYCODE_STRING: - s = tigetstr((char *) ent->name); - if (s == NULL || s == (char *) -1) + for (j = 0; j < tty_term_ncodes(); j++) { + ent = &tty_term_codes[j]; + if (strncmp(ent->name, caps[i], namelen) != 0) + continue; + if (ent->name[namelen] != '\0') + continue; + + code = &term->codes[j]; + code->type = TTYCODE_NONE; + switch (ent->type) { + case TTYCODE_NONE: break; - code->type = TTYCODE_STRING; - code->value.string = tty_term_strip(s); - break; - case TTYCODE_NUMBER: - n = tigetnum((char *) ent->name); - if (n == -1 || n == -2) + case TTYCODE_STRING: + code->type = TTYCODE_STRING; + code->value.string = tty_term_strip(value); break; - code->type = TTYCODE_NUMBER; - code->value.number = n; - break; - case TTYCODE_FLAG: - n = tigetflag((char *) ent->name); - if (n == -1) + case TTYCODE_NUMBER: + code->type = TTYCODE_NUMBER; + code->value.number = atoi(value); break; - code->type = TTYCODE_FLAG; - code->value.flag = n; - break; + case TTYCODE_FLAG: + code->type = TTYCODE_FLAG; + code->value.flag = (*value == '1'); + break; + } } } - /* Apply terminal overrides. */ - o = options_get_only(global_options, "terminal-overrides"); + /* Apply terminal features. */ + o = options_get_only(global_options, "terminal-features"); a = options_array_first(o); while (a != NULL) { ov = options_array_item_value(a); - tty_term_override(term, ov->string); + s = ov->string; + + offset = 0; + first = tty_term_override_next(s, &offset); + if (first != NULL && fnmatch(first, term->name, 0) == 0) + tty_add_features(feat, s + offset, ":"); a = options_array_next(a); } @@ -513,6 +591,9 @@ tty_term_find(char *name, int fd, char **cause) del_curterm(cur_term); #endif + /* Apply overrides so any capabilities used for features are changed. */ + tty_term_apply_overrides(term); + /* These are always required. */ if (!tty_term_has(term, TTYC_CLEAR)) { xasprintf(cause, "terminal does not support clear"); @@ -523,56 +604,34 @@ tty_term_find(char *name, int fd, char **cause) goto error; } - /* These can be emulated so one of the two is required. */ - if (!tty_term_has(term, TTYC_CUD1) && !tty_term_has(term, TTYC_CUD)) { - xasprintf(cause, "terminal does not support cud1 or cud"); - goto error; - } - - /* Set flag if terminal has 256 colours. */ - if (tty_term_number(term, TTYC_COLORS) >= 256) - term->flags |= TERM_256COLOURS; - - /* Set flag if terminal has RGB colours. */ - if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) || - (tty_term_has(term, TTYC_SETRGBF) && - tty_term_has(term, TTYC_SETRGBB))) - term->flags |= TERM_RGBCOLOURS; - /* - * Terminals without xenl (eat newline glitch) wrap at at $COLUMNS - 1 - * rather than $COLUMNS (the cursor can never be beyond $COLUMNS - 1). + * If TERM has XT or clear starts with CSI then it is safe to assume + * the terminal is derived from the VT100. This controls whether device + * attributes requests are sent to get more information. * - * This is irritating, most notably because it is impossible to write - * to the very bottom-right of the screen without scrolling. + * This is a bit of a hack but there aren't that many alternatives. + * Worst case tmux will just fall back to using whatever terminfo(5) + * says without trying to correct anything that is missing. * - * Flag the terminal here and apply some workarounds in other places to - * do the best possible. + * Also add few features that VT100-like terminals should either + * support or safely ignore. */ - if (!tty_term_flag(term, TTYC_XENL)) - term->flags |= TERM_NOXENL; - - /* Generate ACS table. If none is present, use nearest ASCII. */ - memset(term->acs, 0, sizeof term->acs); - if (tty_term_has(term, TTYC_ACSC)) - acs = tty_term_string(term, TTYC_ACSC); - else - acs = "a#j+k+l+m+n+o-p-q-r-s-t+u+v+w+x|y~."; - for (; acs[0] != '\0' && acs[1] != '\0'; acs += 2) - term->acs[(u_char) acs[0]][0] = acs[1]; - - /* On terminals with xterm titles (XT), fill in tsl and fsl. */ - if (tty_term_flag(term, TTYC_XT) && - !tty_term_has(term, TTYC_TSL) && - !tty_term_has(term, TTYC_FSL)) { - code = &term->codes[TTYC_TSL]; - code->value.string = xstrdup("\033]0;"); - code->type = TTYCODE_STRING; - code = &term->codes[TTYC_FSL]; - code->value.string = xstrdup("\007"); - code->type = TTYCODE_STRING; + s = tty_term_string(term, TTYC_CLEAR); + if (tty_term_flag(term, TTYC_XT) || strncmp(s, "\033[", 2) == 0) { + term->flags |= TERM_VT100LIKE; + tty_add_features(feat, "bpaste,focus,title", ","); } + /* Add RGB feature if terminal has RGB colours. */ + if ((tty_term_flag(term, TTYC_TC) || tty_term_has(term, TTYC_RGB)) && + (!tty_term_has(term, TTYC_SETRGBF) || + !tty_term_has(term, TTYC_SETRGBB))) + tty_add_features(feat, "RGB", ","); + + /* Apply the features and overrides again. */ + if (tty_apply_features(term, *feat)) + tty_term_apply_overrides(term); + /* Log the capabilities. */ for (i = 0; i < tty_term_ncodes(); i++) log_debug("%s%s", name, tty_term_describe(term, i)); @@ -589,10 +648,7 @@ tty_term_free(struct tty_term *term) { u_int i; - if (--term->references != 0) - return; - - LIST_REMOVE(term, entry); + log_debug("removing term %s", term->name); for (i = 0; i < tty_term_ncodes(); i++) { if (term->codes[i].type == TTYCODE_STRING) @@ -600,10 +656,93 @@ tty_term_free(struct tty_term *term) } free(term->codes); + LIST_REMOVE(term, entry); free(term->name); free(term); } +int +tty_term_read_list(const char *name, int fd, char ***caps, u_int *ncaps, + char **cause) +{ + const struct tty_term_code_entry *ent; + int error, n; + u_int i; + const char *s; + char tmp[11]; + + if (setupterm((char *)name, fd, &error) != OK) { + switch (error) { + case 1: + xasprintf(cause, "can't use hardcopy terminal: %s", + name); + break; + case 0: + xasprintf(cause, "missing or unsuitable terminal: %s", + name); + break; + case -1: + xasprintf(cause, "can't find terminfo database"); + break; + default: + xasprintf(cause, "unknown error"); + break; + } + return (-1); + } + + *ncaps = 0; + *caps = NULL; + + for (i = 0; i < tty_term_ncodes(); i++) { + ent = &tty_term_codes[i]; + switch (ent->type) { + case TTYCODE_NONE: + continue; + case TTYCODE_STRING: + s = tigetstr((char *)ent->name); + if (s == NULL || s == (char *)-1) + continue; + break; + case TTYCODE_NUMBER: + n = tigetnum((char *)ent->name); + if (n == -1 || n == -2) + continue; + xsnprintf(tmp, sizeof tmp, "%d", n); + s = tmp; + break; + case TTYCODE_FLAG: + n = tigetflag((char *) ent->name); + if (n == -1) + continue; + if (n) + s = "1"; + else + s = "0"; + break; + } + *caps = xreallocarray(*caps, (*ncaps) + 1, sizeof **caps); + xasprintf(&(*caps)[*ncaps], "%s=%s", ent->name, s); + (*ncaps)++; + } + +#if !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR > 5 || \ + (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 6) + del_curterm(cur_term); +#endif + return (0); +} + +void +tty_term_free_list(char **caps, u_int ncaps) +{ + u_int i; + + for (i = 0; i < ncaps; i++) + free(caps[i]); + free(caps); +} + int tty_term_has(struct tty_term *term, enum tty_code_code code) { diff --git a/tty.c b/tty.c index 6d48c4ec..eb613b08 100644 --- a/tty.c +++ b/tty.c @@ -34,7 +34,7 @@ static int tty_log_fd = -1; -static int tty_client_ready(struct client *, struct window_pane *); +static int tty_client_ready(struct client *); static void tty_set_italics(struct tty *); static int tty_try_colour(struct tty *, int, const char *); @@ -45,12 +45,12 @@ static void tty_cursor_pane_unless_wrap(struct tty *, const struct tty_ctx *, u_int, u_int); static void tty_invalidate(struct tty *); static void tty_colours(struct tty *, const struct grid_cell *); -static void tty_check_fg(struct tty *, struct window_pane *, - struct grid_cell *); -static void tty_check_bg(struct tty *, struct window_pane *, - struct grid_cell *); -static void tty_check_us(struct tty *, struct window_pane *, - struct grid_cell *); +static void tty_check_fg(struct tty *, struct colour_palette *, + struct grid_cell *); +static void tty_check_bg(struct tty *, struct colour_palette *, + struct grid_cell *); +static void tty_check_us(struct tty *, struct colour_palette *, + struct grid_cell *); static void tty_colours_fg(struct tty *, const struct grid_cell *); static void tty_colours_bg(struct tty *, const struct grid_cell *); static void tty_colours_us(struct tty *, const struct grid_cell *); @@ -61,23 +61,21 @@ static void tty_region(struct tty *, u_int, u_int); static void tty_margin_pane(struct tty *, const struct tty_ctx *); static void tty_margin(struct tty *, u_int, u_int); static int tty_large_region(struct tty *, const struct tty_ctx *); -static int tty_fake_bce(const struct tty *, struct window_pane *, u_int); +static int tty_fake_bce(const struct tty *, const struct grid_cell *, + u_int); static void tty_redraw_region(struct tty *, const struct tty_ctx *); static void tty_emulate_repeat(struct tty *, enum tty_code_code, enum tty_code_code, u_int); static void tty_repeat_space(struct tty *, u_int); static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); -static void tty_cell(struct tty *, const struct grid_cell *, - struct window_pane *); -static void tty_default_colours(struct grid_cell *, struct window_pane *); -static void tty_default_attributes(struct tty *, struct window_pane *, - u_int); +static void tty_default_attributes(struct tty *, const struct grid_cell *, + struct colour_palette *, u_int); +static int tty_check_overlay(struct tty *, u_int, u_int); #define tty_use_margin(tty) \ - ((tty->term->flags|tty->term_flags) & TERM_DECSLRM) - -#define tty_pane_full_width(tty, ctx) \ - ((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx) + (tty->term->flags & TERM_DECSLRM) +#define tty_full_width(tty, ctx) \ + ((ctx)->xoff == 0 && (ctx)->sx >= (tty)->sx) #define TTY_BLOCK_INTERVAL (100000 /* 100 milliseconds */) #define TTY_BLOCK_START(tty) (1 + ((tty)->sx * (tty)->sy) * 8) @@ -96,27 +94,19 @@ tty_create_log(void) } int -tty_init(struct tty *tty, struct client *c, int fd, char *term) +tty_init(struct tty *tty, struct client *c) { - if (!isatty(fd)) + if (!isatty(c->fd)) return (-1); memset(tty, 0, sizeof *tty); - - if (term == NULL || *term == '\0') - tty->term_name = xstrdup("unknown"); - else - tty->term_name = xstrdup(term); - - tty->fd = fd; tty->client = c; - tty->cstyle = 0; + tty->cstyle = SCREEN_CURSOR_DEFAULT; tty->ccolour = xstrdup(""); - tty->flags = 0; - tty->term_flags = 0; - + if (tcgetattr(c->fd, &tty->tio) != 0) + return (-1); return (0); } @@ -127,7 +117,7 @@ tty_resize(struct tty *tty) struct winsize ws; u_int sx, sy, xpixel, ypixel; - if (ioctl(tty->fd, TIOCGWINSZ, &ws) != -1) { + if (ioctl(c->fd, TIOCGWINSZ, &ws) != -1) { sx = ws.ws_col; if (sx == 0) { sx = 80; @@ -166,16 +156,21 @@ tty_read_callback(__unused int fd, __unused short events, void *data) { struct tty *tty = data; struct client *c = tty->client; + const char *name = c->name; size_t size = EVBUFFER_LENGTH(tty->in); int nread; - nread = evbuffer_read(tty->in, tty->fd, -1); + nread = evbuffer_read(tty->in, c->fd, -1); if (nread == 0 || nread == -1) { + if (nread == 0) + log_debug("%s: read closed", name); + else + log_debug("%s: read error: %s", name, strerror(errno)); event_del(&tty->event_in); server_client_lost(tty->client); return; } - log_debug("%s: read %d bytes (already %zu)", c->name, nread, size); + log_debug("%s: read %d bytes (already %zu)", name, nread, size); while (tty_keys_next(tty)) ; @@ -239,7 +234,7 @@ tty_write_callback(__unused int fd, __unused short events, void *data) size_t size = EVBUFFER_LENGTH(tty->out); int nwrite; - nwrite = evbuffer_write(tty->out, tty->fd); + nwrite = evbuffer_write(tty->out, c->fd); if (nwrite == -1) return; log_debug("%s: wrote %d bytes (of %zu)", c->name, nwrite, size); @@ -261,7 +256,10 @@ tty_write_callback(__unused int fd, __unused short events, void *data) int tty_open(struct tty *tty, char **cause) { - tty->term = tty_term_find(tty->term_name, tty->fd, cause); + struct client *c = tty->client; + + tty->term = tty_term_create(tty, c->term_name, c->term_caps, + c->term_ncaps, &c->term_features, cause); if (tty->term == NULL) { tty_close(tty); return (-1); @@ -270,13 +268,13 @@ tty_open(struct tty *tty, char **cause) tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER); - event_set(&tty->event_in, tty->fd, EV_PERSIST|EV_READ, + event_set(&tty->event_in, c->fd, EV_PERSIST|EV_READ, tty_read_callback, tty); tty->in = evbuffer_new(); if (tty->in == NULL) fatal("out of memory"); - event_set(&tty->event_out, tty->fd, EV_WRITE, tty_write_callback, tty); + event_set(&tty->event_out, c->fd, EV_WRITE, tty_write_callback, tty); tty->out = evbuffer_new(); if (tty->out == NULL) fatal("out of memory"); @@ -297,7 +295,9 @@ tty_start_timer_callback(__unused int fd, __unused short events, void *data) struct client *c = tty->client; log_debug("%s: start timer fired", c->name); - tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + if ((tty->flags & (TTY_HAVEDA|TTY_HAVEXDA)) == 0) + tty_update_features(tty); + tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); } void @@ -307,21 +307,19 @@ tty_start_tty(struct tty *tty) struct termios tio; struct timeval tv = { .tv_sec = 1 }; - if (tty->fd != -1 && tcgetattr(tty->fd, &tty->tio) == 0) { - setblocking(tty->fd, 0); - event_add(&tty->event_in, NULL); + setblocking(c->fd, 0); + event_add(&tty->event_in, NULL); - memcpy(&tio, &tty->tio, sizeof tio); - tio.c_iflag &= ~(IXON|IXOFF|ICRNL|INLCR|IGNCR|IMAXBEL|ISTRIP); - tio.c_iflag |= IGNBRK; - tio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET); - tio.c_lflag &= ~(IEXTEN|ICANON|ECHO|ECHOE|ECHONL|ECHOCTL| - ECHOPRT|ECHOKE|ISIG); - tio.c_cc[VMIN] = 1; - tio.c_cc[VTIME] = 0; - if (tcsetattr(tty->fd, TCSANOW, &tio) == 0) - tcflush(tty->fd, TCIOFLUSH); - } + memcpy(&tio, &tty->tio, sizeof tio); + tio.c_iflag &= ~(IXON|IXOFF|ICRNL|INLCR|IGNCR|IMAXBEL|ISTRIP); + tio.c_iflag |= IGNBRK; + tio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET); + tio.c_lflag &= ~(IEXTEN|ICANON|ECHO|ECHOE|ECHONL|ECHOCTL|ECHOPRT| + ECHOKE|ISIG); + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + if (tcsetattr(c->fd, TCSANOW, &tio) == 0) + tcflush(c->fd, TCOFLUSH); tty_putcode(tty, TTYC_SMCUP); @@ -335,20 +333,10 @@ tty_start_tty(struct tty *tty) log_debug("%s: using UTF-8 for ACS", c->name); tty_putcode(tty, TTYC_CNORM); - if (tty_term_has(tty->term, TTYC_KMOUS)) - tty_puts(tty, "\033[?1000l\033[?1002l\033[?1006l\033[?1005l"); - - if (tty_term_flag(tty->term, TTYC_XT)) { - if (options_get_number(global_options, "focus-events")) { - tty->flags |= TTY_FOCUS; - tty_puts(tty, "\033[?1004h"); - } - if (~tty->flags & TTY_HAVEDA) - tty_puts(tty, "\033[c"); - if (~tty->flags & TTY_HAVEDSR) - tty_puts(tty, "\033[1337n"); - } else - tty->flags |= (TTY_HAVEDA|TTY_HAVEDSR); + if (tty_term_has(tty->term, TTYC_KMOUS)) { + tty_puts(tty, "\033[?1000l\033[?1002l\033[?1003l"); + tty_puts(tty, "\033[?1006l\033[?1005l"); + } evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); @@ -364,10 +352,26 @@ tty_start_tty(struct tty *tty) tty->mouse_drag_release = NULL; } +void +tty_send_requests(struct tty *tty) +{ + if (~tty->flags & TTY_STARTED) + return; + + if (tty->term->flags & TERM_VT100LIKE) { + if (~tty->flags & TTY_HAVEDA) + tty_puts(tty, "\033[>c"); + if (~tty->flags & TTY_HAVEXDA) + tty_puts(tty, "\033[>q"); + } else + tty->flags |= (TTY_HAVEDA|TTY_HAVEXDA); +} + void tty_stop_tty(struct tty *tty) { - struct winsize ws; + struct client *c = tty->client; + struct winsize ws; if (!(tty->flags & TTY_STARTED)) return; @@ -386,9 +390,9 @@ tty_stop_tty(struct tty *tty) * because the fd is invalid. Things like ssh -t can easily leave us * with a dead tty. */ - if (ioctl(tty->fd, TIOCGWINSZ, &ws) == -1) + if (ioctl(c->fd, TIOCGWINSZ, &ws) == -1) return; - if (tcsetattr(tty->fd, TCSANOW, &tty->tio) == -1) + if (tcsetattr(c->fd, TCSANOW, &tty->tio) == -1) return; tty_raw(tty, tty_term_string2(tty->term, TTYC_CSR, 0, ws.ws_row - 1)); @@ -397,33 +401,33 @@ tty_stop_tty(struct tty *tty) tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0)); tty_raw(tty, tty_term_string(tty->term, TTYC_RMKX)); tty_raw(tty, tty_term_string(tty->term, TTYC_CLEAR)); - if (tty_term_has(tty->term, TTYC_SS) && tty->cstyle != 0) { + if (tty->cstyle != SCREEN_CURSOR_DEFAULT) { if (tty_term_has(tty->term, TTYC_SE)) tty_raw(tty, tty_term_string(tty->term, TTYC_SE)); - else + else if (tty_term_has(tty->term, TTYC_SS)) tty_raw(tty, tty_term_string1(tty->term, TTYC_SS, 0)); } if (tty->mode & MODE_BRACKETPASTE) - tty_raw(tty, "\033[?2004l"); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (*tty->ccolour != '\0') tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); - if (tty_term_has(tty->term, TTYC_KMOUS)) - tty_raw(tty, "\033[?1000l\033[?1002l\033[?1006l\033[?1005l"); - - if (tty_term_flag(tty->term, TTYC_XT)) { - if (tty->flags & TTY_FOCUS) { - tty->flags &= ~TTY_FOCUS; - tty_raw(tty, "\033[?1004l"); - } + if (tty_term_has(tty->term, TTYC_KMOUS)) { + tty_raw(tty, "\033[?1000l\033[?1002l\033[?1003l"); + tty_raw(tty, "\033[?1006l\033[?1005l"); } + if (tty->term->flags & TERM_VT100LIKE) + tty_raw(tty, "\033[?7727l"); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSFCS)); + tty_raw(tty, tty_term_string(tty->term, TTYC_DSEKS)); + if (tty_use_margin(tty)) - tty_raw(tty, "\033[?69l"); /* DECLRMM */ + tty_raw(tty, tty_term_string(tty->term, TTYC_DSMG)); tty_raw(tty, tty_term_string(tty->term, TTYC_RMCUP)); - setblocking(tty->fd, 1); + setblocking(c->fd, 1); } void @@ -444,40 +448,43 @@ tty_close(struct tty *tty) tty->flags &= ~TTY_OPENED; } - - if (tty->fd != -1) { - close(tty->fd); - tty->fd = -1; - } } void tty_free(struct tty *tty) { tty_close(tty); - free(tty->ccolour); - free(tty->term_name); } void -tty_set_flags(struct tty *tty, int flags) +tty_update_features(struct tty *tty) { - tty->term_flags |= flags; + struct client *c = tty->client; + + if (tty_apply_features(tty->term, c->term_features)) + tty_term_apply_overrides(tty->term); if (tty_use_margin(tty)) - tty_puts(tty, "\033[?69h"); /* DECLRMM */ + tty_putcode(tty, TTYC_ENMG); + if (options_get_number(global_options, "extended-keys")) + tty_puts(tty, tty_term_string(tty->term, TTYC_ENEKS)); + if (options_get_number(global_options, "focus-events")) + tty_puts(tty, tty_term_string(tty->term, TTYC_ENFCS)); + if (tty->term->flags & TERM_VT100LIKE) + tty_puts(tty, "\033[?7727h"); } void tty_raw(struct tty *tty, const char *s) { - ssize_t n, slen; - u_int i; + struct client *c = tty->client; + ssize_t n, slen; + u_int i; slen = strlen(s); for (i = 0; i < 5; i++) { - n = write(tty->fd, s, slen); + n = write(c->fd, s, slen); if (n >= 0) { s += n; slen -= n; @@ -566,7 +573,7 @@ tty_putc(struct tty *tty, u_char ch) { const char *acs; - if ((tty->term->flags & TERM_NOXENL) && + if ((tty->term->flags & TERM_NOAM) && ch >= 0x20 && ch != 0x7f && tty->cy == tty->sy - 1 && tty->cx + 1 >= tty->sx) @@ -588,11 +595,11 @@ tty_putc(struct tty *tty, u_char ch) tty->cy++; /* - * On !xenl terminals, force the cursor position to - * where we think it should be after a line wrap - this - * means it works on sensible terminals as well. + * On !am terminals, force the cursor position to where + * we think it should be after a line wrap - this means + * it works on sensible terminals as well. */ - if (tty->term->flags & TERM_NOXENL) + if (tty->term->flags & TERM_NOAM) tty_putcode2(tty, TTYC_CUP, tty->cy, tty->cx); } else tty->cx++; @@ -602,7 +609,7 @@ tty_putc(struct tty *tty, u_char ch) void tty_putn(struct tty *tty, const void *buf, size_t len, u_int width) { - if ((tty->term->flags & TERM_NOXENL) && + if ((tty->term->flags & TERM_NOAM) && tty->cy == tty->sy - 1 && tty->cx + len >= tty->sx) len = tty->sx - tty->cx - 1; @@ -656,69 +663,127 @@ tty_force_cursor_colour(struct tty *tty, const char *ccolour) tty->ccolour = xstrdup(ccolour); } +static void +tty_update_cursor(struct tty *tty, int mode, int changed, struct screen *s) +{ + enum screen_cursor_style cstyle; + + /* Set cursor colour if changed. */ + if (s != NULL && strcmp(s->ccolour, tty->ccolour) != 0) + tty_force_cursor_colour(tty, s->ccolour); + + /* If cursor is off, set as invisible. */ + if (~mode & MODE_CURSOR) { + if (changed & MODE_CURSOR) + tty_putcode(tty, TTYC_CIVIS); + return; + } + + /* Check if blinking or very visible flag changed or style changed. */ + if (s == NULL) + cstyle = tty->cstyle; + else + cstyle = s->cstyle; + if ((changed & CURSOR_MODES) == 0 && cstyle == tty->cstyle) + return; + + /* + * Set cursor style. If an explicit style has been set with DECSCUSR, + * set it if supported, otherwise send cvvis for blinking styles. + * + * If no style, has been set (SCREEN_CURSOR_DEFAULT), then send cvvis + * if either the blinking or very visible flags are set. + */ + tty_putcode(tty, TTYC_CNORM); + switch (cstyle) { + case SCREEN_CURSOR_DEFAULT: + if (tty->cstyle != SCREEN_CURSOR_DEFAULT) { + if (tty_term_has(tty->term, TTYC_SE)) + tty_putcode(tty, TTYC_SE); + else + tty_putcode1(tty, TTYC_SS, 0); + } + if (mode & (MODE_CURSOR_BLINKING|MODE_CURSOR_VERY_VISIBLE)) + tty_putcode(tty, TTYC_CVVIS); + break; + case SCREEN_CURSOR_BLOCK: + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_CURSOR_BLINKING) + tty_putcode1(tty, TTYC_SS, 1); + else + tty_putcode1(tty, TTYC_SS, 2); + } else if (mode & MODE_CURSOR_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + case SCREEN_CURSOR_UNDERLINE: + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_CURSOR_BLINKING) + tty_putcode1(tty, TTYC_SS, 3); + else + tty_putcode1(tty, TTYC_SS, 4); + } else if (mode & MODE_CURSOR_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + case SCREEN_CURSOR_BAR: + if (tty_term_has(tty->term, TTYC_SS)) { + if (mode & MODE_CURSOR_BLINKING) + tty_putcode1(tty, TTYC_SS, 5); + else + tty_putcode1(tty, TTYC_SS, 6); + } else if (mode & MODE_CURSOR_BLINKING) + tty_putcode(tty, TTYC_CVVIS); + break; + } + tty->cstyle = cstyle; + } + void tty_update_mode(struct tty *tty, int mode, struct screen *s) { - int changed; - - if (s != NULL && strcmp(s->ccolour, tty->ccolour) != 0) - tty_force_cursor_colour(tty, s->ccolour); + struct client *c = tty->client; + int changed; if (tty->flags & TTY_NOCURSOR) mode &= ~MODE_CURSOR; changed = mode ^ tty->mode; - if (changed & MODE_BLINKING) { - if (tty_term_has(tty->term, TTYC_CVVIS)) - tty_putcode(tty, TTYC_CVVIS); - else - tty_putcode(tty, TTYC_CNORM); - changed |= MODE_CURSOR; + if (log_get_level() != 0 && changed != 0) { + log_debug("%s: current mode %s", c->name, + screen_mode_to_string(tty->mode)); + log_debug("%s: setting mode %s", c->name, + screen_mode_to_string(mode)); } - if (changed & MODE_CURSOR) { - if (mode & MODE_CURSOR) - tty_putcode(tty, TTYC_CNORM); - else - tty_putcode(tty, TTYC_CIVIS); - } - if (s != NULL && tty->cstyle != s->cstyle) { - if (tty_term_has(tty->term, TTYC_SS)) { - if (s->cstyle == 0 && - tty_term_has(tty->term, TTYC_SE)) - tty_putcode(tty, TTYC_SE); - else - tty_putcode1(tty, TTYC_SS, s->cstyle); - } - tty->cstyle = s->cstyle; - } - if (changed & ALL_MOUSE_MODES) { - if (mode & ALL_MOUSE_MODES) { - /* - * Enable the SGR (1006) extension unconditionally, as - * it is safe from misinterpretation. - */ - tty_puts(tty, "\033[?1006h"); - if (mode & MODE_MOUSE_ALL) - tty_puts(tty, "\033[?1003h"); - else if (mode & MODE_MOUSE_BUTTON) - tty_puts(tty, "\033[?1002h"); - else if (mode & MODE_MOUSE_STANDARD) - tty_puts(tty, "\033[?1000h"); - } else { - if (tty->mode & MODE_MOUSE_ALL) - tty_puts(tty, "\033[?1003l"); - else if (tty->mode & MODE_MOUSE_BUTTON) - tty_puts(tty, "\033[?1002l"); - else if (tty->mode & MODE_MOUSE_STANDARD) - tty_puts(tty, "\033[?1000l"); + + tty_update_cursor(tty, mode, changed, s); + if ((changed & ALL_MOUSE_MODES) && + tty_term_has(tty->term, TTYC_KMOUS)) { + /* + * If the mouse modes have changed, clear any that are set and + * apply again. There are differences in how terminals track + * the various bits. + */ + if (tty->mode & MODE_MOUSE_SGR) tty_puts(tty, "\033[?1006l"); - } + if (tty->mode & MODE_MOUSE_STANDARD) + tty_puts(tty, "\033[?1000l"); + if (tty->mode & MODE_MOUSE_BUTTON) + tty_puts(tty, "\033[?1002l"); + if (tty->mode & MODE_MOUSE_ALL) + tty_puts(tty, "\033[?1003l"); + if (mode & ALL_MOUSE_MODES) + tty_puts(tty, "\033[?1006h"); + if (mode & MODE_MOUSE_STANDARD) + tty_puts(tty, "\033[?1000h"); + if (mode & MODE_MOUSE_BUTTON) + tty_puts(tty, "\033[?1002h"); + if (mode & MODE_MOUSE_ALL) + tty_puts(tty, "\033[?1003h"); } if (changed & MODE_BRACKETPASTE) { if (mode & MODE_BRACKETPASTE) - tty_puts(tty, "\033[?2004h"); + tty_putcode(tty, TTYC_ENBP); else - tty_puts(tty, "\033[?2004l"); + tty_putcode(tty, TTYC_DSBP); } tty->mode = mode; } @@ -779,7 +844,7 @@ tty_window_offset1(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) { struct client *c = tty->client; struct window *w = c->session->curw->window; - struct window_pane *wp = w->active; + struct window_pane *wp = server_client_get_pane(c); u_int cx, cy, lines; lines = status_line_size(c); @@ -885,9 +950,7 @@ tty_update_client_offset(struct client *c) static int tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - - return (ctx->orlower - ctx->orupper >= screen_size_y(wp->screen) / 2); + return (ctx->orlower - ctx->orupper >= ctx->sy / 2); } /* @@ -895,18 +958,11 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) * emulated. */ static int -tty_fake_bce(const struct tty *tty, struct window_pane *wp, u_int bg) +tty_fake_bce(const struct tty *tty, const struct grid_cell *gc, u_int bg) { - struct grid_cell gc; - if (tty_term_flag(tty->term, TTYC_BCE)) return (0); - - memcpy(&gc, &grid_default_cell, sizeof gc); - if (wp != NULL) - tty_default_colours(&gc, wp); - - if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc.bg)) + if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc->bg)) return (1); return (0); } @@ -919,45 +975,35 @@ tty_fake_bce(const struct tty *tty, struct window_pane *wp, u_int bg) static void tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; + struct client *c = tty->client; u_int i; /* - * If region is large, schedule a window redraw. In most cases this is - * likely to be followed by some more scrolling. + * If region is large, schedule a redraw. In most cases this is likely + * to be followed by some more scrolling. */ if (tty_large_region(tty, ctx)) { - wp->flags |= PANE_REDRAW; + log_debug("%s: %s large redraw", __func__, c->name); + ctx->redraw_cb(ctx); return; } - if (ctx->ocy < ctx->orupper || ctx->ocy > ctx->orlower) { - for (i = ctx->ocy; i < screen_size_y(s); i++) - tty_draw_pane(tty, ctx, i); - } else { - for (i = ctx->orupper; i <= ctx->orlower; i++) - tty_draw_pane(tty, ctx, i); - } + for (i = ctx->orupper; i <= ctx->orlower; i++) + tty_draw_pane(tty, ctx, i); } /* Is this position visible in the pane? */ static int -tty_is_visible(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, - u_int nx, u_int ny) +tty_is_visible(__unused struct tty *tty, const struct tty_ctx *ctx, u_int px, + u_int py, u_int nx, u_int ny) { - u_int xoff = ctx->xoff + px, yoff = ctx->yoff + py, lines; + u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py; if (!ctx->bigger) return (1); - if (status_at_line(tty->client) == 0) - lines = status_line_size(tty->client); - else - lines = 0; - - if (xoff + nx <= ctx->ox || xoff >= ctx->ox + ctx->sx || - yoff + ny <= ctx->oy || yoff >= lines + ctx->oy + ctx->sy) + if (xoff + nx <= ctx->wox || xoff >= ctx->wox + ctx->wsx || + yoff + ny <= ctx->woy || yoff >= ctx->woy + ctx->wsy) return (0); return (1); } @@ -967,33 +1013,32 @@ static int tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, u_int nx, u_int *i, u_int *x, u_int *rx, u_int *ry) { - struct window_pane *wp = ctx->wp; - u_int xoff = wp->xoff + px; + u_int xoff = ctx->rxoff + px; if (!tty_is_visible(tty, ctx, px, py, nx, 1)) return (0); - *ry = ctx->yoff + py - ctx->oy; + *ry = ctx->yoff + py - ctx->woy; - if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { /* All visible. */ *i = 0; - *x = ctx->xoff + px - ctx->ox; + *x = ctx->xoff + px - ctx->wox; *rx = nx; - } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { /* Both left and right not visible. */ - *i = ctx->ox; + *i = ctx->wox; *x = 0; - *rx = ctx->sx; - } else if (xoff < ctx->ox) { + *rx = ctx->wsx; + } else if (xoff < ctx->wox) { /* Left not visible. */ - *i = ctx->ox - (ctx->xoff + px); + *i = ctx->wox - (ctx->xoff + px); *x = 0; *rx = nx - *i; } else { /* Right not visible. */ *i = 0; - *x = (ctx->xoff + px) - ctx->ox; - *rx = ctx->sx - *x; + *x = (ctx->xoff + px) - ctx->wox; + *rx = ctx->wsx - *x; } if (*rx > nx) fatalx("%s: x too big, %u > %u", __func__, *rx, nx); @@ -1003,10 +1048,11 @@ tty_clamp_line(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, /* Clear a line. */ static void -tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, - u_int nx, u_int bg) +tty_clear_line(struct tty *tty, const struct grid_cell *defaults, u_int py, + u_int px, u_int nx, u_int bg) { struct client *c = tty->client; + u_int i; log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); @@ -1015,7 +1061,7 @@ tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, return; /* If genuine BCE is available, can try escape sequences. */ - if (!tty_fake_bce(tty, wp, bg)) { + if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) { /* Off the end of the line, use EL if available. */ if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) { tty_cursor(tty, px, py); @@ -1038,9 +1084,22 @@ tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, } } - /* Couldn't use an escape sequence, use spaces. */ + /* + * Couldn't use an escape sequence, use spaces. Clear only the visible + * bit if there is an overlay. + */ + for (i = 0; i < nx; i++) { + if (!tty_check_overlay(tty, px + i, py)) + break; + } tty_cursor(tty, px, py); - tty_repeat_space(tty, nx); + tty_repeat_space(tty, i); + for (; i < nx; i++) { + if (tty_check_overlay(tty, px + i, py)) + break; + } + tty_cursor(tty, px + i, py); + tty_repeat_space(tty, nx - i); } /* Clear a line, adjusting to visible part of pane. */ @@ -1054,7 +1113,7 @@ tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); if (tty_clamp_line(tty, ctx, px, py, nx, &i, &x, &rx, &ry)) - tty_clear_line(tty, ctx->wp, ry, x, rx, bg); + tty_clear_line(tty, &ctx->defaults, ry, x, rx, bg); } /* Clamp area position to visible part of pane. */ @@ -1063,56 +1122,55 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, u_int nx, u_int ny, u_int *i, u_int *j, u_int *x, u_int *y, u_int *rx, u_int *ry) { - struct window_pane *wp = ctx->wp; - u_int xoff = wp->xoff + px, yoff = wp->yoff + py; + u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py; if (!tty_is_visible(tty, ctx, px, py, nx, ny)) return (0); - if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) { /* All visible. */ *i = 0; - *x = ctx->xoff + px - ctx->ox; + *x = ctx->xoff + px - ctx->wox; *rx = nx; - } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + } else if (xoff < ctx->wox && xoff + nx > ctx->wox + ctx->wsx) { /* Both left and right not visible. */ - *i = ctx->ox; + *i = ctx->wox; *x = 0; - *rx = ctx->sx; - } else if (xoff < ctx->ox) { + *rx = ctx->wsx; + } else if (xoff < ctx->wox) { /* Left not visible. */ - *i = ctx->ox - (ctx->xoff + px); + *i = ctx->wox - (ctx->xoff + px); *x = 0; *rx = nx - *i; } else { /* Right not visible. */ *i = 0; - *x = (ctx->xoff + px) - ctx->ox; - *rx = ctx->sx - *x; + *x = (ctx->xoff + px) - ctx->wox; + *rx = ctx->wsx - *x; } if (*rx > nx) fatalx("%s: x too big, %u > %u", __func__, *rx, nx); - if (yoff >= ctx->oy && yoff + ny <= ctx->oy + ctx->sy) { + if (yoff >= ctx->woy && yoff + ny <= ctx->woy + ctx->wsy) { /* All visible. */ *j = 0; - *y = ctx->yoff + py - ctx->oy; + *y = ctx->yoff + py - ctx->woy; *ry = ny; - } else if (yoff < ctx->oy && yoff + ny > ctx->oy + ctx->sy) { + } else if (yoff < ctx->woy && yoff + ny > ctx->woy + ctx->wsy) { /* Both top and bottom not visible. */ - *j = ctx->oy; + *j = ctx->woy; *y = 0; - *ry = ctx->sy; - } else if (yoff < ctx->oy) { + *ry = ctx->wsy; + } else if (yoff < ctx->woy) { /* Top not visible. */ - *j = ctx->oy - (ctx->yoff + py); + *j = ctx->woy - (ctx->yoff + py); *y = 0; *ry = ny - *j; } else { /* Bottom not visible. */ *j = 0; - *y = (ctx->yoff + py) - ctx->oy; - *ry = ctx->sy - *y; + *y = (ctx->yoff + py) - ctx->woy; + *ry = ctx->wsy - *y; } if (*ry > ny) fatalx("%s: y too big, %u > %u", __func__, *ry, ny); @@ -1122,8 +1180,8 @@ tty_clamp_area(struct tty *tty, const struct tty_ctx *ctx, u_int px, u_int py, /* Clear an area, adjusting to visible part of pane. */ static void -tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, - u_int px, u_int nx, u_int bg) +tty_clear_area(struct tty *tty, const struct grid_cell *defaults, u_int py, + u_int ny, u_int px, u_int nx, u_int bg) { struct client *c = tty->client; u_int yy; @@ -1136,7 +1194,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, return; /* If genuine BCE is available, can try escape sequences. */ - if (!tty_fake_bce(tty, wp, bg)) { + if (c->overlay_check == NULL && !tty_fake_bce(tty, defaults, bg)) { /* Use ED if clearing off the bottom of the terminal. */ if (px == 0 && px + nx >= tty->sx && @@ -1152,8 +1210,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, * background colour isn't default (because it doesn't work * after SGR 0). */ - if (((tty->term->flags|tty->term_flags) & TERM_DECFRA) && - !COLOUR_DEFAULT(bg)) { + if ((tty->term->flags & TERM_DECFRA) && !COLOUR_DEFAULT(bg)) { xsnprintf(tmp, sizeof tmp, "\033[32;%u;%u;%u;%u$x", py + 1, px + 1, py + ny, px + nx); tty_puts(tty, tmp); @@ -1190,7 +1247,7 @@ tty_clear_area(struct tty *tty, struct window_pane *wp, u_int py, u_int ny, /* Couldn't use an escape sequence, loop over the lines. */ for (yy = py; yy < py + ny; yy++) - tty_clear_line(tty, wp, yy, px, nx, bg); + tty_clear_line(tty, defaults, yy, px, nx, bg); } /* Clear an area in a pane. */ @@ -1201,59 +1258,80 @@ tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, u_int i, j, x, y, rx, ry; if (tty_clamp_area(tty, ctx, px, py, nx, ny, &i, &j, &x, &y, &rx, &ry)) - tty_clear_area(tty, ctx->wp, y, ry, x, rx, bg); + tty_clear_area(tty, &ctx->defaults, y, ry, x, rx, bg); } static void tty_draw_pane(struct tty *tty, const struct tty_ctx *ctx, u_int py) { - struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; - u_int nx = screen_size_x(s), i, x, rx, ry; + struct screen *s = ctx->s; + u_int nx = ctx->sx, i, x, rx, ry; log_debug("%s: %s %u %d", __func__, tty->client->name, py, ctx->bigger); if (!ctx->bigger) { - tty_draw_line(tty, wp, s, 0, py, nx, ctx->xoff, ctx->yoff + py); + tty_draw_line(tty, s, 0, py, nx, ctx->xoff, ctx->yoff + py, + &ctx->defaults, ctx->palette); return; } - if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) - tty_draw_line(tty, wp, s, i, py, rx, x, ry); + if (tty_clamp_line(tty, ctx, 0, py, nx, &i, &x, &rx, &ry)) { + tty_draw_line(tty, s, i, py, rx, x, ry, &ctx->defaults, + ctx->palette); + } } static const struct grid_cell * tty_check_codeset(struct tty *tty, const struct grid_cell *gc) { static struct grid_cell new; - u_int n; + int c; /* Characters less than 0x7f are always fine, no matter what. */ if (gc->data.size == 1 && *gc->data.data < 0x7f) return (gc); /* UTF-8 terminal and a UTF-8 character - fine. */ - if (tty->flags & TTY_UTF8) + if (tty->client->flags & CLIENT_UTF8) return (gc); + memcpy(&new, gc, sizeof new); + + /* See if this can be mapped to an ACS character. */ + c = tty_acs_reverse_get(tty, gc->data.data, gc->data.size); + if (c != -1) { + utf8_set(&new.data, c); + new.attr |= GRID_ATTR_CHARSET; + return (&new); + } /* Replace by the right number of underscores. */ - n = gc->data.width; - if (n > UTF8_SIZE) - n = UTF8_SIZE; - memcpy(&new, gc, sizeof new); - new.data.size = n; - memset(new.data.data, '_', n); + new.data.size = gc->data.width; + if (new.data.size > UTF8_SIZE) + new.data.size = UTF8_SIZE; + memset(new.data.data, '_', new.data.size); return (&new); } +static int +tty_check_overlay(struct tty *tty, u_int px, u_int py) +{ + struct client *c = tty->client; + + if (c->overlay_check == NULL) + return (1); + return (c->overlay_check(c, c->overlay_data, px, py)); +} + void -tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, - u_int px, u_int py, u_int nx, u_int atx, u_int aty) +tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, + u_int atx, u_int aty, const struct grid_cell *defaults, + struct colour_palette *palette) { struct grid *gd = s->grid; struct grid_cell gc, last; const struct grid_cell *gcp; struct grid_line *gl; - u_int i, j, ux, sx, width; + struct client *c = tty->client; + u_int i, j, ux, sx, width, hidden; int flags, cleared = 0, wrapped = 0; char buf[512]; size_t len; @@ -1261,6 +1339,8 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, px, py, nx, atx, aty); + log_debug("%s: defaults: fg=%d, bg=%d", __func__, defaults->fg, + defaults->bg); /* * py is the line in the screen to draw. @@ -1295,8 +1375,7 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, gl = NULL; else gl = grid_get_line(gd, gd->hsize + py - 1); - if (wp == NULL || - gl == NULL || + if (gl == NULL || (~gl->flags & GRID_LINE_WRAPPED) || atx != 0 || tty->cx < tty->sx || @@ -1305,8 +1384,9 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, atx == 0 && px + sx != nx && tty_term_has(tty->term, TTYC_EL1) && - !tty_fake_bce(tty, wp, 8)) { - tty_default_attributes(tty, wp, 8); + !tty_fake_bce(tty, defaults, 8) && + c->overlay_check == NULL) { + tty_default_attributes(tty, defaults, palette, 8); tty_cursor(tty, nx - 1, aty); tty_putcode(tty, TTYC_EL1); cleared = 1; @@ -1324,7 +1404,8 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, grid_view_get_cell(gd, px + i, py, &gc); gcp = tty_check_codeset(tty, &gc); if (len != 0 && - ((gcp->attr & GRID_ATTR_CHARSET) || + (!tty_check_overlay(tty, atx + ux + width, aty) || + (gcp->attr & GRID_ATTR_CHARSET) || gcp->flags != last.flags || gcp->attr != last.attr || gcp->fg != last.fg || @@ -1332,11 +1413,11 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, gcp->us != last.us || ux + width + gcp->data.width > nx || (sizeof buf) - len < gcp->data.size)) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared", __func__, len); - tty_clear_line(tty, wp, aty, atx + ux, width, - last.bg); + tty_clear_line(tty, defaults, aty, atx + ux, + width, last.bg); } else { if (!wrapped || atx != 0 || ux != 0) tty_cursor(tty, atx + ux, aty); @@ -1353,32 +1434,50 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, screen_select_cell(s, &last, gcp); else memcpy(&last, gcp, sizeof last); - if (ux + gcp->data.width > nx) { - tty_attributes(tty, &last, wp); - tty_cursor(tty, atx + ux, aty); - for (j = 0; j < gcp->data.width; j++) { - if (ux + j > nx) - break; - tty_putc(tty, ' '); - ux++; + + hidden = 0; + for (j = 0; j < gcp->data.width; j++) { + if (!tty_check_overlay(tty, atx + ux + j, aty)) + hidden++; + } + if (hidden != 0 && hidden == gcp->data.width) { + if (~gcp->flags & GRID_FLAG_PADDING) + ux += gcp->data.width; + } else if (hidden != 0 || ux + gcp->data.width > nx) { + if (~gcp->flags & GRID_FLAG_PADDING) { + tty_attributes(tty, &last, defaults, palette); + tty_cursor(tty, atx + ux, aty); + for (j = 0; j < gcp->data.width; j++) { + if (ux > nx) + break; + if (tty_check_overlay(tty, atx + ux, + aty)) + tty_putc(tty, ' '); + else { + tty_cursor(tty, atx + ux + 1, + aty); + } + ux++; + } } } else if (gcp->attr & GRID_ATTR_CHARSET) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.size; j++) tty_putc(tty, gcp->data.data[j]); - ux += gc.data.width; - } else { + ux += gcp->data.width; + } else if (~gcp->flags & GRID_FLAG_PADDING) { memcpy(buf + len, gcp->data.data, gcp->data.size); len += gcp->data.size; width += gcp->data.width; } } if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { - tty_attributes(tty, &last, wp); + tty_attributes(tty, &last, defaults, palette); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared (end)", __func__, len); - tty_clear_line(tty, wp, aty, atx + ux, width, last.bg); + tty_clear_line(tty, defaults, aty, atx + ux, width, + last.bg); } else { if (!wrapped || atx != 0 || ux != 0) tty_cursor(tty, atx + ux, aty); @@ -1390,8 +1489,8 @@ tty_draw_line(struct tty *tty, struct window_pane *wp, struct screen *s, if (!cleared && ux < nx) { log_debug("%s: %u to end of line (%zu cleared)", __func__, nx - ux, len); - tty_default_attributes(tty, wp, 8); - tty_clear_line(tty, wp, aty, atx + ux, nx - ux, 8); + tty_default_attributes(tty, defaults, palette, 8); + tty_clear_line(tty, defaults, aty, atx + ux, nx - ux, 8); } tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags; @@ -1430,8 +1529,37 @@ tty_draw_images(struct tty *tty, struct window_pane *wp, struct screen *s) } } +tty_sync_start(struct tty *tty) +{ + if (tty->flags & TTY_BLOCK) + return; + if (tty->flags & TTY_SYNCING) + return; + tty->flags |= TTY_SYNCING; + + if (tty_term_has(tty->term, TTYC_SYNC)) { + log_debug("%s sync start", tty->client->name); + tty_putcode1(tty, TTYC_SYNC, 1); + } +} + +void +tty_sync_end(struct tty *tty) +{ + if (tty->flags & TTY_BLOCK) + return; + if (~tty->flags & TTY_SYNCING) + return; + tty->flags &= ~TTY_SYNCING; + + if (tty_term_has(tty->term, TTYC_SYNC)) { + log_debug("%s sync end", tty->client->name); + tty_putcode1(tty, TTYC_SYNC, 2); + } +} + static int -tty_client_ready(struct client *c, struct window_pane *wp) +tty_client_ready(struct client *c) { if (c->session == NULL || c->tty.term == NULL) return (0); @@ -1439,10 +1567,6 @@ tty_client_ready(struct client *c, struct window_pane *wp) return (0); if (c->tty.flags & TTY_FREEZE) return (0); - if (c->session->curw->window != wp->window) - return (0); - if (wp->layout_cell == NULL) - return (0); return (1); } @@ -1450,27 +1574,19 @@ void tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - struct client *c; + struct client *c; + int state; - if (wp == NULL) + if (ctx->set_client_cb == NULL) return; - if (wp->flags & (PANE_REDRAW|PANE_DROP)) - return; - TAILQ_FOREACH(c, &clients, entry) { - if (!tty_client_ready(c, wp)) + if (!tty_client_ready(c)) + continue; + state = ctx->set_client_cb(ctx, c); + if (state == -1) + break; + if (state == 0) continue; - - ctx->bigger = tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, - &ctx->sx, &ctx->sy); - - ctx->xoff = wp->xoff; - ctx->yoff = wp->yoff; - - if (status_at_line(c) == 0) - ctx->yoff += status_line_size(c); - cmdfn(&c->tty, ctx); } } @@ -1478,18 +1594,19 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; + struct client *c = tty->client; if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || - tty_fake_bce(tty, wp, ctx->bg) || + !tty_full_width(tty, ctx) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && - !tty_term_has(tty->term, TTYC_ICH1))) { + !tty_term_has(tty->term, TTYC_ICH1)) || + c->overlay_check != NULL) { tty_draw_pane(tty, ctx, ctx->ocy); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1499,18 +1616,19 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; + struct client *c = tty->client; if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || - tty_fake_bce(tty, wp, ctx->bg) || + !tty_full_width(tty, ctx) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && - !tty_term_has(tty->term, TTYC_DCH1))) { + !tty_term_has(tty->term, TTYC_DCH1)) || + c->overlay_check != NULL) { tty_draw_pane(tty, ctx, ctx->ocy); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1520,37 +1638,29 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) { - if (ctx->bigger) { - tty_draw_pane(tty, ctx, ctx->ocy); - return; - } + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_default_attributes(tty, ctx->wp, ctx->bg); - - tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); - - if (tty_term_has(tty->term, TTYC_ECH) && - !tty_fake_bce(tty, ctx->wp, 8)) - tty_putcode1(tty, TTYC_ECH, ctx->num); - else - tty_repeat_space(tty, ctx->num); + tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, ctx->num, ctx->bg); } void tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { + struct client *c = tty->client; + if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || - tty_fake_bce(tty, ctx->wp, ctx->bg) || + !tty_full_width(tty, ctx) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_IL1) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, ctx->wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1563,18 +1673,21 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { + struct client *c = tty->client; + if (ctx->bigger || - !tty_pane_full_width(tty, ctx) || - tty_fake_bce(tty, ctx->wp, ctx->bg) || + !tty_full_width(tty, ctx) || + tty_fake_bce(tty, &ctx->defaults, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || !tty_term_has(tty->term, TTYC_DL1) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, ctx->wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); @@ -1587,33 +1700,25 @@ tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int nx; + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_default_attributes(tty, wp, ctx->bg); - - nx = screen_size_x(wp->screen); - tty_clear_pane_line(tty, ctx, ctx->ocy, 0, nx, ctx->bg); + tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->sx, ctx->bg); } void tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int nx; + u_int nx = ctx->sx - ctx->ocx; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - nx = screen_size_x(wp->screen) - ctx->ocx; tty_clear_pane_line(tty, ctx, ctx->ocy, ctx->ocx, nx, ctx->bg); } void tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } @@ -1621,24 +1726,25 @@ tty_cmd_clearstartofline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; + struct client *c = tty->client; if (ctx->ocy != ctx->orupper) return; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && !tty_term_has(tty->term, TTYC_RIN)) || - ctx->wp->sx == 1 || - ctx->wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1653,22 +1759,23 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; + struct client *c = tty->client; if (ctx->ocy != ctx->orlower) return; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1694,20 +1801,21 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int i; + struct client *c = tty->client; + u_int i; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1720,7 +1828,10 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) for (i = 0; i < ctx->num; i++) tty_putc(tty, '\n'); } else { - tty_cursor(tty, 0, tty->cy); + if (tty->cy == UINT_MAX) + tty_cursor(tty, 0, 0); + else + tty_cursor(tty, 0, tty->cy); tty_putcode1(tty, TTYC_INDN, ctx->num); } } @@ -1728,22 +1839,23 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int i; + u_int i; + struct client *c = tty->client; if (ctx->bigger || - (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || - tty_fake_bce(tty, wp, 8) || + (!tty_full_width(tty, ctx) && !tty_use_margin(tty)) || + tty_fake_bce(tty, &ctx->defaults, 8) || !tty_term_has(tty->term, TTYC_CSR) || (!tty_term_has(tty->term, TTYC_RI) && !tty_term_has(tty->term, TTYC_RIN)) || - wp->sx == 1 || - wp->sy == 1) { + ctx->sx == 1 || + ctx->sy == 1 || + c->overlay_check != NULL) { tty_redraw_region(tty, ctx); return; } - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); @@ -1760,23 +1872,22 @@ tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = ctx->ocy + 1; - ny = screen_size_y(wp->screen) - ctx->ocy - 1; + ny = ctx->sy - ctx->ocy - 1; tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); px = ctx->ocx; - nx = screen_size_x(wp->screen) - ctx->ocx; + nx = ctx->sx - ctx->ocx; py = ctx->ocy; tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg); @@ -1785,16 +1896,15 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = 0; ny = ctx->ocy; @@ -1810,18 +1920,17 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - u_int px, py, nx, ny; + u_int px, py, nx, ny; - tty_default_attributes(tty, wp, ctx->bg); + tty_default_attributes(tty, &ctx->defaults, ctx->palette, ctx->bg); - tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); px = 0; - nx = screen_size_x(wp->screen); + nx = ctx->sx; py = 0; - ny = screen_size_y(wp->screen); + ny = ctx->sy; tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); } @@ -1829,23 +1938,21 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; - struct screen *s = wp->screen; - u_int i, j; + u_int i, j; if (ctx->bigger) { - wp->flags |= PANE_REDRAW; + ctx->redraw_cb(ctx); return; } - tty_attributes(tty, &grid_default_cell, wp); + tty_attributes(tty, &grid_default_cell, &ctx->defaults, ctx->palette); - tty_region_pane(tty, ctx, 0, screen_size_y(s) - 1); + tty_region_pane(tty, ctx, 0, ctx->sy - 1); tty_margin_off(tty); - for (j = 0; j < screen_size_y(s); j++) { + for (j = 0; j < ctx->sy; j++) { tty_cursor_pane(tty, ctx, 0, j); - for (i = 0; i < screen_size_x(s); i++) + for (i = 0; i < ctx->sx; i++) tty_putc(tty, 'E'); } } @@ -1853,67 +1960,106 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { - if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1)) + const struct grid_cell *gcp = ctx->cell; + struct screen *s = ctx->s; + u_int i, px, py; + + px = ctx->xoff + ctx->ocx - ctx->wox; + py = ctx->yoff + ctx->ocy - ctx->woy; + if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1) || + (gcp->data.width == 1 && !tty_check_overlay(tty, px, py))) return; - if (ctx->xoff + ctx->ocx - ctx->ox > tty->sx - 1 && + /* Handle partially obstructed wide characters. */ + if (gcp->data.width > 1) { + for (i = 0; i < gcp->data.width; i++) { + if (!tty_check_overlay(tty, px + i, py)) { + tty_draw_line(tty, s, s->cx, s->cy, + gcp->data.width, px, py, &ctx->defaults, + ctx->palette); + return; + } + } + } + + if (ctx->xoff + ctx->ocx - ctx->wox > tty->sx - 1 && ctx->ocy == ctx->orlower && - tty_pane_full_width(tty, ctx)) + tty_full_width(tty, ctx)) tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_cell(tty, ctx->cell, ctx->wp); + tty_cell(tty, ctx->cell, &ctx->defaults, ctx->palette); } void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { - struct window_pane *wp = ctx->wp; + u_int i, hide = 0; if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, ctx->num, 1)) return; if (ctx->bigger && - (ctx->xoff + ctx->ocx < ctx->ox || - ctx->xoff + ctx->ocx + ctx->num > ctx->ox + ctx->sx)) { + (ctx->xoff + ctx->ocx < ctx->wox || + ctx->xoff + ctx->ocx + ctx->num > ctx->wox + ctx->wsx)) { if (!ctx->wrapped || - !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_NOXENL) || + !tty_full_width(tty, ctx) || + (tty->term->flags & TERM_NOAM) || ctx->xoff + ctx->ocx != 0 || ctx->yoff + ctx->ocy != tty->cy + 1 || tty->cx < tty->sx || tty->cy == tty->rlower) tty_draw_pane(tty, ctx, ctx->ocy); else - wp->flags |= PANE_REDRAW; + ctx->redraw_cb(ctx); return; } tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); - tty_attributes(tty, ctx->cell, ctx->wp); - tty_putn(tty, ctx->ptr, ctx->num, ctx->num); + tty_attributes(tty, ctx->cell, &ctx->defaults, ctx->palette); + for (i = 0; i < ctx->num; i++) { + if (!tty_check_overlay(tty, tty->cx + i, tty->cy)) + break; + } + tty_putn(tty, ctx->ptr, i, i); + for (; i < ctx->num; i++) { + if (tty_check_overlay(tty, tty->cx + hide, tty->cy)) + break; + hide++; + } + tty_cursor(tty, tty->cx + hide, tty->cy); + tty_putn(tty, (char *)ctx->ptr + i, ctx->num - i, ctx->num - i); } void tty_cmd_setselection(struct tty *tty, const struct tty_ctx *ctx) { - char *buf; - size_t off; + tty_set_selection(tty, ctx->ptr, ctx->num); +} +void +tty_set_selection(struct tty *tty, const char *buf, size_t len) +{ + char *encoded; + size_t size; + + if (~tty->flags & TTY_STARTED) + return; if (!tty_term_has(tty->term, TTYC_MS)) return; - off = 4 * ((ctx->num + 2) / 3) + 1; /* storage for base64 */ - buf = xmalloc(off); + size = 4 * ((len + 2) / 3) + 1; /* storage for base64 */ + encoded = xmalloc(size); - b64_ntop(ctx->ptr, ctx->num, buf, off); - tty_putcode_ptr2(tty, TTYC_MS, "", buf); + b64_ntop(buf, len, encoded, size); + tty_putcode_ptr2(tty, TTYC_MS, "", encoded); + tty->client->redraw = EVBUFFER_LENGTH(tty->out); - free(buf); + free(encoded); } void @@ -1970,12 +2116,32 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) } static void -tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) +tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) +{ + if (ctx->num == 0x11) { + /* + * This is an overlay and a command that moves the cursor so + * start synchronized updates. + */ + tty_sync_start(tty); + } else if (~ctx->num & 0x10) { + /* + * This is a pane. If there is an overlay, always start; + * otherwise, only if requested. + */ + if (ctx->num || tty->client->overlay_draw != NULL) + tty_sync_start(tty); + } +} + +static void +tty_cell(struct tty *tty, const struct grid_cell *gc, + const struct grid_cell *defaults, struct colour_palette *palette) { const struct grid_cell *gcp; /* Skip last character if terminal is stupid. */ - if ((tty->term->flags & TERM_NOXENL) && + if ((tty->term->flags & TERM_NOAM) && tty->cy == tty->sy - 1 && tty->cx == tty->sx - 1) return; @@ -1984,12 +2150,13 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) if (gc->flags & GRID_FLAG_PADDING) return; - /* Set the attributes. */ - tty_attributes(tty, gc, wp); - - /* Get the cell and if ASCII write with putc to do ACS translation. */ + /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); + tty_attributes(tty, gcp, defaults, palette); + + /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { + tty_attributes(tty, gcp, defaults, palette); if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; tty_putc(tty, *gcp->data.data); @@ -2011,27 +2178,22 @@ tty_reset(struct tty *tty) tty_putcode(tty, TTYC_SGR0); memcpy(gc, &grid_default_cell, sizeof *gc); } - memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell); - tty->last_wp = -1; } static void tty_invalidate(struct tty *tty) { memcpy(&tty->cell, &grid_default_cell, sizeof tty->cell); - memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell); - tty->last_wp = -1; tty->cx = tty->cy = UINT_MAX; - tty->rupper = tty->rleft = UINT_MAX; tty->rlower = tty->rright = UINT_MAX; if (tty->flags & TTY_STARTED) { if (tty_use_margin(tty)) - tty_puts(tty, "\033[?69h"); /* DECLRMM */ + tty_putcode(tty, TTYC_ENMG); tty_putcode(tty, TTYC_SGR0); tty->mode = ALL_MODES; @@ -2056,8 +2218,8 @@ static void tty_region_pane(struct tty *tty, const struct tty_ctx *ctx, u_int rupper, u_int rlower) { - tty_region(tty, ctx->yoff + rupper - ctx->oy, - ctx->yoff + rlower - ctx->oy); + tty_region(tty, ctx->yoff + rupper - ctx->woy, + ctx->yoff + rlower - ctx->woy); } /* Set region at absolute position. */ @@ -2078,8 +2240,12 @@ tty_region(struct tty *tty, u_int rupper, u_int rlower) * flag so further output causes a line feed). As a workaround, do an * explicit move to 0 first. */ - if (tty->cx >= tty->sx) - tty_cursor(tty, 0, tty->cy); + if (tty->cx >= tty->sx) { + if (tty->cy == UINT_MAX) + tty_cursor(tty, 0, 0); + else + tty_cursor(tty, 0, tty->cy); + } tty_putcode2(tty, TTYC_CSR, tty->rupper, tty->rlower); tty->cx = tty->cy = UINT_MAX; @@ -2096,16 +2262,14 @@ tty_margin_off(struct tty *tty) static void tty_margin_pane(struct tty *tty, const struct tty_ctx *ctx) { - tty_margin(tty, ctx->xoff - ctx->ox, - ctx->xoff + ctx->wp->sx - 1 - ctx->ox); + tty_margin(tty, ctx->xoff - ctx->wox, + ctx->xoff + ctx->sx - 1 - ctx->wox); } /* Set margin at absolute position. */ static void tty_margin(struct tty *tty, u_int rleft, u_int rright) { - char s[64]; - if (!tty_use_margin(tty)) return; if (tty->rleft == rleft && tty->rright == rright) @@ -2117,10 +2281,9 @@ tty_margin(struct tty *tty, u_int rleft, u_int rright) tty->rright = rright; if (rleft == 0 && rright == tty->sx - 1) - snprintf(s, sizeof s, "\033[s"); + tty_putcode(tty, TTYC_CLMG); else - snprintf(s, sizeof s, "\033[%u;%us", rleft + 1, rright + 1); - tty_puts(tty, s); + tty_putcode2(tty, TTYC_CMG, rleft, rright); tty->cx = tty->cy = UINT_MAX; } @@ -2133,8 +2296,8 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, u_int cx, u_int cy) { if (!ctx->wrapped || - !tty_pane_full_width(tty, ctx) || - (tty->term->flags & TERM_NOXENL) || + !tty_full_width(tty, ctx) || + (tty->term->flags & TERM_NOAM) || ctx->xoff + cx != 0 || ctx->yoff + cy != tty->cy + 1 || tty->cx < tty->sx || @@ -2148,7 +2311,7 @@ tty_cursor_pane_unless_wrap(struct tty *tty, const struct tty_ctx *ctx, static void tty_cursor_pane(struct tty *tty, const struct tty_ctx *ctx, u_int cx, u_int cy) { - tty_cursor(tty, ctx->xoff + cx - ctx->ox, ctx->yoff + cy - ctx->oy); + tty_cursor(tty, ctx->xoff + cx - ctx->wox, ctx->yoff + cy - ctx->woy); } /* Move cursor to absolute position. */ @@ -2159,6 +2322,9 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) u_int thisx, thisy; int change; + if (tty->flags & TTY_BLOCK) + return; + if (cx > tty->sx - 1) cx = tty->sx - 1; @@ -2290,27 +2456,26 @@ out: void tty_attributes(struct tty *tty, const struct grid_cell *gc, - struct window_pane *wp) + const struct grid_cell *defaults, struct colour_palette *palette) { struct grid_cell *tc = &tty->cell, gc2; int changed; - /* Ignore cell if it is the same as the last one. */ - if (wp != NULL && - (int)wp->id == tty->last_wp && - ~(wp->flags & PANE_STYLECHANGED) && - gc->attr == tty->last_cell.attr && - gc->fg == tty->last_cell.fg && - gc->bg == tty->last_cell.bg && - gc->us == tty->last_cell.us) - return; - tty->last_wp = (wp != NULL ? (int)wp->id : -1); - memcpy(&tty->last_cell, gc, sizeof tty->last_cell); - /* Copy cell and update default colours. */ memcpy(&gc2, gc, sizeof gc2); - if (wp != NULL) - tty_default_colours(&gc2, wp); + if (~gc->flags & GRID_FLAG_NOPALETTE) { + if (gc2.fg == 8) + gc2.fg = defaults->fg; + if (gc2.bg == 8) + gc2.bg = defaults->bg; + } + + /* Ignore cell if it is the same as the last one. */ + if (gc2.attr == tty->last_cell.attr && + gc2.fg == tty->last_cell.fg && + gc2.bg == tty->last_cell.bg && + gc2.us == tty->last_cell.us) + return; /* * If no setab, try to use the reverse attribute as a best-effort for a @@ -2328,9 +2493,9 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, } /* Fix up the colours if necessary. */ - tty_check_fg(tty, wp, &gc2); - tty_check_bg(tty, wp, &gc2); - tty_check_us(tty, wp, &gc2); + tty_check_fg(tty, palette, &gc2); + tty_check_bg(tty, palette, &gc2); + tty_check_us(tty, palette, &gc2); /* * If any bits are being cleared or the underline colour is now default, @@ -2385,6 +2550,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_putcode(tty, TTYC_SMOL); if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_SMACS); + + memcpy(&tty->last_cell, &gc2, sizeof tty->last_cell); } static void @@ -2443,13 +2610,14 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) if (!COLOUR_DEFAULT(gc->bg) && gc->bg != tc->bg) tty_colours_bg(tty, gc); - /* Set the underscore color. */ + /* Set the underscore colour. */ if (gc->us != tc->us) tty_colours_us(tty, gc); } static void -tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) +tty_check_fg(struct tty *tty, struct colour_palette *palette, + struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -2464,21 +2632,21 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) c = gc->fg; if (c < 8 && gc->attr & GRID_ATTR_BRIGHT) c += 90; - if ((c = window_pane_get_palette(wp, c)) != -1) + if ((c = colour_palette_get(palette, c)) != -1) gc->fg = c; } /* Is this a 24-bit colour? */ if (gc->fg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) + if (tty->term->flags & TERM_RGBCOLOURS) return; colour_split_rgb(gc->fg, &r, &g, &b); gc->fg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ - if ((tty->term->flags|tty->term_flags) & TERM_256COLOURS) + if (tty->term->flags & TERM_256COLOURS) colours = 256; else colours = tty_term_number(tty->term, TTYC_COLORS); @@ -2486,7 +2654,7 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Is this a 256-colour colour? */ if (gc->fg & COLOUR_FLAG_256) { /* And not a 256 colour mode? */ - if (colours != 256) { + if (colours < 256) { gc->fg = colour_256to16(gc->fg); if (gc->fg & 8) { gc->fg &= 7; @@ -2505,7 +2673,8 @@ tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) } static void -tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) +tty_check_bg(struct tty *tty, struct colour_palette *palette, + struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -2513,21 +2682,21 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) /* Perform substitution if this pane has a palette. */ if (~gc->flags & GRID_FLAG_NOPALETTE) { - if ((c = window_pane_get_palette(wp, gc->bg)) != -1) + if ((c = colour_palette_get(palette, gc->bg)) != -1) gc->bg = c; } /* Is this a 24-bit colour? */ if (gc->bg & COLOUR_FLAG_RGB) { /* Not a 24-bit terminal? Translate to 256-colour palette. */ - if ((tty->term->flags|tty->term_flags) & TERM_RGBCOLOURS) + if (tty->term->flags & TERM_RGBCOLOURS) return; colour_split_rgb(gc->bg, &r, &g, &b); gc->bg = colour_find_rgb(r, g, b); } /* How many colours does this terminal have? */ - if ((tty->term->flags|tty->term_flags) & TERM_256COLOURS) + if (tty->term->flags & TERM_256COLOURS) colours = 256; else colours = tty_term_number(tty->term, TTYC_COLORS); @@ -2539,7 +2708,7 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) * palette. Bold background doesn't exist portably, so just * discard the bold bit if set. */ - if (colours != 256) { + if (colours < 256) { gc->bg = colour_256to16(gc->bg); if (gc->bg & 8) { gc->bg &= 7; @@ -2556,14 +2725,14 @@ tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) } static void -tty_check_us(__unused struct tty *tty, struct window_pane *wp, +tty_check_us(__unused struct tty *tty, struct colour_palette *palette, struct grid_cell *gc) { int c; /* Perform substitution if this pane has a palette. */ if (~gc->flags & GRID_FLAG_NOPALETTE) { - if ((c = window_pane_get_palette(wp, gc->us)) != -1) + if ((c = colour_palette_get(palette, gc->us)) != -1) gc->us = c; } @@ -2581,25 +2750,25 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc) /* Is this a 24-bit or 256-colour colour? */ if (gc->fg & COLOUR_FLAG_RGB || gc->fg & COLOUR_FLAG_256) { if (tty_try_colour(tty, gc->fg, "38") == 0) - goto save_fg; + goto save; /* Should not get here, already converted in tty_check_fg. */ return; } /* Is this an aixterm bright colour? */ if (gc->fg >= 90 && gc->fg <= 97) { - if (tty->term_flags & TERM_256COLOURS) { + if (tty->term->flags & TERM_256COLOURS) { xsnprintf(s, sizeof s, "\033[%dm", gc->fg); tty_puts(tty, s); } else tty_putcode1(tty, TTYC_SETAF, gc->fg - 90 + 8); - goto save_fg; + goto save; } /* Otherwise set the foreground colour. */ tty_putcode1(tty, TTYC_SETAF, gc->fg); -save_fg: +save: /* Save the new values in the terminal current cell. */ tc->fg = gc->fg; } @@ -2613,25 +2782,25 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc) /* Is this a 24-bit or 256-colour colour? */ if (gc->bg & COLOUR_FLAG_RGB || gc->bg & COLOUR_FLAG_256) { if (tty_try_colour(tty, gc->bg, "48") == 0) - goto save_bg; + goto save; /* Should not get here, already converted in tty_check_bg. */ return; } /* Is this an aixterm bright colour? */ if (gc->bg >= 90 && gc->bg <= 97) { - if (tty->term_flags & TERM_256COLOURS) { + if (tty->term->flags & TERM_256COLOURS) { xsnprintf(s, sizeof s, "\033[%dm", gc->bg + 10); tty_puts(tty, s); } else tty_putcode1(tty, TTYC_SETAB, gc->bg - 90 + 8); - goto save_bg; + goto save; } /* Otherwise set the background colour. */ tty_putcode1(tty, TTYC_SETAB, gc->bg); -save_bg: +save: /* Save the new values in the terminal current cell. */ tc->bg = gc->bg; } @@ -2643,20 +2812,34 @@ tty_colours_us(struct tty *tty, const struct grid_cell *gc) u_int c; u_char r, g, b; + /* Clear underline colour. */ + if (gc->us == 0) { + tty_putcode(tty, TTYC_OL); + goto save; + } + /* Must be an RGB colour - this should never happen. */ if (~gc->us & COLOUR_FLAG_RGB) return; /* - * Setulc follows the ncurses(3) one argument "direct colour" + * Setulc and setal follows the ncurses(3) one argument "direct colour" * capability format. Calculate the colour value. */ colour_split_rgb(gc->us, &r, &g, &b); c = (65536 * r) + (256 * g) + b; - /* Write the colour. */ - tty_putcode1(tty, TTYC_SETULC, c); + /* + * Write the colour. Only use setal if the RGB flag is set because the + * non-RGB version may be wrong. + */ + if (tty_term_has(tty->term, TTYC_SETULC)) + tty_putcode1(tty, TTYC_SETULC, c); + else if (tty_term_has(tty->term, TTYC_SETAL) && + tty_term_has(tty->term, TTYC_RGB)) + tty_putcode1(tty, TTYC_SETAL, c); +save: /* Save the new values in the terminal current cell. */ tc->us = gc->us; } @@ -2665,122 +2848,79 @@ static int tty_try_colour(struct tty *tty, int colour, const char *type) { u_char r, g, b; - char s[32]; if (colour & COLOUR_FLAG_256) { - /* - * If the user has specified -2 to the client (meaning - * TERM_256COLOURS is set), setaf and setab may not work (or - * they may not want to use them), so send the usual sequence. - * - * Also if RGB is set, setaf and setab do not support the 256 - * colour palette so use the sequences directly there too. - */ - if ((tty->term_flags & TERM_256COLOURS) || - tty_term_has(tty->term, TTYC_RGB)) - goto fallback_256; - - /* - * If the terminfo entry has 256 colours and setaf and setab - * exist, assume that they work correctly. - */ - if (tty->term->flags & TERM_256COLOURS) { - if (*type == '3') { - if (!tty_term_has(tty->term, TTYC_SETAF)) - goto fallback_256; - tty_putcode1(tty, TTYC_SETAF, colour & 0xff); - } else { - if (!tty_term_has(tty->term, TTYC_SETAB)) - goto fallback_256; - tty_putcode1(tty, TTYC_SETAB, colour & 0xff); - } - return (0); - } - goto fallback_256; + if (*type == '3' && tty_term_has(tty->term, TTYC_SETAF)) + tty_putcode1(tty, TTYC_SETAF, colour & 0xff); + else if (tty_term_has(tty->term, TTYC_SETAB)) + tty_putcode1(tty, TTYC_SETAB, colour & 0xff); + return (0); } if (colour & COLOUR_FLAG_RGB) { colour_split_rgb(colour & 0xffffff, &r, &g, &b); - if (*type == '3') { - if (!tty_term_has(tty->term, TTYC_SETRGBF)) - goto fallback_rgb; + if (*type == '3' && tty_term_has(tty->term, TTYC_SETRGBF)) tty_putcode3(tty, TTYC_SETRGBF, r, g, b); - } else { - if (!tty_term_has(tty->term, TTYC_SETRGBB)) - goto fallback_rgb; + else if (tty_term_has(tty->term, TTYC_SETRGBB)) tty_putcode3(tty, TTYC_SETRGBB, r, g, b); - } return (0); } return (-1); - -fallback_256: - xsnprintf(s, sizeof s, "\033[%s;5;%dm", type, colour & 0xff); - log_debug("%s: 256 colour fallback: %s", tty->client->name, s); - tty_puts(tty, s); - return (0); - -fallback_rgb: - xsnprintf(s, sizeof s, "\033[%s;2;%d;%d;%dm", type, r, g, b); - log_debug("%s: RGB colour fallback: %s", tty->client->name, s); - tty_puts(tty, s); - return (0); } static void +tty_window_default_style(struct grid_cell *gc, struct window_pane *wp) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->fg = wp->palette.fg; + gc->bg = wp->palette.bg; +} + +void tty_default_colours(struct grid_cell *gc, struct window_pane *wp) { - struct options *oo = wp->options; - struct style *style, *active_style; - int c; + struct options *oo = wp->options; + struct format_tree *ft; + + memcpy(gc, &grid_default_cell, sizeof *gc); if (wp->flags & PANE_STYLECHANGED) { + log_debug("%%%u: style changed", wp->id); wp->flags &= ~PANE_STYLECHANGED; - active_style = options_get_style(oo, "window-active-style"); - style = options_get_style(oo, "window-style"); - - style_copy(&wp->cached_active_style, active_style); - style_copy(&wp->cached_style, style); - } else { - active_style = &wp->cached_active_style; - style = &wp->cached_style; + ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, + FORMAT_NOJOBS); + format_defaults(ft, NULL, NULL, NULL, wp); + tty_window_default_style(&wp->cached_active_gc, wp); + style_add(&wp->cached_active_gc, oo, "window-active-style", ft); + tty_window_default_style(&wp->cached_gc, wp); + style_add(&wp->cached_gc, oo, "window-style", ft); + format_free(ft); } if (gc->fg == 8) { - if (wp == wp->window->active && active_style->gc.fg != 8) - gc->fg = active_style->gc.fg; + if (wp == wp->window->active && wp->cached_active_gc.fg != 8) + gc->fg = wp->cached_active_gc.fg; else - gc->fg = style->gc.fg; - - if (gc->fg != 8) { - c = window_pane_get_palette(wp, gc->fg); - if (c != -1) - gc->fg = c; - } + gc->fg = wp->cached_gc.fg; } if (gc->bg == 8) { - if (wp == wp->window->active && active_style->gc.bg != 8) - gc->bg = active_style->gc.bg; + if (wp == wp->window->active && wp->cached_active_gc.bg != 8) + gc->bg = wp->cached_active_gc.bg; else - gc->bg = style->gc.bg; - - if (gc->bg != 8) { - c = window_pane_get_palette(wp, gc->bg); - if (c != -1) - gc->bg = c; - } + gc->bg = wp->cached_gc.bg; } } static void -tty_default_attributes(struct tty *tty, struct window_pane *wp, u_int bg) +tty_default_attributes(struct tty *tty, const struct grid_cell *defaults, + struct colour_palette *palette, u_int bg) { - static struct grid_cell gc; + struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; - tty_attributes(tty, &gc, wp); + tty_attributes(tty, &gc, defaults, palette); } diff --git a/utf8.c b/utf8.c index b4e448f7..56f20cbb 100644 --- a/utf8.c +++ b/utf8.c @@ -26,7 +26,167 @@ #include "tmux.h" -static int utf8_width(wchar_t); +struct utf8_item { + RB_ENTRY(utf8_item) index_entry; + u_int index; + + RB_ENTRY(utf8_item) data_entry; + char data[UTF8_SIZE]; + u_char size; +}; + +static int +utf8_data_cmp(struct utf8_item *ui1, struct utf8_item *ui2) +{ + if (ui1->size < ui2->size) + return (-1); + if (ui1->size > ui2->size) + return (1); + return (memcmp(ui1->data, ui2->data, ui1->size)); +} +RB_HEAD(utf8_data_tree, utf8_item); +RB_GENERATE_STATIC(utf8_data_tree, utf8_item, data_entry, utf8_data_cmp); +static struct utf8_data_tree utf8_data_tree = RB_INITIALIZER(utf8_data_tree); + +static int +utf8_index_cmp(struct utf8_item *ui1, struct utf8_item *ui2) +{ + if (ui1->index < ui2->index) + return (-1); + if (ui1->index > ui2->index) + return (1); + return (0); +} +RB_HEAD(utf8_index_tree, utf8_item); +RB_GENERATE_STATIC(utf8_index_tree, utf8_item, index_entry, utf8_index_cmp); +static struct utf8_index_tree utf8_index_tree = RB_INITIALIZER(utf8_index_tree); + +static u_int utf8_next_index; + +#define UTF8_GET_SIZE(uc) (((uc) >> 24) & 0x1f) +#define UTF8_GET_WIDTH(uc) (((uc) >> 29) - 1) + +#define UTF8_SET_SIZE(size) (((utf8_char)(size)) << 24) +#define UTF8_SET_WIDTH(width) ((((utf8_char)(width)) + 1) << 29) + +/* Get a UTF-8 item from data. */ +static struct utf8_item * +utf8_item_by_data(const char *data, size_t size) +{ + struct utf8_item ui; + + memcpy(ui.data, data, size); + ui.size = size; + + return (RB_FIND(utf8_data_tree, &utf8_data_tree, &ui)); +} + +/* Get a UTF-8 item from data. */ +static struct utf8_item * +utf8_item_by_index(u_int index) +{ + struct utf8_item ui; + + ui.index = index; + + return (RB_FIND(utf8_index_tree, &utf8_index_tree, &ui)); +} + +/* Add a UTF-8 item. */ +static int +utf8_put_item(const char *data, size_t size, u_int *index) +{ + struct utf8_item *ui; + + ui = utf8_item_by_data(data, size); + if (ui != NULL) { + *index = ui->index; + log_debug("%s: found %.*s = %u", __func__, (int)size, data, + *index); + return (0); + } + + if (utf8_next_index == 0xffffff + 1) + return (-1); + + ui = xcalloc(1, sizeof *ui); + ui->index = utf8_next_index++; + RB_INSERT(utf8_index_tree, &utf8_index_tree, ui); + + memcpy(ui->data, data, size); + ui->size = size; + RB_INSERT(utf8_data_tree, &utf8_data_tree, ui); + + *index = ui->index; + log_debug("%s: added %.*s = %u", __func__, (int)size, data, *index); + return (0); +} + +/* Get UTF-8 character from data. */ +enum utf8_state +utf8_from_data(const struct utf8_data *ud, utf8_char *uc) +{ + u_int index; + + if (ud->width > 2) + fatalx("invalid UTF-8 width: %u", ud->width); + + if (ud->size > UTF8_SIZE) + goto fail; + if (ud->size <= 3) { + index = (((utf8_char)ud->data[2] << 16)| + ((utf8_char)ud->data[1] << 8)| + ((utf8_char)ud->data[0])); + } else if (utf8_put_item(ud->data, ud->size, &index) != 0) + goto fail; + *uc = UTF8_SET_SIZE(ud->size)|UTF8_SET_WIDTH(ud->width)|index; + log_debug("%s: (%d %d %.*s) -> %08x", __func__, ud->width, ud->size, + (int)ud->size, ud->data, *uc); + return (UTF8_DONE); + +fail: + if (ud->width == 0) + *uc = UTF8_SET_SIZE(0)|UTF8_SET_WIDTH(0); + else if (ud->width == 1) + *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x20; + else + *uc = UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|0x2020; + return (UTF8_ERROR); +} + +/* Get UTF-8 data from character. */ +void +utf8_to_data(utf8_char uc, struct utf8_data *ud) +{ + struct utf8_item *ui; + u_int index; + + memset(ud, 0, sizeof *ud); + ud->size = ud->have = UTF8_GET_SIZE(uc); + ud->width = UTF8_GET_WIDTH(uc); + + if (ud->size <= 3) { + ud->data[2] = (uc >> 16); + ud->data[1] = ((uc >> 8) & 0xff); + ud->data[0] = (uc & 0xff); + } else { + index = (uc & 0xffffff); + if ((ui = utf8_item_by_index(index)) == NULL) + memset(ud->data, ' ', ud->size); + else + memcpy(ud->data, ui->data, ud->size); + } + + log_debug("%s: %08x -> (%d %d %.*s)", __func__, uc, ud->width, ud->size, + (int)ud->size, ud->data); +} + +/* Get UTF-8 character from a single ASCII character. */ +u_int +utf8_build_one(u_char ch) +{ + return (UTF8_SET_SIZE(1)|UTF8_SET_WIDTH(1)|ch); +} /* Set a single character. */ void @@ -50,6 +210,51 @@ utf8_copy(struct utf8_data *to, const struct utf8_data *from) to->data[i] = '\0'; } +/* Get width of Unicode character. */ +static enum utf8_state +utf8_width(struct utf8_data *ud, int *width) +{ + wchar_t wc; + +#ifdef HAVE_UTF8PROC + switch (utf8proc_mbtowc(&wc, ud->data, ud->size)) { +#else + switch (mbtowc(&wc, ud->data, ud->size)) { +#endif + case -1: + log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data, + errno); + mbtowc(NULL, NULL, MB_CUR_MAX); + return (UTF8_ERROR); + case 0: + return (UTF8_ERROR); + } +#ifdef HAVE_UTF8PROC + *width = utf8proc_wcwidth(wc); +#else + *width = wcwidth(wc); +#endif + if (*width >= 0 && *width <= 0xff) + return (UTF8_DONE); + log_debug("UTF-8 %.*s, wcwidth() %d", (int)ud->size, ud->data, *width); + +#ifndef __OpenBSD__ + /* + * Many platforms (particularly and inevitably OS X) have no width for + * relatively common characters (wcwidth() returns -1); assume width 1 + * in this case. This will be wrong for genuinely nonprintable + * characters, but they should be rare. We may pass through stuff that + * ideally we would block, but this is no worse than sending the same + * to the terminal without tmux. + */ + if (*width < 0) { + *width = 1; + return (UTF8_DONE); + } +#endif + return (UTF8_ERROR); +} + /* * Open UTF-8 sequence. * @@ -77,7 +282,6 @@ utf8_open(struct utf8_data *ud, u_char ch) enum utf8_state utf8_append(struct utf8_data *ud, u_char ch) { - wchar_t wc; int width; if (ud->have >= ud->size) @@ -94,91 +298,13 @@ utf8_append(struct utf8_data *ud, u_char ch) if (ud->width == 0xff) return (UTF8_ERROR); - - if (utf8_combine(ud, &wc) != UTF8_DONE) - return (UTF8_ERROR); - if ((width = utf8_width(wc)) < 0) + if (utf8_width(ud, &width) != UTF8_DONE) return (UTF8_ERROR); ud->width = width; return (UTF8_DONE); } -/* Get width of Unicode character. */ -static int -utf8_width(wchar_t wc) -{ - int width; - -#ifdef HAVE_UTF8PROC - width = utf8proc_wcwidth(wc); -#else - width = wcwidth(wc); -#endif - if (width < 0 || width > 0xff) { - log_debug("Unicode %04lx, wcwidth() %d", (long)wc, width); - -#ifndef __OpenBSD__ - /* - * Many platforms (particularly and inevitably OS X) have no - * width for relatively common characters (wcwidth() returns - * -1); assume width 1 in this case. This will be wrong for - * genuinely nonprintable characters, but they should be - * rare. We may pass through stuff that ideally we would block, - * but this is no worse than sending the same to the terminal - * without tmux. - */ - if (width < 0) - return (1); -#endif - return (-1); - } - return (width); -} - -/* Combine UTF-8 into Unicode. */ -enum utf8_state -utf8_combine(const struct utf8_data *ud, wchar_t *wc) -{ -#ifdef HAVE_UTF8PROC - switch (utf8proc_mbtowc(wc, ud->data, ud->size)) { -#else - switch (mbtowc(wc, ud->data, ud->size)) { -#endif - case -1: - log_debug("UTF-8 %.*s, mbtowc() %d", (int)ud->size, ud->data, - errno); - mbtowc(NULL, NULL, MB_CUR_MAX); - return (UTF8_ERROR); - case 0: - return (UTF8_ERROR); - default: - return (UTF8_DONE); - } -} - -/* Split Unicode into UTF-8. */ -enum utf8_state -utf8_split(wchar_t wc, struct utf8_data *ud) -{ - char s[MB_LEN_MAX]; - int slen; - -#ifdef HAVE_UTF8PROC - slen = utf8proc_wctomb(s, wc); -#else - slen = wctomb(s, wc); -#endif - if (slen <= 0 || slen > (int)sizeof ud->data) - return (UTF8_ERROR); - - memcpy(ud->data, s, slen); - ud->size = slen; - - ud->width = utf8_width(wc); - return (UTF8_DONE); -} - /* * Encode len characters from src into dst, which is guaranteed to have four * bytes available for each character from src (for \abc or UTF-8) plus space @@ -188,13 +314,10 @@ int utf8_strvis(char *dst, const char *src, size_t len, int flag) { struct utf8_data ud; - const char *start, *end; + const char *start = dst, *end = src + len; enum utf8_state more; size_t i; - start = dst; - end = src + len; - while (src < end) { if ((more = utf8_open(&ud, *src)) == UTF8_MORE) { while (++src < end && more == UTF8_MORE) @@ -220,7 +343,6 @@ utf8_strvis(char *dst, const char *src, size_t len, int flag) dst = vis(dst, src[0], flag, '\0'); src++; } - *dst = '\0'; return (dst - start); } @@ -239,13 +361,27 @@ utf8_stravis(char **dst, const char *src, int flag) return (len); } +/* Same as utf8_strvis but allocate the buffer. */ +int +utf8_stravisx(char **dst, const char *src, size_t srclen, int flag) +{ + char *buf; + int len; + + buf = xreallocarray(NULL, 4, srclen + 1); + len = utf8_strvis(buf, src, srclen, flag); + + *dst = xrealloc(buf, len + 1); + return (len); +} + /* Does this string contain anything that isn't valid UTF-8? */ int utf8_isvalid(const char *s) { - struct utf8_data ud; - const char *end; - enum utf8_state more; + struct utf8_data ud; + const char *end; + enum utf8_state more; end = s + strlen(s); while (s < end) { @@ -271,15 +407,12 @@ utf8_isvalid(const char *s) char * utf8_sanitize(const char *src) { - char *dst; - size_t n; - enum utf8_state more; - struct utf8_data ud; - u_int i; + char *dst = NULL; + size_t n = 0; + enum utf8_state more; + struct utf8_data ud; + u_int i; - dst = NULL; - - n = 0; while (*src != '\0') { dst = xreallocarray(dst, n + 1, sizeof *dst); if ((more = utf8_open(&ud, *src)) == UTF8_MORE) { @@ -300,7 +433,6 @@ utf8_sanitize(const char *src) dst[n++] = '_'; src++; } - dst = xreallocarray(dst, n + 1, sizeof *dst); dst[n] = '\0'; return (dst); @@ -322,9 +454,8 @@ u_int utf8_strwidth(const struct utf8_data *s, ssize_t n) { ssize_t i; - u_int width; + u_int width = 0; - width = 0; for (i = 0; s[i].size != 0; i++) { if (n != -1 && n == i) break; @@ -340,13 +471,10 @@ utf8_strwidth(const struct utf8_data *s, ssize_t n) struct utf8_data * utf8_fromcstr(const char *src) { - struct utf8_data *dst; - size_t n; + struct utf8_data *dst = NULL; + size_t n = 0; enum utf8_state more; - dst = NULL; - - n = 0; while (*src != '\0') { dst = xreallocarray(dst, n + 1, sizeof *dst); if ((more = utf8_open(&dst[n], *src)) == UTF8_MORE) { @@ -362,7 +490,6 @@ utf8_fromcstr(const char *src) n++; src++; } - dst = xreallocarray(dst, n + 1, sizeof *dst); dst[n].size = 0; return (dst); @@ -372,18 +499,14 @@ utf8_fromcstr(const char *src) char * utf8_tocstr(struct utf8_data *src) { - char *dst; - size_t n; + char *dst = NULL; + size_t n = 0; - dst = NULL; - - n = 0; for(; src->size != 0; src++) { dst = xreallocarray(dst, n + src->size, 1); memcpy(dst + n, src->data, src->size); n += src->size; } - dst = xreallocarray(dst, n + 1, 1); dst[n] = '\0'; return (dst); @@ -421,7 +544,7 @@ utf8_padcstr(const char *s, u_int width) { size_t slen; char *out; - u_int n, i; + u_int n, i; n = utf8_cstrwidth(s); if (n >= width) @@ -442,7 +565,7 @@ utf8_rpadcstr(const char *s, u_int width) { size_t slen; char *out; - u_int n, i; + u_int n, i; n = utf8_cstrwidth(s); if (n >= width) diff --git a/window-buffer.c b/window-buffer.c index a1939b0f..07851e75 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -18,9 +18,11 @@ #include +#include #include #include #include +#include #include "tmux.h" @@ -29,6 +31,7 @@ static struct screen *window_buffer_init(struct window_mode_entry *, static void window_buffer_free(struct window_mode_entry *); static void window_buffer_resize(struct window_mode_entry *, u_int, u_int); +static void window_buffer_update(struct window_mode_entry *); static void window_buffer_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -36,7 +39,18 @@ static void window_buffer_key(struct window_mode_entry *, #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" #define WINDOW_BUFFER_DEFAULT_FORMAT \ - "#{buffer_size} bytes (#{t:buffer_created})" + "#{t/p:buffer_created}: #{buffer_sample}" + +#define WINDOW_BUFFER_DEFAULT_KEY_FORMAT \ + "#{?#{e|<:#{line},10}," \ + "#{line}" \ + "," \ + "#{?#{e|<:#{line},36}," \ + "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \ + "," \ + "" \ + "}" \ + "}" static const struct menu_item window_buffer_menu_items[] = { { "Paste", 'p', NULL }, @@ -61,6 +75,7 @@ const struct window_mode window_buffer_mode = { .init = window_buffer_init, .free = window_buffer_free, .resize = window_buffer_resize, + .update = window_buffer_update, .key = window_buffer_key, }; @@ -89,11 +104,18 @@ struct window_buffer_modedata { struct mode_tree_data *data; char *command; char *format; + char *key_format; struct window_buffer_itemdata **item_list; u_int item_size; }; +struct window_buffer_editdata { + u_int wp_id; + char *name; + struct paste_buffer *pb; +}; + static struct window_buffer_itemdata * window_buffer_add_item(struct window_buffer_modedata *data) { @@ -222,7 +244,8 @@ window_buffer_draw(__unused void *modedata, void *itemdata, while (end != pdata + psize && *end != '\n') end++; buf = xreallocarray(buf, 4, end - start + 1); - utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_TAB); + utf8_strvis(buf, start, end - start, + VIS_OCTAL|VIS_CSTYLE|VIS_TAB); if (*buf != '\0') { screen_write_cursormove(ctx, cx, cy + i, 0); screen_write_nputs(ctx, sx, &grid_default_cell, "%s", @@ -265,6 +288,41 @@ window_buffer_menu(void *modedata, struct client *c, key_code key) window_buffer_key(wme, c, NULL, NULL, key, NULL); } +static key_code +window_buffer_get_key(void *modedata, void *itemdata, u_int line) +{ + struct window_buffer_modedata *data = modedata; + struct window_buffer_itemdata *item = itemdata; + struct format_tree *ft; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; + struct paste_buffer *pb; + char *expanded; + key_code key; + + if (cmd_find_valid_state(&data->fs)) { + s = data->fs.s; + wl = data->fs.wl; + wp = data->fs.wp; + } + pb = paste_get_name(item->name); + if (pb == NULL) + return KEYC_NONE; + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, NULL, 0, NULL); + format_defaults(ft, NULL, s, wl, wp); + format_defaults_paste_buffer(ft, pb); + format_add(ft, "line", "%u", line); + + expanded = format_expand(ft, data->key_format); + key = key_string_lookup_string(expanded); + free(expanded); + format_free(ft); + return key; +} + static struct screen * window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, struct args *args) @@ -281,15 +339,19 @@ window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT); else data->format = xstrdup(args_get(args, 'F')); - if (args == NULL || args->argc == 0) + if (args == NULL || !args_has(args, 'K')) + data->key_format = xstrdup(WINDOW_BUFFER_DEFAULT_KEY_FORMAT); + else + data->key_format = xstrdup(args_get(args, 'K')); + if (args == NULL || args_count(args) == 0) data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND); else - data->command = xstrdup(args->argv[0]); + data->command = xstrdup(args_string(args, 0)); data->data = mode_tree_start(wp, args, window_buffer_build, - window_buffer_draw, window_buffer_search, window_buffer_menu, data, - window_buffer_menu_items, window_buffer_sort_list, - nitems(window_buffer_sort_list), &s); + window_buffer_draw, window_buffer_search, window_buffer_menu, NULL, + window_buffer_get_key, data, window_buffer_menu_items, + window_buffer_sort_list, nitems(window_buffer_sort_list), &s); mode_tree_zoom(data->data, args); mode_tree_build(data->data); @@ -314,6 +376,7 @@ window_buffer_free(struct window_mode_entry *wme) free(data->item_list); free(data->format); + free(data->key_format); free(data->command); free(data); @@ -327,6 +390,16 @@ window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_buffer_update(struct window_mode_entry *wme) +{ + struct window_buffer_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static void window_buffer_do_delete(void *modedata, void *itemdata, __unused struct client *c, __unused key_code key) @@ -347,12 +420,83 @@ window_buffer_do_paste(void *modedata, void *itemdata, struct client *c, { struct window_buffer_modedata *data = modedata; struct window_buffer_itemdata *item = itemdata; - struct paste_buffer *pb; - if ((pb = paste_get_name(item->name)) != NULL) + if (paste_get_name(item->name) != NULL) mode_tree_run_command(c, NULL, data->command, item->name); } +static void +window_buffer_finish_edit(struct window_buffer_editdata *ed) +{ + free(ed->name); + free(ed); +} + +static void +window_buffer_edit_close_cb(char *buf, size_t len, void *arg) +{ + struct window_buffer_editdata *ed = arg; + size_t oldlen; + const char *oldbuf; + struct paste_buffer *pb; + struct window_pane *wp; + struct window_buffer_modedata *data; + struct window_mode_entry *wme; + + if (buf == NULL || len == 0) { + window_buffer_finish_edit(ed); + return; + } + + pb = paste_get_name(ed->name); + if (pb == NULL || pb != ed->pb) { + window_buffer_finish_edit(ed); + return; + } + + oldbuf = paste_buffer_data(pb, &oldlen); + if (oldlen != '\0' && + oldbuf[oldlen - 1] != '\n' && + buf[len - 1] == '\n') + len--; + if (len != 0) + paste_replace(pb, buf, len); + + wp = window_pane_find_by_id(ed->wp_id); + if (wp != NULL) { + wme = TAILQ_FIRST(&wp->modes); + if (wme->mode == &window_buffer_mode) { + data = wme->data; + mode_tree_build(data->data); + mode_tree_draw(data->data); + } + wp->flags |= PANE_REDRAW; + } + window_buffer_finish_edit(ed); +} + +static void +window_buffer_start_edit(struct window_buffer_modedata *data, + struct window_buffer_itemdata *item, struct client *c) +{ + struct paste_buffer *pb; + const char *buf; + size_t len; + struct window_buffer_editdata *ed; + + if ((pb = paste_get_name(item->name)) == NULL) + return; + buf = paste_buffer_data(pb, &len); + + ed = xcalloc(1, sizeof *ed); + ed->wp_id = data->wp->id; + ed->name = xstrdup(paste_buffer_name(pb)); + ed->pb = pb; + + if (popup_editor(c, buf, len, window_buffer_edit_close_cb, ed) != 0) + window_buffer_finish_edit(ed); +} + static void window_buffer_key(struct window_mode_entry *wme, struct client *c, __unused struct session *s, __unused struct winlink *wl, key_code key, @@ -366,6 +510,10 @@ window_buffer_key(struct window_mode_entry *wme, struct client *c, finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { + case 'e': + item = mode_tree_get_current(mtd); + window_buffer_start_edit(data, item, c); + break; case 'd': item = mode_tree_get_current(mtd); window_buffer_do_delete(data, item, c, key); diff --git a/window-client.c b/window-client.c index 4688cbf3..00f36c7c 100644 --- a/window-client.c +++ b/window-client.c @@ -30,6 +30,7 @@ static struct screen *window_client_init(struct window_mode_entry *, static void window_client_free(struct window_mode_entry *); static void window_client_resize(struct window_mode_entry *, u_int, u_int); +static void window_client_update(struct window_mode_entry *); static void window_client_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -37,8 +38,18 @@ static void window_client_key(struct window_mode_entry *, #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'" #define WINDOW_CLIENT_DEFAULT_FORMAT \ - "session #{session_name} " \ - "(#{client_width}x#{client_height}, #{t:client_activity})" + "#{t/p:client_activity}: session #{session_name}" + +#define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \ + "#{?#{e|<:#{line},10}," \ + "#{line}" \ + "," \ + "#{?#{e|<:#{line},36}," \ + "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \ + "," \ + "" \ + "}" \ + "}" static const struct menu_item window_client_menu_items[] = { { "Detach", 'd', NULL }, @@ -60,6 +71,7 @@ const struct window_mode window_client_mode = { .init = window_client_init, .free = window_client_free, .resize = window_client_resize, + .update = window_client_update, .key = window_client_key, }; @@ -86,6 +98,7 @@ struct window_client_modedata { struct mode_tree_data *data; char *format; + char *key_format; char *command; struct window_client_itemdata **item_list; @@ -167,7 +180,7 @@ window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, data->item_size = 0; TAILQ_FOREACH(c, &clients, entry) { - if (c->session == NULL || (c->flags & (CLIENT_DETACHING))) + if (c->session == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) continue; item = window_client_add_item(data); @@ -251,6 +264,26 @@ window_client_menu(void *modedata, struct client *c, key_code key) window_client_key(wme, c, NULL, NULL, key, NULL); } +static key_code +window_client_get_key(void *modedata, void *itemdata, u_int line) +{ + struct window_client_modedata *data = modedata; + struct window_client_itemdata *item = itemdata; + struct format_tree *ft; + char *expanded; + key_code key; + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, item->c, NULL, 0, NULL); + format_add(ft, "line", "%u", line); + + expanded = format_expand(ft, data->key_format); + key = key_string_lookup_string(expanded); + free(expanded); + format_free(ft); + return key; +} + static struct screen * window_client_init(struct window_mode_entry *wme, __unused struct cmd_find_state *fs, struct args *args) @@ -266,15 +299,19 @@ window_client_init(struct window_mode_entry *wme, data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT); else data->format = xstrdup(args_get(args, 'F')); - if (args == NULL || args->argc == 0) + if (args == NULL || !args_has(args, 'K')) + data->key_format = xstrdup(WINDOW_CLIENT_DEFAULT_KEY_FORMAT); + else + data->key_format = xstrdup(args_get(args, 'K')); + if (args == NULL || args_count(args) == 0) data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND); else - data->command = xstrdup(args->argv[0]); + data->command = xstrdup(args_string(args, 0)); data->data = mode_tree_start(wp, args, window_client_build, - window_client_draw, NULL, window_client_menu, data, - window_client_menu_items, window_client_sort_list, - nitems(window_client_sort_list), &s); + window_client_draw, NULL, window_client_menu, NULL, + window_client_get_key, data, window_client_menu_items, + window_client_sort_list, nitems(window_client_sort_list), &s); mode_tree_zoom(data->data, args); mode_tree_build(data->data); @@ -299,6 +336,7 @@ window_client_free(struct window_mode_entry *wme) free(data->item_list); free(data->format); + free(data->key_format); free(data->command); free(data); @@ -312,6 +350,16 @@ window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_client_update(struct window_mode_entry *wme) +{ + struct window_client_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static void window_client_do_detach(void *modedata, void *itemdata, __unused struct client *c, key_code key) diff --git a/window-clock.c b/window-clock.c index 45d4d47b..8cef3f9a 100644 --- a/window-clock.c +++ b/window-clock.c @@ -219,7 +219,7 @@ window_clock_draw_screen(struct window_mode_entry *wme) colour = options_get_number(wp->window->options, "clock-mode-colour"); style = options_get_number(wp->window->options, "clock-mode-style"); - screen_write_start(&ctx, NULL, s); + screen_write_start(&ctx, s); t = time(NULL); tm = localtime(&t); diff --git a/window-copy.c b/window-copy.c index 301fc16e..c1a31b48 100644 --- a/window-copy.c +++ b/window-copy.c @@ -22,9 +22,12 @@ #include #include #include +#include #include "tmux.h" +struct window_copy_mode_data; + static const char *window_copy_key_table(struct window_mode_entry *); static void window_copy_command(struct window_mode_entry *, struct client *, struct session *, struct winlink *, struct args *, @@ -41,7 +44,6 @@ static void window_copy_pageup1(struct window_mode_entry *, int); static int window_copy_pagedown(struct window_mode_entry *, int, int); 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); @@ -50,31 +52,30 @@ static void window_copy_write_line(struct window_mode_entry *, 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 void window_copy_scroll_to(struct window_mode_entry *, u_int, u_int); +static char *window_copy_match_at_cursor(struct window_copy_mode_data *); +static void window_copy_scroll_to(struct window_mode_entry *, u_int, u_int, + int); static int window_copy_search_compare(struct grid *, u_int, u_int, struct grid *, u_int, int); static int window_copy_search_lr(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); static int window_copy_search_rl(struct grid *, struct grid *, u_int *, u_int, u_int, u_int, int); -static int window_copy_search_lr_regex(struct grid *, struct grid *, - u_int *, u_int *, u_int, u_int, u_int, int); -static int window_copy_search_rl_regex(struct grid *, struct grid *, - u_int *, u_int *, u_int, u_int, u_int, int); -static int window_copy_last_regex(struct grid *gd, u_int py, u_int first, - u_int last, u_int len, u_int *ppx, u_int *psx, - const char *buf, const regex_t *preg, int eflags); +static int window_copy_last_regex(struct grid *, u_int, u_int, u_int, + u_int, u_int *, u_int *, const char *, const regex_t *, + int); +static int window_copy_search_mark_at(struct window_copy_mode_data *, + u_int, u_int, u_int *); static char *window_copy_stringify(struct grid *, u_int, u_int, u_int, char *, u_int *); -static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, u_int *, - const char *str); +static void window_copy_cstrtocellpos(struct grid *, u_int, u_int *, + u_int *, const char *); static int window_copy_search_marks(struct window_mode_entry *, - struct screen *, int); + struct screen *, int, int); static void window_copy_clear_marks(struct window_mode_entry *); -static void window_copy_move_left(struct screen *, u_int *, u_int *, int); -static void window_copy_move_right(struct screen *, u_int *, u_int *, int); static int window_copy_is_lowercase(const char *); +static void window_copy_search_back_overlap(struct grid *, regex_t *, + u_int *, u_int *, u_int *, u_int); static int window_copy_search_jump(struct window_mode_entry *, struct grid *, struct grid *, u_int, u_int, u_int, int, int, int, int); @@ -87,12 +88,15 @@ static void window_copy_update_cursor(struct window_mode_entry *, u_int, static void window_copy_start_selection(struct window_mode_entry *); static int window_copy_adjust_selection(struct window_mode_entry *, u_int *, u_int *); -static int window_copy_set_selection(struct window_mode_entry *, int); -static int window_copy_update_selection(struct window_mode_entry *, int); -static void window_copy_synchronize_cursor(struct window_mode_entry *); +static int window_copy_set_selection(struct window_mode_entry *, int, int); +static int window_copy_update_selection(struct window_mode_entry *, int, + int); +static void window_copy_synchronize_cursor(struct window_mode_entry *, int); static void *window_copy_get_selection(struct window_mode_entry *, size_t *); static void window_copy_copy_buffer(struct window_mode_entry *, const char *, void *, size_t); +static void window_copy_pipe(struct window_mode_entry *, + struct session *, const char *); static void window_copy_copy_pipe(struct window_mode_entry *, struct session *, const char *, const char *); static void window_copy_copy_selection(struct window_mode_entry *, @@ -110,7 +114,7 @@ static void window_copy_cursor_back_to_indentation( static void window_copy_cursor_end_of_line(struct window_mode_entry *); static void window_copy_other_end(struct window_mode_entry *); static void window_copy_cursor_left(struct window_mode_entry *); -static void window_copy_cursor_right(struct window_mode_entry *); +static void window_copy_cursor_right(struct window_mode_entry *, int); static void window_copy_cursor_up(struct window_mode_entry *, int); static void window_copy_cursor_down(struct window_mode_entry *, int); static void window_copy_cursor_jump(struct window_mode_entry *); @@ -119,16 +123,25 @@ static void window_copy_cursor_jump_to(struct window_mode_entry *); static void window_copy_cursor_jump_to_back(struct window_mode_entry *); static void window_copy_cursor_next_word(struct window_mode_entry *, const char *); +static void window_copy_cursor_next_word_end_pos(struct window_mode_entry *, + const char *, u_int *, u_int *); static void window_copy_cursor_next_word_end(struct window_mode_entry *, - const char *); + const char *, int); +static void window_copy_cursor_previous_word_pos(struct window_mode_entry *, + const char *, u_int *, u_int *); static void window_copy_cursor_previous_word(struct window_mode_entry *, const char *, int); static void window_copy_scroll_up(struct window_mode_entry *, u_int); static void window_copy_scroll_down(struct window_mode_entry *, u_int); -static void window_copy_rectangle_toggle(struct window_mode_entry *); +static void window_copy_rectangle_set(struct window_mode_entry *, int); static void window_copy_move_mouse(struct mouse_event *); static void window_copy_drag_update(struct client *, struct mouse_event *); static void window_copy_drag_release(struct client *, struct mouse_event *); +static void window_copy_jump_to_mark(struct window_mode_entry *); +static void window_copy_acquire_cursor_up(struct window_mode_entry *, + u_int, u_int, u_int, u_int, u_int); +static void window_copy_acquire_cursor_down(struct window_mode_entry *, + u_int, u_int, u_int, u_int, u_int, u_int, int); const struct window_mode window_copy_mode = { .name = "copy-mode", @@ -174,6 +187,12 @@ enum window_copy_cmd_action { WINDOW_COPY_CMD_CANCEL, }; +enum window_copy_cmd_clear { + WINDOW_COPY_CMD_CLEAR_ALWAYS, + WINDOW_COPY_CMD_CLEAR_NEVER, + WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, +}; + struct window_copy_cmd_state { struct window_mode_entry *wme; struct args *args; @@ -204,6 +223,8 @@ struct window_copy_mode_data { struct screen *backing; int backing_written; /* backing display started */ + int viewmode; /* view mode entered */ + u_int oy; /* number of lines scrolled up */ u_int selx; /* beginning of selection */ @@ -226,6 +247,23 @@ struct window_copy_mode_data { } lineflag; /* line selection mode */ int rectflag; /* in rectangle copy mode? */ int scroll_exit; /* exit on scroll to end? */ + int hide_position; /* hide position marker */ + + enum { + SEL_CHAR, /* select one char at a time */ + SEL_WORD, /* select one word at a time */ + SEL_LINE, /* select one line at a time */ + } selflag; + + const char *separators; /* word separators */ + + u_int dx; /* drag start position */ + u_int dy; + + u_int selrx; /* selection reset positions */ + u_int selry; + u_int endselrx; + u_int endselry; u_int cx; u_int cy; @@ -233,17 +271,29 @@ struct window_copy_mode_data { u_int lastcx; /* position in last line w/ content */ u_int lastsx; /* size of last line w/ content */ + u_int mx; /* mark position */ + u_int my; + int showmark; + int searchtype; + int searchdirection; + int searchregex; char *searchstr; - bitstr_t *searchmark; - u_int searchcount; - int searchthis; + u_char *searchmark; + int searchcount; + int searchmore; + int searchall; int searchx; int searchy; int searcho; + u_char searchgen; - int jumptype; - char jumpchar; + int timeout; /* search has timed out */ +#define WINDOW_COPY_SEARCH_TIMEOUT 10000 +#define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200 + + int jumptype; + struct utf8_data *jumpchar; struct event dragtimer; #define WINDOW_COPY_DRAG_REPEAT_TIME 50000 @@ -273,6 +323,66 @@ window_copy_scroll_timer(__unused int fd, __unused short events, void *arg) } } +static struct screen * +window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx, + u_int *cy, int trim) +{ + struct screen *dst; + const struct grid_line *gl; + u_int sy, wx, wy; + int reflow; + + dst = xcalloc(1, sizeof *dst); + + sy = screen_hsize(src) + screen_size_y(src); + if (trim) { + while (sy > screen_hsize(src)) { + gl = grid_peek_line(src->grid, sy - 1); + if (gl->cellused != 0) + break; + sy--; + } + } + log_debug("%s: target screen is %ux%u, source %ux%u", __func__, + screen_size_x(src), sy, screen_size_x(hint), + screen_hsize(src) + screen_size_y(src)); + screen_init(dst, screen_size_x(src), sy, screen_hlimit(src)); + + /* + * Ensure history is on for the backing grid so lines are not deleted + * during resizing. + */ + dst->grid->flags |= GRID_HISTORY; + grid_duplicate_lines(dst->grid, 0, src->grid, 0, sy); + + dst->grid->sy = sy - screen_hsize(src); + dst->grid->hsize = screen_hsize(src); + dst->grid->hscrolled = src->grid->hscrolled; + if (src->cy > dst->grid->sy - 1) { + dst->cx = 0; + dst->cy = dst->grid->sy - 1; + } else { + dst->cx = src->cx; + dst->cy = src->cy; + } + + if (cx != NULL && cy != NULL) { + *cx = dst->cx; + *cy = screen_hsize(dst) + dst->cy; + reflow = (screen_size_x(hint) != screen_size_x(dst)); + } + else + reflow = 0; + if (reflow) + grid_wrap_position(dst->grid, *cx, *cy, &wx, &wy); + screen_resize_cursor(dst, screen_size_x(hint), screen_size_y(hint), 1, + 0, 0); + if (reflow) + grid_unwrap_position(dst->grid, cx, cy, wx, wy); + + return (dst); +} + static struct window_copy_mode_data * window_copy_common_init(struct window_mode_entry *wme) { @@ -284,19 +394,22 @@ window_copy_common_init(struct window_mode_entry *wme) data->cursordrag = CURSORDRAG_NONE; data->lineflag = LINE_SEL_NONE; + data->selflag = SEL_CHAR; if (wp->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHUP; + data->searchregex = wp->searchregex; data->searchstr = xstrdup(wp->searchstr); } else { data->searchtype = WINDOW_COPY_OFF; + data->searchregex = 0; data->searchstr = NULL; } - data->searchmark = NULL; data->searchx = data->searchy = data->searcho = -1; + data->searchall = 1; data->jumptype = WINDOW_COPY_OFF; - data->jumpchar = '\0'; + data->jumpchar = NULL; screen_init(&data->screen, screen_size_x(base), screen_size_y(base), 0); data->modekeys = options_get_number(wp->window->options, "mode-keys"); @@ -310,26 +423,35 @@ static struct screen * window_copy_init(struct window_mode_entry *wme, __unused struct cmd_find_state *fs, struct args *args) { - struct window_pane *wp = wme->wp; + struct window_pane *wp = wme->swp; struct window_copy_mode_data *data; + struct screen *base = &wp->base; struct screen_write_ctx ctx; - u_int i; + u_int i, cx, cy; data = window_copy_common_init(wme); + data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy, + wme->swp != wme->wp); - if (wp->fd != -1 && wp->disabled++ == 0) - bufferevent_disable(wp->event, EV_READ|EV_WRITE); - - data->backing = &wp->base; - data->cx = data->backing->cx; - data->cy = data->backing->cy; + data->cx = cx; + if (cy < screen_hsize(data->backing)) { + data->cy = 0; + data->oy = screen_hsize(data->backing) - cy; + } else { + data->cy = cy - screen_hsize(data->backing); + data->oy = 0; + } data->scroll_exit = args_has(args, 'e'); + data->hide_position = args_has(args, 'H'); data->screen.cx = data->cx; data->screen.cy = data->cy; + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 0; - screen_write_start(&ctx, NULL, &data->screen); + screen_write_start(&ctx, &data->screen); for (i = 0; i < screen_size_y(&data->screen); i++) window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, data->cx, data->cy, 0); @@ -348,9 +470,13 @@ window_copy_view_init(struct window_mode_entry *wme, struct screen *s; data = window_copy_common_init(wme); + data->viewmode = 1; data->backing = s = xmalloc(sizeof *data->backing); screen_init(s, screen_size_x(base), screen_size_y(base), UINT_MAX); + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 0; return (&data->screen); } @@ -358,23 +484,18 @@ window_copy_view_init(struct window_mode_entry *wme, static void window_copy_free(struct window_mode_entry *wme) { - struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; evtimer_del(&data->dragtimer); - if (wp->fd != -1 && --wp->disabled == 0) - bufferevent_enable(wp->event, EV_READ|EV_WRITE); - free(data->searchmark); free(data->searchstr); + free(data->jumpchar); + + screen_free(data->backing); + free(data->backing); - if (data->backing != &wp->base) { - screen_free(data->backing); - free(data->backing); - } screen_free(&data->screen); - free(data); } @@ -398,13 +519,10 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) struct grid_cell gc; u_int old_hsize, old_cy; - if (backing == &wp->base) - return; - memcpy(&gc, &grid_default_cell, sizeof gc); old_hsize = screen_hsize(data->backing); - screen_write_start(&back_ctx, NULL, backing); + screen_write_start(&back_ctx, backing); if (data->backing_written) { /* * On the second or later line, do a CRLF before writing @@ -420,7 +538,7 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) data->oy += screen_hsize(data->backing) - old_hsize; - screen_write_start(&ctx, wp, &data->screen); + screen_write_start_pane(&ctx, wp, &data->screen); /* * If the history has changed, draw the top line. @@ -482,7 +600,9 @@ window_copy_pageup1(struct window_mode_entry *wme, int half_page) window_copy_cursor_end_of_line(wme); } - window_copy_update_selection(wme, 1); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 1, 0); window_copy_redraw_screen(wme); } @@ -530,7 +650,9 @@ window_copy_pagedown(struct window_mode_entry *wme, int half_page, if (scroll_exit && data->oy == 0) return (1); - window_copy_update_selection(wme, 1); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 1, 0); window_copy_redraw_screen(wme); return (0); } @@ -549,7 +671,7 @@ window_copy_previous_paragraph(struct window_mode_entry *wme) while (oy > 0 && window_copy_find_length(wme, oy) > 0) oy--; - window_copy_scroll_to(wme, 0, oy); + window_copy_scroll_to(wme, 0, oy, 0); } static void @@ -569,14 +691,63 @@ window_copy_next_paragraph(struct window_mode_entry *wme) oy++; ox = window_copy_find_length(wme, oy); - window_copy_scroll_to(wme, ox, oy); + window_copy_scroll_to(wme, ox, oy, 0); +} + +char * +window_copy_get_word(struct window_pane *wp, u_int x, u_int y) +{ + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->screen.grid; + + return (format_grid_word(gd, x, gd->hsize + y)); +} + +char * +window_copy_get_line(struct window_pane *wp, u_int y) +{ + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->screen.grid; + + return (format_grid_line(gd, gd->hsize + y)); +} + +static void * +window_copy_cursor_word_cb(struct format_tree *ft) +{ + struct window_pane *wp = format_get_pane(ft); + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; + + return (window_copy_get_word(wp, data->cx, data->cy)); +} + +static void * +window_copy_cursor_line_cb(struct format_tree *ft) +{ + struct window_pane *wp = format_get_pane(ft); + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; + + return (window_copy_get_line(wp, data->cy)); +} + +static void * +window_copy_search_match_cb(struct format_tree *ft) +{ + struct window_pane *wp = format_get_pane(ft); + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; + + return (window_copy_match_at_cursor(data)); } static void window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) { struct window_copy_mode_data *data = wme->data; - char *s; format_add(ft, "scroll_position", "%d", data->oy); format_add(ft, "rectangle_toggle", "%d", data->rectflag); @@ -595,53 +766,64 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) } else format_add(ft, "selection_active", "%d", 0); - s = format_grid_word(data->screen.grid, data->cx, data->cy); - if (s != NULL) { - format_add(ft, "copy_cursor_word", "%s", s); - free(s); - } + format_add(ft, "search_present", "%d", data->searchmark != NULL); + format_add_cb(ft, "search_match", window_copy_search_match_cb); - s = format_grid_line(data->screen.grid, data->cy); - if (s != NULL) { - format_add(ft, "copy_cursor_line", "%s", s); - free(s); - } + format_add_cb(ft, "copy_cursor_word", window_copy_cursor_word_cb); + format_add_cb(ft, "copy_cursor_line", window_copy_cursor_line_cb); +} + +static void +window_copy_size_changed(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + struct screen *s = &data->screen; + struct screen_write_ctx ctx; + int search = (data->searchmark != NULL); + + window_copy_clear_selection(wme); + window_copy_clear_marks(wme); + + screen_write_start(&ctx, s); + window_copy_write_lines(wme, &ctx, 0, screen_size_y(s)); + screen_write_stop(&ctx); + + if (search && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 0); + data->searchx = data->cx; + data->searchy = data->cy; + data->searcho = data->oy; } static void window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { - struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; - struct screen_write_ctx ctx; - int search; + struct grid *gd = data->backing->grid; + u_int cx, cy, wx, wy; + int reflow; - screen_resize(s, sx, sy, 1); - if (data->backing != &wp->base) - screen_resize(data->backing, sx, sy, 1); + screen_resize(s, sx, sy, 0); + cx = data->cx; + cy = gd->hsize + data->cy - data->oy; + reflow = (gd->sx != sx); + if (reflow) + grid_wrap_position(gd, cx, cy, &wx, &wy); + screen_resize_cursor(data->backing, sx, sy, 1, 0, 0); + if (reflow) + grid_unwrap_position(gd, &cx, &cy, wx, wy); - if (data->cy > sy - 1) - data->cy = sy - 1; - if (data->cx > sx) - data->cx = sx; - if (data->oy > screen_hsize(data->backing)) - data->oy = screen_hsize(data->backing); - - search = (data->searchmark != NULL); - window_copy_clear_selection(wme); - window_copy_clear_marks(wme); - - screen_write_start(&ctx, NULL, s); - window_copy_write_lines(wme, &ctx, 0, screen_size_y(s) - 1); - screen_write_stop(&ctx); - - if (search) - window_copy_search_marks(wme, NULL, 1); - data->searchx = data->cx; - data->searchy = data->cy; - data->searcho = data->oy; + data->cx = cx; + if (cy < gd->hsize) { + data->cy = 0; + data->oy = gd->hsize - cy; + } else { + data->cy = cy - gd->hsize; + data->oy = 0; + } + window_copy_size_changed(wme); window_copy_redraw_screen(wme); } @@ -655,6 +837,32 @@ window_copy_key_table(struct window_mode_entry *wme) return ("copy-mode"); } +static int +window_copy_expand_search_string(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + const char *ss = args_string(cs->args, 1); + char *expanded; + + if (ss == NULL || *ss == '\0') + return (0); + + if (args_has(cs->args, 'F')) { + expanded = format_single(NULL, ss, NULL, NULL, NULL, wme->wp); + if (*expanded == '\0') { + free(expanded); + return (0); + } + free(data->searchstr); + data->searchstr = expanded; + } else { + free(data->searchstr); + data->searchstr = xstrdup(ss); + } + return (1); +} + static enum window_copy_cmd_action window_copy_cmd_append_selection(struct window_copy_cmd_state *cs) { @@ -702,6 +910,7 @@ window_copy_cmd_begin_selection(struct window_copy_cmd_state *cs) } data->lineflag = LINE_SEL_NONE; + data->selflag = SEL_CHAR; window_copy_start_selection(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -714,6 +923,7 @@ window_copy_cmd_stop_selection(struct window_copy_cmd_state *cs) data->cursordrag = CURSORDRAG_NONE; data->lineflag = LINE_SEL_NONE; + data->selflag = SEL_CHAR; return (WINDOW_COPY_CMD_NOTHING); } @@ -726,7 +936,7 @@ window_copy_cmd_bottom_line(struct window_copy_cmd_state *cs) data->cx = 0; data->cy = screen_size_y(&data->screen) - 1; - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -746,18 +956,34 @@ window_copy_cmd_clear_selection(struct window_copy_cmd_state *cs) } static enum window_copy_cmd_action -window_copy_cmd_copy_end_of_line(struct window_copy_cmd_state *cs) +window_copy_do_copy_end_of_line(struct window_copy_cmd_state *cs, int pipe, + int cancel) { - struct window_mode_entry *wme = cs->wme; - struct client *c = cs->c; - struct session *s = cs->s; - struct winlink *wl = cs->wl; - struct window_pane *wp = wme->wp; - u_int np = wme->prefix; - char *prefix = NULL; + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + u_int count = args_count(cs->args); + u_int np = wme->prefix, ocx, ocy, ooy; + struct window_copy_mode_data *data = wme->data; + char *prefix = NULL, *command = NULL; + const char *arg1 = args_string(cs->args, 1); + const char *arg2 = args_string(cs->args, 2); - if (cs->args->argc == 2) - prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + if (pipe) { + if (count == 3) + prefix = format_single(NULL, arg2, c, s, wl, wp); + if (s != NULL && count > 1 && *arg1 != '\0') + command = format_single(NULL, arg1, c, s, wl, wp); + } else { + if (count == 2) + prefix = format_single(NULL, arg1, c, s, wl, wp); + } + + ocx = data->cx; + ocy = data->cy; + ooy = data->oy; window_copy_start_selection(wme); for (; np > 1; np--) @@ -765,30 +991,83 @@ window_copy_cmd_copy_end_of_line(struct window_copy_cmd_state *cs) window_copy_cursor_end_of_line(wme); if (s != NULL) { - window_copy_copy_selection(wme, prefix); + if (pipe) + window_copy_copy_pipe(wme, s, prefix, command); + else + window_copy_copy_selection(wme, prefix); - free(prefix); - return (WINDOW_COPY_CMD_CANCEL); + if (cancel) { + free(prefix); + free(command); + return (WINDOW_COPY_CMD_CANCEL); + } } + window_copy_clear_selection(wme); + + data->cx = ocx; + data->cy = ocy; + data->oy = ooy; free(prefix); + free(command); return (WINDOW_COPY_CMD_REDRAW); } static enum window_copy_cmd_action -window_copy_cmd_copy_line(struct window_copy_cmd_state *cs) +window_copy_cmd_copy_end_of_line(struct window_copy_cmd_state *cs) { - struct window_mode_entry *wme = cs->wme; - struct client *c = cs->c; - struct session *s = cs->s; - struct winlink *wl = cs->wl; - struct window_pane *wp = wme->wp; - u_int np = wme->prefix; - char *prefix = NULL; + return (window_copy_do_copy_end_of_line(cs, 0, 0)); +} - if (cs->args->argc == 2) - prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); +static enum window_copy_cmd_action +window_copy_cmd_copy_end_of_line_and_cancel(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_end_of_line(cs, 0, 1)); +} +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_end_of_line(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_end_of_line(cs, 1, 0)); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_end_of_line_and_cancel( + struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_end_of_line(cs, 1, 1)); +} + +static enum window_copy_cmd_action +window_copy_do_copy_line(struct window_copy_cmd_state *cs, int pipe, int cancel) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; + u_int count = args_count(cs->args); + u_int np = wme->prefix, ocx, ocy, ooy; + char *prefix = NULL, *command = NULL; + const char *arg1 = args_string(cs->args, 1); + const char *arg2 = args_string(cs->args, 2); + + if (pipe) { + if (count == 3) + prefix = format_single(NULL, arg2, c, s, wl, wp); + if (s != NULL && count > 1 && *arg1 != '\0') + command = format_single(NULL, arg1, c, s, wl, wp); + } else { + if (count == 2) + prefix = format_single(NULL, arg1, c, s, wl, wp); + } + + ocx = data->cx; + ocy = data->cy; + ooy = data->oy; + + data->selflag = SEL_CHAR; window_copy_cursor_start_of_line(wme); window_copy_start_selection(wme); for (; np > 1; np--) @@ -796,16 +1075,52 @@ window_copy_cmd_copy_line(struct window_copy_cmd_state *cs) window_copy_cursor_end_of_line(wme); if (s != NULL) { - window_copy_copy_selection(wme, prefix); + if (pipe) + window_copy_copy_pipe(wme, s, prefix, command); + else + window_copy_copy_selection(wme, prefix); - free(prefix); - return (WINDOW_COPY_CMD_CANCEL); + if (cancel) { + free(prefix); + free(command); + return (WINDOW_COPY_CMD_CANCEL); + } } + window_copy_clear_selection(wme); + + data->cx = ocx; + data->cy = ocy; + data->oy = ooy; free(prefix); + free(command); return (WINDOW_COPY_CMD_REDRAW); } +static enum window_copy_cmd_action +window_copy_cmd_copy_line(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_line(cs, 0, 0)); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_line_and_cancel(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_line(cs, 0, 1)); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_line(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_line(cs, 1, 0)); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_line_and_cancel(struct window_copy_cmd_state *cs) +{ + return (window_copy_do_copy_line(cs, 1, 1)); +} + static enum window_copy_cmd_action window_copy_cmd_copy_selection_no_clear(struct window_copy_cmd_state *cs) { @@ -815,9 +1130,10 @@ window_copy_cmd_copy_selection_no_clear(struct window_copy_cmd_state *cs) struct winlink *wl = cs->wl; struct window_pane *wp = wme->wp; char *prefix = NULL; + const char *arg1 = args_string(cs->args, 1); - if (cs->args->argc == 2) - prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + if (arg1 != NULL) + prefix = format_single(NULL, arg1, c, s, wl, wp); if (s != NULL) window_copy_copy_selection(wme, prefix); @@ -887,10 +1203,13 @@ static enum window_copy_cmd_action window_copy_cmd_cursor_right(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; - for (; np != 0; np--) - window_copy_cursor_right(wme); + for (; np != 0; np--) { + window_copy_cursor_right(wme, data->screen.sel != NULL && + data->rectflag); + } return (WINDOW_COPY_CMD_NOTHING); } @@ -958,17 +1277,20 @@ window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; + struct screen *s = data->backing; u_int oy; - oy = screen_hsize(data->backing) + data->cy - data->oy; + oy = screen_hsize(s) + data->cy - data->oy; if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely) window_copy_other_end(wme); data->cy = screen_size_y(&data->screen) - 1; - data->cx = window_copy_find_length(wme, data->cy); + data->cx = window_copy_find_length(wme, screen_hsize(s) + data->cy); data->oy = 0; - window_copy_update_selection(wme, 1); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -987,7 +1309,9 @@ window_copy_cmd_history_top(struct window_copy_cmd_state *cs) data->cx = 0; data->oy = screen_hsize(data->backing); - window_copy_update_selection(wme, 1); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -1056,7 +1380,7 @@ window_copy_cmd_middle_line(struct window_copy_cmd_state *cs) data->cx = 0; data->cy = (screen_size_y(&data->screen) - 1) / 2; - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -1101,7 +1425,7 @@ window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs) tried = 1; goto retry; } - window_copy_cursor_previous_word(wme, "}]) ", 1); + window_copy_cursor_previous_word(wme, close, 1); } continue; } @@ -1140,7 +1464,7 @@ window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs) /* Move the cursor to the found location if any. */ if (!failed) - window_copy_scroll_to(wme, px, py); + window_copy_scroll_to(wme, px, py, 0); } return (WINDOW_COPY_CMD_NOTHING); @@ -1191,16 +1515,16 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs) sx = data->cx; sy = screen_hsize(s) + data->cy - data->oy; - window_copy_scroll_to(wme, px, py); + window_copy_scroll_to(wme, px, py, 0); window_copy_cmd_previous_matching_bracket(cs); px = data->cx; py = screen_hsize(s) + data->cy - data->oy; grid_get_cell(s->grid, px, py, &gc); - if (gc.data.size != 1 || - (gc.flags & GRID_FLAG_PADDING) || - strchr(close, *gc.data.data) == NULL) - window_copy_scroll_to(wme, sx, sy); + if (gc.data.size == 1 && + (~gc.flags & GRID_FLAG_PADDING) && + strchr(close, *gc.data.data) != NULL) + window_copy_scroll_to(wme, sx, sy, 0); break; } @@ -1213,7 +1537,7 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs) tried = 1; goto retry; } - window_copy_cursor_next_word_end(wme, "{[( "); + window_copy_cursor_next_word_end(wme, open, 0); continue; } /* For vi, continue searching for bracket until EOL. */ @@ -1261,7 +1585,7 @@ window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs) /* Move the cursor to the found location if any. */ if (!failed) - window_copy_scroll_to(wme, px, py); + window_copy_scroll_to(wme, px, py, 0); } return (WINDOW_COPY_CMD_NOTHING); @@ -1285,7 +1609,7 @@ window_copy_cmd_next_space(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_next_word(wme, " "); + window_copy_cursor_next_word(wme, ""); return (WINDOW_COPY_CMD_NOTHING); } @@ -1296,7 +1620,7 @@ window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_next_word_end(wme, " "); + window_copy_cursor_next_word_end(wme, "", 0); return (WINDOW_COPY_CMD_NOTHING); } @@ -1304,13 +1628,13 @@ static enum window_copy_cmd_action window_copy_cmd_next_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_next_word(wme, ws); + window_copy_cursor_next_word(wme, separators); return (WINDOW_COPY_CMD_NOTHING); } @@ -1318,13 +1642,13 @@ static enum window_copy_cmd_action window_copy_cmd_next_word_end(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_next_word_end(wme, ws); + window_copy_cursor_next_word_end(wme, separators, 0); return (WINDOW_COPY_CMD_NOTHING); } @@ -1333,7 +1657,9 @@ window_copy_cmd_other_end(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; u_int np = wme->prefix; + struct window_copy_mode_data *data = wme->data; + data->selflag = SEL_CHAR; if ((np % 2) != 0) window_copy_other_end(wme); return (WINDOW_COPY_CMD_NOTHING); @@ -1395,7 +1721,7 @@ window_copy_cmd_previous_space(struct window_copy_cmd_state *cs) u_int np = wme->prefix; for (; np != 0; np--) - window_copy_cursor_previous_word(wme, " ", 1); + window_copy_cursor_previous_word(wme, "", 1); return (WINDOW_COPY_CMD_NOTHING); } @@ -1403,13 +1729,37 @@ static enum window_copy_cmd_action window_copy_cmd_previous_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; u_int np = wme->prefix; - const char *ws; + const char *separators; + + separators = options_get_string(cs->s->options, "word-separators"); - ws = options_get_string(s->options, "word-separators"); for (; np != 0; np--) - window_copy_cursor_previous_word(wme, ws, 1); + window_copy_cursor_previous_word(wme, separators, 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_rectangle_on(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->lineflag = LINE_SEL_NONE; + window_copy_rectangle_set(wme, 1); + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_rectangle_off(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->lineflag = LINE_SEL_NONE; + window_copy_rectangle_set(wme, 0); + return (WINDOW_COPY_CMD_NOTHING); } @@ -1420,7 +1770,7 @@ window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs) struct window_copy_mode_data *data = wme->data; data->lineflag = LINE_SEL_NONE; - window_copy_rectangle_toggle(wme); + window_copy_rectangle_set(wme, !data->rectflag); return (WINDOW_COPY_CMD_NOTHING); } @@ -1473,10 +1823,10 @@ window_copy_cmd_search_again(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_up(wme, 1); + window_copy_search_up(wme, data->searchregex); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_down(wme, 1); + window_copy_search_down(wme, data->searchregex); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1490,10 +1840,10 @@ window_copy_cmd_search_reverse(struct window_copy_cmd_state *cs) if (data->searchtype == WINDOW_COPY_SEARCHUP) { for (; np != 0; np--) - window_copy_search_down(wme, 1); + window_copy_search_down(wme, data->searchregex); } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { for (; np != 0; np--) - window_copy_search_up(wme, 1); + window_copy_search_up(wme, data->searchregex); } return (WINDOW_COPY_CMD_NOTHING); } @@ -1507,12 +1857,22 @@ window_copy_cmd_select_line(struct window_copy_cmd_state *cs) data->lineflag = LINE_SEL_LEFT_RIGHT; data->rectflag = 0; + data->selflag = SEL_LINE; + data->dx = data->cx; + data->dy = screen_hsize(data->backing) + data->cy - data->oy; window_copy_cursor_start_of_line(wme); + data->selrx = data->cx; + data->selry = screen_hsize(data->backing) + data->cy - data->oy; + data->endselry = data->selry; window_copy_start_selection(wme); - for (; np > 1; np--) - window_copy_cursor_down(wme, 0); window_copy_cursor_end_of_line(wme); + data->endselry = screen_hsize(data->backing) + data->cy - data->oy; + data->endselrx = window_copy_find_length(wme, data->endselry); + for (; np > 1; np--) { + window_copy_cursor_down(wme, 0); + window_copy_cursor_end_of_line(wme); + } return (WINDOW_COPY_CMD_REDRAW); } @@ -1521,33 +1881,63 @@ static enum window_copy_cmd_action window_copy_cmd_select_word(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - struct session *s = cs->s; + struct options *session_options = cs->s->options; struct window_copy_mode_data *data = wme->data; - const char *ws; - u_int px, py, xx; + u_int px, py, nextx, nexty; data->lineflag = LINE_SEL_LEFT_RIGHT; data->rectflag = 0; + data->selflag = SEL_WORD; + data->dx = data->cx; + data->dy = screen_hsize(data->backing) + data->cy - data->oy; + data->separators = options_get_string(session_options, + "word-separators"); + window_copy_cursor_previous_word(wme, data->separators, 0); px = data->cx; py = screen_hsize(data->backing) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - - ws = options_get_string(s->options, "word-separators"); - window_copy_cursor_previous_word(wme, ws, 0); + data->selrx = px; + data->selry = py; window_copy_start_selection(wme); - if (px >= xx || !window_copy_in_set(wme, px + 1, py, ws)) - window_copy_cursor_next_word_end(wme, ws); + /* Handle single character words. */ + nextx = px + 1; + nexty = py; + if (grid_get_line(data->backing->grid, nexty)->flags & + GRID_LINE_WRAPPED && nextx > screen_size_x(data->backing) - 1) { + nextx = 0; + nexty++; + } + if (px >= window_copy_find_length(wme, py) || + !window_copy_in_set(wme, nextx, nexty, WHITESPACE)) + window_copy_cursor_next_word_end(wme, data->separators, 1); else { window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) + if (window_copy_update_selection(wme, 1, 1)) window_copy_redraw_lines(wme, data->cy, 1); } + data->endselrx = data->cx; + data->endselry = screen_hsize(data->backing) + data->cy - data->oy; + if (data->dy > data->endselry) { + data->dy = data->endselry; + data->dx = data->endselrx; + } else if (data->dx > data->endselrx) + data->dx = data->endselrx; return (WINDOW_COPY_CMD_REDRAW); } +static enum window_copy_cmd_action +window_copy_cmd_set_mark(struct window_copy_cmd_state *cs) +{ + struct window_copy_mode_data *data = cs->wme->data; + + data->mx = data->cx; + data->my = screen_hsize(data->backing) + data->cy - data->oy; + data->showmark = 1; + return (WINDOW_COPY_CMD_REDRAW); +} + static enum window_copy_cmd_action window_copy_cmd_start_of_line(struct window_copy_cmd_state *cs) { @@ -1566,7 +1956,7 @@ window_copy_cmd_top_line(struct window_copy_cmd_state *cs) data->cx = 0; data->cy = 0; - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } @@ -1578,17 +1968,17 @@ window_copy_cmd_copy_pipe_no_clear(struct window_copy_cmd_state *cs) struct session *s = cs->s; struct winlink *wl = cs->wl; struct window_pane *wp = wme->wp; - char *command = NULL; - char *prefix = NULL; + char *command = NULL, *prefix = NULL; + const char *arg1 = args_string(cs->args, 1); + const char *arg2 = args_string(cs->args, 2); - if (cs->args->argc == 3) - prefix = format_single(NULL, cs->args->argv[2], c, s, wl, wp); + if (arg2 != NULL) + prefix = format_single(NULL, arg2, c, s, wl, wp); - if (s != NULL && *cs->args->argv[1] != '\0') { - command = format_single(NULL, cs->args->argv[1], c, s, wl, wp); - window_copy_copy_pipe(wme, s, prefix, command); - free(command); - } + if (s != NULL && arg1 != NULL && *arg1 != '\0') + command = format_single(NULL, arg1, c, s, wl, wp); + window_copy_copy_pipe(wme, s, prefix, command); + free(command); free(prefix); return (WINDOW_COPY_CMD_NOTHING); @@ -1614,14 +2004,53 @@ window_copy_cmd_copy_pipe_and_cancel(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); } +static enum window_copy_cmd_action +window_copy_cmd_pipe_no_clear(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + char *command = NULL; + const char *arg1 = args_string(cs->args, 1); + + if (s != NULL && arg1 != NULL && *arg1 != '\0') + command = format_single(NULL, arg1, c, s, wl, wp); + window_copy_pipe(wme, s, command); + free(command); + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_CANCEL); +} + static enum window_copy_cmd_action window_copy_cmd_goto_line(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); - if (*argument != '\0') - window_copy_goto_line(wme, argument); + if (*arg1 != '\0') + window_copy_goto_line(wme, arg1); return (WINDOW_COPY_CMD_NOTHING); } @@ -1631,11 +2060,12 @@ window_copy_cmd_jump_backward(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; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); - if (*argument != '\0') { + if (*arg1 != '\0') { data->jumptype = WINDOW_COPY_JUMPBACKWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(arg1); for (; np != 0; np--) window_copy_cursor_jump_back(wme); } @@ -1648,11 +2078,12 @@ window_copy_cmd_jump_forward(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; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); - if (*argument != '\0') { + if (*arg1 != '\0') { data->jumptype = WINDOW_COPY_JUMPFORWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(arg1); for (; np != 0; np--) window_copy_cursor_jump(wme); } @@ -1665,11 +2096,12 @@ window_copy_cmd_jump_to_backward(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; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); - if (*argument != '\0') { + if (*arg1 != '\0') { data->jumptype = WINDOW_COPY_JUMPTOBACKWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(arg1); for (; np != 0; np--) window_copy_cursor_jump_to_back(wme); } @@ -1682,109 +2114,133 @@ window_copy_cmd_jump_to_forward(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; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); - if (*argument != '\0') { + if (*arg1 != '\0') { data->jumptype = WINDOW_COPY_JUMPTOFORWARD; - data->jumpchar = *argument; + free(data->jumpchar); + data->jumpchar = utf8_fromcstr(arg1); for (; np != 0; np--) window_copy_cursor_jump_to(wme); } return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_jump_to_mark(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_jump_to_mark(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_search_backward(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; - const char *argument; - char *expanded; - if (cs->args->argc == 2) { - argument = cs->args->argv[1]; - if (*argument != '\0') { - if (args_has(cs->args, 'F')) { - expanded = format_single(NULL, argument, NULL, - NULL, NULL, wme->wp); - if (*expanded == '\0') { - free(expanded); - return (WINDOW_COPY_CMD_NOTHING); - } - free(data->searchstr); - data->searchstr = expanded; - } else { - free(data->searchstr); - data->searchstr = xstrdup(argument); - } - } - } + if (!window_copy_expand_search_string(cs)) + return (WINDOW_COPY_CMD_NOTHING); + if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHUP; + data->searchregex = 1; + data->timeout = 0; for (; np != 0; np--) window_copy_search_up(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_search_backward_text(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; + + if (!window_copy_expand_search_string(cs)) + return (WINDOW_COPY_CMD_NOTHING); + + if (data->searchstr != NULL) { + data->searchtype = WINDOW_COPY_SEARCHUP; + data->searchregex = 0; + data->timeout = 0; + for (; np != 0; np--) + window_copy_search_up(wme, 0); + } + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_search_forward(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; - const char *argument; - char *expanded; - if (cs->args->argc == 2) { - argument = cs->args->argv[1]; - if (*argument != '\0') { - if (args_has(cs->args, 'F')) { - expanded = format_single(NULL, argument, NULL, - NULL, NULL, wme->wp); - if (*expanded == '\0') { - free(expanded); - return (WINDOW_COPY_CMD_NOTHING); - } - free(data->searchstr); - data->searchstr = expanded; - } else { - free(data->searchstr); - data->searchstr = xstrdup(argument); - } - } - } + if (!window_copy_expand_search_string(cs)) + return (WINDOW_COPY_CMD_NOTHING); + if (data->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHDOWN; + data->searchregex = 1; + data->timeout = 0; for (; np != 0; np--) window_copy_search_down(wme, 1); } return (WINDOW_COPY_CMD_NOTHING); } +static enum window_copy_cmd_action +window_copy_cmd_search_forward_text(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; + + if (!window_copy_expand_search_string(cs)) + return (WINDOW_COPY_CMD_NOTHING); + + if (data->searchstr != NULL) { + data->searchtype = WINDOW_COPY_SEARCHDOWN; + data->searchregex = 0; + data->timeout = 0; + for (; np != 0; np--) + window_copy_search_down(wme, 0); + } + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); const char *ss = data->searchstr; char prefix; enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING; - prefix = *argument++; + data->timeout = 0; + + log_debug("%s: %s", __func__, arg1); + + prefix = *arg1++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; - } else if (ss != NULL && strcmp(argument, ss) != 0) { + } else if (ss != NULL && strcmp(arg1, ss) != 0) { data->cx = data->searchx; data->cy = data->searchy; data->oy = data->searcho; action = WINDOW_COPY_CMD_REDRAW; } - if (*argument == '\0') { + if (*arg1 == '\0') { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1792,8 +2248,9 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) case '=': case '-': data->searchtype = WINDOW_COPY_SEARCHUP; + data->searchregex = 0; free(data->searchstr); - data->searchstr = xstrdup(argument); + data->searchstr = xstrdup(arg1); if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); @@ -1801,8 +2258,9 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs) break; case '+': data->searchtype = WINDOW_COPY_SEARCHDOWN; + data->searchregex = 0; free(data->searchstr); - data->searchstr = xstrdup(argument); + data->searchstr = xstrdup(arg1); if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); @@ -1817,23 +2275,27 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; - const char *argument = cs->args->argv[1]; + const char *arg1 = args_string(cs->args, 1); const char *ss = data->searchstr; char prefix; enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING; - prefix = *argument++; + data->timeout = 0; + + log_debug("%s: %s", __func__, arg1); + + prefix = *arg1++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; - } else if (ss != NULL && strcmp(argument, ss) != 0) { + } else if (ss != NULL && strcmp(arg1, ss) != 0) { data->cx = data->searchx; data->cy = data->searchy; data->oy = data->searcho; action = WINDOW_COPY_CMD_REDRAW; } - if (*argument == '\0') { + if (*arg1 == '\0') { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); } @@ -1841,8 +2303,9 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) case '=': case '+': data->searchtype = WINDOW_COPY_SEARCHDOWN; + data->searchregex = 0; free(data->searchstr); - data->searchstr = xstrdup(argument); + data->searchstr = xstrdup(arg1); if (!window_copy_search_down(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); @@ -1850,8 +2313,9 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) break; case '-': data->searchtype = WINDOW_COPY_SEARCHUP; + data->searchregex = 0; free(data->searchstr); - data->searchstr = xstrdup(argument); + data->searchstr = xstrdup(arg1); if (!window_copy_search_up(wme, 0)) { window_copy_clear_marks(wme); return (WINDOW_COPY_CMD_REDRAW); @@ -1860,139 +2324,505 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs) return (action); } +static enum window_copy_cmd_action +window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_pane *wp = wme->swp; + struct window_copy_mode_data *data = wme->data; + + if (data->viewmode) + return (WINDOW_COPY_CMD_NOTHING); + + screen_free(data->backing); + free(data->backing); + data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL, NULL, wme->swp != wme->wp); + + window_copy_size_changed(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + static const struct { const char *command; - int minargs; - int maxargs; - int ismotion; + u_int minargs; + u_int maxargs; + enum window_copy_cmd_clear clear; enum window_copy_cmd_action (*f)(struct window_copy_cmd_state *); } window_copy_cmd_table[] = { - { "append-selection", 0, 0, 0, - window_copy_cmd_append_selection }, - { "append-selection-and-cancel", 0, 0, 0, - window_copy_cmd_append_selection_and_cancel }, - { "back-to-indentation", 0, 0, 0, - window_copy_cmd_back_to_indentation }, - { "begin-selection", 0, 0, 0, - window_copy_cmd_begin_selection }, - { "bottom-line", 0, 0, 1, - window_copy_cmd_bottom_line }, - { "cancel", 0, 0, 0, - window_copy_cmd_cancel }, - { "clear-selection", 0, 0, 0, - window_copy_cmd_clear_selection }, - { "copy-end-of-line", 0, 1, 0, - window_copy_cmd_copy_end_of_line }, - { "copy-line", 0, 1, 0, - window_copy_cmd_copy_line }, - { "copy-pipe-no-clear", 1, 2, 0, - window_copy_cmd_copy_pipe_no_clear }, - { "copy-pipe", 1, 2, 0, - window_copy_cmd_copy_pipe }, - { "copy-pipe-and-cancel", 1, 2, 0, - window_copy_cmd_copy_pipe_and_cancel }, - { "copy-selection-no-clear", 0, 1, 0, - window_copy_cmd_copy_selection_no_clear }, - { "copy-selection", 0, 1, 0, - window_copy_cmd_copy_selection }, - { "copy-selection-and-cancel", 0, 1, 0, - window_copy_cmd_copy_selection_and_cancel }, - { "cursor-down", 0, 0, 1, - window_copy_cmd_cursor_down }, - { "cursor-down-and-cancel", 0, 0, 0, - window_copy_cmd_cursor_down_and_cancel }, - { "cursor-left", 0, 0, 1, - window_copy_cmd_cursor_left }, - { "cursor-right", 0, 0, 1, - window_copy_cmd_cursor_right }, - { "cursor-up", 0, 0, 1, - window_copy_cmd_cursor_up }, - { "end-of-line", 0, 0, 1, - window_copy_cmd_end_of_line }, - { "goto-line", 1, 1, 1, - window_copy_cmd_goto_line }, - { "halfpage-down", 0, 0, 1, - window_copy_cmd_halfpage_down }, - { "halfpage-down-and-cancel", 0, 0, 0, - window_copy_cmd_halfpage_down_and_cancel }, - { "halfpage-up", 0, 0, 1, - window_copy_cmd_halfpage_up }, - { "history-bottom", 0, 0, 1, - window_copy_cmd_history_bottom }, - { "history-top", 0, 0, 1, - window_copy_cmd_history_top }, - { "jump-again", 0, 0, 1, - window_copy_cmd_jump_again }, - { "jump-backward", 1, 1, 1, - window_copy_cmd_jump_backward }, - { "jump-forward", 1, 1, 1, - window_copy_cmd_jump_forward }, - { "jump-reverse", 0, 0, 1, - window_copy_cmd_jump_reverse }, - { "jump-to-backward", 1, 1, 1, - window_copy_cmd_jump_to_backward }, - { "jump-to-forward", 1, 1, 1, - window_copy_cmd_jump_to_forward }, - { "middle-line", 0, 0, 1, - window_copy_cmd_middle_line }, - { "next-matching-bracket", 0, 0, 0, - window_copy_cmd_next_matching_bracket }, - { "next-paragraph", 0, 0, 1, - window_copy_cmd_next_paragraph }, - { "next-space", 0, 0, 1, - window_copy_cmd_next_space }, - { "next-space-end", 0, 0, 1, - window_copy_cmd_next_space_end }, - { "next-word", 0, 0, 1, - window_copy_cmd_next_word }, - { "next-word-end", 0, 0, 1, - window_copy_cmd_next_word_end }, - { "other-end", 0, 0, 1, - window_copy_cmd_other_end }, - { "page-down", 0, 0, 1, - window_copy_cmd_page_down }, - { "page-down-and-cancel", 0, 0, 0, - window_copy_cmd_page_down_and_cancel }, - { "page-up", 0, 0, 1, - window_copy_cmd_page_up }, - { "previous-matching-bracket", 0, 0, 0, - window_copy_cmd_previous_matching_bracket }, - { "previous-paragraph", 0, 0, 1, - window_copy_cmd_previous_paragraph }, - { "previous-space", 0, 0, 1, - window_copy_cmd_previous_space }, - { "previous-word", 0, 0, 1, - window_copy_cmd_previous_word }, - { "rectangle-toggle", 0, 0, 0, - window_copy_cmd_rectangle_toggle }, - { "scroll-down", 0, 0, 1, - window_copy_cmd_scroll_down }, - { "scroll-down-and-cancel", 0, 0, 0, - window_copy_cmd_scroll_down_and_cancel }, - { "scroll-up", 0, 0, 1, - window_copy_cmd_scroll_up }, - { "search-again", 0, 0, 0, - window_copy_cmd_search_again }, - { "search-backward", 0, 1, 0, - window_copy_cmd_search_backward }, - { "search-backward-incremental", 1, 1, 0, - window_copy_cmd_search_backward_incremental }, - { "search-forward", 0, 1, 0, - window_copy_cmd_search_forward }, - { "search-forward-incremental", 1, 1, 0, - window_copy_cmd_search_forward_incremental }, - { "search-reverse", 0, 0, 0, - window_copy_cmd_search_reverse }, - { "select-line", 0, 0, 0, - window_copy_cmd_select_line }, - { "select-word", 0, 0, 0, - window_copy_cmd_select_word }, - { "start-of-line", 0, 0, 1, - window_copy_cmd_start_of_line }, - { "stop-selection", 0, 0, 0, - window_copy_cmd_stop_selection }, - { "top-line", 0, 0, 1, - window_copy_cmd_top_line }, + { .command = "append-selection", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_append_selection + }, + { .command = "append-selection-and-cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_append_selection_and_cancel + }, + { .command = "back-to-indentation", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_back_to_indentation + }, + { .command = "begin-selection", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_begin_selection + }, + { .command = "bottom-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_bottom_line + }, + { .command = "cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_cancel + }, + { .command = "clear-selection", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_clear_selection + }, + { .command = "copy-end-of-line", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_end_of_line + }, + { .command = "copy-end-of-line-and-cancel", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_end_of_line_and_cancel + }, + { .command = "copy-pipe-end-of-line", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_end_of_line + }, + { .command = "copy-pipe-end-of-line-and-cancel", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel + }, + { .command = "copy-line", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_line + }, + { .command = "copy-line-and-cancel", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_line_and_cancel + }, + { .command = "copy-pipe-line", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_line + }, + { .command = "copy-pipe-line-and-cancel", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_line_and_cancel + }, + { .command = "copy-pipe-no-clear", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_copy_pipe_no_clear + }, + { .command = "copy-pipe", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe + }, + { .command = "copy-pipe-and-cancel", + .minargs = 0, + .maxargs = 2, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_and_cancel + }, + { .command = "copy-selection-no-clear", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_copy_selection_no_clear + }, + { .command = "copy-selection", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_selection + }, + { .command = "copy-selection-and-cancel", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_selection_and_cancel + }, + { .command = "cursor-down", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_cursor_down + }, + { .command = "cursor-down-and-cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_cursor_down_and_cancel + }, + { .command = "cursor-left", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_cursor_left + }, + { .command = "cursor-right", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_cursor_right + }, + { .command = "cursor-up", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_cursor_up + }, + { .command = "end-of-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_end_of_line + }, + { .command = "goto-line", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_goto_line + }, + { .command = "halfpage-down", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_halfpage_down + }, + { .command = "halfpage-down-and-cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_halfpage_down_and_cancel + }, + { .command = "halfpage-up", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_halfpage_up + }, + { .command = "history-bottom", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_history_bottom + }, + { .command = "history-top", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_history_top + }, + { .command = "jump-again", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_again + }, + { .command = "jump-backward", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_backward + }, + { .command = "jump-forward", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_forward + }, + { .command = "jump-reverse", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_reverse + }, + { .command = "jump-to-backward", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_to_backward + }, + { .command = "jump-to-forward", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_jump_to_forward + }, + { .command = "jump-to-mark", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_jump_to_mark + }, + { .command = "middle-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_middle_line + }, + { .command = "next-matching-bracket", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_next_matching_bracket + }, + { .command = "next-paragraph", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_next_paragraph + }, + { .command = "next-space", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_next_space + }, + { .command = "next-space-end", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_next_space_end + }, + { .command = "next-word", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_next_word + }, + { .command = "next-word-end", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_next_word_end + }, + { .command = "other-end", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_other_end + }, + { .command = "page-down", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_page_down + }, + { .command = "page-down-and-cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_page_down_and_cancel + }, + { .command = "page-up", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_page_up + }, + { .command = "pipe-no-clear", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_pipe_no_clear + }, + { .command = "pipe", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_pipe + }, + { .command = "pipe-and-cancel", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_pipe_and_cancel + }, + { .command = "previous-matching-bracket", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_previous_matching_bracket + }, + { .command = "previous-paragraph", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_previous_paragraph + }, + { .command = "previous-space", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_previous_space + }, + { .command = "previous-word", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_previous_word + }, + { .command = "rectangle-on", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_rectangle_on + }, + { .command = "rectangle-off", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_rectangle_off + }, + { .command = "rectangle-toggle", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_rectangle_toggle + }, + { .command = "refresh-from-pane", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_refresh_from_pane + }, + { .command = "scroll-down", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_scroll_down + }, + { .command = "scroll-down-and-cancel", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_scroll_down_and_cancel + }, + { .command = "scroll-up", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_scroll_up + }, + { .command = "search-again", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_again + }, + { .command = "search-backward", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_backward + }, + { .command = "search-backward-text", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_backward_text + }, + { .command = "search-backward-incremental", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_backward_incremental + }, + { .command = "search-forward", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_forward + }, + { .command = "search-forward-text", + .minargs = 0, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_forward_text + }, + { .command = "search-forward-incremental", + .minargs = 1, + .maxargs = 1, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_forward_incremental + }, + { .command = "search-reverse", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_search_reverse + }, + { .command = "select-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_select_line + }, + { .command = "select-word", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_select_word + }, + { .command = "set-mark", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_set_mark + }, + { .command = "start-of-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_start_of_line + }, + { .command = "stop-selection", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_stop_selection + }, + { .command = "top-line", + .minargs = 0, + .maxargs = 0, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_top_line + } }; static void @@ -2003,13 +2833,14 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, struct window_copy_mode_data *data = wme->data; struct window_copy_cmd_state cs; enum window_copy_cmd_action action; + enum window_copy_cmd_clear clear = WINDOW_COPY_CMD_CLEAR_NEVER; const char *command; - u_int i; - int ismotion = 0, keys; + u_int i, count = args_count(args); + int keys; - if (args->argc == 0) + if (count == 0) return; - command = args->argv[0]; + command = args_string(args, 0); if (m != NULL && m->valid && !MOUSE_WHEEL(m->b)) window_copy_move_mouse(m); @@ -2025,23 +2856,23 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, action = WINDOW_COPY_CMD_NOTHING; for (i = 0; i < nitems(window_copy_cmd_table); i++) { if (strcmp(window_copy_cmd_table[i].command, command) == 0) { - if (args->argc - 1 < window_copy_cmd_table[i].minargs || - args->argc - 1 > window_copy_cmd_table[i].maxargs) + if (count - 1 < window_copy_cmd_table[i].minargs || + count - 1 > window_copy_cmd_table[i].maxargs) break; - ismotion = window_copy_cmd_table[i].ismotion; - action = window_copy_cmd_table[i].f (&cs); + clear = window_copy_cmd_table[i].clear; + action = window_copy_cmd_table[i].f(&cs); break; } } if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) { keys = options_get_number(wme->wp->window->options, "mode-keys"); - if (keys != MODEKEY_VI || !ismotion) { + if (clear == WINDOW_COPY_CMD_CLEAR_EMACS_ONLY && + keys == MODEKEY_VI) + clear = WINDOW_COPY_CMD_CLEAR_NEVER; + if (clear != WINDOW_COPY_CMD_CLEAR_NEVER) { window_copy_clear_marks(wme); data->searchx = data->searchy = -1; - } else if (data->searchthis != -1) { - data->searchthis = -1; - action = WINDOW_COPY_CMD_REDRAW; } if (action == WINDOW_COPY_CMD_NOTHING) action = WINDOW_COPY_CMD_REDRAW; @@ -2055,7 +2886,8 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, } static void -window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py) +window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py, + int no_redraw) { struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; @@ -2080,8 +2912,11 @@ window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py) data->oy = gd->hsize - offset; } - window_copy_update_selection(wme, 1); - window_copy_redraw_screen(wme); + if (!no_redraw && data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 1, 0); + if (!no_redraw) + window_copy_redraw_screen(wme); } static int @@ -2106,8 +2941,8 @@ window_copy_search_compare(struct grid *gd, u_int px, u_int py, } static int -window_copy_search_lr(struct grid *gd, - struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis) +window_copy_search_lr(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py, + u_int first, u_int last, int cis) { u_int ax, bx, px, pywrap, endline; int matched; @@ -2180,14 +3015,12 @@ window_copy_search_rl(struct grid *gd, } static int -window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, - u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py, + u_int first, u_int last, regex_t *reg) { - int cflags = REG_EXTENDED, eflags = 0; + int eflags = 0; u_int endline, foundx, foundy, len, pywrap, size = 1; - u_int ssize = 1; - char *buf, *sbuf; - regex_t reg; + char *buf; regmatch_t regmatch; struct grid_line *gl; @@ -2198,19 +3031,7 @@ window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, if (first >= last) return (0); - sbuf = xmalloc(ssize); - sbuf[0] = '\0'; - sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); - if (sbuf == NULL) - return (0); - /* Set flags for regex search. */ - if (cis) - cflags |= REG_ICASE; - if (regcomp(®, sbuf, cflags) != 0) { - free(sbuf); - return (0); - } if (first != 0) eflags |= REG_NOTBOL; @@ -2230,7 +3051,8 @@ window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, len += gd->sx; } - if (regexec(®, buf, 1, ®match, eflags) == 0) { + if (regexec(reg, buf, 1, ®match, eflags) == 0 && + regmatch.rm_so != regmatch.rm_eo) { foundx = first; foundy = py; window_copy_cstrtocellpos(gd, len, &foundx, &foundy, @@ -2246,15 +3068,11 @@ window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, foundy--; } *psx -= *ppx; - regfree(®); - free(sbuf); free(buf); return (1); } } - regfree(®); - free(sbuf); free(buf); *ppx = 0; *psx = 0; @@ -2262,28 +3080,15 @@ window_copy_search_lr_regex(struct grid *gd, struct grid *sgd, } static int -window_copy_search_rl_regex(struct grid *gd, struct grid *sgd, - u_int *ppx, u_int *psx, u_int py, u_int first, u_int last, int cis) +window_copy_search_rl_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py, + u_int first, u_int last, regex_t *reg) { - int cflags = REG_EXTENDED, eflags = 0; - u_int endline, len, pywrap, size = 1, ssize = 1; - char *buf, *sbuf; - regex_t reg; + int eflags = 0; + u_int endline, len, pywrap, size = 1; + char *buf; struct grid_line *gl; - sbuf = xmalloc(ssize); - sbuf[0] = '\0'; - sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); - if (sbuf == NULL) - return (0); - /* Set flags for regex search. */ - if (cis) - cflags |= REG_ICASE; - if (regcomp(®, sbuf, cflags) != 0) { - free(sbuf); - return (0); - } if (first != 0) eflags |= REG_NOTBOL; @@ -2304,22 +3109,53 @@ window_copy_search_rl_regex(struct grid *gd, struct grid *sgd, } if (window_copy_last_regex(gd, py, first, last, len, ppx, psx, buf, - ®, eflags)) + reg, eflags)) { - regfree(®); - free(sbuf); free(buf); return (1); } - regfree(®); - free(sbuf); free(buf); *ppx = 0; *psx = 0; return (0); } +static const char * +window_copy_cellstring(const struct grid_line *gl, u_int px, size_t *size, + int *allocated) +{ + static struct utf8_data ud; + struct grid_cell_entry *gce; + char *copy; + + if (px >= gl->cellsize) { + *size = 1; + *allocated = 0; + return (" "); + } + + gce = &gl->celldata[px]; + if (gce->flags & GRID_FLAG_PADDING) { + *size = 0; + *allocated = 0; + return (NULL); + } + if (~gce->flags & GRID_FLAG_EXTENDED) { + *size = 1; + *allocated = 0; + return (&gce->data.data); + } + + utf8_to_data(gl->extddata[gce->offset].data, &ud); + *size = ud.size; + *allocated = 1; + + copy = xmalloc(ud.size); + memcpy(copy, ud.data, ud.size); + return (copy); +} + /* Find last match in given range. */ static int window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last, @@ -2333,6 +3169,8 @@ window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last, foundy = py; oldx = first; while (regexec(preg, buf + px, 1, ®match, eflags) == 0) { + if (regmatch.rm_so == regmatch.rm_eo) + break; window_copy_cstrtocellpos(gd, len, &foundx, &foundy, buf + px + regmatch.rm_so); if (foundy > py || foundx >= last) @@ -2374,20 +3212,36 @@ static char * window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last, char *buf, u_int *size) { - u_int ax, bx, newsize; - struct grid_cell gc; + u_int ax, bx, newsize = *size; + const struct grid_line *gl; + const char *d; + size_t bufsize = 1024, dlen; + int allocated; + while (bufsize < newsize) + bufsize *= 2; + buf = xrealloc(buf, bufsize); + + gl = grid_peek_line(gd, py); bx = *size - 1; - newsize = *size; for (ax = first; ax < last; ax++) { - grid_get_cell(gd, ax, py, &gc); - newsize += gc.data.size; - buf = xrealloc(buf, newsize); - memcpy(buf + bx, gc.data.data, gc.data.size); - bx += gc.data.size; + d = window_copy_cellstring(gl, ax, &dlen, &allocated); + newsize += dlen; + while (bufsize < newsize) { + bufsize *= 2; + buf = xrealloc(buf, bufsize); + } + if (dlen == 1) + buf[bx++] = *d; + else { + memcpy(buf + bx, d, dlen); + bx += dlen; + } + if (allocated) + free((void *)d); } - buf[newsize - 1] = '\0'; + *size = newsize; return (buf); } @@ -2397,57 +3251,66 @@ static void window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy, const char *str) { - u_int cell, ccell, px, pywrap; - int match; - const char *cstr; - char *celldata, **cells; - struct grid_cell gc; - - /* Set up staggered array of cell contents. This speeds up search. */ - cells = xreallocarray(NULL, ncells, sizeof cells[0]); + u_int cell, ccell, px, pywrap, pos, len; + int match; + const struct grid_line *gl; + const char *d; + size_t dlen; + struct { + const char *d; + size_t dlen; + int allocated; + } *cells; /* Populate the array of cell data. */ + cells = xreallocarray(NULL, ncells, sizeof cells[0]); cell = 0; px = *ppx; pywrap = *ppy; + gl = grid_peek_line(gd, pywrap); while (cell < ncells) { - grid_get_cell(gd, px, pywrap, &gc); - celldata = xmalloc(gc.data.size + 1); - memcpy(celldata, gc.data.data, gc.data.size); - celldata[gc.data.size] = '\0'; - cells[cell] = celldata; + cells[cell].d = window_copy_cellstring(gl, px, + &cells[cell].dlen, &cells[cell].allocated); cell++; - px = (px + 1) % gd->sx; - if (px == 0) + px++; + if (px == gd->sx) { + px = 0; pywrap++; + gl = grid_peek_line(gd, pywrap); + } } /* Locate starting cell. */ cell = 0; + len = strlen(str); while (cell < ncells) { ccell = cell; - cstr = str; + pos = 0; match = 1; while (ccell < ncells) { - /* Anchor found to the end. */ - if (*cstr == '\0') { + if (str[pos] == '\0') { match = 0; break; } - - celldata = cells[ccell]; - while (*celldata != '\0' && *cstr != '\0') { - if (*celldata++ != *cstr++) { + d = cells[ccell].d; + dlen = cells[ccell].dlen; + if (dlen == 1) { + if (str[pos] != *d) { match = 0; break; } + pos++; + } else { + if (dlen > len - pos) + dlen = len - pos; + if (memcmp(str + pos, d, dlen) != 0) { + match = 0; + break; + } + pos += dlen; } - - if (!match) - break; ccell++; } - if (match) break; cell++; @@ -2465,8 +3328,10 @@ window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy, *ppy = pywrap; /* Free cell data. */ - for (cell = 0; cell < ncells; cell++) - free(cells[cell]); + for (cell = 0; cell < ncells; cell++) { + if (cells[cell].allocated) + free((void *)cells[cell].d); + } free(cells); } @@ -2515,6 +3380,48 @@ window_copy_is_lowercase(const char *ptr) return (1); } +/* + * Handle backward wrapped regex searches with overlapping matches. In this case + * find the longest overlapping match from previous wrapped lines. + */ +static void +window_copy_search_back_overlap(struct grid *gd, regex_t *preg, u_int *ppx, + u_int *psx, u_int *ppy, u_int endline) +{ + u_int endx, endy, oldendx, oldendy, px, py, sx; + int found = 1; + + oldendx = *ppx + *psx; + oldendy = *ppy - 1; + while (oldendx > gd->sx - 1) { + oldendx -= gd->sx; + oldendy++; + } + endx = oldendx; + endy = oldendy; + px = *ppx; + py = *ppy; + while (found && px == 0 && py - 1 > endline && + grid_get_line(gd, py - 2)->flags & GRID_LINE_WRAPPED && + endx == oldendx && endy == oldendy) { + py--; + found = window_copy_search_rl_regex(gd, &px, &sx, py - 1, 0, + gd->sx, preg); + if (found) { + endx = px + sx; + endy = py - 1; + while (endx > gd->sx - 1) { + endx -= gd->sx; + endy++; + } + if (endx == oldendx && endy == oldendy) { + *ppx = px; + *ppy = py; + } + } + } +} + /* * Search for text stored in sgd starting from position fx,fy up to endline. If * found, jump to it. If cis then ignore case. The direction is 0 for searching @@ -2526,29 +3433,50 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap, int direction, int regex) { - u_int i, px, sx; - int found = 0; + u_int i, px, sx, ssize = 1; + int found = 0, cflags = REG_EXTENDED; + char *sbuf; + regex_t reg; + + if (regex) { + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize); + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + free(sbuf); + } if (direction) { for (i = fy; i <= endline; i++) { - if (regex) - found = window_copy_search_lr_regex(gd, sgd, - &px, &sx, i, fx, gd->sx, cis); - else + if (regex) { + found = window_copy_search_lr_regex(gd, + &px, &sx, i, fx, gd->sx, ®); + } else { found = window_copy_search_lr(gd, sgd, &px, i, fx, gd->sx, cis); + } if (found) break; fx = 0; } } else { for (i = fy + 1; endline < i; i--) { - if (regex) - found = window_copy_search_rl_regex(gd, sgd, - &px, &sx, i - 1, 0, fx + 1, cis); - else + if (regex) { + found = window_copy_search_rl_regex(gd, + &px, &sx, i - 1, 0, fx + 1, ®); + if (found) { + window_copy_search_back_overlap(gd, + ®, &px, &sx, &i, endline); + } + } else { found = window_copy_search_rl(gd, sgd, &px, i - 1, 0, fx + 1, cis); + } if (found) { i--; break; @@ -2556,9 +3484,11 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, fx = gd->sx - 1; } } + if (regex) + regfree(®); if (found) { - window_copy_scroll_to(wme, px, i); + window_copy_scroll_to(wme, px, i, 1); return (1); } if (wrap) { @@ -2570,6 +3500,29 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd, return (0); } +static void +window_copy_move_after_search_mark(struct window_copy_mode_data *data, + u_int *fx, u_int *fy, int wrapflag) +{ + struct screen *s = data->backing; + u_int at, start; + + if (window_copy_search_mark_at(data, *fx, *fy, &start) == 0 && + data->searchmark[start] != 0) { + while (window_copy_search_mark_at(data, *fx, *fy, &at) == 0) { + if (data->searchmark[at] != data->searchmark[start]) + break; + /* Stop if not wrapping and at the end of the grid. */ + if (!wrapflag && + *fx == screen_size_x(s) - 1 && + *fy == screen_hsize(s) + screen_size_y(s) - 1) + break; + + window_copy_move_right(s, fx, fy, wrapflag); + } + } +} + /* * Search in for text searchstr. If direction is 0 then search up, otherwise * down. @@ -2582,56 +3535,186 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex) struct screen *s = data->backing, ss; struct screen_write_ctx ctx; struct grid *gd = s->grid; - u_int fx, fy, endline; - int wrapflag, cis, found; + const char *str = data->searchstr; + u_int at, endline, fx, fy, start; + int cis, found, keys, visible_only; + int wrapflag; + if (regex && str[strcspn(str, "^$*+()?[].\\")] == '\0') + regex = 0; + + data->searchdirection = direction; + + if (data->timeout) + return (0); + + if (data->searchall || wp->searchstr == NULL || + wp->searchregex != regex) { + visible_only = 0; + data->searchall = 0; + } else + visible_only = (strcmp(wp->searchstr, str) == 0); free(wp->searchstr); - wp->searchstr = xstrdup(data->searchstr); + wp->searchstr = xstrdup(str); + wp->searchregex = regex; fx = data->cx; fy = screen_hsize(data->backing) - data->oy + data->cy; - screen_init(&ss, screen_write_strlen("%s", data->searchstr), 1, 0); - screen_write_start(&ctx, NULL, &ss); - screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", data->searchstr); + screen_init(&ss, screen_write_strlen("%s", str), 1, 0); + screen_write_start(&ctx, &ss); + screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", str); screen_write_stop(&ctx); wrapflag = options_get_number(wp->window->options, "wrap-search"); - cis = window_copy_is_lowercase(data->searchstr); + cis = window_copy_is_lowercase(str); + + keys = options_get_number(wp->window->options, "mode-keys"); if (direction) { - window_copy_move_right(s, &fx, &fy, wrapflag); + /* + * Behave according to mode-keys. If it is emacs, search forward + * leaves the cursor after the match. If it is vi, the cursor + * remains at the beginning of the match, regardless of + * direction, which means that we need to start the next search + * after the term the cursor is currently on when searching + * forward. + */ + if (keys == MODEKEY_VI) { + if (data->searchmark != NULL) + window_copy_move_after_search_mark(data, &fx, + &fy, wrapflag); + else { + /* + * When there are no search marks, start the + * search after the current cursor position. + */ + window_copy_move_right(s, &fx, &fy, wrapflag); + } + } endline = gd->hsize + gd->sy - 1; - } else { + } + else { window_copy_move_left(s, &fx, &fy, wrapflag); endline = 0; } found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, wrapflag, direction, regex); + if (found) { + window_copy_search_marks(wme, &ss, regex, visible_only); + fx = data->cx; + fy = screen_hsize(data->backing) - data->oy + data->cy; - if (window_copy_search_marks(wme, &ss, regex)) - window_copy_redraw_screen(wme); + /* + * When searching forward, if the cursor is not at the beginning + * of the mark, search again. + */ + if (direction && + window_copy_search_mark_at(data, fx, fy, &at) == 0 && + at > 0 && + data->searchmark[at] == data->searchmark[at - 1]) { + window_copy_move_after_search_mark(data, &fx, &fy, + wrapflag); + window_copy_search_jump(wme, gd, ss.grid, fx, + fy, endline, cis, wrapflag, direction, + regex); + fx = data->cx; + fy = screen_hsize(data->backing) - data->oy + data->cy; + } + + if (direction) { + /* + * When in Emacs mode, position the cursor just after + * the mark. + */ + if (keys == MODEKEY_EMACS) { + window_copy_move_after_search_mark(data, &fx, + &fy, wrapflag); + data->cx = fx; + data->cy = fy - screen_hsize(data->backing) + + data-> oy; + } + } + else { + /* + * When searching backward, position the cursor at the + * beginning of the mark. + */ + if (window_copy_search_mark_at(data, fx, fy, + &start) == 0) { + while (window_copy_search_mark_at(data, fx, fy, + &at) == 0 && + data->searchmark[at] == + data->searchmark[start]) { + data->cx = fx; + data->cy = fy - + screen_hsize(data->backing) + + data-> oy; + if (at == 0) + break; + + window_copy_move_left(s, &fx, &fy, 0); + } + } + } + } + window_copy_redraw_screen(wme); screen_free(&ss); return (found); } +static void +window_copy_visible_lines(struct window_copy_mode_data *data, u_int *start, + u_int *end) +{ + struct grid *gd = data->backing->grid; + const struct grid_line *gl; + + for (*start = gd->hsize - data->oy; *start > 0; (*start)--) { + gl = grid_peek_line(gd, (*start) - 1); + if (~gl->flags & GRID_LINE_WRAPPED) + break; + } + *end = gd->hsize - data->oy + gd->sy; +} + +static int +window_copy_search_mark_at(struct window_copy_mode_data *data, u_int px, + u_int py, u_int *at) +{ + struct screen *s = data->backing; + struct grid *gd = s->grid; + + if (py < gd->hsize - data->oy) + return (-1); + if (py > gd->hsize - data->oy + gd->sy - 1) + return (-1); + *at = ((py - (gd->hsize - data->oy)) * gd->sx) + px; + return (0); +} + static int window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, - int regex) + int regex, int visible_only) { struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing, ss; struct screen_write_ctx ctx; struct grid *gd = s->grid; - int found, cis, which = -1; - u_int px, py, b, nfound = 0, width; + int found, cis, stopped = 0; + int cflags = REG_EXTENDED; + u_int px, py, i, b, nfound = 0, width; + u_int ssize = 1, start, end; + char *sbuf; + regex_t reg; + uint64_t stop = 0, tstart, t; if (ssp == NULL) { width = screen_write_strlen("%s", data->searchstr); screen_init(&ss, width, 1, 0); - screen_write_start(&ctx, NULL, &ss); + screen_write_start(&ctx, &ss); screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", data->searchstr); screen_write_stop(&ctx); @@ -2641,46 +3724,111 @@ window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp, cis = window_copy_is_lowercase(data->searchstr); - free(data->searchmark); - data->searchmark = bit_alloc((gd->hsize + gd->sy) * gd->sx); + if (regex) { + sbuf = xmalloc(ssize); + sbuf[0] = '\0'; + sbuf = window_copy_stringify(ssp->grid, 0, 0, ssp->grid->sx, + sbuf, &ssize); + if (cis) + cflags |= REG_ICASE; + if (regcomp(®, sbuf, cflags) != 0) { + free(sbuf); + return (0); + } + free(sbuf); + } + tstart = get_timer(); - for (py = 0; py < gd->hsize + gd->sy; py++) { + if (visible_only) + window_copy_visible_lines(data, &start, &end); + else { + start = 0; + end = gd->hsize + gd->sy; + stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT; + } + +again: + free(data->searchmark); + data->searchmark = xcalloc(gd->sx, gd->sy); + data->searchgen = 1; + + for (py = start; py < end; py++) { px = 0; for (;;) { if (regex) { found = window_copy_search_lr_regex(gd, - ssp->grid, &px, &width, py, px, - gd->sx, cis); + &px, &width, py, px, gd->sx, ®); if (!found) break; - } - else { + } else { found = window_copy_search_lr(gd, ssp->grid, - &px, py, px, gd->sx, cis); + &px, py, px, gd->sx, cis); if (!found) break; } - nfound++; - if (px == data->cx && py == gd->hsize + data->cy - data->oy) - which = nfound; - b = (py * gd->sx) + px; - bit_nset(data->searchmark, b, b + width - 1); + if (window_copy_search_mark_at(data, px, py, &b) == 0) { + if (b + width > gd->sx * gd->sy) + width = (gd->sx * gd->sy) - b; + for (i = b; i < b + width; i++) { + if (data->searchmark[i] != 0) + continue; + data->searchmark[i] = data->searchgen; + } + if (data->searchgen == UCHAR_MAX) + data->searchgen = 1; + else + data->searchgen++; + } + px += width; + } - px++; + t = get_timer(); + if (t - tstart > WINDOW_COPY_SEARCH_TIMEOUT) { + data->timeout = 1; + break; + } + if (stop != 0 && t > stop) { + stopped = 1; + break; + } + } + if (data->timeout) { + window_copy_clear_marks(wme); + goto out; + } + + if (stopped && stop != 0) { + /* Try again but just the visible context. */ + window_copy_visible_lines(data, &start, &end); + stop = 0; + goto again; + } + + if (!visible_only) { + if (stopped) { + if (nfound > 1000) + data->searchcount = 1000; + else if (nfound > 100) + data->searchcount = 100; + else if (nfound > 10) + data->searchcount = 10; + else + data->searchcount = -1; + data->searchmore = 1; + } else { + data->searchcount = nfound; + data->searchmore = 0; } } - if (which != -1) - data->searchthis = 1 + nfound - which; - else - data->searchthis = -1; - data->searchcount = nfound; - +out: if (ssp == &ss) screen_free(&ss); - return (nfound); + if (regex) + regfree(®); + return (1); } static void @@ -2718,10 +3866,166 @@ window_copy_goto_line(struct window_mode_entry *wme, const char *linestr) lineno = screen_hsize(data->backing); data->oy = lineno; - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 0); window_copy_redraw_screen(wme); } +static void +window_copy_match_start_end(struct window_copy_mode_data *data, u_int at, + u_int *start, u_int *end) +{ + struct grid *gd = data->backing->grid; + u_int last = (gd->sy * gd->sx) - 1; + u_char mark = data->searchmark[at]; + + *start = *end = at; + while (*start != 0 && data->searchmark[*start] == mark) + (*start)--; + if (data->searchmark[*start] != mark) + (*start)++; + while (*end != last && data->searchmark[*end] == mark) + (*end)++; + if (data->searchmark[*end] != mark) + (*end)--; +} + +static char * +window_copy_match_at_cursor(struct window_copy_mode_data *data) +{ + struct grid *gd = data->backing->grid; + struct grid_cell gc; + u_int at, start, end, cy, px, py; + u_int sx = screen_size_x(data->backing); + char *buf = NULL; + size_t len = 0; + + if (data->searchmark == NULL) + return (NULL); + + cy = screen_hsize(data->backing) - data->oy + data->cy; + if (window_copy_search_mark_at(data, data->cx, cy, &at) != 0) + return (NULL); + if (data->searchmark[at] == 0) { + /* Allow one position after the match. */ + if (at == 0 || data->searchmark[--at] == 0) + return (NULL); + } + window_copy_match_start_end(data, at, &start, &end); + + /* + * Cells will not be set in the marked array unless they are valid text + * and wrapping will be taken care of, so we can just copy. + */ + for (at = start; at <= end; at++) { + py = at / sx; + px = at - (py * sx); + + grid_get_cell(gd, px, gd->hsize + py - data->oy, &gc); + buf = xrealloc(buf, len + gc.data.size + 1); + memcpy(buf + len, gc.data.data, gc.data.size); + len += gc.data.size; + } + if (len != 0) + buf[len] = '\0'; + return (buf); +} + +static void +window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy, + struct grid_cell *gc, const struct grid_cell *mgc, + const struct grid_cell *cgc, const struct grid_cell *mkgc) +{ + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; + u_int mark, start, end, cy, cursor, current; + int inv = 0, found = 0; + int keys; + + if (data->showmark && fy == data->my) { + gc->attr = mkgc->attr; + if (fx == data->mx) + inv = 1; + if (inv) { + gc->fg = mkgc->bg; + gc->bg = mkgc->fg; + } + else { + gc->fg = mkgc->fg; + gc->bg = mkgc->bg; + } + } + + if (data->searchmark == NULL) + return; + + if (window_copy_search_mark_at(data, fx, fy, ¤t) != 0) + return; + mark = data->searchmark[current]; + if (mark == 0) + return; + + cy = screen_hsize(data->backing) - data->oy + data->cy; + if (window_copy_search_mark_at(data, data->cx, cy, &cursor) == 0) { + keys = options_get_number(wp->window->options, "mode-keys"); + if (cursor != 0 && + keys == MODEKEY_EMACS && + data->searchdirection) { + if (data->searchmark[cursor - 1] == mark) { + cursor--; + found = 1; + } + } else if (data->searchmark[cursor] == mark) + found = 1; + if (found) { + window_copy_match_start_end(data, cursor, &start, &end); + if (current >= start && current <= end) { + gc->attr = cgc->attr; + if (inv) { + gc->fg = cgc->bg; + gc->bg = cgc->fg; + } + else { + gc->fg = cgc->fg; + gc->bg = cgc->bg; + } + return; + } + } + } + + gc->attr = mgc->attr; + if (inv) { + gc->fg = mgc->bg; + gc->bg = mgc->fg; + } + else { + gc->fg = mgc->fg; + gc->bg = mgc->bg; + } +} + +static void +window_copy_write_one(struct window_mode_entry *wme, + struct screen_write_ctx *ctx, u_int py, u_int fy, u_int nx, + const struct grid_cell *mgc, const struct grid_cell *cgc, + const struct grid_cell *mkgc) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + struct grid_cell gc; + u_int fx; + + screen_write_cursormove(ctx, 0, py, 0); + for (fx = 0; fx < nx; fx++) { + grid_get_cell(gd, fx, fy, &gc); + if (fx + gc.data.width <= nx) { + window_copy_update_style(wme, fx, fy, &gc, mgc, cgc, + mkgc); + screen_write_cell(ctx, &gc); + } + } +} + static void window_copy_write_line(struct window_mode_entry *wme, struct screen_write_ctx *ctx, u_int py) @@ -2730,27 +4034,38 @@ window_copy_write_line(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; - struct grid_cell gc; + struct grid_cell gc, mgc, cgc, mkgc; char hdr[512]; size_t size = 0; + u_int hsize = screen_hsize(data->backing); - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); gc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&mgc, oo, "copy-mode-match-style", NULL); + mgc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&cgc, oo, "copy-mode-current-match-style", NULL); + cgc.flags |= GRID_FLAG_NOPALETTE; + style_apply(&mkgc, oo, "copy-mode-mark-style", NULL); + mkgc.flags |= GRID_FLAG_NOPALETTE; - if (py == 0 && s->rupper < s->rlower) { + if (py == 0 && s->rupper < s->rlower && !data->hide_position) { if (data->searchmark == NULL) { - size = xsnprintf(hdr, sizeof hdr, - "[%u/%u]", data->oy, screen_hsize(data->backing)); - } else { - if (data->searchthis == -1) { + if (data->timeout) { size = xsnprintf(hdr, sizeof hdr, - "(%u results) [%d/%u]", data->searchcount, - data->oy, screen_hsize(data->backing)); + "(timed out) [%u/%u]", data->oy, hsize); } else { size = xsnprintf(hdr, sizeof hdr, - "(%u/%u results) [%d/%u]", data->searchthis, - data->searchcount, data->oy, - screen_hsize(data->backing)); + "[%u/%u]", data->oy, hsize); + } + } else { + if (data->searchcount == -1) { + size = xsnprintf(hdr, sizeof hdr, + "[%u/%u]", data->oy, hsize); + } else { + size = xsnprintf(hdr, sizeof hdr, + "(%d%s results) [%u/%u]", data->searchcount, + data->searchmore ? "+" : "", data->oy, + hsize); } } if (size > screen_size_x(s)) @@ -2761,16 +4076,13 @@ window_copy_write_line(struct window_mode_entry *wme, size = 0; if (size < screen_size_x(s)) { - screen_write_cursormove(ctx, 0, py, 0); - screen_write_copy(ctx, data->backing, 0, - (screen_hsize(data->backing) - data->oy) + py, - screen_size_x(s) - size, 1, data->searchmark, &gc); + window_copy_write_one(wme, ctx, py, hsize - data->oy + py, + screen_size_x(s) - size, &mgc, &cgc, &mkgc); } if (py == data->cy && data->cx == screen_size_x(s)) { - memcpy(&gc, &grid_default_cell, sizeof gc); screen_write_cursormove(ctx, screen_size_x(s) - 1, py, 0); - screen_write_putc(ctx, &gc, '$'); + screen_write_putc(ctx, &grid_default_cell, '$'); } } @@ -2788,6 +4100,7 @@ static void window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y) { struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; u_int new_y, start, end; new_y = data->cy; @@ -2798,6 +4111,16 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y) start = new_y; end = old_y; } + + /* + * In word selection mode the first word on the line below the cursor + * might be selected, so add this line to the redraw area. + */ + if (data->selflag == SEL_WORD) { + /* Last grid line in data coordinates. */ + if (end < gd->sy + data->oy - 1) + end++; + } window_copy_redraw_lines(wme, start, end - start + 1); } @@ -2809,7 +4132,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) struct screen_write_ctx ctx; u_int i; - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); for (i = py; i < py + ny; i++) window_copy_write_line(wme, &ctx, i); screen_write_cursormove(&ctx, data->cx, data->cy, 0); @@ -2825,22 +4148,87 @@ window_copy_redraw_screen(struct window_mode_entry *wme) } static void -window_copy_synchronize_cursor(struct window_mode_entry *wme) +window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin, + int no_reset) { struct window_copy_mode_data *data = wme->data; u_int xx, yy; xx = data->cx; yy = screen_hsize(data->backing) + data->cy - data->oy; + switch (data->selflag) { + case SEL_WORD: + if (no_reset) + break; + begin = 0; + if (data->dy > yy || (data->dy == yy && data->dx > xx)) { + /* Right to left selection. */ + window_copy_cursor_previous_word_pos(wme, + data->separators, &xx, &yy); + begin = 1; + + /* Reset the end. */ + data->endselx = data->endselrx; + data->endsely = data->endselry; + } else { + /* Left to right selection. */ + if (xx >= window_copy_find_length(wme, yy) || + !window_copy_in_set(wme, xx + 1, yy, WHITESPACE)) { + window_copy_cursor_next_word_end_pos(wme, + data->separators, &xx, &yy); + } + + /* Reset the start. */ + data->selx = data->selrx; + data->sely = data->selry; + } + break; + case SEL_LINE: + if (no_reset) + break; + begin = 0; + if (data->dy > yy) { + /* Right to left selection. */ + xx = 0; + begin = 1; + + /* Reset the end. */ + data->endselx = data->endselrx; + data->endsely = data->endselry; + } else { + /* Left to right selection. */ + if (yy < data->endselry) + yy = data->endselry; + xx = window_copy_find_length(wme, yy); + + /* Reset the start. */ + data->selx = data->selrx; + data->sely = data->selry; + } + break; + case SEL_CHAR: + break; + } + if (begin) { + data->selx = xx; + data->sely = yy; + } else { + data->endselx = xx; + data->endsely = yy; + } +} + +static void +window_copy_synchronize_cursor(struct window_mode_entry *wme, int no_reset) +{ + struct window_copy_mode_data *data = wme->data; switch (data->cursordrag) { case CURSORDRAG_ENDSEL: - data->endselx = xx; - data->endsely = yy; + window_copy_synchronize_cursor_end(wme, 0, no_reset); break; case CURSORDRAG_SEL: - data->selx = xx; - data->sely = yy; + window_copy_synchronize_cursor_end(wme, 1, no_reset); break; case CURSORDRAG_NONE: break; @@ -2863,7 +4251,7 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) if (data->cx == screen_size_x(s)) window_copy_redraw_lines(wme, data->cy, 1); else { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } @@ -2882,7 +4270,7 @@ window_copy_start_selection(struct window_mode_entry *wme) data->cursordrag = CURSORDRAG_ENDSEL; - window_copy_set_selection(wme, 1); + window_copy_set_selection(wme, 1, 0); } static int @@ -2919,18 +4307,20 @@ window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx, } static int -window_copy_update_selection(struct window_mode_entry *wme, int may_redraw) +window_copy_update_selection(struct window_mode_entry *wme, int may_redraw, + int no_reset) { struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; if (s->sel == NULL && data->lineflag == LINE_SEL_NONE) return (0); - return (window_copy_set_selection(wme, may_redraw)); + return (window_copy_set_selection(wme, may_redraw, no_reset)); } static int -window_copy_set_selection(struct window_mode_entry *wme, int may_redraw) +window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, + int no_reset) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; @@ -2940,7 +4330,7 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw) u_int sx, sy, cy, endsx, endsy; int startrelpos, endrelpos; - window_copy_synchronize_cursor(wme); + window_copy_synchronize_cursor(wme, no_reset); /* Adjust the selection. */ sx = data->selx; @@ -2960,7 +4350,7 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw) } /* Set colours and selection. */ - style_apply(&gc, oo, "mode-style"); + style_apply(&gc, oo, "mode-style", NULL); gc.flags |= GRID_FLAG_NOPALETTE; screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag, data->modekeys, &gc); @@ -3003,8 +4393,14 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) u_int firstsx, lastex, restex, restsx, selx; int keys; - if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) - return (NULL); + if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) { + buf = window_copy_match_at_cursor(data); + if (buf != NULL) + *len = strlen(buf); + else + *len = 0; + return (buf); + } buf = xmalloc(1); off = 0; @@ -3094,10 +4490,15 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) /* Don't bother if no data. */ if (off == 0) { free(buf); + *len = 0; return (NULL); } - if (keys == MODEKEY_EMACS || lastex <= ey_last) - off -= 1; /* remove final \n (unless at end in vi mode) */ + /* Remove final \n (unless at end in vi mode). */ + if (keys == MODEKEY_EMACS || lastex <= ey_last) { + if (~grid_get_line(data->backing->grid, ey)->flags & + GRID_LINE_WRAPPED || lastex != ey_last) + off -= 1; + } *len = off; return (buf); } @@ -3110,7 +4511,7 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, struct screen_write_ctx ctx; if (options_get_number(global_options, "set-clipboard") != 0) { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -3119,21 +4520,43 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, paste_add(prefix, buf, len); } -static void -window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, - const char *prefix, const char *command) +static void * +window_copy_pipe_run(struct window_mode_entry *wme, struct session *s, + const char *cmd, size_t *len) { void *buf; - size_t len; struct job *job; - buf = window_copy_get_selection(wme, &len); - if (buf == NULL) - return; + buf = window_copy_get_selection(wme, len); + if (cmd == NULL || *cmd == '\0') + cmd = options_get_string(global_options, "copy-command"); + if (cmd != NULL && *cmd != '\0') { + job = job_run(cmd, 0, NULL, s, NULL, NULL, NULL, NULL, NULL, + JOB_NOWAIT, -1, -1); + bufferevent_write(job_get_event(job), buf, *len); + } + return (buf); +} - job = job_run(command, s, NULL, NULL, NULL, NULL, NULL, JOB_NOWAIT); - bufferevent_write(job_get_event(job), buf, len); - window_copy_copy_buffer(wme, prefix, buf, len); +static void +window_copy_pipe(struct window_mode_entry *wme, struct session *s, + const char *cmd) +{ + size_t len; + + window_copy_pipe_run(wme, s, cmd, &len); +} + +static void +window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, + const char *prefix, const char *cmd) +{ + void *buf; + size_t len; + + buf = window_copy_pipe_run(wme, s, cmd, &len); + if (buf != NULL) + window_copy_copy_buffer(wme, prefix, buf, len); } static void @@ -3162,7 +4585,7 @@ window_copy_append_selection(struct window_mode_entry *wme) return; if (options_get_number(global_options, "set-clipboard") != 0) { - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, buf, len); screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); @@ -3250,6 +4673,7 @@ window_copy_clear_selection(struct window_mode_entry *wme) data->cursordrag = CURSORDRAG_NONE; data->lineflag = LINE_SEL_NONE; + data->selflag = SEL_CHAR; py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); @@ -3283,43 +4707,37 @@ window_copy_cursor_start_of_line(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid *gd = back_s->grid; - u_int py; + struct grid_reader gr; + u_int px, py, oldy, hsize; - if (data->cx == 0 && data->lineflag == LINE_SEL_NONE) { - py = screen_hsize(back_s) + data->cy - data->oy; - while (py > 0 && - grid_get_line(gd, py - 1)->flags & GRID_LINE_WRAPPED) { - window_copy_cursor_up(wme, 0); - py = screen_hsize(back_s) + data->cy - data->oy; - } - } - window_copy_update_cursor(wme, 0, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_start_of_line(&gr, 1); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void window_copy_cursor_back_to_indentation(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; - u_int px, py, xx; - struct grid_cell gc; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, oldy, hsize; - px = 0; - py = screen_hsize(data->backing) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - while (px < xx) { - grid_get_cell(data->backing->grid, px, py, &gc); - if (gc.data.size != 1 || *gc.data.data != ' ') - break; - px++; - } - - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_back_to_indentation(&gr); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void @@ -3327,32 +4745,22 @@ window_copy_cursor_end_of_line(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid *gd = back_s->grid; - struct grid_line *gl; - u_int px, py; + struct grid_reader gr; + u_int px, py, oldy, hsize; - py = screen_hsize(back_s) + data->cy - data->oy; - px = window_copy_find_length(wme, py); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - if (data->cx == px && data->lineflag == LINE_SEL_NONE) { - if (data->screen.sel != NULL && data->rectflag) - px = screen_size_x(back_s); - gl = grid_get_line(gd, py); - if (gl->flags & GRID_LINE_WRAPPED) { - while (py < gd->sy + gd->hsize) { - gl = grid_get_line(gd, py); - if (~gl->flags & GRID_LINE_WRAPPED) - break; - window_copy_cursor_down(wme, 0); - py = screen_hsize(back_s) + data->cy - data->oy; - } - px = window_copy_find_length(wme, py); - } - } - window_copy_update_cursor(wme, px, data->cy); - - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); + grid_reader_start(&gr, back_s->grid, px, py); + if (data->screen.sel != NULL && data->rectflag) + grid_reader_cursor_end_of_line(&gr, 1, 1); + else + grid_reader_cursor_end_of_line(&gr, 1, 0); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } static void @@ -3402,7 +4810,7 @@ window_copy_other_end(struct window_mode_entry *wme) } else data->cy = cy + sely - yy; - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 1); window_copy_redraw_screen(wme); } @@ -3410,57 +4818,39 @@ static void window_copy_cursor_left(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; - u_int py, cx; - struct grid_cell gc; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, oldy, hsize; - py = screen_hsize(data->backing) + data->cy - data->oy; - cx = data->cx; - while (cx > 0) { - grid_get_cell(data->backing->grid, cx, py, &gc); - if (~gc.flags & GRID_FLAG_PADDING) - break; - cx--; - } - if (cx == 0 && py > 0) { - window_copy_cursor_up(wme, 0); - window_copy_cursor_end_of_line(wme); - } else if (cx > 0) { - window_copy_update_cursor(wme, cx - 1, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - } + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_left(&gr, 1); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void -window_copy_cursor_right(struct window_mode_entry *wme) +window_copy_cursor_right(struct window_mode_entry *wme, int all) { struct window_copy_mode_data *data = wme->data; - u_int px, py, yy, cx, cy; - struct grid_cell gc; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, oldy, hsize; - py = screen_hsize(data->backing) + data->cy - data->oy; - yy = screen_hsize(data->backing) + screen_size_y(data->backing) - 1; - if (data->screen.sel != NULL && data->rectflag) - px = screen_size_x(&data->screen); - else - px = window_copy_find_length(wme, py); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - if (data->cx >= px && py < yy) { - window_copy_cursor_start_of_line(wme); - window_copy_cursor_down(wme, 0); - } else if (data->cx < px) { - cx = data->cx + 1; - cy = screen_hsize(data->backing) + data->cy - data->oy; - while (cx < px) { - grid_get_cell(data->backing->grid, cx, cy, &gc); - if (~gc.flags & GRID_FLAG_PADDING) - break; - cx++; - } - window_copy_update_cursor(wme, cx, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - } + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_right(&gr, 1, all); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } static void @@ -3469,10 +4859,12 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; + int norectsel; + norectsel = data->screen.sel == NULL || !data->rectflag; oy = screen_hsize(data->backing) + data->cy - data->oy; ox = window_copy_find_length(wme, oy); - if (data->cx != ox) { + if (norectsel && data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } @@ -3481,7 +4873,8 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_other_end(wme); if (scroll_only || data->cy == 0) { - data->cx = data->lastcx; + if (norectsel) + data->cx = data->lastcx; window_copy_scroll_down(wme, 1); if (scroll_only) { if (data->cy == screen_size_y(s) - 1) @@ -3490,8 +4883,12 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) window_copy_redraw_lines(wme, data->cy, 2); } } else { - window_copy_update_cursor(wme, data->lastcx, data->cy - 1); - if (window_copy_update_selection(wme, 1)) { + if (norectsel) { + window_copy_update_cursor(wme, data->lastcx, + data->cy - 1); + } else + 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); else @@ -3499,18 +4896,35 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) } } - if (data->screen.sel == NULL || !data->rectflag) { + if (norectsel) { py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wme); + { + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } if (data->lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wme); + { + py = screen_hsize(data->backing) + data->cy - data->oy; + if (data->rectflag) + px = screen_size_x(data->backing); + else + 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); + } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wme); + { + window_copy_update_cursor(wme, 0, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } static void @@ -3519,10 +4933,12 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; + int norectsel; + norectsel = data->screen.sel == NULL || !data->rectflag; oy = screen_hsize(data->backing) + data->cy - data->oy; ox = window_copy_find_length(wme, oy); - if (data->cx != ox) { + if (norectsel && data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } @@ -3531,28 +4947,50 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) window_copy_other_end(wme); if (scroll_only || data->cy == screen_size_y(s) - 1) { - data->cx = data->lastcx; + if (norectsel) + 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); } else { - window_copy_update_cursor(wme, data->lastcx, data->cy + 1); - if (window_copy_update_selection(wme, 1)) + if (norectsel) { + window_copy_update_cursor(wme, data->lastcx, + data->cy + 1); + } 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); } - if (data->screen.sel == NULL || !data->rectflag) { + if (norectsel) { py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wme); + { + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } if (data->lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wme); + { + py = screen_hsize(data->backing) + data->cy - data->oy; + if (data->rectflag) + px = screen_size_x(data->backing); + else + 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); + } else if (data->lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wme); + { + window_copy_update_cursor(wme, 0, data->cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, data->cy, 1); + } } static void @@ -3560,23 +4998,19 @@ window_copy_cursor_jump(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py, xx; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx + 1; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - while (px < xx) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - px++; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump(&gr, data->jumpchar)) { + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, + screen_size_y(back_s), data->oy, oldy, px, py, 0); } } @@ -3585,27 +5019,20 @@ window_copy_cursor_jump_back(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - if (px > 0) - px--; - - for (;;) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - if (px == 0) - break; - px--; + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_left(&gr, 0); + if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) { + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, + py); } } @@ -3614,23 +5041,20 @@ window_copy_cursor_jump_to(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py, xx; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx + 2; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - while (px < xx) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px - 1, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - px++; + grid_reader_start(&gr, back_s->grid, px, py); + if (grid_reader_cursor_jump(&gr, data->jumpchar)) { + grid_reader_cursor_left(&gr, 1); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, + screen_size_y(back_s), data->oy, oldy, px, py, 0); } } @@ -3639,30 +5063,22 @@ window_copy_cursor_jump_to_back(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct grid_cell gc; - u_int px, py; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - if (px > 0) - px--; - - if (px > 0) - px--; - - for (;;) { - grid_get_cell(back_s->grid, px, py, &gc); - if (!(gc.flags & GRID_FLAG_PADDING) && - gc.data.size == 1 && *gc.data.data == data->jumpchar) { - window_copy_update_cursor(wme, px + 1, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); - return; - } - if (px == 0) - break; - px--; + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_left(&gr, 0); + grid_reader_cursor_left(&gr, 0); + if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) { + grid_reader_cursor_right(&gr, 1, 0); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, + py); } } @@ -3672,93 +5088,100 @@ window_copy_cursor_next_word(struct window_mode_entry *wme, { struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - u_int px, py, xx, yy; - int expected = 0; + struct grid_reader gr; + u_int px, py, oldy, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - /* - * First skip past any nonword characters and then any word characters. - * - * expected is initially set to 0 for the former and then 1 for the - * latter. - */ - do { - while (px > xx || - window_copy_in_set(wme, px, py, separators) == expected) { - /* Move down if we're past the end of the line. */ - if (px > xx) { - if (py == yy) - return; - window_copy_cursor_down(wme, 0); - px = 0; - - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - } else - px++; - } - expected = !expected; - } while (expected == 1); - - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_next_word(&gr, separators); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, 0); } +/* Compute the next place where a word ends. */ static void -window_copy_cursor_next_word_end(struct window_mode_entry *wme, - const char *separators) +window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme, + const char *separators, u_int *ppx, u_int *ppy) { struct window_pane *wp = wme->wp; struct window_copy_mode_data *data = wme->data; struct options *oo = wp->window->options; struct screen *back_s = data->backing; - u_int px, py, xx, yy; - int keys, expected = 1; + struct grid_reader gr; + u_int px, py, hsize; px = data->cx; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; - keys = options_get_number(oo, "mode-keys"); - if (keys == MODEKEY_VI && !window_copy_in_set(wme, px, py, separators)) - px++; + grid_reader_start(&gr, back_s->grid, px, py); + if (options_get_number(oo, "mode-keys") == MODEKEY_VI) { + if (!grid_reader_in_set(&gr, WHITESPACE)) + grid_reader_cursor_right(&gr, 0, 0); + grid_reader_cursor_next_word_end(&gr, separators); + grid_reader_cursor_left(&gr, 1); + } else + grid_reader_cursor_next_word_end(&gr, separators); + grid_reader_get_cursor(&gr, &px, &py); + *ppx = px; + *ppy = py; +} - /* - * First skip past any word characters, then any nonword characters. - * - * expected is initially set to 1 for the former and then 0 for the - * latter. - */ - do { - while (px > xx || - window_copy_in_set(wme, px, py, separators) == expected) { - /* Move down if we're past the end of the line. */ - if (px > xx) { - if (py == yy) - return; - window_copy_cursor_down(wme, 0); - px = 0; +/* Move to the next place where a word ends. */ +static void +window_copy_cursor_next_word_end(struct window_mode_entry *wme, + const char *separators, int no_reset) +{ + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; + struct options *oo = wp->window->options; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, oldy, hsize; - py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wme, py); - } else - px++; - } - expected = !expected; - } while (expected == 0); + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - if (keys == MODEKEY_VI && px != 0) - px--; + grid_reader_start(&gr, back_s->grid, px, py); + if (options_get_number(oo, "mode-keys") == MODEKEY_VI) { + if (!grid_reader_in_set(&gr, WHITESPACE)) + grid_reader_cursor_right(&gr, 0, 0); + grid_reader_cursor_next_word_end(&gr, separators); + grid_reader_cursor_left(&gr, 1); + } else + grid_reader_cursor_next_word_end(&gr, separators); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s), + data->oy, oldy, px, py, no_reset); +} - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); +/* Compute the previous place where a word begins. */ +static void +window_copy_cursor_previous_word_pos(struct window_mode_entry *wme, + const char *separators, u_int *ppx, u_int *ppy) +{ + struct window_copy_mode_data *data = wme->data; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, hsize; + + px = data->cx; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_previous_word(&gr, separators, /* already= */ 0, + /* stop_at_eol= */ 1); + grid_reader_get_cursor(&gr, &px, &py); + *ppx = px; + *ppy = py; } /* Move to the previous place where a word begins. */ @@ -3767,44 +5190,26 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme, const char *separators, int already) { struct window_copy_mode_data *data = wme->data; - u_int px, py; + struct window *w = wme->wp->window; + struct screen *back_s = data->backing; + struct grid_reader gr; + u_int px, py, oldy, hsize; + int stop_at_eol; + + if (options_get_number(w->options, "mode-keys") == MODEKEY_EMACS) + stop_at_eol = 1; + else + stop_at_eol = 0; px = data->cx; - py = screen_hsize(data->backing) + data->cy - data->oy; + hsize = screen_hsize(back_s); + py = hsize + data->cy - data->oy; + oldy = data->cy; - /* Move back to the previous word character. */ - if (already || window_copy_in_set(wme, px, py, separators)) { - for (;;) { - if (px > 0) { - px--; - if (!window_copy_in_set(wme, px, py, separators)) - break; - } else { - if (data->cy == 0 && - (screen_hsize(data->backing) == 0 || - data->oy >= screen_hsize(data->backing) - 1)) - goto out; - window_copy_cursor_up(wme, 0); - - py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wme, py); - - /* Stop if separator at EOL. */ - if (px > 0 && - window_copy_in_set(wme, px - 1, py, separators)) - break; - } - } - } - - /* Move back to the beginning of this word. */ - while (px > 0 && !window_copy_in_set(wme, px - 1, py, separators)) - px--; - -out: - window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wme, 1)) - window_copy_redraw_lines(wme, data->cy, 1); + grid_reader_start(&gr, back_s->grid, px, py); + grid_reader_cursor_previous_word(&gr, separators, already, stop_at_eol); + grid_reader_get_cursor(&gr, &px, &py); + window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py); } static void @@ -3821,9 +5226,11 @@ window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) return; data->oy -= ny; - window_copy_update_selection(wme, 0); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 0, 0); - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); 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); @@ -3855,9 +5262,11 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) return; data->oy += ny; - window_copy_update_selection(wme, 0); + if (data->searchmark != NULL && !data->timeout) + window_copy_search_marks(wme, NULL, data->searchregex, 1); + window_copy_update_selection(wme, 0, 0); - screen_write_start(&ctx, wp, NULL); + screen_write_start_pane(&ctx, wp, NULL); screen_write_cursormove(&ctx, 0, 0, 0); screen_write_insertline(&ctx, ny, 8); window_copy_write_lines(wme, &ctx, 0, ny); @@ -3870,19 +5279,19 @@ window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) } static void -window_copy_rectangle_toggle(struct window_mode_entry *wme) +window_copy_rectangle_set(struct window_mode_entry *wme, int rectflag) { struct window_copy_mode_data *data = wme->data; u_int px, py; - data->rectflag = !data->rectflag; + data->rectflag = rectflag; py = screen_hsize(data->backing) + data->cy - data->oy; px = window_copy_find_length(wme, py); if (data->cx > px) window_copy_update_cursor(wme, px, data->cy); - window_copy_update_selection(wme, 1); + window_copy_update_selection(wme, 1, 0); window_copy_redraw_screen(wme); } @@ -3913,7 +5322,8 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) { struct window_pane *wp; struct window_mode_entry *wme; - u_int x, y; + struct window_copy_mode_data *data; + u_int x, y, yg; if (c == NULL) return; @@ -3933,10 +5343,30 @@ window_copy_start_drag(struct client *c, struct mouse_event *m) c->tty.mouse_drag_update = window_copy_drag_update; c->tty.mouse_drag_release = window_copy_drag_release; - window_copy_update_cursor(wme, x, y); - window_copy_start_selection(wme); - window_copy_redraw_screen(wme); + data = wme->data; + yg = screen_hsize(data->backing) + y - data->oy; + if (x < data->selrx || x > data->endselrx || yg != data->selry) + data->selflag = SEL_CHAR; + switch (data->selflag) { + case SEL_WORD: + if (data->separators != NULL) { + window_copy_update_cursor(wme, x, y); + window_copy_cursor_previous_word_pos(wme, + data->separators, &x, &y); + y -= screen_hsize(data->backing) - data->oy; + } + window_copy_update_cursor(wme, x, y); + break; + case SEL_LINE: + window_copy_update_cursor(wme, 0, y); + break; + case SEL_CHAR: + window_copy_update_cursor(wme, x, y); + window_copy_start_selection(wme); + break; + } + window_copy_redraw_screen(wme); window_copy_drag_update(c, m); } @@ -3972,7 +5402,7 @@ window_copy_drag_update(struct client *c, struct mouse_event *m) old_cy = data->cy; window_copy_update_cursor(wme, x, y); - if (window_copy_update_selection(wme, 1)) + if (window_copy_update_selection(wme, 1, 0)) window_copy_redraw_selection(wme, old_cy); if (old_cy != data->cy || old_cx == data->cx) { if (y == 0) { @@ -4007,3 +5437,81 @@ window_copy_drag_release(struct client *c, struct mouse_event *m) data = wme->data; evtimer_del(&data->dragtimer); } + +static void +window_copy_jump_to_mark(struct window_mode_entry *wme) +{ + struct window_copy_mode_data *data = wme->data; + u_int tmx, tmy; + + tmx = data->cx; + tmy = screen_hsize(data->backing) + data->cy - data->oy; + data->cx = data->mx; + if (data->my < screen_hsize(data->backing)) { + data->cy = 0; + data->oy = screen_hsize(data->backing) - data->my; + } else { + data->cy = data->my - screen_hsize(data->backing); + data->oy = 0; + } + data->mx = tmx; + data->my = tmy; + data->showmark = 1; + window_copy_update_selection(wme, 0, 0); + window_copy_redraw_screen(wme); +} + +/* Scroll up if the cursor went off the visible screen. */ +static void +window_copy_acquire_cursor_up(struct window_mode_entry *wme, u_int hsize, + u_int oy, u_int oldy, u_int px, u_int py) +{ + u_int cy, yy, ny, nd; + + yy = hsize - oy; + if (py < yy) { + ny = yy - py; + cy = 0; + nd = 1; + } else { + ny = 0; + cy = py - yy; + nd = oldy - cy + 1; + } + while (ny > 0) { + window_copy_cursor_up(wme, 1); + ny--; + } + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, 0)) + window_copy_redraw_lines(wme, cy, nd); +} + +/* Scroll down if the cursor went off the visible screen. */ +static void +window_copy_acquire_cursor_down(struct window_mode_entry *wme, u_int hsize, + u_int sy, u_int oy, u_int oldy, u_int px, u_int py, int no_reset) +{ + u_int cy, yy, ny, nd; + + cy = py - hsize + oy; + yy = sy - 1; + if (cy > yy) { + ny = cy - yy; + oldy = yy; + nd = 1; + } else { + ny = 0; + nd = cy - oldy + 1; + } + while (ny > 0) { + window_copy_cursor_down(wme, 1); + ny--; + } + if (cy > yy) + window_copy_update_cursor(wme, px, yy); + else + window_copy_update_cursor(wme, px, cy); + if (window_copy_update_selection(wme, 1, no_reset)) + window_copy_redraw_lines(wme, oldy, nd); +} diff --git a/window-customize.c b/window-customize.c new file mode 100644 index 00000000..0f09eba8 --- /dev/null +++ b/window-customize.c @@ -0,0 +1,1514 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 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 + +#include "tmux.h" + +static struct screen *window_customize_init(struct window_mode_entry *, + 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_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); + +#define WINDOW_CUSTOMIZE_DEFAULT_FORMAT \ + "#{?is_option," \ + "#{?option_is_global,,#[reverse](#{option_scope})#[default] }" \ + "#[ignore]" \ + "#{option_value}#{?option_unit, #{option_unit},}" \ + "," \ + "#{key}" \ + "}" + +static const struct menu_item window_customize_menu_items[] = { + { "Select", '\r', NULL }, + { "Expand", KEYC_RIGHT, NULL }, + { "", KEYC_NONE, NULL }, + { "Tag", 't', NULL }, + { "Tag All", '\024', NULL }, + { "Tag None", 'T', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + +const struct window_mode window_customize_mode = { + .name = "options-mode", + .default_format = WINDOW_CUSTOMIZE_DEFAULT_FORMAT, + + .init = window_customize_init, + .free = window_customize_free, + .resize = window_customize_resize, + .key = window_customize_key, +}; + +enum window_customize_scope { + WINDOW_CUSTOMIZE_NONE, + WINDOW_CUSTOMIZE_KEY, + WINDOW_CUSTOMIZE_SERVER, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, + WINDOW_CUSTOMIZE_SESSION, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, + WINDOW_CUSTOMIZE_WINDOW, + WINDOW_CUSTOMIZE_PANE +}; + +enum window_customize_change { + WINDOW_CUSTOMIZE_UNSET, + WINDOW_CUSTOMIZE_RESET, +}; + +struct window_customize_itemdata { + struct window_customize_modedata *data; + enum window_customize_scope scope; + + char *table; + key_code key; + + struct options *oo; + char *name; + int idx; +}; + +struct window_customize_modedata { + struct window_pane *wp; + int dead; + int references; + + struct mode_tree_data *data; + char *format; + int hide_global; + + struct window_customize_itemdata **item_list; + u_int item_size; + + struct cmd_find_state fs; + enum window_customize_change change; +}; + +static uint64_t +window_customize_get_tag(struct options_entry *o, int idx, + const struct options_table_entry *oe) +{ + uint64_t offset; + + if (oe == NULL) + return ((uint64_t)o); + offset = ((char *)oe - (char *)options_table) / sizeof *options_table; + return ((2ULL << 62)|(offset << 32)|((idx + 1) << 1)|1); +} + +static struct options * +window_customize_get_tree(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + switch (scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + return (NULL); + case WINDOW_CUSTOMIZE_SERVER: + return (global_options); + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + return (global_s_options); + case WINDOW_CUSTOMIZE_SESSION: + return (fs->s->options); + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + return (global_w_options); + case WINDOW_CUSTOMIZE_WINDOW: + return (fs->w->options); + case WINDOW_CUSTOMIZE_PANE: + return (fs->wp->options); + } + return (NULL); +} + +static int +window_customize_check_item(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct cmd_find_state *fsp) +{ + struct cmd_find_state fs; + + if (fsp == NULL) + fsp = &fs; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(fsp, &data->fs); + else + cmd_find_from_pane(fsp, data->wp, 0); + return (item->oo == window_customize_get_tree(item->scope, fsp)); +} + +static int +window_customize_get_key(struct window_customize_itemdata *item, + struct key_table **ktp, struct key_binding **bdp) +{ + struct key_table *kt; + struct key_binding *bd; + + kt = key_bindings_get_table(item->table, 0); + if (kt == NULL) + return (0); + bd = key_bindings_get(kt, item->key); + if (bd == NULL) + return (0); + + if (ktp != NULL) + *ktp = kt; + if (bdp != NULL) + *bdp = bd; + return (1); +} + +static char * +window_customize_scope_text(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + char *s; + u_int idx; + + switch (scope) { + case WINDOW_CUSTOMIZE_PANE: + window_pane_index(fs->wp, &idx); + xasprintf(&s, "pane %u", idx); + break; + case WINDOW_CUSTOMIZE_SESSION: + xasprintf(&s, "session %s", fs->s->name); + break; + case WINDOW_CUSTOMIZE_WINDOW: + xasprintf(&s, "window %u", fs->wl->idx); + break; + default: + s = xstrdup(""); + break; + } + return (s); +} + +static struct window_customize_itemdata * +window_customize_add_item(struct window_customize_modedata *data) +{ + struct window_customize_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_customize_free_item(struct window_customize_itemdata *item) +{ + free(item->table); + free(item->name); + free(item); +} + +static void +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) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + struct window_customize_itemdata *item; + struct options_array_item *ai; + char *name, *value, *text; + u_int idx; + uint64_t tag; + + ai = options_array_first(o); + while (ai != NULL) { + idx = options_array_item_index(ai); + + xasprintf(&name, "%s[%u]", options_name(o), idx); + format_add(ft, "option_name", "%s", name); + value = options_to_string(o, idx, 0); + format_add(ft, "option_value", "%s", value); + + item = window_customize_add_item(data); + item->scope = scope; + item->oo = oo; + item->name = xstrdup(options_name(o)); + item->idx = idx; + + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, idx, oe); + mode_tree_add(data->data, top, item, tag, name, text, -1); + free(text); + + free(name); + free(value); + + ai = options_array_next(ai); + } +} + +static void +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 struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + const char *name = options_name(o); + struct window_customize_itemdata *item; + char *text, *expanded, *value; + int global = 0, array = 0; + uint64_t tag; + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)) + return; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) + array = 1; + + if (scope == WINDOW_CUSTOMIZE_SERVER || + scope == WINDOW_CUSTOMIZE_GLOBAL_SESSION || + scope == WINDOW_CUSTOMIZE_GLOBAL_WINDOW) + global = 1; + if (data->hide_global && global) + return; + + format_add(ft, "option_name", "%s", name); + format_add(ft, "option_is_global", "%d", global); + format_add(ft, "option_is_array", "%d", array); + + text = window_customize_scope_text(scope, fs); + format_add(ft, "option_scope", "%s", text); + free(text); + + if (oe != NULL && oe->unit != NULL) + format_add(ft, "option_unit", "%s", oe->unit); + else + format_add(ft, "option_unit", "%s", ""); + + if (!array) { + value = options_to_string(o, -1, 0); + format_add(ft, "option_value", "%s", value); + free(value); + } + + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + return; + } + free(expanded); + } + item = window_customize_add_item(data); + item->oo = oo; + item->scope = scope; + item->name = xstrdup(name); + item->idx = -1; + + if (array) + text = NULL; + else + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, -1, oe); + 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); +} + +static void +window_customize_find_user_options(struct options *oo, const char ***list, + u_int *size) +{ + struct options_entry *o; + const char *name; + u_int i; + + o = options_first(oo); + while (o != NULL) { + name = options_name(o); + if (*name != '@') { + o = options_next(o); + continue; + } + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], name) == 0) + break; + } + if (i != *size) { + o = options_next(o); + continue; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = name; + + o = options_next(o); + } +} + +static void +window_customize_build_options(struct window_customize_modedata *data, + const char *title, uint64_t tag, + 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 mode_tree_item *top; + struct options_entry *o = NULL, *loop; + const char **list = NULL, *name; + u_int size = 0, i; + enum window_customize_scope scope; + + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + mode_tree_no_tag(top); + + /* + * We get the options from the first tree, but build it using the + * values from the other two. Any tree can have user options so we need + * to build a separate list of them. + */ + + window_customize_find_user_options(oo0, &list, &size); + if (oo1 != NULL) + window_customize_find_user_options(oo1, &list, &size); + if (oo2 != NULL) + window_customize_find_user_options(oo2, &list, &size); + + for (i = 0; i < size; i++) { + if (oo2 != NULL) + o = options_get(oo0, list[i]); + if (o == NULL && oo1 != NULL) + o = options_get(oo1, list[i]); + if (o == NULL) + o = options_get(oo2, list[i]); + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + } + free(list); + + loop = options_first(oo0); + while (loop != NULL) { + name = options_name(loop); + if (*name == '@') { + loop = options_next(loop); + continue; + } + if (oo2 != NULL) + o = options_get(oo2, name); + else if (oo1 != NULL) + o = options_get(oo1, name); + else + o = loop; + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + loop = options_next(loop); + } +} + +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 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; + + xasprintf(&title, "Key Table - %s", kt->name); + top = mode_tree_add(data->data, NULL, NULL, tag, 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"); + + bd = key_bindings_first(kt); + while (bd != NULL) { + format_add(ft, "key", "%s", key_string_lookup_key(bd->key, 0)); + if (bd->note != NULL) + format_add(ft, "key_note", "%s", bd->note); + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + continue; + } + free(expanded); + } + + item = window_customize_add_item(data); + item->scope = WINDOW_CUSTOMIZE_KEY; + item->table = xstrdup(kt->name); + item->key = bd->key; + item->name = xstrdup(key_string_lookup_key(item->key, 0)); + item->idx = -1; + + expanded = format_expand(ft, data->format); + child = mode_tree_add(data->data, top, item, (uint64_t)bd, + expanded, NULL, 0); + free(expanded); + + tmp = cmd_list_print(bd->cmdlist, 0); + xasprintf(&text, "#[ignore]%s", tmp); + free(tmp); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(0 << 1)|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 + text = xstrdup(""); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(1 << 1)|1, "Note", text, -1); + mode_tree_draw_as_parent(mti); + mode_tree_no_tag(mti); + free(text); + + if (bd->flags & KEY_BINDING_REPEAT) + flag = "on"; + else + flag = "off"; + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(2 << 1)|1, "Repeat", flag, -1); + mode_tree_draw_as_parent(mti); + mode_tree_no_tag(mti); + + bd = key_bindings_next(kt, bd); + } + + format_free(ft); +} + +static void +window_customize_build(void *modedata, + __unused struct mode_tree_sort_criteria *sort_crit, __unused uint64_t *tag, + const char *filter) +{ + struct window_customize_modedata *data = modedata; + struct cmd_find_state fs; + struct format_tree *ft; + u_int i; + struct key_table *kt; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(&fs, &data->fs); + else + cmd_find_from_pane(&fs, data->wp, 0); + + ft = format_create_from_state(NULL, NULL, &fs); + format_add(ft, "is_option", "1"); + format_add(ft, "is_key", "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); + 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); + 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); + + format_free(ft); + ft = format_create_from_state(NULL, NULL, &fs); + + 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; + } + kt = key_bindings_next_table(kt); + } + + format_free(ft); +} + +static void +window_customize_draw_key(__unused 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 key_table *kt; + struct key_binding *bd, *default_bd; + const char *note, *period = ""; + char *cmd, *default_cmd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + note = bd->note; + if (note == NULL) + note = "There is no note for this key."; + if (*note != '\0' && note[strlen (note) - 1] != '.') + period = "."; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s%s", + note, period)) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + 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")) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + cmd = cmd_list_print(bd->cmdlist, 0); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Command: %s", cmd)) { + free(cmd); + return; + } + default_bd = key_bindings_get_default(kt, bd->key); + if (default_bd != NULL) { + default_cmd = cmd_list_print(default_bd->cmdlist, 0); + 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)) { + free(default_cmd); + free(cmd); + return; + } + free(default_cmd); + } + free(cmd); +} + +static void +window_customize_draw_option(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; + int idx; + struct options_entry *o, *parent; + struct options *go, *wo; + const struct options_table_entry *oe; + struct grid_cell gc; + const char **choice, *text, *name; + const char *space = "", *unit = ""; + char *value = NULL, *expanded; + char *default_value = NULL; + char choices[256] = ""; + struct cmd_find_state fs; + struct format_tree *ft; + + if (!window_customize_check_item(data, item, &fs)) + return; + name = item->name; + idx = item->idx; + + o = options_get(item->oo, name); + if (o == NULL) + return; + oe = options_table_entry(o); + + if (oe != NULL && oe->unit != NULL) { + space = " "; + unit = oe->unit; + } + ft = format_create_from_state(NULL, NULL, &fs); + + if (oe == NULL) + text = "This is a user option."; + else if (oe->text == NULL) + 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)) + goto out; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + if (oe == NULL) + text = "user"; + else if ((oe->scope & (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) == + (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) + text = "window and pane"; + else if (oe->scope & OPTIONS_TABLE_WINDOW) + text = "window"; + else if (oe->scope & OPTIONS_TABLE_SESSION) + 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 (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx != -1) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, + "This is an array option, index %u.", idx)) + goto out; + } else { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, "This is an array option.")) + goto out; + } + if (idx == -1) + goto out; + } + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + value = options_to_string(o, idx, 0); + if (oe != NULL && idx == -1) { + default_value = options_default_to_string(oe); + if (strcmp(default_value, value) == 0) { + free(default_value); + 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 (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", + expanded)) + goto out; + } + free(expanded); + } + if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + for (choice = oe->choices; *choice != NULL; choice++) { + strlcat(choices, *choice, sizeof choices); + 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", + choices)) + goto out; + } + if (oe != NULL && oe->type == OPTIONS_TABLE_COLOUR) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a colour option: ")) + goto out; + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = options_get_number(item->oo, name); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_STYLE)) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a style option: ")) + goto out; + style_apply(&gc, item->oo, name, ft); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + 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)) + goto out; + } + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy > cy + sy - 1) + goto out; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + wo = NULL; + go = NULL; + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_PANE: + wo = options_get_parent(item->oo); + go = options_get_parent(wo); + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_SESSION: + wo = NULL; + go = options_get_parent(item->oo); + break; + default: + wo = NULL; + go = NULL; + break; + } + } + if (wo != NULL && options_owner(o) != wo) { + parent = options_get_only(wo, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 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)) + goto out; + } + } + if (go != NULL && options_owner(o) != go) { + parent = options_get_only(go, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 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)) + goto out; + } + } + +out: + free(value); + free(default_value); + format_free(ft); +} + +static void +window_customize_draw(void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item = itemdata; + + if (item == NULL) + return; + + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_draw_key(data, item, ctx, sx, sy); + else + window_customize_draw_option(data, item, ctx, sx, sy); +} + +static void +window_customize_menu(void *modedata, struct client *c, key_code key) +{ + struct window_customize_modedata *data = modedata; + struct window_pane *wp = data->wp; + struct window_mode_entry *wme; + + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->data != modedata) + return; + window_customize_key(wme, c, NULL, NULL, key, NULL); +} + +static u_int +window_customize_height(__unused void *modedata, __unused u_int height) +{ + return (12); +} + +static struct screen * +window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs, + struct args *args) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data; + struct screen *s; + + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + data->references = 1; + + memcpy(&data->fs, fs, sizeof data->fs); + + if (args == NULL || !args_has(args, 'F')) + data->format = xstrdup(WINDOW_CUSTOMIZE_DEFAULT_FORMAT); + else + data->format = xstrdup(args_get(args, 'F')); + + data->data = mode_tree_start(wp, args, window_customize_build, + window_customize_draw, NULL, window_customize_menu, + window_customize_height, NULL, data, window_customize_menu_items, + NULL, 0, &s); + mode_tree_zoom(data->data, args); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + return (s); +} + +static void +window_customize_destroy(struct window_customize_modedata *data) +{ + u_int i; + + if (--data->references != 0) + return; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + + free(data->format); + + free(data); +} + +static void +window_customize_free(struct window_mode_entry *wme) +{ + struct window_customize_modedata *data = wme->data; + + if (data == NULL) + return; + + data->dead = 1; + mode_tree_free(data->data); + window_customize_destroy(data); +} + +static void +window_customize_resize(struct window_mode_entry *wme, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = wme->data; + + mode_tree_resize(data->data, sx, sy); +} + +static void +window_customize_free_callback(void *modedata) +{ + window_customize_destroy(modedata); +} + +static void +window_customize_free_item_callback(void *itemdata) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + + window_customize_free_item(item); + window_customize_destroy(data); +} + +static int +window_customize_set_option_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo = item->oo; + const char *name = item->name; + char *cause; + int idx = item->idx; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (0); + o = options_get(oo, name); + if (o == NULL) + return (0); + oe = options_table_entry(o); + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + for (idx = 0; idx < INT_MAX; idx++) { + if (options_array_get(o, idx) == NULL) + break; + } + } + if (options_array_set(o, idx, s, 0, &cause) != 0) + goto fail; + } else { + if (options_from_string(oo, oe, name, s, 0, &cause) != 0) + goto fail; + } + + options_push_changes(item->name); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *cause = toupper((u_char)*cause); + status_message_set(c, -1, 1, 0, "%s", cause); + free(cause); + return (0); +} + +static void +window_customize_set_option(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item, int global, int pane) +{ + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo; + struct window_customize_itemdata *new_item; + int flag, idx = item->idx; + enum window_customize_scope scope = WINDOW_CUSTOMIZE_NONE; + u_int choice; + const char *name = item->name, *space = ""; + char *prompt, *value, *text; + struct cmd_find_state fs; + + if (item == NULL || !window_customize_check_item(data, item, &fs)) + return; + o = options_get(item->oo, name); + if (o == NULL) + return; + + oe = options_table_entry(o); + if (oe != NULL && ~oe->scope & OPTIONS_TABLE_PANE) + pane = 0; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + scope = item->scope; + oo = item->oo; + } else { + if (global) { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_SESSION: + scope = WINDOW_CUSTOMIZE_GLOBAL_SESSION; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + scope = WINDOW_CUSTOMIZE_GLOBAL_WINDOW; + break; + } + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_SESSION: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + scope = WINDOW_CUSTOMIZE_SESSION; + break; + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + } + } + if (scope == item->scope) + oo = item->oo; + else + oo = window_customize_get_tree(scope, &fs); + } + + if (oe != NULL && oe->type == OPTIONS_TABLE_FLAG) { + flag = options_get_number(oo, name); + options_set_number(oo, name, !flag); + } else if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + choice = options_get_number(oo, name); + if (oe->choices[choice + 1] == NULL) + choice = 0; + else + choice++; + options_set_number(oo, name, choice); + } else { + text = window_customize_scope_text(scope, &fs); + if (*text != '\0') + space = ", for "; + else if (scope != WINDOW_CUSTOMIZE_SERVER) + space = ", global"; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + xasprintf(&prompt, "(%s[+]%s%s) ", name, space, + text); + } else { + xasprintf(&prompt, "(%s[%d]%s%s) ", name, idx, + space, text); + } + } else + xasprintf(&prompt, "(%s%s%s) ", name, space, text); + free(text); + + value = options_to_string(o, idx, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = scope; + new_item->oo = oo; + new_item->name = xstrdup(name); + new_item->idx = idx; + + data->references++; + status_prompt_set(c, NULL, prompt, value, + window_customize_set_option_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + + free(prompt); + free(value); + } +} + +static void +window_customize_unset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options_entry *o; + + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + + o = options_get(item->oo, item->name); + if (o == NULL) + return; + if (item->idx != -1 && item == mode_tree_get_current(data->data)) + mode_tree_up(data->data, 0); + options_remove_or_default(o, item->idx, NULL); +} + +static void +window_customize_reset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options *oo; + struct options_entry *o; + + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + if (item->idx != -1) + return; + + oo = item->oo; + while (oo != NULL) { + o = options_get_only(item->oo, item->name); + if (o != NULL) + options_remove_or_default(o, -1, NULL); + oo = options_get_parent(oo); + } +} + +static int +window_customize_set_command_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + struct cmd_parse_result *pr; + char *error; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + pr = cmd_parse_from_string(s, NULL); + switch (pr->status) { + case CMD_PARSE_ERROR: + error = pr->error; + goto fail; + case CMD_PARSE_SUCCESS: + break; + } + cmd_list_free(bd->cmdlist); + bd->cmdlist = pr->cmdlist; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *error = toupper((u_char)*error); + status_message_set(c, -1, 1, 0, "%s", error); + free(error); + return (0); +} + +static int +window_customize_set_note_callback(__unused struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + free((void *)bd->note); + bd->note = xstrdup(s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_customize_set_key(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + key_code key = item->key; + struct key_binding *bd; + const char *s; + char *prompt, *value; + struct window_customize_itemdata *new_item; + + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return; + + s = mode_tree_get_current_name(data->data); + if (strcmp(s, "Repeat") == 0) + bd->flags ^= KEY_BINDING_REPEAT; + else if (strcmp(s, "Command") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0)); + value = cmd_list_print(bd->cmdlist, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, NULL, prompt, value, + window_customize_set_command_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + free(value); + } else if (strcmp(s, "Note") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0)); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, NULL, prompt, + (bd->note == NULL ? "" : bd->note), + window_customize_set_note_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + } +} + +static void +window_customize_unset_key(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct key_table *kt; + struct key_binding *bd; + + 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); + mode_tree_up(data->data, 0); + } + key_bindings_remove(kt->name, bd->key); +} + +static void +window_customize_reset_key(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct key_table *kt; + struct key_binding *dd, *bd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + dd = key_bindings_get_default(kt, bd->key); + if (dd != NULL && bd->cmdlist == dd->cmdlist) + return; + if (dd == NULL && item == mode_tree_get_current(data->data)) { + mode_tree_collapse_current(data->data); + mode_tree_up(data->data, 0); + } + key_bindings_reset(kt->name, bd->key); +} + +static void +window_customize_change_each(void *modedata, void *itemdata, + __unused struct client *c, __unused key_code key) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item = itemdata; + + switch (data->change) { + case WINDOW_CUSTOMIZE_UNSET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else + window_customize_unset_option(data, item); + break; + case WINDOW_CUSTOMIZE_RESET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_reset_key(data, item); + else + window_customize_reset_option(data, item); + break; + } + if (item->scope != WINDOW_CUSTOMIZE_KEY) + options_push_changes(item->name); +} + +static int +window_customize_change_current_callback(__unused struct client *c, + void *modedata, const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + item = mode_tree_get_current(data->data); + switch (data->change) { + case WINDOW_CUSTOMIZE_UNSET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else + window_customize_unset_option(data, item); + break; + case WINDOW_CUSTOMIZE_RESET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_reset_key(data, item); + else + window_customize_reset_option(data, item); + break; + } + if (item->scope != WINDOW_CUSTOMIZE_KEY) + options_push_changes(item->name); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static int +window_customize_change_tagged_callback(struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + mode_tree_each_tagged(data->data, window_customize_change_each, c, + KEYC_NONE, 0); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + 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, + struct mouse_event *m) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data = wme->data; + struct window_customize_itemdata *item, *new_item; + int finished, idx; + char *prompt; + u_int tagged; + + item = mode_tree_get_current(data->data); + 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 '\r': + case 's': + if (item == NULL) + break; + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_set_key(c, data, item); + else { + window_customize_set_option(c, data, item, 0, 1); + options_push_changes(item->name); + } + mode_tree_build(data->data); + break; + case 'w': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_option(c, data, item, 0, 0); + options_push_changes(item->name); + mode_tree_build(data->data); + break; + case 'S': + case 'W': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + 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->idx != -1) + break; + xasprintf(&prompt, "Reset %s to default? ", item->name); + data->references++; + data->change = WINDOW_CUSTOMIZE_RESET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'D': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Reset %u tagged to default? ", tagged); + data->references++; + data->change = WINDOW_CUSTOMIZE_RESET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'u': + if (item == NULL) + break; + idx = item->idx; + if (idx != -1) + xasprintf(&prompt, "Unset %s[%d]? ", item->name, idx); + else + xasprintf(&prompt, "Unset %s? ", item->name); + data->references++; + data->change = WINDOW_CUSTOMIZE_UNSET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'U': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Unset %u tagged? ", tagged); + data->references++; + data->change = WINDOW_CUSTOMIZE_UNSET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'H': + data->hide_global = !data->hide_global; + mode_tree_build(data->data); + break; + } + if (finished) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window-tree.c b/window-tree.c index 009e93c6..4998e1aa 100644 --- a/window-tree.c +++ b/window-tree.c @@ -29,6 +29,7 @@ static struct screen *window_tree_init(struct window_mode_entry *, static void window_tree_free(struct window_mode_entry *); static void window_tree_resize(struct window_mode_entry *, u_int, u_int); +static void window_tree_update(struct window_mode_entry *); static void window_tree_key(struct window_mode_entry *, struct client *, struct session *, struct winlink *, key_code, struct mouse_event *); @@ -37,12 +38,14 @@ static void window_tree_key(struct window_mode_entry *, #define WINDOW_TREE_DEFAULT_FORMAT \ "#{?pane_format," \ - "#{pane_current_command} \"#{pane_title}\"" \ + "#{?pane_marked,#[reverse],}" \ + "#{pane_current_command}#{?pane_active,*,}#{?pane_marked,M,}" \ + "#{?#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}},: \"#{pane_title}\",}" \ "," \ "#{?window_format," \ - "#{window_name}#{window_flags} " \ - "(#{window_panes} panes)" \ - "#{?#{==:#{window_panes},1}, \"#{pane_title}\",}" \ + "#{?window_marked_flag,#[reverse],}" \ + "#{window_name}#{window_flags}" \ + "#{?#{&&:#{==:#{window_panes},1},#{&&:#{pane_title},#{!=:#{pane_title},#{host_short}}}},: \"#{pane_title}\",}" \ "," \ "#{session_windows} windows" \ "#{?session_grouped, " \ @@ -53,9 +56,21 @@ static void window_tree_key(struct window_mode_entry *, "}" \ "}" +#define WINDOW_TREE_DEFAULT_KEY_FORMAT \ + "#{?#{e|<:#{line},10}," \ + "#{line}" \ + "," \ + "#{?#{e|<:#{line},36}," \ + "M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \ + "," \ + "" \ + "}" \ + "}" + static const struct menu_item window_tree_menu_items[] = { - { "Select", 'E', NULL }, - { "Expand", 'R', NULL }, + { "Select", '\r', NULL }, + { "Expand", KEYC_RIGHT, NULL }, + { "Mark", 'm', NULL }, { "", KEYC_NONE, NULL }, { "Tag", 't', NULL }, { "Tag All", '\024', NULL }, @@ -76,6 +91,7 @@ const struct window_mode window_tree_mode = { .init = window_tree_init, .free = window_tree_free, .resize = window_tree_resize, + .update = window_tree_update, .key = window_tree_key, }; @@ -112,6 +128,7 @@ struct window_tree_modedata { struct mode_tree_data *data; char *format; + char *key_format; char *command; int squash_groups; @@ -833,7 +850,7 @@ window_tree_search(__unused void *modedata, void *itemdata, const char *ss) return (0); retval = (strstr(cmd, ss) != NULL); free(cmd); - return retval; + return (retval); } return (0); } @@ -851,6 +868,35 @@ window_tree_menu(void *modedata, struct client *c, key_code key) window_tree_key(wme, c, NULL, NULL, key, NULL); } +static key_code +window_tree_get_key(void *modedata, void *itemdata, u_int line) +{ + struct window_tree_modedata *data = modedata; + struct window_tree_itemdata *item = itemdata; + struct format_tree *ft; + struct session *s; + struct winlink *wl; + struct window_pane *wp; + char *expanded; + key_code key; + + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + window_tree_pull_item(item, &s, &wl, &wp); + if (item->type == WINDOW_TREE_SESSION) + format_defaults(ft, NULL, s, NULL, NULL); + else if (item->type == WINDOW_TREE_WINDOW) + format_defaults(ft, NULL, s, wl, NULL); + else + format_defaults(ft, NULL, s, wl, wp); + format_add(ft, "line", "%u", line); + + expanded = format_expand(ft, data->key_format); + key = key_string_lookup_string(expanded); + free(expanded); + format_free(ft); + return key; +} + static struct screen * window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, struct args *args) @@ -875,16 +921,20 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT); else data->format = xstrdup(args_get(args, 'F')); - if (args == NULL || args->argc == 0) + if (args == NULL || !args_has(args, 'K')) + data->key_format = xstrdup(WINDOW_TREE_DEFAULT_KEY_FORMAT); + else + data->key_format = xstrdup(args_get(args, 'K')); + if (args == NULL || args_count(args) == 0) data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND); else - data->command = xstrdup(args->argv[0]); + data->command = xstrdup(args_string(args, 0)); data->squash_groups = !args_has(args, 'G'); data->data = mode_tree_start(wp, args, window_tree_build, - window_tree_draw, window_tree_search, window_tree_menu, data, - window_tree_menu_items, window_tree_sort_list, - nitems(window_tree_sort_list), &s); + window_tree_draw, window_tree_search, window_tree_menu, NULL, + window_tree_get_key, data, window_tree_menu_items, + window_tree_sort_list, nitems(window_tree_sort_list), &s); mode_tree_zoom(data->data, args); mode_tree_build(data->data); @@ -908,6 +958,7 @@ window_tree_destroy(struct window_tree_modedata *data) free(data->item_list); free(data->format); + free(data->key_format); free(data->command); free(data); @@ -934,6 +985,16 @@ window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy) mode_tree_resize(data->data, sx, sy); } +static void +window_tree_update(struct window_mode_entry *wme) +{ + struct window_tree_modedata *data = wme->data; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; +} + static char * window_tree_get_target(struct window_tree_itemdata *item, struct cmd_find_state *fs) @@ -1051,7 +1112,7 @@ window_tree_kill_each(__unused void *modedata, void *itemdata, break; case WINDOW_TREE_WINDOW: if (wl != NULL) - server_kill_window(wl->window); + server_kill_window(wl->window, 0); break; case WINDOW_TREE_PANE: if (wp != NULL) @@ -1073,6 +1134,7 @@ window_tree_kill_current_callback(struct client *c, void *modedata, return (0); window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE); + server_renumber_all(); data->references++; cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); @@ -1093,6 +1155,7 @@ window_tree_kill_tagged_callback(struct client *c, void *modedata, return (0); mode_tree_each_tagged(mtd, window_tree_kill_each, c, KEYC_NONE, 1); + server_renumber_all(); data->references++; cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); @@ -1170,7 +1233,7 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, struct window_tree_modedata *data = wme->data; struct window_tree_itemdata *item, *new_item; char *name, *prompt = NULL; - struct cmd_find_state fs; + struct cmd_find_state fs, *fsp = &data->fs; int finished; u_int tagged, x, y, idx; struct session *ns; @@ -1192,6 +1255,21 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, case '>': data->offset++; break; + case 'H': + mode_tree_expand(data->data, (uint64_t)fsp->s); + mode_tree_expand(data->data, (uint64_t)fsp->wl); + if (!mode_tree_set_current(data->data, (uint64_t)wme->wp)) + mode_tree_set_current(data->data, (uint64_t)fsp->wl); + break; + case 'm': + window_tree_pull_item(item, &ns, &nwl, &nwp); + server_set_marked(ns, nwl, nwp); + mode_tree_build(data->data); + break; + case 'M': + server_clear_marked(); + mode_tree_build(data->data); + break; case 'x': window_tree_pull_item(item, &ns, &nwl, &nwp); switch (item->type) { @@ -1216,9 +1294,9 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, if (prompt == NULL) break; data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_tree_kill_current_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT); + data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case 'X': @@ -1227,9 +1305,9 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, break; xasprintf(&prompt, "Kill %u tagged? ", tagged); data->references++; - status_prompt_set(c, prompt, "", + status_prompt_set(c, NULL, prompt, "", window_tree_kill_tagged_callback, window_tree_command_free, - data, PROMPT_SINGLE|PROMPT_NOFORMAT); + data, PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case ':': @@ -1239,8 +1317,9 @@ window_tree_key(struct window_mode_entry *wme, struct client *c, else xasprintf(&prompt, "(current) "); data->references++; - status_prompt_set(c, prompt, "", window_tree_command_callback, - window_tree_command_free, data, PROMPT_NOFORMAT); + status_prompt_set(c, NULL, prompt, "", + window_tree_command_callback, window_tree_command_free, + data, PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); free(prompt); break; case '\r': diff --git a/window.c b/window.c index f911f186..d3e3f60f 100644 --- a/window.c +++ b/window.c @@ -61,17 +61,6 @@ static u_int next_window_pane_id; static u_int next_window_id; static u_int next_active_point; -/* List of window modes. */ -const struct window_mode *all_window_modes[] = { - &window_buffer_mode, - &window_client_mode, - &window_clock_mode, - &window_copy_mode, - &window_tree_mode, - &window_view_mode, - NULL -}; - struct window_pane_input_data { struct cmdq_item *item; u_int wp; @@ -327,6 +316,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->sx = sx; w->sy = sy; + w->manual_sx = sx; + w->manual_sy = sy; w->xpixel = xpixel; w->ypixel = ypixel; @@ -340,6 +331,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) window_update_activity(w); + log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy, + w->xpixel, w->ypixel); return (w); } @@ -423,8 +416,8 @@ window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) ypixel = DEFAULT_YPIXEL; log_debug("%s: @%u resize %ux%u (%ux%u)", __func__, w->id, sx, sy, - xpixel == -1 ? w->xpixel : xpixel, - ypixel == -1 ? w->ypixel : ypixel); + xpixel == -1 ? w->xpixel : (u_int)xpixel, + ypixel == -1 ? w->ypixel : (u_int)ypixel); w->sx = sx; w->sy = sy; if (xpixel != -1) @@ -434,7 +427,7 @@ window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) } void -window_pane_send_resize(struct window_pane *wp, int yadjust) +window_pane_send_resize(struct window_pane *wp, u_int sx, u_int sy) { struct window *w = wp->window; struct winsize ws; @@ -442,9 +435,11 @@ window_pane_send_resize(struct window_pane *wp, int yadjust) if (wp->fd == -1) return; + log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, sx, sy); + memset(&ws, 0, sizeof ws); - ws.ws_col = wp->sx; - ws.ws_row = wp->sy + yadjust; + ws.ws_col = sx; + ws.ws_row = sy; ws.ws_xpixel = w->xpixel * ws.ws_col; ws.ws_ypixel = w->ypixel * ws.ws_row; if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) @@ -472,6 +467,52 @@ window_has_pane(struct window *w, struct window_pane *wp) return (0); } +void +window_update_focus(struct window *w) +{ + if (w != NULL) { + log_debug("%s: @%u", __func__, w->id); + window_pane_update_focus(w->active); + } +} + +void +window_pane_update_focus(struct window_pane *wp) +{ + struct client *c; + int focused = 0; + + if (wp != NULL) { + if (wp != wp->window->active) + focused = 0; + else { + TAILQ_FOREACH(c, &clients, entry) { + if (c->session != NULL && + c->session->attached != 0 && + (c->flags & CLIENT_FOCUSED) && + c->session->curw->window == wp->window) { + focused = 1; + break; + } + } + } + if (!focused && (wp->flags & PANE_FOCUSED)) { + log_debug("%s: %%%u focus out", __func__, wp->id); + if (wp->base.mode & MODE_FOCUSON) + bufferevent_write(wp->event, "\033[O", 3); + notify_pane("pane-focus-out", wp); + wp->flags &= ~PANE_FOCUSED; + } else if (focused && (~wp->flags & PANE_FOCUSED)) { + log_debug("%s: %%%u focus in", __func__, wp->id); + if (wp->base.mode & MODE_FOCUSON) + bufferevent_write(wp->event, "\033[I", 3); + notify_pane("pane-focus-in", wp); + wp->flags |= PANE_FOCUSED; + } else + log_debug("%s: %%%u focus unchanged", __func__, wp->id); + } +} + int window_set_active_pane(struct window *w, struct window_pane *wp, int notify) { @@ -485,6 +526,11 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; + if (options_get_number(global_options, "focus-events")) { + window_pane_update_focus(w->last); + window_pane_update_focus(w->active); + } + tty_update_window_offset(w); if (notify) @@ -492,11 +538,19 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) return (1); } +static int +window_pane_get_palette(struct window_pane *wp, int c) +{ + if (wp == NULL) + return (-1); + return (colour_palette_get(&wp->palette, c)); +} + void window_redraw_active_switch(struct window *w, struct window_pane *wp) { - struct style *sy1, *sy2; - int c1, c2; + struct grid_cell *gc1, *gc2; + int c1, c2; if (wp == w->active) return; @@ -506,18 +560,18 @@ window_redraw_active_switch(struct window *w, struct window_pane *wp) * If the active and inactive styles or palettes are different, * need to redraw the panes. */ - sy1 = &wp->cached_style; - sy2 = &wp->cached_active_style; - if (!style_equal(sy1, sy2)) + gc1 = &wp->cached_gc; + gc2 = &wp->cached_active_gc; + if (!grid_cells_look_equal(gc1, gc2)) wp->flags |= PANE_REDRAW; else { - c1 = window_pane_get_palette(wp, sy1->gc.fg); - c2 = window_pane_get_palette(wp, sy2->gc.fg); + c1 = window_pane_get_palette(wp, gc1->fg); + c2 = window_pane_get_palette(wp, gc2->fg); if (c1 != c2) wp->flags |= PANE_REDRAW; else { - c1 = window_pane_get_palette(wp, sy1->gc.bg); - c2 = window_pane_get_palette(wp, sy2->gc.bg); + c1 = window_pane_get_palette(wp, gc1->bg); + c2 = window_pane_get_palette(wp, gc2->bg); if (c1 != c2) wp->flags |= PANE_REDRAW; } @@ -631,18 +685,18 @@ window_unzoom(struct window *w) wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; } - layout_fix_panes(w); + layout_fix_panes(w, NULL); notify_window("window-layout-changed", w); return (0); } int -window_push_zoom(struct window *w, int flag) +window_push_zoom(struct window *w, int always, int flag) { log_debug("%s: @%u %d", __func__, w->id, flag && (w->flags & WINDOW_ZOOMED)); - if (flag && (w->flags & WINDOW_ZOOMED)) + if (flag && (always || (w->flags & WINDOW_ZOOMED))) w->flags |= WINDOW_WASZOOMED; else w->flags &= ~WINDOW_WASZOOMED; @@ -801,15 +855,18 @@ window_destroy_panes(struct window *w) } const char * -window_printable_flags(struct winlink *wl) +window_printable_flags(struct winlink *wl, int escape) { struct session *s = wl->session; static char flags[32]; int pos; pos = 0; - if (wl->flags & WINLINK_ACTIVITY) + if (wl->flags & WINLINK_ACTIVITY) { flags[pos++] = '#'; + if (escape) + flags[pos++] = '#'; + } if (wl->flags & WINLINK_BELL) flags[pos++] = '!'; if (wl->flags & WINLINK_SILENCE) @@ -864,31 +921,19 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); - wp->argc = 0; - wp->argv = NULL; - wp->shell = NULL; - wp->cwd = NULL; - wp->fd = -1; - wp->event = NULL; TAILQ_INIT(&wp->modes); - wp->layout_cell = NULL; + TAILQ_INIT (&wp->resize_queue); - wp->xoff = 0; - wp->yoff = 0; - - wp->sx = wp->osx = sx; - wp->sy = wp->osx = sy; + wp->sx = sx; + wp->sy = sy; wp->pipe_fd = -1; - wp->pipe_off = 0; - wp->pipe_event = NULL; - wp->saved_grid = NULL; - wp->saved_cx = UINT_MAX; - wp->saved_cy = UINT_MAX; + colour_palette_init(&wp->palette); + colour_palette_from_option(&wp->palette, wp->options); screen_init(&wp->base, sx, sy, hlimit); wp->screen = &wp->base; @@ -898,14 +943,15 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) if (gethostname(host, sizeof host) == 0) screen_set_title(&wp->base, host); - input_init(wp); - return (wp); } static void window_pane_destroy(struct window_pane *wp) { + struct window_pane_resize *r; + struct window_pane_resize *r1; + window_pane_reset_mode_all(wp); free(wp->searchstr); @@ -916,14 +962,12 @@ window_pane_destroy(struct window_pane *wp) bufferevent_free(wp->event); close(wp->fd); } - - input_free(wp); + if (wp->ictx != NULL) + input_free(wp->ictx); screen_free(&wp->status_screen); screen_free(&wp->base); - if (wp->saved_grid != NULL) - grid_destroy(wp->saved_grid); if (wp->pipe_fd != -1) { bufferevent_free(wp->pipe_event); @@ -932,6 +976,10 @@ window_pane_destroy(struct window_pane *wp) if (event_initialized(&wp->resize_timer)) event_del(&wp->resize_timer); + TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) { + TAILQ_REMOVE(&wp->resize_queue, r, entry); + free(r); + } RB_REMOVE(window_pane_tree, &all_window_panes, wp); @@ -939,29 +987,36 @@ window_pane_destroy(struct window_pane *wp) free((void *)wp->cwd); free(wp->shell); cmd_free_argv(wp->argc, wp->argv); - free(wp->palette); + colour_palette_free(&wp->palette); free(wp); } static void window_pane_read_callback(__unused struct bufferevent *bufev, void *data) { - struct window_pane *wp = data; - struct evbuffer *evb = wp->event->input; - size_t size = EVBUFFER_LENGTH(evb); - char *new_data; - size_t new_size; + struct window_pane *wp = data; + struct evbuffer *evb = wp->event->input; + struct window_pane_offset *wpo = &wp->pipe_offset; + size_t size = EVBUFFER_LENGTH(evb); + char *new_data; + size_t new_size; + struct client *c; - new_size = size - wp->pipe_off; - if (wp->pipe_fd != -1 && new_size > 0) { - new_data = EVBUFFER_DATA(evb) + wp->pipe_off; - bufferevent_write(wp->pipe_event, new_data, new_size); + if (wp->pipe_fd != -1) { + new_data = window_pane_get_new_data(wp, wpo, &new_size); + if (new_size > 0) { + bufferevent_write(wp->pipe_event, new_data, new_size); + window_pane_update_used_data(wp, wpo, new_size); + } } log_debug("%%%u has %zu bytes", wp->id, size); - input_parse(wp); - - wp->pipe_off = EVBUFFER_LENGTH(evb); + TAILQ_FOREACH(c, &clients, entry) { + if (c->session != NULL && (c->flags & CLIENT_CONTROL)) + control_write_output(c, wp); + } + input_parse_pane(wp); + bufferevent_disable(wp->event, EV_READ); } static void @@ -984,8 +1039,8 @@ window_pane_set_event(struct window_pane *wp) wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, window_pane_error_callback, wp); + wp->ictx = input_init(wp, wp->event, &wp->palette); - bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); bufferevent_enable(wp->event, EV_READ|EV_WRITE); } @@ -993,197 +1048,39 @@ void window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) { struct window_mode_entry *wme; + struct window_pane_resize *r; if (sx == wp->sx && sy == wp->sy) return; + + r = xmalloc (sizeof *r); + r->sx = sx; + r->sy = sy; + r->osx = wp->sx; + r->osy = wp->sy; + TAILQ_INSERT_TAIL (&wp->resize_queue, r, entry); + wp->sx = sx; wp->sy = sy; log_debug("%s: %%%u resize %ux%u", __func__, wp->id, sx, sy); - screen_resize(&wp->base, sx, sy, wp->saved_grid == NULL); + screen_resize(&wp->base, sx, sy, wp->base.saved_grid == NULL); wme = TAILQ_FIRST(&wp->modes); if (wme != NULL && wme->mode->resize != NULL) wme->mode->resize(wme, sx, sy); - - wp->flags |= (PANE_RESIZE|PANE_RESIZED); -} - -/* - * Enter alternative screen mode. A copy of the visible screen is saved and the - * history is not updated - */ -void -window_pane_alternate_on(struct window_pane *wp, struct grid_cell *gc, - int cursor) -{ - struct screen *s = &wp->base; - u_int sx, sy; - - if (wp->saved_grid != NULL) - return; - if (!options_get_number(wp->options, "alternate-screen")) - return; - sx = screen_size_x(s); - sy = screen_size_y(s); - - wp->saved_grid = grid_create(sx, sy, 0); - grid_duplicate_lines(wp->saved_grid, 0, s->grid, screen_hsize(s), sy); - if (cursor) { - wp->saved_cx = s->cx; - wp->saved_cy = s->cy; - } - memcpy(&wp->saved_cell, gc, sizeof wp->saved_cell); - - grid_view_clear(s->grid, 0, 0, sx, sy, 8); - - wp->base.grid->flags &= ~GRID_HISTORY; - - wp->flags |= PANE_REDRAW; -} - -/* Exit alternate screen mode and restore the copied grid. */ -void -window_pane_alternate_off(struct window_pane *wp, struct grid_cell *gc, - int cursor) -{ - struct screen *s = &wp->base; - u_int sx, sy; - - if (!options_get_number(wp->options, "alternate-screen")) - return; - - /* - * Restore the cursor position and cell. This happens even if not - * currently in the alternate screen. - */ - if (cursor && wp->saved_cx != UINT_MAX && wp->saved_cy != UINT_MAX) { - s->cx = wp->saved_cx; - if (s->cx > screen_size_x(s) - 1) - s->cx = screen_size_x(s) - 1; - s->cy = wp->saved_cy; - if (s->cy > screen_size_y(s) - 1) - s->cy = screen_size_y(s) - 1; - memcpy(gc, &wp->saved_cell, sizeof *gc); - } - - if (wp->saved_grid == NULL) - return; - sx = screen_size_x(s); - sy = screen_size_y(s); - - /* - * If the current size is bigger, temporarily resize to the old size - * before copying back. - */ - if (sy > wp->saved_grid->sy) - screen_resize(s, sx, wp->saved_grid->sy, 1); - - /* Restore the saved grid. */ - grid_duplicate_lines(s->grid, screen_hsize(s), wp->saved_grid, 0, sy); - - /* - * Turn history back on (so resize can use it) and then resize back to - * the current size. - */ - wp->base.grid->flags |= GRID_HISTORY; - if (sy > wp->saved_grid->sy || sx != wp->saved_grid->sx) - screen_resize(s, sx, sy, 1); - - grid_destroy(wp->saved_grid); - wp->saved_grid = NULL; - - wp->flags |= PANE_REDRAW; -} - -void -window_pane_set_palette(struct window_pane *wp, u_int n, int colour) -{ - if (n > 0xff) - return; - - if (wp->palette == NULL) - wp->palette = xcalloc(0x100, sizeof *wp->palette); - - wp->palette[n] = colour; - wp->flags |= PANE_REDRAW; -} - -void -window_pane_unset_palette(struct window_pane *wp, u_int n) -{ - if (n > 0xff || wp->palette == NULL) - return; - - wp->palette[n] = 0; - wp->flags |= PANE_REDRAW; -} - -void -window_pane_reset_palette(struct window_pane *wp) -{ - if (wp->palette == NULL) - return; - - free(wp->palette); - wp->palette = NULL; - wp->flags |= PANE_REDRAW; } int -window_pane_get_palette(struct window_pane *wp, int c) +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) { - int new; - - if (wp == NULL || wp->palette == NULL) - return (-1); - - new = -1; - if (c < 8) - new = wp->palette[c]; - else if (c >= 90 && c <= 97) - new = wp->palette[8 + c - 90]; - else if (c & COLOUR_FLAG_256) - new = wp->palette[c & ~COLOUR_FLAG_256]; - if (new == 0) - return (-1); - return (new); -} - -static void -window_pane_mode_timer(__unused int fd, __unused short events, void *arg) -{ - struct window_pane *wp = arg; - struct timeval tv = { .tv_sec = 10 }; - int n = 0; - - evtimer_del(&wp->modetimer); - evtimer_add(&wp->modetimer, &tv); - - log_debug("%%%u in mode: last=%ld", wp->id, (long)wp->modelast); - - if (wp->modelast < time(NULL) - WINDOW_MODE_TIMEOUT) { - if (ioctl(wp->fd, FIONREAD, &n) == -1 || n > 0) - window_pane_reset_mode_all(wp); - } -} - -int -window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, - struct cmd_find_state *fs, struct args *args) -{ - struct timeval tv = { .tv_sec = 10 }; struct window_mode_entry *wme; if (!TAILQ_EMPTY(&wp->modes) && TAILQ_FIRST(&wp->modes)->mode == mode) return (1); - wp->modelast = time(NULL); - if (TAILQ_EMPTY(&wp->modes)) { - evtimer_set(&wp->modetimer, window_pane_mode_timer, wp); - evtimer_add(&wp->modetimer, &tv); - } - TAILQ_FOREACH(wme, &wp->modes, entry) { if (wme->mode == mode) break; @@ -1194,6 +1091,7 @@ window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, } else { wme = xcalloc(1, sizeof *wme); wme->wp = wp; + wme->swp = swp; wme->mode = mode; wme->prefix = 1; TAILQ_INSERT_HEAD(&wp->modes, wme, entry); @@ -1203,6 +1101,7 @@ window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, wp->screen = wme->screen; wp->flags |= (PANE_REDRAW|PANE_CHANGED); + server_redraw_window_borders(wp->window); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); @@ -1225,7 +1124,6 @@ window_pane_reset_mode(struct window_pane *wp) next = TAILQ_FIRST(&wp->modes); if (next == NULL) { log_debug("%s: no next mode", __func__); - evtimer_del(&wp->modetimer); wp->screen = &wp->base; } else { log_debug("%s: next mode is %s", __func__, next->mode->name); @@ -1235,6 +1133,7 @@ window_pane_reset_mode(struct window_pane *wp) } wp->flags |= (PANE_REDRAW|PANE_CHANGED); + server_redraw_window_borders(wp->window); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); } @@ -1246,42 +1145,50 @@ window_pane_reset_mode_all(struct window_pane *wp) window_pane_reset_mode(wp); } +static void +window_pane_copy_key(struct window_pane *wp, key_code key) +{ + struct window_pane *loop; + + TAILQ_FOREACH(loop, &wp->window->panes, entry) { + if (loop != wp && + TAILQ_EMPTY(&loop->modes) && + loop->fd != -1 && + (~loop->flags & PANE_INPUTOFF) && + window_pane_visible(loop) && + options_get_number(loop->options, "synchronize-panes")) + input_key_pane(loop, key, NULL); + } +} + int window_pane_key(struct window_pane *wp, struct client *c, struct session *s, struct winlink *wl, key_code key, struct mouse_event *m) { struct window_mode_entry *wme; - struct window_pane *wp2; if (KEYC_IS_MOUSE(key) && m == NULL) return (-1); wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { - wp->modelast = time(NULL); - if (wme->mode->key != NULL) - wme->mode->key(wme, c, s, wl, (key & ~KEYC_XTERM), m); + if (wme->mode->key != NULL && c != NULL) { + key &= ~KEYC_MASK_FLAGS; + wme->mode->key(wme, c, s, wl, key, m); + } return (0); } if (wp->fd == -1 || wp->flags & PANE_INPUTOFF) return (0); - if (input_key(wp, key, m) != 0) + if (input_key_pane(wp, key, m) != 0) return (-1); if (KEYC_IS_MOUSE(key)) return (0); - if (options_get_number(wp->window->options, "synchronize-panes")) { - TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (wp2 != wp && - TAILQ_EMPTY(&wp2->modes) && - wp2->fd != -1 && - (~wp2->flags & PANE_INPUTOFF) && - window_pane_visible(wp2)) - input_key(wp2, key, NULL); - } - } + if (options_get_number(wp->options, "synchronize-panes")) + window_pane_copy_key(wp, key); return (0); } @@ -1324,7 +1231,7 @@ window_pane_search(struct window_pane *wp, const char *term, int regex, } log_debug("%s: %s", __func__, line); if (!regex) - found = (fnmatch(new, line, 0) == 0); + found = (fnmatch(new, line, flags) == 0); else found = (regexec(&r, line, 0, NULL, 0) == 0); free(line); @@ -1590,13 +1497,16 @@ winlink_clear_flags(struct winlink *wl) /* Shuffle window indexes up. */ int -winlink_shuffle_up(struct session *s, struct winlink *wl) +winlink_shuffle_up(struct session *s, struct winlink *wl, int before) { int idx, last; if (wl == NULL) return (-1); - idx = wl->idx + 1; + if (before) + idx = wl->idx; + else + idx = wl->idx + 1; /* Find the next free index. */ for (last = idx; last < INT_MAX; last++) { @@ -1609,8 +1519,9 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) /* Move everything from last - 1 to idx up a bit. */ for (; last > idx; last--) { wl = winlink_find_by_index(&s->windows, last - 1); - server_link_window(s, wl, s, last, 0, 0, NULL); - server_unlink_window(s, wl); + RB_REMOVE(winlinks, &s->windows, wl); + wl->idx++; + RB_INSERT(winlinks, &s->windows, wl); } return (idx); @@ -1626,7 +1537,7 @@ window_pane_input_callback(struct client *c, __unused const char *path, size_t len = EVBUFFER_LENGTH(buffer); wp = window_pane_find_by_id(cdata->wp); - if (wp == NULL || closed || error != 0 || c->flags & CLIENT_DEAD) { + if (wp == NULL || closed || error != 0 || (c->flags & CLIENT_DEAD)) { if (wp == NULL) c->flags |= CLIENT_EXIT; @@ -1645,13 +1556,17 @@ int window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, char **cause) { - struct client *c = item->client; + struct client *c = cmdq_get_client(item); struct window_pane_input_data *cdata; if (~wp->flags & PANE_EMPTY) { *cause = xstrdup("pane is not empty"); return (-1); } + if (c->flags & (CLIENT_DEAD|CLIENT_EXITED)) + return (1); + if (c->session != NULL) + return (1); cdata = xmalloc(sizeof *cdata); cdata->item = item; @@ -1662,3 +1577,24 @@ window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, return (0); } + +void * +window_pane_get_new_data(struct window_pane *wp, + struct window_pane_offset *wpo, size_t *size) +{ + size_t used = wpo->used - wp->base_offset; + + *size = EVBUFFER_LENGTH(wp->event->input) - used; + return (EVBUFFER_DATA(wp->event->input) + used); +} + +void +window_pane_update_used_data(struct window_pane *wp, + struct window_pane_offset *wpo, size_t size) +{ + size_t used = wpo->used - wp->base_offset; + + if (size > EVBUFFER_LENGTH(wp->event->input) - used) + size = EVBUFFER_LENGTH(wp->event->input) - used; + wpo->used += size; +} diff --git a/xmalloc.h b/xmalloc.h index d11d4bf1..26009dd8 100644 --- a/xmalloc.h +++ b/xmalloc.h @@ -34,12 +34,14 @@ int xasprintf(char **, const char *, ...) __attribute__((__format__ (printf, 2, 3))) __attribute__((__nonnull__ (2))); int xvasprintf(char **, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))) __attribute__((__nonnull__ (2))); int xsnprintf(char *, size_t, const char *, ...) __attribute__((__format__ (printf, 3, 4))) __attribute__((__nonnull__ (3))) __attribute__((__bounded__ (__string__, 1, 2))); int xvsnprintf(char *, size_t, const char *, va_list) + __attribute__((__format__ (printf, 3, 0))) __attribute__((__nonnull__ (3))) __attribute__((__bounded__ (__string__, 1, 2))); diff --git a/xterm-keys.c b/xterm-keys.c deleted file mode 100644 index b10c10db..00000000 --- a/xterm-keys.c +++ /dev/null @@ -1,252 +0,0 @@ -/* $OpenBSD$ */ - -/* - * 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 "tmux.h" - -/* - * xterm-style function keys append one of the following values before the last - * character: - * - * 2 Shift - * 3 Alt - * 4 Shift + Alt - * 5 Ctrl - * 6 Shift + Ctrl - * 7 Alt + Ctrl - * 8 Shift + Alt + Ctrl - * - * Rather than parsing them, just match against a table. - * - * There are three forms for F1-F4 (\\033O_P and \\033O1;_P and \\033[1;_P). - * We accept any but always output the latter (it comes first in the table). - */ - -static int xterm_keys_match(const char *, const char *, size_t, size_t *, - key_code *); -static int xterm_keys_modifiers(const char *, size_t, size_t *, - key_code *); - -struct xterm_keys_entry { - key_code key; - const char *template; -}; - -static const struct xterm_keys_entry xterm_keys_table[] = { - { KEYC_F1, "\033[1;_P" }, - { KEYC_F1, "\033O1;_P" }, - { KEYC_F1, "\033O_P" }, - { KEYC_F2, "\033[1;_Q" }, - { KEYC_F2, "\033O1;_Q" }, - { KEYC_F2, "\033O_Q" }, - { KEYC_F3, "\033[1;_R" }, - { KEYC_F3, "\033O1;_R" }, - { KEYC_F3, "\033O_R" }, - { KEYC_F4, "\033[1;_S" }, - { KEYC_F4, "\033O1;_S" }, - { KEYC_F4, "\033O_S" }, - { KEYC_F5, "\033[15;_~" }, - { KEYC_F6, "\033[17;_~" }, - { KEYC_F7, "\033[18;_~" }, - { KEYC_F8, "\033[19;_~" }, - { KEYC_F9, "\033[20;_~" }, - { KEYC_F10, "\033[21;_~" }, - { KEYC_F11, "\033[23;_~" }, - { KEYC_F12, "\033[24;_~" }, - { KEYC_UP, "\033[1;_A" }, - { KEYC_DOWN, "\033[1;_B" }, - { KEYC_RIGHT, "\033[1;_C" }, - { KEYC_LEFT, "\033[1;_D" }, - { KEYC_HOME, "\033[1;_H" }, - { KEYC_END, "\033[1;_F" }, - { KEYC_PPAGE, "\033[5;_~" }, - { KEYC_NPAGE, "\033[6;_~" }, - { KEYC_IC, "\033[2;_~" }, - { KEYC_DC, "\033[3;_~" }, - - { '!', "\033[27;_;33~" }, - { '#', "\033[27;_;35~" }, - { '(', "\033[27;_;40~" }, - { ')', "\033[27;_;41~" }, - { '+', "\033[27;_;43~" }, - { ',', "\033[27;_;44~" }, - { '-', "\033[27;_;45~" }, - { '.', "\033[27;_;46~" }, - { '0', "\033[27;_;48~" }, - { '1', "\033[27;_;49~" }, - { '2', "\033[27;_;50~" }, - { '3', "\033[27;_;51~" }, - { '4', "\033[27;_;52~" }, - { '5', "\033[27;_;53~" }, - { '6', "\033[27;_;54~" }, - { '7', "\033[27;_;55~" }, - { '8', "\033[27;_;56~" }, - { '9', "\033[27;_;57~" }, - { ':', "\033[27;_;58~" }, - { ';', "\033[27;_;59~" }, - { '<', "\033[27;_;60~" }, - { '=', "\033[27;_;61~" }, - { '>', "\033[27;_;62~" }, - { '?', "\033[27;_;63~" }, - { '\'', "\033[27;_;39~" }, - { '\r', "\033[27;_;13~" }, - { '\t', "\033[27;_;9~" }, -}; - -/* - * Match key against buffer, treating _ as a wildcard. Return -1 for no match, - * 0 for match, 1 if the end of the buffer is reached (need more data). - */ -static int -xterm_keys_match(const char *template, const char *buf, size_t len, - size_t *size, key_code *modifiers) -{ - size_t pos; - int retval; - - *modifiers = 0; - - if (len == 0) - return (0); - - pos = 0; - do { - if (*template == '_') { - retval = xterm_keys_modifiers(buf, len, &pos, - modifiers); - if (retval != 0) - return (retval); - continue; - } - if (buf[pos] != *template) - return (-1); - pos++; - } while (*++template != '\0' && pos != len); - - if (*template != '\0') /* partial */ - return (1); - - *size = pos; - return (0); -} - -/* Find modifiers from buffer. */ -static int -xterm_keys_modifiers(const char *buf, size_t len, size_t *pos, - key_code *modifiers) -{ - u_int flags; - - if (len - *pos < 2) - return (1); - - if (buf[*pos] < '0' || buf[*pos] > '9') - return (-1); - flags = buf[(*pos)++] - '0'; - if (buf[*pos] >= '0' && buf[*pos] <= '9') - flags = (flags * 10) + (buf[(*pos)++] - '0'); - flags -= 1; - - *modifiers = 0; - if (flags & 1) - *modifiers |= KEYC_SHIFT; - if (flags & 2) - *modifiers |= KEYC_ESCAPE; - if (flags & 4) - *modifiers |= KEYC_CTRL; - if (flags & 8) - *modifiers |= KEYC_ESCAPE; - return (0); -} - -/* - * Lookup key from a buffer against the table. Returns 0 for found (and the - * key), -1 for not found, 1 for partial match. - */ -int -xterm_keys_find(const char *buf, size_t len, size_t *size, key_code *key) -{ - const struct xterm_keys_entry *entry; - u_int i; - int matched; - key_code modifiers; - - for (i = 0; i < nitems(xterm_keys_table); i++) { - entry = &xterm_keys_table[i]; - - matched = xterm_keys_match(entry->template, buf, len, size, - &modifiers); - if (matched == -1) - continue; - if (matched == 0) - *key = (entry->key|modifiers|KEYC_XTERM); - return (matched); - } - return (-1); -} - -/* Lookup a key number from the table. */ -char * -xterm_keys_lookup(key_code key) -{ - const struct xterm_keys_entry *entry; - u_int i; - key_code modifiers; - char *out; - - modifiers = 1; - if (key & KEYC_SHIFT) - modifiers += 1; - if (key & KEYC_ESCAPE) - modifiers += 2; - if (key & KEYC_CTRL) - modifiers += 4; - - /* - * If the key has no modifiers, return NULL and let it fall through to - * the normal lookup. - */ - if (modifiers == 1) - return (NULL); - - /* - * If this has the escape modifier, but was not originally an xterm - * key, it may be a genuine escape + key. So don't pass it through as - * an xterm key or programs like vi may be confused. - */ - if ((key & (KEYC_ESCAPE|KEYC_XTERM)) == KEYC_ESCAPE) - return (NULL); - - /* Otherwise, find the key in the table. */ - key &= KEYC_MASK_KEY; - for (i = 0; i < nitems(xterm_keys_table); i++) { - entry = &xterm_keys_table[i]; - if (key == entry->key) - break; - } - if (i == nitems(xterm_keys_table)) - return (NULL); - - /* Copy the template and replace the modifier. */ - out = xstrdup(entry->template); - out[strcspn(out, "_")] = '0' + modifiers; - return (out); -}