diff --git a/.gitignore b/.gitignore index ad18d1de71..d01a0166a6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ Makefile.in configure tmux.1.* *.dSYM +cmd-parse.c diff --git a/.travis.yml b/.travis.yml index a1d7e427cd..fd85bf61c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,16 @@ language: c -matrix: - include: - - compiler: gcc - - compiler: clang - env: CFLAGS="-g -O2" + +os: + - linux + - osx + +compiler: + - gcc + - clang + before_install: - - sudo apt-get update -qq - - sudo apt-get -y install debhelper autotools-dev dh-autoreconf file libncurses5-dev libevent-dev pkg-config libutempter-dev build-essential -script: (CFLAGS= ./autogen.sh) && ./configure --enable-debug && make + - 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 + +script: + - ./autogen.sh && ./configure && make diff --git a/CHANGES b/CHANGES index dc88ae807c..7b352f56ca 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,417 @@ -CHANGES FROM 2.5 TO 2.6-rc3, 11 September 2017 +CHANGES FROM 3.0 to X.X + +* Do not use bright when emulating 256 colours on an 8 colour terminal because + it is also bold on some terminals. + +* Add a "latest" window-size option which tries to size windows based on the + most recently used client. This is now the default. + +* Make select-pane -P set window-active-style also to match previous behaviour. + +* Do not truncate list-keys output. + +* Turn automatic-rename back on if the \033k rename escape sequence is used + with an empty name. + +* Add support for percentage sizes for resize-pane ("-x 10%"). Also change + split-window and join-pane -l to accept similar percentages and deprecate the + -p flag. + +* Add -F flag to send-keys to expand formats in search-backward and forward + copy mode commands, this makes it easier to use the cursor_word and + cursor_line formats. + +* Add formats for word and line at cursor position in copy mode. + +* Add formats for cursor and selection position in copy mode + +* Support all the forms of RGB colour strings in OSC sequences rather than + requiring two digits. + +* Limit lazy resize to panes in attached sessions only + +* Add an option to set the key sent by backspace for those whose system uses ^H + rather than ^?. + +* Change new-session -A without a session name (that is, no -s option also) to + attach to the best existing session like attach-session rather than a new + one. + +* Change window-size default from smallest to latest. + +* Add simple support for OSC 7 (result is available in the pane_path format). + +* Add push-default and pop-default for styles which change the colours and + attributes used for #[default]. These are used in status-format to restore + the behaviour of window-status-style being the default for + window-status-format. + +* Add window_marked_flag. + +* Add cursor-down-and-cancel in copy mode. + +* Default to previous search string for search-forward and search-backward. + +* Add -Z flag to rotate-window, select-pane, swap-pane, switch-client to + preserve zoomed state. + +* Add -N to capture-pane to preserve trailing spaces. + +* Add reverse sorting in tree, client and buffer modes. + +CHANGES FROM 2.9 to 3.0 + +* Workaround invalid layout strings generated by older tmux versions and add + some additional sanity checks + +* xterm 348 now disables margins when resized, so send DECLRMM again after + resize. + +* Add support for the SD (scroll down) escape sequence. + +* Expand arguments to C and s format modifiers to match the m modifier. + +* Add support for underscore colours (Setulc capability must be added with + terminal-overrides as described in tmux(1)). + +* Add a "fill" style attribute for the fill colour of the drawing area (where + appropriate). + +* New -H flag to send-keys to send literal keys. + +* Format variables for pane mouse modes (mouse_utf8_flag and mouse_sgr_flag) + and for origin mode (origin_flag). + +* Add -F to refresh-client for flags for control mode clients, only one flag + (no-output) supported at the moment. + +* Add a few vi(1) keys for menus. + +* Add pane options, set with set-option -p and displayed with show-options -p. + Pane options inherit from window options (so every pane option is also + a window option). The pane style is now configured by setting window-style + and window-active-style in the pane options; select-pane -P and -g now change + the option but are no longer documented. + +* Do not document set-window-option and show-window-options. set-option -w and + show-options -w should be used instead. + +* Add a -A flag to show-options to show parent options as well (they are marked + with a *). + +* Resize panes lazily - do not resize unless they are in an attached, active + window. + +* Add regular expression support for the format search, match and substitute + modifiers and make them able to ignore case. find-window now accepts -r to + use regular expressions. + +* Do not use $TMUX to find the session because for windows in multiple sessions + it is wrong as often as it is right, and for windows in one session it is + pointless. Instead use TMUX_PANE if it is present. + +* Do not always resize the window back to its original size after applying a + layout, keep it at the layout size until it must be resized (for example when + attached and window-size is not manual). + +* Add new-session -X and attach-session -x to send SIGHUP to parent when + detaching (like detach-client -P). + +* Support for octal escapes in strings (such as \007) and improve list-keys + output so it parses correctly if copied into a configuration file. + +* INCOMPATIBLE: Add a new {} syntax to the configuration file. This is a string + similar to single quotes but also includes newlines and allows commands that + take other commands as string arguments to be expressed more clearly and + without additional escaping. + + A literal { and } or a string containing { or } must now be escaped or + quoted, for example '{' and '}' instead of { or }, or 'X#{foo}' instead of + X#{foo}. + +* New <, >, <= and >= comparison operators for formats. + +* Improve escaping of special characters in list-keys output. + +* INCOMPATIBLE: tmux's configuration parsing has changed to use yacc(1). There + is one incompatible change: a \ on its own must be escaped or quoted as + either \\ or '\' (the latter works on older tmux versions). + + Entirely the same parser is now used for parsing the configuration file + and for string commands. This means that constructs previously only + available in .tmux.conf, such as %if, can now be used in string commands + (for example, those given to if-shell - not commands invoked from the + shell, they are still parsed by the shell itself). + +* Add support for the overline attribute (SGR 53). The Smol capability is + needed in terminal-overrides. + +* Add the ability to create simple menus. Introduces new command + display-menu. Default menus are bound to MouseDown3 on the status line; + MouseDown3 or M-MouseDown3 on panes; MouseDown3 in tree, client and + buffer modes; and C-b C-m and C-b M-m. + +* Allow panes to be empty (no command). They can be created either by piping to + split-window -I, or by passing an empty command ('') to split-window. Output + can be sent to an existing empty window with display-message -I. + +* Add keys to jump between matching brackets (emacs C-M-f and C-M-b, vi %). + +* Add a -e flag to new-window, split-window, respawn-window, respawn-pane to + pass environment variables into the newly created process. + +* Hooks are now stored in the options tree as array options, allowing them to + have multiple separate commands. set-hook and show-hooks remain but + set-option and show-options can now also be used (show-options will only show + hooks if given the -H flag). Hooks with multiple commands are run in index + order. + +* Automatically scroll if dragging to create a selection with the mouse and the + cursor reaches the top or bottom line. + +* Add -no-clear variants of copy-selection and copy-pipe which do not clear the + selection after copying. Make copy-pipe clear the selection by default to be + consistent with copy-selection. + +* Add an argument to copy commands to set the prefix for the buffer name, this + (for example) allows buffers for different sessions to be named separately. + +* Update session activity on focus event. + +* Pass target from source-file into the config file parser so formats in %if + and %endif have access to more useful variables. + +* Add the ability to infer an option type (server, session, window) from its + name to show-options (it was already present in set-option). + +CHANGES FROM 2.9 to 2.9a + +* Fix bugs in select-pane and the main-horizontal and main-vertical layouts. + +CHANGES FROM 2.8 to 2.9 + +* Attempt to preserve horizontal cursor position as well as vertical with + reflow. + +* Rewrite main-vertical and horizontal and change layouts to better handle the + case where all panes won't fit into the window size, reduce problems with + pane border status lines and fix other bugs mostly found by Thomas Sattler. + +* Add format variables for the default formats in the various modes + (tree_mode_format and so on) and add a -a flag to display-message to list + variables with values. + +* Add a -v flag to display-message to show verbose messages as the format is + parsed, this allows formats to be debugged + +* Add support for HPA (\033[`). + +* Add support for origin mode (\033[?6h). + +* No longer clear history on RIS. + +* Extend the #[] style syntax and use that together with previous format + changes to allow the status line to be entirely configured with a single + option. + + Now that it is possible to configure their content, enable the existing code + that lets the status line be multiple lines in height. The status option can + now take a value of 2, 3, 4 or 5 (as well as the previous on or off) to + configure more than one line. The new status-format array option configures + the format of each line, the default just references the existing status-* + options, although some of the more obscure status options may be eliminated + in time. + + Additions to the #[] syntax are: "align" to specify alignment (left, centre, + right), "list" for the window list and "range" to configure ranges of text + for the mouse bindings. + + The "align" keyword can also be used to specify alignment of entries in tree + mode and the pane status lines. + +* Add E: and T: format modifiers to expand a format twice (useful to expand the + value of an option). + +* The individual -fg, -bg and -attr options have been removed; they + were superseded by -style options in tmux 1.9. + +* Allow more than one mode to be opened in a pane. Modes are kept on a stack + and retrieved if the same mode is entered again. Exiting the active mode goes + back to the previous one. + +* When showing command output in copy mode, call it view mode instead (affects + pane_mode format). + +* Add -b to display-panes like run-shell. + +* Handle UTF-8 in word-separators option. + +* New "terminal" colour allowing options to use the terminal default colour + rather than inheriting the default from a parent option. + +* Do not move the cursor in copy mode when the mouse wheel is used. + +* Use the same working directory rules for jobs as new windows rather than + always starting in the user's home. + +* Allow panes to be one line or column in size. + +* Go to last line when goto-line number is out of range in copy mode. + +* Yank previously cut text if any with C-y in the command prompt, only use the + buffer if no text has been cut. + +* Add q: format modifier to quote shell special characters. + +* Add StatusLeft and StatusRight mouse locations (keys such as + MouseDown1StatusLeft) for the status-left and status-right areas of the + status line. + +* Add -Z to find-window. + +* Support for windows larger than the client. This adds two new options, + window-size and default-size, and a new command, resize-window. The + force-width and force-height options and the session_width and session_height + formats have been removed. + + The new window-size option tells tmux how to work out the size of windows: + largest means it picks the size of the largest session, smallest the smallest + session (similar to the old behaviour) and manual means that it does not + automatically resize windows. aggressive-resize modifies the choice of + session for largest and smallest as it did before. + + If a window is in a session attached to a client that is too small, only part + of the window is shown. tmux attempts to keep the cursor visible, so the part + of the window displayed is changed as the cursor moves (with a small delay, + to try and avoid excess redrawing when applications redraw status lines or + similar that are not currently visible). + + Drawing windows which are larger than the client is not as efficient as those + which fit, particularly when the cursor moves, so it is recommended to avoid + using this on slow machines or networks (set window-size to smallest or + manual). + + The resize-window command can be used to resize a window manually. If it is + used, the window-size option is automatically set to manual for the window + (undo this with "setw -u window-size"). resize-window works in a similar way + to resize-pane (-U -D -L -R -x -y flags) but also has -a and -A flags. -a + sets the window to the size of the smallest client (what it would be if + window-size was smallest) and -A the largest. + + For the same behaviour as force-width or force-height, use resize-window -x + or -y. + + If the global window-size option is set to manual, the default-size option is + used for new windows. If -x or -y is used with new-session, that sets the + default-size option for the new session. + + The maximum size of a window is 10000x10000. But expect applications to + complain and higher memory use if making a window that big. The minimum size + is the size required for the current layout including borders. + + The refresh-client command can be used to pan around a window, -U -D -L -R + moves up, down, left or right and -c returns to automatic cursor + tracking. The position is reset when the current window is changed. + +CHANGES FROM 2.7 to 2.8 + +* Make display-panes block the client until a pane is chosen or it + times out. + +* Clear history on RIS like most other terminals do. + +* Add an "Any" key to run a command if a key is pressed that is not + bound in the current key table. + +* Expand formats in load-buffer and save-buffer. + +* Add a rectangle_toggle format. + +* Add set-hook -R to run a hook immediately. + +* Add README.ja. + +* Add pane focus hooks. + +* Allow any punctuation as separator for s/x/y not only /. + +* Improve resizing with the mouse (fix resizing the wrong pane in some + layouts, and allow resizing multiple panes at the same time). + +* Allow , and } to be escaped in formats as #, and #}. + +* Add KRB5CCNAME to update-environment. + +* Change meaning of -c to display-message so the client is used if it + matches the session given to -t. + +* Fixes to : form of SGR. + +* Add x and X to choose-tree to kill sessions, windows or panes. + +CHANGES FROM 2.6 TO 2.7 + +* Remove EVENT_* variables from environment on platforms where tmux uses them + so they do not pass on to panes. + +* Fixes for hooks at server exit. + +* Remove SGR 10 (was equivalent to SGR 0 but no other terminal seems to do + this). + +* Expand formats in window and session names. + +* Add -Z flag to choose-tree, choose-client, choose-buffer to automatically + zoom the pane when the mode is entered and unzoom when it exits, assuming the + pane is not already zoomed. This is now part of the default key bindings. + +* Add C-g to exit modes with emacs keys. + +* Add exit-empty option to exit server if no sessions (defaults to on). + +* Show if a filter is present in choose modes. + +* Add pipe-pane -I to to connect stdin of the child process. + +* Performance improvements for reflow. + +* Use RGB terminfo(5) capability to detect RGB colour terminals (the existing + Tc extension remains unchanged). + +* Support for ISO colon-separated SGR sequences. + +* Add select-layout -E to spread panes out evenly (bound to E key). + +* Support wide characters properly when reflowing. + +* Pass PWD to new panes as a hint to shells, as well as calling chdir(). + +* Performance improvements for the various choose modes. + +* Only show first member of session groups in tree mode (-G flag to choose-tree + to show all). + +* Support %else in config files to match %if; from Brad Town in GitHub issue + 1071. + +* Fix "kind" terminfo(5) capability to be S-Down not S-Up. + +* Add a box around the preview label in tree mode. + +* Show exit status and time in the remain-on-exit pane text; from Timo + Boettcher in GitHub issue 1103. + +* Correctly use pane-base-index in tree mode. + +* Change the allow-rename option default to off. + +* Support for xterm(1) title stack escape sequences (GitHub issue 1075 from + Brad Town). + +* Correctly remove padding cells to fix a UTF-8 display problem (GitHub issue + 1090). + +CHANGES FROM 2.5 TO 2.6, 05 October 2017 * Add select-pane -T to set pane title. @@ -193,7 +606,7 @@ CHANGES FROM 2.4 TO 2.5, 09 May 2017 * Do not redraw a client unless we realistically think it can accept the data - defer redraws until the client has nothing else waiting to write. - + CHANGES FROM 2.3 TO 2.4, 20 April 2017 Incompatible Changes @@ -411,7 +824,7 @@ Normal Changes * Copy mode is exited if the history is cleared whilst in copy-mode. * 'copy-mode' learned '-e' to exit copy-mode when scrolling to end. -CHANGES FROM 1.9a TO 2.0, 6 March 2015 +CHANGES FROM 1.9a TO 2.0, 06 March 2015 Incompatible Changes ==================== @@ -479,7 +892,7 @@ Normal Changes ============== * Fix crash due to uninitialized lastwp member of layout_cell -* Fix -fg/-bg/-style with 256 colour terminals. +* Fix -fg/-bg/-style with 256 colour terminals. CHANGES FROM 1.8 TO 1.9, 20 February 2014 @@ -837,7 +1250,7 @@ CHANGES FROM 1.2 TO 1.3, 18 July 2010 * Run job commands explicitly in the global environment (which can be modified with setenv -g), rather than with the environment tmux started with. * Use the machine's hostname as the default title, instead of an empty string. -* Prevent double free if the window option remain-on-exit is set. +* Prevent double free if the window option remain-on-exit is set. * Key string conversions rewritten. * Mark zombie windows as dead in the choose-window list. * Tiled layout added. @@ -939,7 +1352,7 @@ CHANGES FROM 1.0 TO 1.1, 05 November 2009 * New lock-client (alias lockc), and lock-session (alias locks) commands to lock a particular client, or all clients attached to a session. * Support C-n/C-p/C-v/M-v with emacs keys in choice mode. -* Use : for goto line rather than g in vi mode. +* Use : for goto line rather than g in vi mode. * Try to guess which client to use when no target client was specified. Finds the current session, and if only one client is present, use it. Otherwise, return the most recently used client. @@ -1070,7 +1483,7 @@ The list of older changes is below. * main-horizontal layout and main-pane-height option to match vertical. * New window option main-pane-width to set the width of the large left pane with - main-vertical (was left-vertical) layout. + main-vertical (was left-vertical) layout. * Lots of layout cleanup. manual layout is now manual-vertical. 16 May 2009 diff --git a/CONTRIBUTING b/CONTRIBUTING deleted file mode 100644 index 467c5c3a87..0000000000 --- a/CONTRIBUTING +++ /dev/null @@ -1,33 +0,0 @@ -When reporting issues: - -YOU MUST INCLUDE THE TMUX VERSION - -DO NOT OPEN AN ISSUE THAT DOES NOT MENTION THE TMUX VERSION - -Please also include: - -- your platform (Linux, OS X, or whatever); -- a brief description of the problem with steps to reproduce; -- a minimal tmux config, if you can't reproduce without a config; -- your terminal, and $TERM inside and outside of tmux; -- logs from tmux (see below); -- at most one or two screenshots, if helpful. - -This should include at least the output of: - - $ uname -sp && tmux -V && echo $TERM - -Please do not report bugs (crashes, incorrect behaviour) without reproducing on -a tmux built from Git master. - -Note that TERM inside tmux must be a variant of screen or tmux (for example: -screen or screen-256color, tmux or tmux-256color). Please ensure this is the -case before opening an issue. - -To run tmux without a config and get logs, run: - - tmux -Ltest kill-server - tmux -vv -Ltest -f/dev/null new - -Then reproduce the problem, exit tmux, and attach the tmux-server-*.log file -from the current directory to the issue. diff --git a/COPYING b/COPYING index 9d96b3e650..fd02a999c7 100644 --- a/COPYING +++ b/COPYING @@ -1,10 +1,7 @@ THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE. -The README, CHANGES, FAQ and TODO files are licensed under the ISC -license. Files under examples/ remain copyright their authors unless otherwise -stated in the file but permission has been received to distribute them with -tmux. All other files have a license and copyright notice at their start, -typically: +The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All +other files have a license and copyright notice at their start, typically: Copyright (c) diff --git a/ISSUE_TEMPLATE b/ISSUE_TEMPLATE deleted file mode 100644 index d786f923c9..0000000000 --- a/ISSUE_TEMPLATE +++ /dev/null @@ -1 +0,0 @@ -Please read https://raw.githubusercontent.com/tmux/tmux/master/CONTRIBUTING diff --git a/Makefile.am b/Makefile.am index 4951a00e3c..7ac3d6ac21 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,12 +2,13 @@ # Obvious program stuff. bin_PROGRAMS = tmux -CLEANFILES = tmux.1.mdoc tmux.1.man +CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c # Distribution tarball options. EXTRA_DIST = \ - CHANGES README COPYING example_tmux.conf compat/*.[ch] \ + CHANGES README README.ja COPYING example_tmux.conf \ osdep-*.c mdoc2man.awk tmux.1 +dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. AM_CPPFLAGS += @XOPEN_DEFINES@ -DTMUX_CONF="\"$(sysconfdir)/tmux.conf\"" @@ -71,6 +72,7 @@ dist_tmux_SOURCES = \ cmd-confirm-before.c \ cmd-copy-mode.c \ cmd-detach-client.c \ + cmd-display-menu.c \ cmd-display-message.c \ cmd-display-panes.c \ cmd-find-window.c \ @@ -87,12 +89,12 @@ dist_tmux_SOURCES = \ cmd-list-panes.c \ cmd-list-sessions.c \ cmd-list-windows.c \ - cmd-list.c \ cmd-load-buffer.c \ cmd-lock-server.c \ cmd-move-window.c \ cmd-new-session.c \ cmd-new-window.c \ + cmd-parse.y \ cmd-paste-buffer.c \ cmd-pipe-pane.c \ cmd-queue.c \ @@ -100,6 +102,7 @@ dist_tmux_SOURCES = \ cmd-rename-session.c \ cmd-rename-window.c \ cmd-resize-pane.c \ + cmd-resize-window.c \ cmd-respawn-pane.c \ cmd-respawn-window.c \ cmd-rotate-window.c \ @@ -111,14 +114,12 @@ dist_tmux_SOURCES = \ cmd-send-keys.c \ cmd-set-buffer.c \ cmd-set-environment.c \ - cmd-set-hook.c \ cmd-set-option.c \ cmd-show-environment.c \ cmd-show-messages.c \ cmd-show-options.c \ cmd-source-file.c \ cmd-split-window.c \ - cmd-string.c \ cmd-swap-pane.c \ cmd-swap-window.c \ cmd-switch-client.c \ @@ -131,9 +132,9 @@ dist_tmux_SOURCES = \ control.c \ environ.c \ format.c \ + format-draw.c \ grid-view.c \ grid.c \ - hooks.c \ input-keys.c \ input.c \ job.c \ @@ -143,6 +144,7 @@ dist_tmux_SOURCES = \ layout-set.c \ layout.c \ log.c \ + menu.c \ mode-tree.c \ names.c \ notify.c \ @@ -150,6 +152,7 @@ dist_tmux_SOURCES = \ options.c \ paste.c \ proc.c \ + regsub.c \ resize.c \ screen-redraw.c \ screen-write.c \ @@ -158,6 +161,8 @@ dist_tmux_SOURCES = \ server-fn.c \ server.c \ session.c \ + sixel.c \ + spawn.c \ status.c \ style.c \ tmux.c \ @@ -195,7 +200,7 @@ install-exec-hook: >$(srcdir)/tmux.1.mdoc; \ else \ sed -e "s|@SYSCONFDIR@|$(sysconfdir)|g" $(srcdir)/tmux.1| \ - $(AWK) -f$(srcdir)/mdoc2man.awk >$(srcdir)/tmux.1.man; \ + $(AWK) -f $(srcdir)/mdoc2man.awk >$(srcdir)/tmux.1.man; \ fi $(mkdir_p) $(DESTDIR)$(mandir)/man1 $(INSTALL_DATA) $(srcdir)/tmux.1.@MANFORMAT@ \ diff --git a/RANTS.md b/RANTS.md new file mode 100644 index 0000000000..ed9251994c --- /dev/null +++ b/RANTS.md @@ -0,0 +1,356 @@ +> A long rant on why I believe sixel support has been blocked deliberately and unfairly + +I'm sick and tired of the current sixel situation. This may not gain me friends, but +I've decided to speak up. + +## Some maintainers are holding users hostage to favor their preferred formats + +Sorry guys, but I don't want you to hold Linux terminal users hostage for your +petty concerns over what is the "right" way to do something like sixels. + +It's something that has been done successfully for over 30 years. It's 2021, +and we should be able to do litterate programming in the console, with full +graphical support. Currently, it is way too hard, due to artificial hurdles, +such as the lack of sixel support. This situation is totally artificial, since +various patches have been released for a long time on other platforms. + +Just because of your pettiness, I had to use a graphical RStudio in uni, which +put my old computer on its knees. That's not cool, as I could totally have done +everything from a commandline, if only Linux has supported back then what has been +supported by Windows from a long time. + +It's not like it's impossible: the patches had been written. + +Still, you could successfully block sixel support by having control over the +terminal emulators or the libraries. Ok, but now, try to prevent your users +from running sixel-tmux! + +See, I learned from what you guys often say: I know free software maintainers +owe their users nothing, and apparently not even the basic concern of not +torpedoing ongoing efforts to enhance their software and support more formats. + +So I owe you nothing either, and I will release the software I wish had existed. + +Also, you guys like to say that the only voices that matters are from those who +can code? Hmm, ok for that too. Let's see how you like the code I release to +show the pointlessness of your petty fights, and free your users from your +questionable decisions. + +## Technical reasons can not justify this + +Now, if you have tolerated my rants so far, please cool down with me, and read +for example this enlightening discussion about supporting the various font +styles (bold, bright, faint...) to understand the complexity of the issues we +have to deal with in a terminal: https://github.com/microsoft/terminal/issues/2916 + +You will see that ultimately, it comes to a question of preferences: + +- Windows in general has sanctified the principle of backwards compatibility; + a typical example is that you can run unmodified old binaries from say 20 + years ago on a modern Windows installation with everything working just as + before, + +- Linux maintainers often prefer to sacrifice backwards compatibility in the + hope of achieving better results in the long run. + +After all, why not? In the absolute, each approach has its pros and cons, but when it +comes to the cons-ole (pun intended) and terminal emulators, the Linux approach +seems very misguided. + +They try to dress it up with positive terms, like "brave" and "hope", see for +example egmontkob comment in +https://github.com/microsoft/terminal/issues/2916#issuecomment-544999801: "But +again it's mostly a question of project vision: where does WT want to go? Is it +legacy and backwards compatibility, or being a decent modern future environment +that is more important? Does it want to play safe, or be brave?" + +Dude, in the word of terminals, playing safe is being brave, as backwards +compatibility requires tremendous creativity and complexity, so that we can all +keep enjoying the programs we love instead of breaking them every now and then +on a whim. + +## Ranting again on the poor state of terminal emulation on Linux + +You may not be convinced, but if you care about pretty text support in the console, +compare the situation on Windows with the situation on Linux: given the bad +results achieved by terminal emulators on Linux as described in my other long rant on +https://github.com/csdvrx/cutexterm#why-did-you-make-cutexterm-warning-long-rant +I have a lot of reasons to believe the Linux approach is wrong. + +Why is there not a single free software terminal having feature parity with mintty? + +Why in 2021 is a pimped up XTerm the best that Linux has to offer? + +What will happen as Wayland replaces X? + +Linux terminal emulators always fail, but in a quite consistent fashion, let's +grant them that much: they strive to achieve only the bare minimum, they show +an utter disrespect for the established standards, and a lack of concern for +achieving excellency. In a way, I understand why they don't care about backwards +compatibility: they have much bigger problems to deal with before addressing that! + +And it's not just because I want working working text ligatures, or low latency +in my terminal: that may be my pet concern, but just it's just a canary in the +mine for many more issues. Still, if you need more data, look at the latency +test from https://danluu.com/term-latency/ + +Everybody has an excuse for ligatures: they are new, or not everybody likes +them. For latency, it's the fault of gnome, or X, or wayland... but how come +the gigantic XTerm is now the faster in the block? Because it has had the +benefit of time and stable specs? + +Ok then, what about sixels? They are over 30 years old, and you can implement +them from scratch, so there can be no such excuse! + +## Sixel Sabotage in VTE + +Do you really wonder why there's no sixel support in VTE in 2021, given that a +first patch implenting sixel support in VTE was released over 5 years ago? + +Check https://bugzilla.gnome.org/show_bug.cgi?id=729204#c3 and you will +understand that's because sixels are akin to blasphemy to some people: sixels +are too technically impure, or not fancy, or something more elusive because +when faced with technical arguments, they can't explain their irrational dislikes! + +Egmontkob has expressed multiple times his dislike of sixels like +https://github.com/thestinger/termite/issues/539#issuecomment-345259308 "Alas +no standard seems to emerge that doesn't suck big time (sixel is so last +millennium that it hurts)" + +You know, terminals are last milleniums, and so is Unix, but it works great! + +For the longest time, Egmontkob plan was to veto sixel support in VTE, for the +machiavellian reason of affording more time to other formats to try to catch up +https://bugzilla.gnome.org/show_bug.cgi?id=729204#c26 "Having sixel support +surely looks like a great win in the short term: more apps can display +pictures, users are happier. I'd argue however that in the long term, adding +sixel support is straight harmful, for three main reasons. One is that it +pushes the entire ecosystem in the wrong direction. If VTE, which has about 50% +of market share within Linux, add this, it will encourage more terminals and +more apps to add this absolutely broken format, or happily stick to this if +it's already added, rather than looking for a better solution. It makes it +harder to argue and cooperate across terminals for a better one." + +I couldn't disagree more with the whole thing, and I wish I had something, +anything, working 5 years ago: compared to being stuck with RStudio, even 256 +colors sixels would have been a blessing! + +And what do you have to show for these 5 years of delays? Is there a new +technically superior and more adopted format? If not, what do you gain from +stalling even more? Don't you think we would be in a much better place if the +original patch had been integrated? It would certainly have allowed more tools +to take advantage of graphics in the console, like iTerm2 did for the Macs! + +So what about we try to move forward now? Because I think trying the same +approach again and again while failing to learn from the valuable lessons of +failure is very close to the definition of stupidy: you have tried to hold +sixel support back, you have failed. When are you going to concede victory? +Do you need 5 more years? 10 maybe? + +If you need a funny example, watch Bart Simpson vs the Hamster, experiment 2, +the electrified cupcake on https://www.youtube.com/watch?v=5pxG4yd8U3U and you +will understand my issues, or why I recommend mintty in particular, and Windows +in general: the VTE situation about sixels is just an emerged part of the +iceberg: the issues run much deeper, and are much more serious. + +## Technical arguments again + +They may even be resolved someday, as quite a few people seem to understand the +situation on both technical and non-technical aspects, like Bastien Nocera in +https://bugzilla.gnome.org/show_bug.cgi?id=729204#c7 : "Sixel support seems +interesting because it's an existing protocol, not something supported by few +terminals that likely won't find actual buy-in in those very old protocols." + +See also Hans Petter Jansson in https://bugzilla.gnome.org/show_bug.cgi?id=729204#c22 +"I think sixels is an ok choice for a graphics protocol, mostly because it's +the most widely supported one, but also because I've managed to convince myself +the quantization it requires on the application side can be fairly efficient. +The palettized and RLE-encoded data strikes a reasonable balance between +bandwidth and CPU usage with a fairly simple encoder. Palette size also +provides a size/quality knob, much like it does in PNG. On top of that, ssh +allows for compression, which can mitigate the ASCII redundancy if you've got +the CPU headroom for zlib and don't mind the cryptanalysis implications." + +That is a valid technical argument. + +And HPA has done many great things, like a decent competitor to derasterize with +https://github.com/hpjansson/chafa ; also he also seem to be able to see beyond +technologies, and towards the users concerns and needs: see how chafa has added +support for both Kitty and iTerm2 on top of its own derasterize and sixel: +https://hpjansson.org/blag/2021/09/16/chafa-1-8-terminal-graphics-with-a-side-of-everything/ + +This is the *right* way to do: compatibility does not mean competition. If +anyone takes this sixel-tmux and add the ability to ingest Kitty/iTerm2 +graphics (or pass them through, or convert between them and sixels...) I will +welcome it, and call that both a progress and a good thing! Ultimately, I do +not care that much about sixels: I care about doing things the right way, and +preserving the users freedoms! + +But with the Linux mentality of sacrificing backwards compatibility whenever +convenient, I don't think it's a safe bet to hope the nice people like HPA will +win in the end. + +This is why I released sixel-tmux. That's also why I use Windows besides the +wonderful mintty being the number 1 choice for terminal afficionados, it gives +more options in general: I like that because I don't like depending on people +who seem stuck in a desire to be lord-of-the-flies. Unfortunately, there +seem to be quite a few in the free software world... + +### Ok but why release sixel-tmux now? + +If you have noticed the current sixel-tmux is based on a tmux branch from 2019, +and concluded from it that sixel-tmux must have been finalized in early 2020, +you have a keen eye and a sharp mind! + +Or maybe you have followed my posts, like this one from december 2020 on +https://github.com/microsoft/terminal/issues/448#issuecomment-744192801 where I +did include screenshots and a description of the features. + +sixel-tmux hasn't changed much, because I've been quite busy in 2021. + +Initially, I didn't even want to release all of sixel-tmux: it was made for a +custom order by a large client, and I kept it for my own personal use, as I +don't like sharing my toys. + +Also, they are more like elaborate hack for very specific problems that most +people do not have, so I'm afraid they will not understand how or why to use +them instead of better alternatives (like, mintty if you want to gnuplot!) + +On top of that, given the recent discussions and progress on +https://gitlab.gnome.org/GNOME/vte/-/issues/253 like Autumn Lamonte I had some +hope gnome-terminal would feature sixel support by default in a major linux +distribution. + +However, it's not the case, and you will still need for example to "build from git +master using -Dsixel=true meson option" which is too complicated for most +users, in a world of flatpaks and docker images, especially with the multiple +dependancies (and the risk of breaking gnome) + +Just yesterday, the VTE milestone was moved yet again - now sixels are planned +for vte 0.68. + +I do not think it's acceptable to keep delaying: the VTE maintainers had 5 +years to get the ball rolling with Yatli patches. + +Personally, I gave them almost a full year, thinking the situation had improved. + +It hasn't. I do not wish to tolerate that one more day. So sixel-tmux is out now, as incomplete and imperfect as it may be. Deal with it, VTE! + +Am I trying to force your hand and make VTE include sixel support? You bet! + +## Sixel support: sixel-tmux to the rescue! + +If some people think adding sixel support by default to VTE is beyond them, +the VTE-based terminal users will now be able to simply use sixel-tmux as a +"gateway drug" to get into what these maintainers may believe to be the worse +format. Maybe I can get them into worse and worse stuff, say cutexterm to get +native sixels, then maybe mintty on Windows to get perfect font support? + +Then maybe these users will say "Bye Linux", and they will move to Windows +Terminal when Sixel support is finally shipping? Recently, there have been +many positive signs this may eventually happen, like PR 11152 adding support +for DECRQSS cf https://github.com/microsoft/terminal/pull/11152 + +If it doesn't happen, mintty is still great, and who knows, when Windows +Terminal reaches feature parity, maybe I'll be tempted to fork it and add sixel +support? It must be possible, as there are several private branches adding +sixel support already. A little polishing could go a long way. I could even put +the end result on the Windows Store and let users decide what they like best... + +Yes, for free software fanatics, by encouraging Linux terminal users to move to +Windows, I'm a bad person. But you know what? I'm proud of it :) Because +Windows Terminal is at least trying to escape from the global mediocrity of Linux +terminal emulators, and starting from scratch has achieved tremendous progress +in just a few years. + +### Ok, but why a fork? + +To support slow terminal with little bandwith, tmux started implementing +some controversial features after version 2.3. For example, when a lot of text +arrives, tmux start silently discarding the output until a redraw can be done. + +Bug reports + mention +serious issues, such as being unable to cat a long text file without some parts +being cut off, so this questionable feature for slow terminals impacts many usecases. + +Among other things, it breaks sixel support. Even when not using sixels, it +seems very wrong for tmux to be taking the initiative of silently discarding +parts of the outputs, without providing any command line flags to override that +"feature". + +As it can't be disabled, this anti-feature is not just a questionable default, +but a perverse feature: I believe breaking the integrity of the output of +whatever command was run, with no recourse, is not acceptable for a terminal multiplexer. + +Also, refusing to even provide a command-line toggle to opt-out is not +respectful of the end user freedom: no one should have to track patches to +allow basic functionality like properly displaying long text files! + +## Sixel sabotage again? + +The maintainer of tmux explicitely said several times that even if a patch was provided +to support sixels, the functionality would never be added to tmux, and it would +have to be a fork + + +By becoming a permanent fork of tmux, sixel-tmux does just what was asked so nicely! + +Once again, free software maintainers love saying "my way or the highway", so +personally I take them to heart, and take the high way by doing exactly what +they askeed for :) + +## Is this fork just for sixels? + +sixel-tmux is not just made for sixels but for all graphical things that can no +longer be properly displayed inside tmux, including long text files. + +It would have been better if the changes had had at least a fighting chance to +be upstreamed. + +Unfortunately, a permanent fork seems necessary as again, the problem seems to +be more general, and spreading: tmux intercept and improperly rewrites various +escapes codes, breaking other things + + +At this point, it is very unlikely that tmux will be fixed: all these +questionable choices start piling up, and make the correct display of what is +not just simple plain text inside tmux harder and harder. + +This is a sad situation as tmux was one of the best terminal multiplexer, and +supported what GNU screen couldn't when too many questionable choices had been +accumulated and frozen in over 32 years of spaghetti code. + +Therefore, all patches adding functionality to sixel-tmux are welcome- +including cleanups, and backports of important tmux features. + +Even the features that may affect the proper display of graphics and text are +welcome, as long as they are off by default, since obscure usecases like limited +bandwidth are now as rare as serial ports. + +### History + +Hayaki Saito wrote a patch and released +a tmux-sixel fork in 2015. As tmux keep adding features, Chris Steinbach + tried to integrate them in +tmux-sixel. Patches were released by others to add to the version of tmux +officialy packaged by distributions to have tmux-sixel features, like Mehdi +Abaakouk + +Yatao Li had a branch that was the closet to upstream +tmux, but the most recent changes dated from 2017 and some sixel isues remained. + +When I tried them, none had full support for ANSI SGR parameters. In 2019, as +there was no recent activity on any of these, I forked the most recent branch, +fixed the remaining issues to have good sixel support at least in the current +buffer, added the missing SGR parameters I needed, and fixed issues brough by +the inclusion of unavoidable anti-features in mainline tmux. + +By late 2019, given some general progress of few important issues, I rebased on tmux +main. Derasterize support was added on top of that in 2020. + +After a year of hesitations on what to do, the new sixel-tmux code was released +in 2021, with the ambition of introducing more people to sixels without asking +them to change their terminal emulators, thanks to the derasterize fallback +mode. + diff --git a/README b/README deleted file mode 100644 index 056d56914a..0000000000 --- a/README +++ /dev/null @@ -1,76 +0,0 @@ -Welcome to tmux! - -tmux is a "terminal multiplexer", it enables a number of terminals (or windows) -to be accessed and controlled from a single terminal. tmux is intended to be a -simple, modern, BSD-licensed alternative to programs such as GNU screen. - -This release runs on OpenBSD, FreeBSD, NetBSD, Linux, OS X and Solaris. - -tmux depends on libevent 2.x. Download it from: - - http://libevent.org - -It also depends on ncurses, available from: - - http://invisible-island.net/ncurses/ - -To build and install tmux from a release tarball, use: - - $ ./configure && make - $ 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. - -To get and build the latest from version control: - - $ git clone https://github.com/tmux/tmux.git - $ cd tmux - $ sh autogen.sh - $ ./configure && make - -(Note that this requires at least a working C compiler, make, autoconf, -automake, pkg-config as well as libevent and ncurses libraries and headers.) - -For more information see http://git-scm.com. Patches should be sent by email to -the mailing list at tmux-users@googlegroups.com or submitted through GitHub at -https://github.com/tmux/tmux/issues. - -For documentation on using tmux, see the tmux.1 manpage. It can be viewed from -the source tree with: - - $ nroff -mdoc tmux.1|less - -A small example configuration in example_tmux.conf. - -A vim(1) syntax file is available at: - - https://github.com/ericpruitt/tmux.vim - https://raw.githubusercontent.com/ericpruitt/tmux.vim/master/vim/syntax/tmux.vim - -And a bash(1) completion file at: - - https://github.com/imomaliev/tmux-bash-completion - -For debugging, running tmux with -v or -vv will generate server and client log -files in the current directory. - -tmux mailing lists are available. For general discussion and bug reports: - - https://groups.google.com/forum/#!forum/tmux-users - -And for Git commit emails: - - https://groups.google.com/forum/#!forum/tmux-git - -Subscribe by sending an email to . - -Bug reports, feature suggestions and especially code contributions are most -welcome. Please send by email to: - - tmux-users@googlegroups.com - -This file and the CHANGES, FAQ, SYNCING and TODO files are licensed under the -ISC license. All other files have a license and copyright notice at their start. - --- Nicholas Marriott diff --git a/README.ja b/README.ja new file mode 100644 index 0000000000..1580df5268 --- /dev/null +++ b/README.ja @@ -0,0 +1,62 @@ +tmuxへようこそ! + +tmuxはターミナルマルチプレクサーです。複数のターミナルを一つのスクリーン内に作成し、操作することができます。 +バックグラウンドで処理を実行中に一度スクリーンから離れて後から復帰することも可能です。 + +OpenBSD、FreeBSD、NetBSD、Linux、OS X、Solarisで実行できます。 + +tmuxはlibevent 2.x.に依存します。 下記からダウンロードしてください。 + + http://libevent.org + +また、ncursesも必要です。こちらからどうぞ。 + + http://invisible-island.net/ncurses/ + +tarballでのtmuxのビルドとインストール方法。 + + $ ./configure && make + $ sudo make install + +tmuxはutmp(5)をアップデートするためにutempterを使うことができます。もしインストール済みであればオプション「--enable-utempter」をつけて実行してください。 + +リポジトリから最新バージョンを手に入れるためには下記を実行。 + + $ git clone https://github.com/tmux/tmux.git + $ cd tmux + $ sh autogen.sh + $ ./configure && make + +(ビルドのためにはlibevent、ncurses libraries、headersに加えて、C compiler、make、autoconf、automake、pkg-configが必要です。) + +詳しい情報はhttp://git-scm.comをご覧ください。修正はメール宛、もしくはhttps://github.com/tmux/tmux/issuesにて受け付けています。 + +tmuxのドキュメントについてはtmux.1マニュアルをご覧ください。こちらのコマンドで参照可能です。 + + $ nroff -mdoc tmux.1|less + +サンプル設定は本リポジトリのexample_tmux.confに +また、bash-completionファイルは下記にあります。 + + https://github.com/imomaliev/tmux-bash-completion + +「-v」や「-vv」を指定することでデバッグモードでの起動が可能です。カレントディレクトリにサーバーやクライアントのログファイルが生成されます。 + +議論やバグレポート用のメーリングリストにはこちらから参加可能です。 + + https://groups.google.com/forum/#!forum/tmux-users + +gitコミットについての連絡先 + + https://groups.google.com/forum/#!forum/tmux-git + +購読はまでメールをお願いします。 + +バグレポートや機能追加(特にコードへの貢献)は大歓迎です。こちらにご連絡ください。 + + tmux-users@googlegroups.com + +本ファイル、CHANGES、 FAQ、SYNCINGそしてTODOはISC licenseで保護されています。 +その他のファイルのライセンスや著作権については、ファイルの上部に明記されています。 + +-- Nicholas Marriott diff --git a/README.md b/README.md new file mode 100644 index 0000000000..a36c1ea18d --- /dev/null +++ b/README.md @@ -0,0 +1,545 @@ +> sixel-tmux : a terminal multiplexer that display graphics one way or another! + +## Live examples + +![mintty running sixel-tmux displaying sixels inside](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/sixel-tmux_mintty.gif) + +![mintty running sixel-tmux with 2 vertical tabs](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/sixel-tmux_2-vertical-tabs.gif) + +## What is sixel-tmux? + +sixel-tmux is a fork of tmux, focusing on proper graphic (sixels) and text +attributes preservation and representation (bold, underline...). + +While tmux now has a better support of text attributes, it still eats these +yummy sixels! + +The initial description of sixel-tmux said it could display graphics simply +"because it does not eat escape sequences" that constitutes the sixels. + +This new version still does that, but now with with a more ambitious 2nd mode: + +- not only it does not eat escape sequences containing the sixels, which is + the "sixel passthrough" mode, to pass them to any connected terminal that + supports sixels, + +- but it if detects it's connected to a terminal that can't display sixels at + all (like most Linux terminal emulators based on VTE, or hum uhm, a certain + Windows Terminal...), the sixel images are converted into a text rendition + thanks to https://github.com/csdvrx/derasterize which is the ambitious + "fallback mode" + +This is sixel-tmux running inside https://github.com/csdvrx/cutexterm in "sixel passthrough" mode, as you can see from the XTerm right-click menu: +![cutexterm running sixel-tmux and displaying sixels inside](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/cutexterm_sixel-tmux.png) + +This is another sixel-tmux running inside another cutexterm in "sixel fallback" mode, with all the sixel graphics converted into what is often called "ascii-art" by derasterize +![cutexterm running sixel-tmux with derasterized sixels](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/cutexterm_sixel-tmux_derasterized.png) + +Since cutexterm uses xterm, which features proper sixel support, and both cutexterm and mintty examples consist of running the client and the server the same terminal, you may say it's easy, and still not convinced sixel-tmux is cool. + +Here's a regular off-the-shelf (off the Windows store?) Windows Terminal Preview using sixel-tmux to become sixel-aware and display derasterized pictures to replace the sixels sequence: +![sixel-tmux offering derasterized sixels from mintty to Windows Terminal Preview](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/sixel-tmux-inside-windows-terminal.gif) + +Here's another regular Windows Terminal Preview connecting to a sixel-tmux session that has been opened in mintty, with both terminals side-by-side on Windows 10, and Windows Terminal automatically benefitting from the desrasterized fallback mode of the previously viewed sixels from within mintty: notice how sixel-tmux shows pure sixel content for as long as possible, here until Windows Terminal Preview connects to the session +![sixel-tmux offering derasterized sixels from mintty to Windows Terminal Preview](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/sixel-tmux-inside-both-mintty-and-windows-terminal.gif) + +Now, is that cool or what? + +## It's cool, but explain me like I'm 5 + +You can now see cute pictures inside your terminal, even if you shouldn't be +able to do that, say if your terminal is like Windows Terminal and doesn't +support the sixel format!! (at least, not yet) + +This is why this second mode is very ambitious: it is like "putting magical +glasses" on your existing terminal that is currently blind to sixels graphics. + +Thanks to having sixel-tmux magical glasses on, your terminal can now see +sixels without changing anything else: while before it showed you nothing at all, +or just an empty square (due to your terminal not supporting sixels), your +terminal now shows you the best it can do with the sixels! + +Of course, the glasses are not really magical, therefore they are imperfect: +the picture will be blocky due to the technical limitations of derasterizing in +character sized block. Also, the picture will be made with a possibly limited +color palette: if your terminal only supports 16 or 256 colors, sixel-tmux +can't invent the colors you'll be missing. + +Derasterize is simply the best it can do, but still, I believe it is far better +than the alternative of having no picture shown at all! + +## Limitations: the sixels can be replaced by derasterize + +It is a work in progress: for the first mode (pass through) ideally sixel-tmux +should keep a sixel scrollback buffer to store actual sixels sequences instead +of their derasterized versions, to "clip them" as necessary and let the +terminal display them. + +At the moment, a scrollback will often cause the sixels to be replaced by blocks +coming from derasterize, unless you have very specific conditions, like using +your terminal native scrollback buffer with just one tmux window (or not +switching to the others). Also, you must take the precaution of moving tmux +menu bar to the botton of the screen: this is just so you can scrollback +without the textual menu bar messing up your pretty pictures! + +It may sound quite a constraining, but I provide here known good configurations +files, along with some help to configure recommended terminal emulators. + +## Ambitions: having sixel use become mainstream thanks to sixel-tmux (short rant) + +If you've followed on the "magical glasses" analogy, you can see how +derasterize is much more ambitious than just a "fallback" mode for sixel-tmux: +I basically want to force sixels down every (willing) Linux user throats +(because consent is a thing!), to stop the current deadlock situation of +competing formats that has set back the progress of console graphics for decades. + +In case it's not clear enough, I will be very blunt: releasing sixel-tmux with +derasterize support baked-in is my "activist action" to democratize sixels, by +weaponizing my own fork of tmux against all those who have decided that running out +the clock (my apologies for this football metaphor) was a fair move to ensure +the victory for their favorite non-sixel terminal graphic format. + +You don't believe me? They have been blocking patches adding sixels support for +spurious reasons, causing over 5 years of delays for most Linux users. + +Skip if you don't care about the politics, but you can get more details on the +situation if you can stomach my long https://github.com/csdvrx/sixel-tmux/blob/main/RANTS.md + +### Testing sixel-tmux + +sixel-tmux is a fork of tmux, with just one goal: having the most reliable +support of graphics. + +In a console, this means: + 1. ANSI blocks and box-drawing characters to display ASCII and ANSI art + 2. ANSI escape codes, with a focus on SGR parameters to render text attributes + 3. and the best of all: sixel graphics to display graphs + +This seems very simple requirements, yet a regular tmux fails on all 3: + 1. ANSI art is corrupted, + 2. some SGR parameters are supported, but the more exotic features like overline do not work, and some sequences may be intercepted and improperly changed + 3. sixels are not supported. + +For a proper support of all these features, sixel-tmux is necessary but +not sufficient: the terminal must be able to decode and render the sequences +passed by sixel-tmux. + +Therefore, a separate test suite +is provided to first check if a terminal can achieve these goals. + +If you are dissatisfied by your current terminal, please consider mlterm + or mintty that are +tested and known to work well. + +Microsoft Window Terminal is very promising, but does not support sixels yet. +Please upvote the feature request + +You can find a longer list of alternative terminals and terminal multiplexers through +related works such as + +Below, we'll be using the test suite to configure your terminal. If that seems +too complicated, install msys2 and use mintty. If you like tabs and regret +their absence in mintty, try Windows Terminal Preview instead. + +## Configuration + +In an ideal world, each piece of software would seemlessly integrate with the +others, and work flawlessly right after installation. + +We do not live in such a world, so you may need to configure your terminal to +make sure sixel-tmux will work fine. + +The configuration step is optional, but recommended for best results - after +all, if you care to install sixel-tmux instead of sticking to tmux, you may +care about graphics! + +While I may recommend some defaults and test for them, you may have to make +some choices, as terminal issues are extremely complicated: it is sometimes +impossible to achieve perfect results without tweaking applications, since +they often have contradictory requirements. + +#### Step 1. Deploying then using terminfo files with the TERM variable + +Step 1 is to deploy then use a new terminfo that will accurately describe the +capabilities of sixel-tmux, and do the same if necessary for the terminal into +which you will run sixel-tmux: this is achieved by referring to this terminfo +in the TERM variable prior to running sixel-tmux, then referring to another +termifo from within sixel-tmux. + +The terminfo list the capabilities of your terminal. Some prodding and guessing +is possible, but for best results, you want a terminfo that: + +- accurately describes your terminal capabilities, without minimizing them or + overestimating them, (more on that below) + +- uses a name that is common enough to avoid confusing software but not so + common that it tries to make clever guesses based on the name alone, for the + sake of compability with software that has been dead or ununsed for tens of + years + +A gleaming example of such issues is MC, which treats screen-256color as xterm +then proceeds to ignore the kmous entry of the terminfo unless DISPLAY is set, cf +https://unix.stackexchange.com/questions/304960/midnight-commander-force-xterm-permanently +"Midnight Commander treats that as 'xterm' (and decides it supports a mouse) +only if DISPLAY also is set. Ignore the comment (it is wrong), and just read +the source code. The function return value is used in one place, from main.c +(again, ignore the comment...). Midnight Commander ignores the actual contents +of the terminal description, which happens to say that this configuration +supports xterm-style mouse (i.e., the existence of kmous=\E[M, in the +description). It does this to work around its problems using slang, which also +ignores the terminal description, looking only at TERM. This is an old bug, +dating back to the 1990s" + +The main tmux branch has had similar problems with terminfo entries in the +past; ultimately patches have been added to support the core features such as +24-bit color: if you are curious, check +https://sunaku.github.io/tmux-24bit-color.html#usage which describes the +history of the process and explain the basics of what we'll be doing. + +As many applications hardcode workarounds, I have decided to use the "sixel-" +prefix: if they expect tmux or xterm, we should be able to catch them by +surprise! + +### Step 1.1 compiling the provided terminfo + +1. install sixel-tmux.terminfo: `tic sixel-tmux.terminfo; tic -o ~/.terminfo sixel-tmux.terminfo` +2. start sixel-tmux: `sixel-tmux` +3. select sixel-tmux: `export TERM=sixel-tmux` +4. verify sixel-tmux is used: `tput smglr|base64` should return GzcbWz82OWgbWyVpJXAxJWQ7JXAyJWRzGzg= : most sixel aware applications will check for smglr and other sequences before outputting long strings of sixels. + +Once you are done with these 4 steps, you will have a valid sixel-tmux to use +inside sixel-tmux (!!) + +However, before starting sixel-tmux, you must export the appropriate TERM for your +terminal: otherwise, sixel-tmux will know know what your terminal is capable of +(say, how many colors it may support) and could give you ugly graphics. + +#### Step 1.2 exporting TERM to say which terminfo to use + +If you are using mintty, mlterm or the cutexterm configuration of XTerm, you +can safely skip ahead and just use xterm-256color with: `export +TERM=xterm-256color` : all 3 terminals supports the best possible set of features: +most of the fancy text attributes, 256 colors, and truecolors on top! + +If you are using Windows Terminal Preview, you are mostly in the good, but +there are different versions and you may be stuck with an old version or there +may have been regression, so please follow the next 2 steps. + +#### Step 2: Configuring the msys2 features + +I am not a huge fan of WSL2: WSL1 was technically more ambitious. WSL2 is just +a glorified VM. + +I like my processes to interact with eachother, so I'm sticking to MSYS2, and +if you want a great command-line experience on Windows, I recommend you do the +same (especially if you do not have a large amount of RAM!) + +So install msys2 from https://www.msys2.org/, which comes with mintty which you +may want to use as it's currently better than Windows Terminal (unless you need +tabs more than sixels!) + +If you are using msys2 within Windows Terminal, I recommend a commandline of +`C:/msys64/usr/bin/env.exe MSYS='enable_pcon winsymlinks:nativestrict' +MSYSTEM=MSYS /bin/bash.exe --login -i` + +If you want to immediately start sixel-tmux, you will need to use script to create a pseudotty: for example replace the call to bash with `/usr/bin/script -c '/usr/bin/tmux c' /dev/null` to create a new session, or `/usr/bin/script -c '/usr/bin/tmux a' /dev/null` to attach to any existing sixel-tmux session. + +To run from within bash inside Windows Terminal, you do the same, due to the way the console is handled; see https://cygwin.com/pipermail/cygwin/2020-May/244878.html for more details. + +### Step 3: Configuring your 16 colors palette + +You should also use a color palette (called "color scheme" in Windows Terminal) +separating the light colors from the regular colors: Windows Terminal knows the +difference, as you can test yourself from Powershell, for example with +https://gist.githubusercontent.com/mlocati/fdabcaeb8071d5c75a2d51712db24011/raw/b710612d6320df7e146508094e84b92b34c77d48/win10colors.cmd + +If you want to check if you can do the same in bash, the simplest is comparing +red to bright-red (as red is kept unaffected by Solarized which uses 6 of the 8 +colors for shades of grey) with: + +``` +printf "\e[31mTest red \e[91mTest bright red\e[39 +\e[35mTest purple \e[95mTest bright purple\e" +``` + +### Step 3.1: Defining your 16 colors palette + +Here is for example a custom Solarized Light palette, inverting the highlight and regular colors compared to Windows Terminal default Solarized Light theme: +![Windows Terminal Solarized Light custom palette](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/windows-terminal-solarized-custom-palette.jpg) + +The inversion is deliberate: most command-line tools know how to use colors, but very few know how to use SGR attributes, so it makes sense to tweak the rare tools that will take advantage of SGR or the Solarized theme, while keeping everything else on its default settings + +If you want to do the same and add this customized Solarized to your Windows +Terminal, here is the relevant JSON: + +```json + { + "background": "#FFFDE7", + "black": "#073642", + "blue": "#839496", + "brightBlack": "#002B36", + "brightBlue": "#268BD2", + "brightCyan": "#2AA198", + "brightGreen": "#859900", + "brightPurple": "#D33682", + "brightRed": "#DC322F", + "brightWhite": "#EEE8D5", + "brightYellow": "#B58900", + "cursorColor": "#002B36", + "cyan": "#93A1A1", + "foreground": "#58584D", + "green": "#586E75", + "name": "Solarized Light Custom", + "purple": "#6C71C4", + "red": "#CB4B16", + "selectionBackground": "#FFFFFF", + "white": "#FDF6E3", + "yellow": "#657B83" + }, + { + "background": "#002B36", + "black": "#002B36", + "blue": "#839496", + "brightBlack": "#073642", + "brightBlue": "#268BD2", + "brightCyan": "#2AA198", + "brightGreen": "#859900", + "brightPurple": "#D33682", + "brightRed": "#DC322F", + "brightWhite": "#EEE8D5", + "brightYellow": "#B58900", + "cursorColor": "#FFFFFF", + "cyan": "#93A1A1", + "foreground": "#839496", + "green": "#586E75", + "name": "Solarized Dark Custom", + "purple": "#6C71C4", + "red": "#CB4B16", + "selectionBackground": "#FFFFFF", + "white": "#FDF6E3", + "yellow": "#657B83" + }, +``` + +You can also use command line scripts to dynamically change your palette, +here's for example +https://github.com/csdvrx/sixel-testsuite/toggle-solarized-light.sh: (there is a similar file for solarized-dark) + +``` +#!/bin/sh + +# With 24 bit colors support, dynamically change your current palette to solarized-light +echo "attempting toggle of solarized-light" +echo -ne '\eP\e]10;#657B83\a' # Foreground -> base00 +echo -ne '\eP\e]11;#FDF6E3\a' # Background -> base3 +echo -ne '\eP\e]12;#DC322F\a' # Cursor -> red +echo -ne '\eP\e]4;0;#073642\a' # black -> Base02 +echo -ne '\eP\e]4;8;#002B36\a' # bold black -> Base03 +echo -ne '\eP\e]4;1;#DC322F\a' # red -> red +echo -ne '\eP\e]4;9;#CB4B16\a' # bold red -> orange +echo -ne '\eP\e]4;2;#859900\a' # green -> green +echo -ne '\eP\e]4;10;#586E75\a' # bold green -> base01 * +echo -ne '\eP\e]4;3;#B58900\a' # yellow -> yellow +echo -ne '\eP\e]4;11;#657B83\a' # bold yellow -> base00 * +echo -ne '\eP\e]4;4;#268BD2\a' # blue -> blue +echo -ne '\eP\e]4;12;#839496\a' # bold blue -> base0 * +echo -ne '\eP\e]4;5;#D33682\a' # magenta -> magenta +echo -ne '\eP\e]4;13;#6C71C4\a' # bold magenta -> violet +echo -ne '\eP\e]4;6;#2AA198\a' # cyan -> cyan +echo -ne '\eP\e]4;14;#93A1A1\a' # bold cyan -> base1 * +echo -ne '\eP\e]4;7;#EEE8D5\a' # white -> Base2 +echo -ne '\eP\e]4;15;#FDF6E3\a' # bold white -> Base3 +``` + +### Step 3.2: Testing both your 16 colors palette and accessing it through AIXTerm SGR support + +Supporting AIXTerm SGR allow to properly control brightness separately from +bold, using AIXTerm SGR 90 to 96 and 100 to 107 for a brightness control +independant of the SGR 1 attribute that often just means "bold" on Linux. + +In Windows Terminal, AIXTERM SGR codes nicely complement the setting +`"intenseTextStyle": "bold"` for SGR1, allowing Linux users to still access +brightness control through different SGR attributes. + +This is nothing new: it's an approach that has been long known and documented, +cf this example from 7 years ago in +https://unix.stackexchange.com/questions/93814/cant-apply-brightness-to-terminals-background-color + +Given the compatibility matrix of existing terminals from +https://unix-junkie.github.io/christmas/Comparison%20of%20Terminal%20Emulators%20-%20Colour%20Support.html +the support of AIXTerm sequences for brightness control seem highly desirable +to match the behavior of most terminal emulators: if you are not using mintty or Windows Terminal, you should really check if this feature is suported! + +Therefore, another script provides a more complete test: here is what both +the minimal red-purple-brightred-brightpurple script from +https://github.com/csdvrx/sixel-testsuite/aixterm-minimal.sh and the more +complete 16 colors script from +https://github.com/csdvrx/sixel-testsuite/16-colors.sh look like in a Windows +Terminal with a custom Solarized Light palette +![Windows Terminal with Solarized custom running tests](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/windows-terminal-solarized-custom.jpg) + +If you did not do the previous step, the colors may look identical: Windows +Terminal supports AIXTerm SGR attributes for brightness, so it just means you +have a bad palette, for example: + +![Windows Terminal with a bad Solarized custom palette next to mintty](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/windows-terminal-wrong-palette.jpg) + +### Step 4: Configuring 256 colors and 24 bit colors + +Most terminals now support 256 colors, and a few support 24 bit colors, but +ideally, you want both. Fortunately, this is far less complicated than proper +16 color support. + +However, Solarized users do not like 256 color mode, because it often means +approximations of the RGB values of the precise Solarize colors are used, +instead of keeping the 16 precise true color sequences. + +Still, it's a nice thing to have a 256 color mode, as many tools don't know how +to call colors from a 24 bit palette (the setaf, setab capabilities from +terminfo) let alone juggle with the 3 incompatible variants if they don't know +what a terminfo capability is (check +https://github.com/alacritty/alacritty/issues/1485 for an clear presentation of +why SGR 38/48 in ECMA-48 is vague) + +So start with ``tput colors`` which should return 256, if not, you need to go +back to step 1 and export a TERM that will fully represent your terminal, yet +give you the expected answer of 256 when asked with tput. + +For 24-bit or "truecolor" mode, try ``tput setab`` (or tput setaf for the +foreground) if with either you get a string containing e48 of about 63 +characters (use ``tput setab|grep e48|wc`` after the export TERM), you should +be good. + +If you need more explanation, check in sixel-tmux.terminfo source from +config/.terminfo.src: it's meant to be didactic, so it doesn't include any +other terminfo but defines paramters from scratch, and instead fully define +each and every capability, with a few comments for the crunchy parts! + +You will see that Tc is another option, but setaf/setab should be preferred, +and that colors should not return the true value because until 2018 the +terminfo format was constrained to signed 16 bit ; see also +https://stackoverflow.com/questions/36158093/terminal-color-using-terminfo/36163656#36163656 + +``` +# 16m color was Tc, now obsoleted: use setaf, setab +# https://github.com/jwilm/alacritty/issues/1485#issue-346291738 +# +# 256 colors was: colors#0x100, but colors#16777216 is not signed 16 bit so use colors#0x100 +# along with setaf setbg to get 256 as fallback for apps not knowing setaf/setab +``` + +In 2021, the terminfo format change is relatively recent (3 years) ; to play it +safe I have decided to keep using the same colors string: apps are likely to +keep using the alternative ways for a while (like checking for setab). + +If you are interested in more details, including clever ways to detect if +truecolor is supported (try setting the color to a specific truecolor value +then ask the terminal what color it's using and compare the value to what's +expected if it could comply) check https://gist.github.com/XVilka/8346728 + +### Step 5: testing colors + +Now, try sixel-testsuite/256-colors.pl to confirm the 256 colors are working, +then see if you are lucky and can indeed use a 24 bits palette with +sixel-testsuite/24-bit-color.sh which does not use anything form terminfo + +It's a new awk-based script that is both faster and nicer that the previous version, by taking into account the width of your terminal to scale + +Here's what it looks like in Windows Terminal: + +![Windows Terminal in restricted width running the truecolor test](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/truecolor-test1-small.jpg) + +![Windows Terminal in full width running the truecolor test](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/truecolor-test2-fullwidth.jpg) + +### Step 6: font test + +In this final test, we will check if the font you've selected supports the various drawing characters used by terminal tools: +1. start with `cat font-ansi-blocks.txt` to see block drawing characters and a test bear +2. run `font-vt100.sh` to see the basic box drawing characters +2. `cat font-ansi-box.txt` to see the full set of box drawing characters +3. `cat font-dec.txt` to check DEC VT220 extra characters +4. `cat font-test-all.txt` to check unicode support and overall support of the drawing characters +5. Type in the console echo followed by the sign less than, then a space, then the sign equal, then another space, then bring back this line and remove the spaces. + +For the first test, you should see a bear without lines insides its head: +![Font test 1](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/font-test1.jpg) + +For the second test, you should see thin lines: +![Font test 2](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/font-test2.jpg) + +For the third test, you should see lines: +![Font test 3](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/font-test3.jpg) + +For the fourth test, you should see text in various languages, followed by the most important: grids of various patterns: +![Font test 4](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/font-test4.jpg) + +In this test, boxes should be properly aligned, and lines should intersect cleanly. + +In the fifth and final test, if you see a double arrow, congratulations! Your font even supports the programming ligatures! + +![Windows Terminal with Iosevka SS04 supports programming ligatures](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/font-programming-ligatures.jpg) + +If the font test fails, you may need to change your font as it may not support every character; I recommend using Iosevka which supports about everything: I personally like SS04 best, but if you are into fine tuning, check the alternatives options! + +Alternatively, when using mlterm you can tell it to extend your preferred font with one or more other fonts, for example: + +``` + ISO8859_1 = Iosevka SS04 18 + ISO8859_15 = Iosevka SS04 18 + ISO10646_UCS2_1 =Iosevka SS04 18 + ISO10646_UCS2_1_BOLD = Iosevka SS04 18 + U+2500-25ff=Segoe UI Symbol + U+25C6 = Tera Special # Diamond ◆ + U+2409 = Tera Special # Horizontal tab ␉ + U+240C = Tera Special # Form feed ␌ + U+240D = Tera Special # Carrier return ␍ + U+240A = Tera Special # Linefeed ␊ + U+2424 = Tera Special # Newline ␤ + U+240B = Tera Special # Vertical tab ␋ + U+23BA = Tera Special # Horizontal line 1 ⎺ + U+23BB = Tera Special # Horizontal line 3 ⎻ + U+2500 = Tera Special # Horizontal line 5 ─ + U+23BC = Tera Special # Horizontal line 7 ⎼ + U+23BD = Tera Special # Horizontal line 9 ⎽ +``` + +When everything works to your satisfaction, compile sixel-tmux, or run the provided msys2 binary + +### Compiling sixel-tmux + +To compile, first install libevent2 and ncurses along with the C development packages (gcc, make etc) + +If you are using msys2 use pacman -S to install the relevant development packages after checking their version with pacman -sS: +``` +pacman -sS libevent +pacman -sS ncurses +pacman -S msys/libevent-devel +pacman -S msys/ncurses-devel +``` + +After installing them, here is what you should get: + +![pacman from msys2 inside Windows Terminal](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/msys2-ncurses-libevent-installed.jpg) + +You can then compile with: `./configure && make && make -j8 install` + +if you want to run it from Windows Terminal, please use script to create a pty instead of /dev/consN, for example `/usr/bin/script -c '/usr/bin/tmux c' /dev/null` to create a new session or `/usr/bin/script -c '/usr/bin/tmux a' /dev/null` to attach to an existing session. + +You can then run gain the sixel test-suite from to check if what was working in your terminal before sixel-tmux is still working. + +If it isn't, oops: it means sixel-tmux broke something. Patches are welcome! +(this outcome is extremely unlikely, but you know, prepare for the worst, and hope for the best!) + +### Current status and tests failed + +Sixels fully work, but images are not redrawn when returning from another buffer (snake.six) + +vertical split does not plays well with ansi and sixel in passthrough mode + +Overlines and double underlines are not working yet (ansi-vte52.sh) ; the situation is slightly better in mintty: +![ansi-vt52 inside sixel-tmux inside mintty](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/ansi-vt52-inside-sixel-tmux-inside-mintty.jpg) + +than inside Windows Terminal: +![ansi-vt52 inside sixel-tmux inside Windows Terminal](https://raw.githubusercontent.com/csdvrx/sixel-tmux/main/examples/ansi-vt52-inside-sixel-tmux-inside-windows-terminal.jpg) + +dynamic switching of the 16 colors palette is not working (toggle-solarized-dark.sh/toggle-solarized-light.sh) diff --git a/README.tmux.orig b/README.tmux.orig new file mode 100644 index 0000000000..4f577060c4 --- /dev/null +++ b/README.tmux.orig @@ -0,0 +1,75 @@ +Welcome to tmux! + +tmux is a terminal multiplexer: it enables a number of terminals to be created, +accessed, and controlled from a single screen. tmux may be detached from a +screen and continue running in the background, then later reattached. + +This release runs on OpenBSD, FreeBSD, NetBSD, Linux, OS X and Solaris. + +* Dependencies + +tmux depends on libevent 2.x, available from: + + https://github.com/libevent/libevent/releases/latest + +It also depends on ncurses, available from: + + https://invisible-mirror.net/archives/ncurses/ + +* Installation + +To build and install tmux from a release tarball, use: + + $ ./configure && make + $ 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. + +To get and build the latest from version control - note that this requires +autoconf, automake and pkg-config: + + $ git clone https://github.com/tmux/tmux.git + $ cd tmux + $ sh autogen.sh + $ ./configure && make + +* Contributing + +Bug reports, feature suggestions and especially code contributions are most +welcome. Please send by email to: + + tmux-users@googlegroups.com + +Or open a GitHub issue or pull request. + +* Documentation + +For documentation on using tmux, see the tmux.1 manpage. View it from the +source tree with: + + $ nroff -mdoc tmux.1|less + +A small example configuration is in example_tmux.conf. + +A bash(1) completion file is at: + + https://github.com/imomaliev/tmux-bash-completion + +For debugging, run tmux with -v and -vv to generate server and client log files +in the current directory. + +* Support + +The tmux mailing list for general discussion and bug reports is: + + https://groups.google.com/forum/#!forum/tmux-users + +Subscribe by sending an email to: + + tmux-users+subscribe@googlegroups.com + +* License + +This file and the CHANGES files are licensed under the ISC license. All other +files have a license and copyright notice at their start. diff --git a/TODO b/TODO deleted file mode 100644 index 5a8892d2e9..0000000000 --- a/TODO +++ /dev/null @@ -1,130 +0,0 @@ -- command bits and pieces: - * allow multiple targets: fnmatch for -t/-c, for example detach all - clients with -t* - * ' and " should be parsed the same (eg "\e" vs '\e') in config - and command prompt - * last-pane across sessions - * resize-pane -p to match split-window -p - * flag to wait-for to have a timeout and/or to stop waiting when the - client gets a signal - -- make command sequences more usable - * don't require space after ; - * options for error handling: && and ||? - -- options bits and pieces: - * way to set socket path from config file - -- format improvements: - * option to quote format (#{q:session_name}) - * some way to pad # stuff with spaces - * formats to show if a window is linked into multiple sessions, into - multiple attached sessions, and is the active window in multiple - attached sessions? - * comparison operators like < and > (for #{version}?) - * %else statement in config file - -- improve monitor-*: - * straighten out rules for multiple clients - * think about what happens across sessions - * monitor changes within a region - * perhaps monitor /all/ panes in the window not just one - -- improve mouse support: - * bind commands to mouse in different areas? - * commands executed when clicking on a pattern (URL) - -- warts on current naming: - * display-time but message-fg/bg/attr - * list-* vs show-* - -- copy/paste improvements: - * paste w/o trailing whitespace - * command to toggle selection not to move it in copy-mode - * regex searching - * searching in copy mode should unwrap lines, so if you search for "foobar" - then it should be found even if it is now "foo\nbar" (if the WRAP flag - is set on the line) - * capture-pane option to preserve spaces but not join lines - * improve word and line selection in copy mode (for example when - dragging it should select by word. compare how xterm works. GitHub - issue 682) - -- layout stuff - * way to tag a layout as a number/name - * maybe keep last layout + size around and if size reverts just put it - back - * revamp layouts: they are too complicated, should be more closely - integrated, should support hints, layout sets should just be a - special case of custom layouts, and we should support panes that are - not attached to a cell at all. this could be the time to introduce - panelink to replace layout_cell - * way to set hints/limits about pane size for resizing - * panning over window (window larger than visible) - * a mode where one application can cross two panes (ie x|y, width = - COLUMNS/2 but height = ROWS * 2) - * separate active panes for different clients - * way to choose where the freed space goes when a pane is killed: - option to kill-pane? GitHub issue 918 - -- code cleanup - * instead of separate window and session options, just one master - options list with each option having a type (window or session), then - options on window, on session, and global. for window options we look - window->session->global, and for session we look session->global. - problem: what about windows in multiple sessions? there are contexts - where we do not know which session, or where multiple choices makes - no sense... could at least have one global list for all types of - global options and keep separate window,session lists - * the way pane, window, session destroy is handled is too complicated - and the distinction between session.c, window.c and server-fn.c - functions is not clear. could we just have kill_pane(), - kill_window(), unlink_window(), kill_session() that fix up all data - structures (flagging sessions as dead) and return a value to say - whether clients need to be checked for dead sessions? sort of like - session_detach now but more so. or some other scheme to make it - simpler and clearer? also would be nice to remove/rename server-fn.c - * more readable way to work out the various things commands need to - know about the client, notably: - - is this the config file? (cmdq->c == NULL) - - is this a command client? (cmdq->c != NULL && - cmdq->c->session == NULL) - - is this a control client? - - can i do stdin or stdout to this client? - or even guarantee that cmdq->c != NULL and provide a better way to - tell when in the config file - then we use cmdq->c if we need a - client w/o a session else cmd_current_client - -- miscellaneous - * link panes into multiple windows - * live update: server started with -U connects to server, requests - sessions and windows, receives file descriptors - * there are inconsistencies in what we get from old shell and what - comes from config for new sessions and windows. likewise, panes and - jobs and run-shell and lock command all start with slightly different - environments - * multiline status line? separate command prompt and status line? - * automatic pane logging - * marks in history, automatically add (move?) one when pane is changed - * this doesn't work, need pane reference count probably: - bind -n DoubleClick3Status confirm-before -p "kill-window #I? (y/n)" kill-window - * respawn -c flag same as neww etc - * marker lines in history (GitHub issue 1042) - * tree mode stuff: predefined filters, tag-all key, ... - -- hooks - * more hooks for various things - * finish after hooks for special commands. these do not have a hook at - the moment: - attach-session detach-client kill-server respawn-window - swap-window break-pane find-window kill-session rotate-window - switch-client choose-tree if-shell kill-window run-shell - wait-for command-prompt join-pane move-window source-file - confirm-before kill-pane respawn-pane swap-pane - at the moment AFTERHOOK uses current only if target is not valid, - but target is ALWAYS valid - it should use current if no -t flag? - then select-* could use AFTERHOOK - * multiple hooks with the same name? - * finish hooks for notifys - * for session_closed, if no sessions at all, perhaps fake up a - temporary one diff --git a/alerts.c b/alerts.c index 27af3ee469..6fe88a09d1 100644 --- a/alerts.c +++ b/alerts.c @@ -141,9 +141,11 @@ alerts_reset(struct window *w) { struct timeval tv; + if (!event_initialized(&w->alerts_timer)) + evtimer_set(&w->alerts_timer, alerts_timer, w); + w->flags &= ~WINDOW_SILENCE; - if (event_initialized(&w->alerts_timer)) - event_del(&w->alerts_timer); + event_del(&w->alerts_timer); timerclear(&tv); tv.tv_sec = options_get_number(w->options, "monitor-silence"); @@ -158,9 +160,6 @@ alerts_queue(struct window *w, int flags) { alerts_reset(w); - if (!event_initialized(&w->alerts_timer)) - evtimer_set(&w->alerts_timer, alerts_timer, w); - if ((w->flags & flags) != flags) { w->flags |= flags; log_debug("@%u alerts flags added %#x", w->id, flags); @@ -201,8 +200,10 @@ alerts_check_bell(struct window *w) * not check WINLINK_BELL). */ s = wl->session; - if (s->curw != wl) + if (s->curw != wl) { wl->flags |= WINLINK_BELL; + server_status_session(s); + } if (!alerts_action_applies(wl, "bell-action")) continue; notify_winlink("alert-bell", wl); @@ -235,8 +236,10 @@ alerts_check_activity(struct window *w) if (wl->flags & WINLINK_ACTIVITY) continue; s = wl->session; - if (s->curw != wl) + if (s->curw != wl) { wl->flags |= WINLINK_ACTIVITY; + server_status_session(s); + } if (!alerts_action_applies(wl, "activity-action")) continue; notify_winlink("alert-activity", wl); @@ -269,8 +272,10 @@ alerts_check_silence(struct window *w) if (wl->flags & WINLINK_SILENCE) continue; s = wl->session; - if (s->curw != wl) + if (s->curw != wl) { wl->flags |= WINLINK_SILENCE; + server_status_session(s); + } if (!alerts_action_applies(wl, "silence-action")) continue; notify_winlink("alert-silence", wl); diff --git a/arguments.c b/arguments.c index 451ccc42e6..026272afad 100644 --- a/arguments.c +++ b/arguments.c @@ -28,9 +28,16 @@ * Manipulate command arguments. */ +struct args_value { + char *value; + TAILQ_ENTRY(args_value) entry; +}; +TAILQ_HEAD(args_values, args_value); + struct args_entry { u_char flag; - char *value; + struct args_values values; + u_int count; RB_ENTRY(args_entry) entry; }; @@ -92,12 +99,18 @@ args_free(struct args *args) { struct args_entry *entry; struct args_entry *entry1; + struct args_value *value; + struct args_value *value1; cmd_free_argv(args->argc, args->argv); RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) { RB_REMOVE(args_tree, &args->tree, entry); - free(entry->value); + TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) { + TAILQ_REMOVE(&entry->values, value, entry); + free(value->value); + free(value); + } free(entry); } @@ -123,94 +136,146 @@ args_print_add(char **buf, size_t *len, const char *fmt, ...) free(s); } +/* Add value to string. */ +static void +args_print_add_value(char **buf, size_t *len, struct args_entry *entry, + 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; + + if (**buf != '\0') + args_print_add(buf, len, " "); + + escaped = args_escape(argument); + args_print_add(buf, len, "%s", escaped); + free(escaped); +} + /* Print a set of arguments. */ char * args_print(struct args *args) { size_t len; - char *buf, *escaped; - int i, flags; + char *buf; + int i; + u_int j; struct args_entry *entry; - static const char quoted[] = " #\"';$"; + struct args_value *value; len = 1; buf = xcalloc(1, len); /* Process the flags first. */ RB_FOREACH(entry, args_tree, &args->tree) { - if (entry->value != NULL) + if (!TAILQ_EMPTY(&entry->values)) continue; if (*buf == '\0') args_print_add(&buf, &len, "-"); - args_print_add(&buf, &len, "%c", entry->flag); + for (j = 0; j < entry->count; j++) + args_print_add(&buf, &len, "%c", entry->flag); } /* Then the flags with arguments. */ RB_FOREACH(entry, args_tree, &args->tree) { - if (entry->value == NULL) - continue; - - if (*buf != '\0') - args_print_add(&buf, &len, " -%c ", entry->flag); - else - args_print_add(&buf, &len, "-%c ", entry->flag); - - flags = VIS_OCTAL|VIS_TAB|VIS_NL; - if (entry->value[strcspn(entry->value, quoted)] != '\0') - flags |= VIS_DQ; - utf8_stravis(&escaped, entry->value, flags); - if (flags & VIS_DQ) - args_print_add(&buf, &len, "\"%s\"", escaped); - else - args_print_add(&buf, &len, "%s", escaped); - free(escaped); + TAILQ_FOREACH(value, &entry->values, entry) + args_print_add_value(&buf, &len, entry, value); } /* And finally the argument vector. */ - for (i = 0; i < args->argc; i++) { - if (*buf != '\0') - args_print_add(&buf, &len, " "); - - flags = VIS_OCTAL|VIS_TAB|VIS_NL; - if (args->argv[i][strcspn(args->argv[i], quoted)] != '\0') - flags |= VIS_DQ; - utf8_stravis(&escaped, args->argv[i], flags); - if (flags & VIS_DQ) - args_print_add(&buf, &len, "\"%s\"", escaped); - else - args_print_add(&buf, &len, "%s", escaped); - free(escaped); - } + for (i = 0; i < args->argc; i++) + args_print_add_argument(&buf, &len, args->argv[i]); return (buf); } +/* Escape an argument. */ +char * +args_escape(const char *s) +{ + static const char quoted[] = " #\"';${}"; + char *escaped, *result; + int flags; + + if (*s == '\0') + return (xstrdup(s)); + if (s[0] != ' ' && + (strchr(quoted, s[0]) != NULL || s[0] == '~') && + s[1] == '\0') { + xasprintf(&escaped, "\\%c", s[0]); + return (escaped); + } + + flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; + if (s[strcspn(s, quoted)] != '\0') + flags |= VIS_DQ; + utf8_stravis(&escaped, s, flags); + + if (flags & VIS_DQ) { + if (*escaped == '~') + xasprintf(&result, "\"\\%s\"", escaped); + else + xasprintf(&result, "\"%s\"", escaped); + } else { + if (*escaped == '~') + xasprintf(&result, "\\%s", escaped); + else + result = xstrdup(escaped); + } + free(escaped); + return (result); +} + /* Return if an argument is present. */ int args_has(struct args *args, u_char ch) { - return (args_find(args, ch) != NULL); + struct args_entry *entry; + + entry = args_find(args, ch); + if (entry == NULL) + return (0); + return (entry->count); } /* Set argument value in the arguments tree. */ void -args_set(struct args *args, u_char ch, const char *value) +args_set(struct args *args, u_char ch, const char *s) { struct args_entry *entry; + struct args_value *value; - /* Replace existing argument. */ - if ((entry = args_find(args, ch)) != NULL) { - free(entry->value); - entry->value = NULL; - } else { + entry = args_find(args, ch); + if (entry == NULL) { entry = xcalloc(1, sizeof *entry); entry->flag = ch; + entry->count = 1; + TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); - } + } else + entry->count++; - if (value != NULL) - entry->value = xstrdup(value); + if (s != NULL) { + value = xcalloc(1, sizeof *value); + value->value = xstrdup(s); + TAILQ_INSERT_TAIL(&entry->values, value, entry); + } } /* Get argument value. Will be NULL if it isn't present. */ @@ -221,7 +286,34 @@ args_get(struct args *args, u_char ch) if ((entry = args_find(args, ch)) == NULL) return (NULL); - return (entry->value); + return (TAILQ_LAST(&entry->values, args_values)->value); +} + +/* Get first value in argument. */ +const char * +args_first_value(struct args *args, u_char ch, struct args_value **value) +{ + struct args_entry *entry; + + if ((entry = args_find(args, ch)) == NULL) + return (NULL); + + *value = TAILQ_FIRST(&entry->values); + if (*value == NULL) + return (NULL); + return ((*value)->value); +} + +/* Get next value in argument. */ +const char * +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); } /* Convert an argument value to a number. */ @@ -232,13 +324,15 @@ args_strtonum(struct args *args, u_char ch, long long minval, long long maxval, const char *errstr; long long ll; struct args_entry *entry; + struct args_value *value; if ((entry = args_find(args, ch)) == NULL) { *cause = xstrdup("missing"); return (0); } + value = TAILQ_LAST(&entry->values, args_values); - ll = strtonum(entry->value, minval, maxval, &errstr); + ll = strtonum(value->value, minval, maxval, &errstr); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); diff --git a/attributes.c b/attributes.c index 1e45e584cf..5849af2b17 100644 --- a/attributes.c +++ b/attributes.c @@ -25,13 +25,13 @@ const char * attributes_tostring(int attr) { - static char buf[128]; + static char buf[512]; size_t len; if (attr == 0) return ("none"); - len = xsnprintf(buf, sizeof buf, "%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", (attr & GRID_ATTR_BRIGHT) ? "bright," : "", (attr & GRID_ATTR_DIM) ? "dim," : "", (attr & GRID_ATTR_UNDERSCORE) ? "underscore," : "", @@ -39,7 +39,12 @@ attributes_tostring(int attr) (attr & GRID_ATTR_REVERSE) ? "reverse," : "", (attr & GRID_ATTR_HIDDEN) ? "hidden," : "", (attr & GRID_ATTR_ITALICS) ? "italics," : "", - (attr & GRID_ATTR_STRIKETHROUGH) ? "strikethrough," : ""); + (attr & GRID_ATTR_STRIKETHROUGH) ? "strikethrough," : "", + (attr & GRID_ATTR_UNDERSCORE_2) ? "double-underscore," : "", + (attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "", + (attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "", + (attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "", + (attr & GRID_ATTR_OVERLINE) ? "overline," : ""); if (len > 0) buf[len - 1] = '\0'; @@ -52,6 +57,26 @@ attributes_fromstring(const char *str) const char delimiters[] = " ,|"; int attr; size_t end; + u_int i; + struct { + const char* name; + int attr; + } table[] = { + { "bright", GRID_ATTR_BRIGHT }, + { "bold", GRID_ATTR_BRIGHT }, + { "dim", GRID_ATTR_DIM }, + { "underscore", GRID_ATTR_UNDERSCORE }, + { "blink", GRID_ATTR_BLINK }, + { "reverse", GRID_ATTR_REVERSE }, + { "hidden", GRID_ATTR_HIDDEN }, + { "italics", GRID_ATTR_ITALICS }, + { "strikethrough", GRID_ATTR_STRIKETHROUGH }, + { "double-underscore", GRID_ATTR_UNDERSCORE_2 }, + { "curly-underscore", GRID_ATTR_UNDERSCORE_3 }, + { "dotted-underscore", GRID_ATTR_UNDERSCORE_4 }, + { "dashed-underscore", GRID_ATTR_UNDERSCORE_5 }, + { "overline", GRID_ATTR_OVERLINE } + }; if (*str == '\0' || strcspn(str, delimiters) == 0) return (-1); @@ -64,24 +89,15 @@ attributes_fromstring(const char *str) attr = 0; do { end = strcspn(str, delimiters); - if ((end == 6 && strncasecmp(str, "bright", end) == 0) || - (end == 4 && strncasecmp(str, "bold", end) == 0)) - attr |= GRID_ATTR_BRIGHT; - else if (end == 3 && strncasecmp(str, "dim", end) == 0) - attr |= GRID_ATTR_DIM; - else if (end == 10 && strncasecmp(str, "underscore", end) == 0) - attr |= GRID_ATTR_UNDERSCORE; - else if (end == 5 && strncasecmp(str, "blink", end) == 0) - attr |= GRID_ATTR_BLINK; - else if (end == 7 && strncasecmp(str, "reverse", end) == 0) - attr |= GRID_ATTR_REVERSE; - else if (end == 6 && strncasecmp(str, "hidden", end) == 0) - attr |= GRID_ATTR_HIDDEN; - else if (end == 7 && strncasecmp(str, "italics", end) == 0) - attr |= GRID_ATTR_ITALICS; - else if (end == 13 && strncasecmp(str, "strikethrough", end) == 0) - attr |= GRID_ATTR_STRIKETHROUGH; - else + for (i = 0; i < nitems(table); i++) { + if (end != strlen(table[i].name)) + continue; + if (strncasecmp(str, table[i].name, end) == 0) { + attr |= table[i].attr; + break; + } + } + if (i == nitems(table)) return (-1); str += end + strspn(str + end, delimiters); } while (*str != '\0'); diff --git a/cfg.c b/cfg.c index 00ee33b143..c29292b229 100644 --- a/cfg.c +++ b/cfg.c @@ -26,6 +26,7 @@ #include "tmux.h" +struct client *cfg_client; static char *cfg_file; int cfg_finished; static char **cfg_causes; @@ -51,7 +52,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data) cfg_show_causes(RB_MIN(sessions, &sessions)); if (cfg_item != NULL) - cfg_item->flags &= ~CMDQ_WAITING; + cmdq_continue(cfg_item); status_prompt_load_history(); @@ -69,13 +70,12 @@ void start_cfg(void) { const char *home; - int quiet = 0; + int flags = 0; struct client *c; /* - * Configuration files are loaded without a client, so NULL is passed - * into load_cfg() and commands run in the global queue with - * item->client NULL. + * Configuration files are loaded without a client, so commands are run + * in the global queue with item->client NULL. * * However, we must block the initial client (but just the initial * client) so that its command runs after the configuration is loaded. @@ -83,113 +83,76 @@ start_cfg(void) * command queue is currently empty and our callback will be at the * front - we need to get in before MSG_COMMAND. */ - c = TAILQ_FIRST(&clients); + cfg_client = c = TAILQ_FIRST(&clients); if (c != NULL) { cfg_item = cmdq_get_callback(cfg_client_done, NULL); cmdq_append(c, cfg_item); } - load_cfg(TMUX_CONF, NULL, NULL, 1); + if (cfg_file == NULL) + load_cfg(TMUX_CONF, c, NULL, CMD_PARSE_QUIET, NULL); if (cfg_file == NULL && (home = find_home()) != NULL) { xasprintf(&cfg_file, "%s/.tmux.conf", home); - quiet = 1; + flags = CMD_PARSE_QUIET; } if (cfg_file != NULL) - load_cfg(cfg_file, NULL, NULL, quiet); + load_cfg(cfg_file, c, NULL, flags, NULL); cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } int -load_cfg(const char *path, struct client *c, struct cmdq_item *item, int quiet) +load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, + struct cmdq_item **new_item) { FILE *f; - const char delim[3] = { '\\', '\\', '\0' }; - u_int found = 0; - size_t line = 0; - char *buf, *cause1, *p, *q, *s; - struct cmd_list *cmdlist; - struct cmdq_item *new_item; - int condition = 0; - struct format_tree *ft; + struct cmd_parse_input pi; + struct cmd_parse_result *pr; + struct cmdq_item *new_item0; + + if (new_item != NULL) + *new_item = NULL; log_debug("loading %s", path); if ((f = fopen(path, "rb")) == NULL) { - if (errno == ENOENT && quiet) + if (errno == ENOENT && (flags & CMD_PARSE_QUIET)) return (0); cfg_add_cause("%s: %s", path, strerror(errno)); return (-1); } - while ((buf = fparseln(f, NULL, &line, delim, 0)) != NULL) { - log_debug("%s: %s", path, buf); - - p = buf; - while (isspace((u_char)*p)) - p++; - if (*p == '\0') { - free(buf); - continue; - } - q = p + strlen(p) - 1; - while (q != p && isspace((u_char)*q)) - *q-- = '\0'; - - if (condition != 0 && strcmp(p, "%endif") == 0) { - condition = 0; - continue; - } - if (strncmp(p, "%if ", 4) == 0) { - if (condition != 0) { - cfg_add_cause("%s:%zu: nested %%if", path, - line); - continue; - } - ft = format_create(NULL, NULL, FORMAT_NONE, - FORMAT_NOJOBS); - - s = p + 3; - while (isspace((u_char)*s)) - s++; - s = format_expand(ft, s); - if (*s != '\0' && (s[0] != '0' || s[1] != '\0')) - condition = 1; - else - condition = -1; - free(s); - - format_free(ft); - continue; - } - if (condition == -1) - continue; - - cmdlist = cmd_string_parse(p, path, line, &cause1); - if (cmdlist == NULL) { - free(buf); - if (cause1 == NULL) - continue; - cfg_add_cause("%s:%zu: %s", path, line, cause1); - free(cause1); - continue; - } - free(buf); - - if (cmdlist == NULL) - continue; - new_item = cmdq_get_command(cmdlist, NULL, NULL, 0); - if (item != NULL) - cmdq_insert_after(item, new_item); - else - cmdq_append(c, new_item); - cmd_list_free(cmdlist); - - found++; - } + memset(&pi, 0, sizeof pi); + pi.flags = flags; + pi.file = path; + pi.line = 1; + pi.item = item; + pi.c = c; + + 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); + return (-1); + } + if (flags & CMD_PARSE_PARSEONLY) { + cmd_list_free(pr->cmdlist); + return (0); + } + + new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); + if (item != NULL) + cmdq_insert_after(item, new_item0); + else + cmdq_append(NULL, new_item0); + cmd_list_free(pr->cmdlist); - return (found); + if (new_item != NULL) + *new_item = new_item0; + return (0); } void @@ -225,15 +188,17 @@ cfg_print_causes(struct cmdq_item *item) void cfg_show_causes(struct session *s) { - struct window_pane *wp; - u_int i; + struct window_pane *wp; + struct window_mode_entry *wme; + u_int i; if (s == NULL || cfg_ncauses == 0) return; wp = s->curw->window->active; - window_pane_set_mode(wp, &window_copy_mode, NULL, NULL); - window_copy_init_for_output(wp); + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->mode != &window_view_mode) + window_pane_set_mode(wp, &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 0642bdc4fb..204a431b8d 100644 --- a/client.c +++ b/client.c @@ -17,11 +17,10 @@ */ #include -#include #include -#include #include #include +#include #include #include @@ -203,7 +202,7 @@ client_exit_message(void) case CLIENT_EXIT_TERMINATED: return ("terminated"); case CLIENT_EXIT_LOST_SERVER: - return ("lost server"); + return ("server exited unexpectedly"); case CLIENT_EXIT_EXITED: return ("exited"); case CLIENT_EXIT_SERVER_EXITED: @@ -216,14 +215,13 @@ client_exit_message(void) int client_main(struct event_base *base, int argc, char **argv, int flags) { + struct cmd_parse_result *pr; struct cmd *cmd; - struct cmd_list *cmdlist; struct msg_command_data *data; int cmdflags, fd, i; const char *ttynam, *cwd; pid_t ppid; enum msgtype msg; - char *cause, path[PATH_MAX]; struct termios tio, saved_tio; size_t size; @@ -249,14 +247,15 @@ client_main(struct event_base *base, int argc, char **argv, int flags) * later in server) but it is necessary to get the start server * flag. */ - cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause); - if (cmdlist != NULL) { - TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { + pr = cmd_parse_from_arguments(argc, argv, NULL); + if (pr->status == CMD_PARSE_SUCCESS) { + TAILQ_FOREACH(cmd, &pr->cmdlist->list, qentry) { if (cmd->entry->flags & CMD_STARTSERVER) cmdflags |= CMD_STARTSERVER; } - cmd_list_free(cmdlist); - } + cmd_list_free(pr->cmdlist); + } else + free(pr->error); } /* Create client process structure (starts logging). */ @@ -278,10 +277,8 @@ client_main(struct event_base *base, int argc, char **argv, int flags) client_peer = proc_add_peer(client_proc, fd, client_dispatch, NULL); /* Save these before pledge(). */ - if ((cwd = getcwd(path, sizeof path)) == NULL) { - if ((cwd = find_home()) == NULL) - cwd = "/"; - } + if ((cwd = find_cwd()) == NULL && (cwd = find_home()) == NULL) + cwd = "/"; if ((ttynam = ttyname(STDIN_FILENO)) == NULL) ttynam = ""; @@ -338,6 +335,10 @@ client_main(struct event_base *base, int argc, char **argv, int flags) size = 0; for (i = 0; i < argc; i++) size += strlen(argv[i]) + 1; + if (size > MAX_IMSGSIZE - (sizeof *data)) { + fprintf(stderr, "command too long\n"); + return (1); + } data = xmalloc((sizeof *data) + size); /* Prepare command for server. */ @@ -435,7 +436,7 @@ client_stdin_callback(__unused int fd, __unused short events, struct msg_stdin_data data; data.size = read(STDIN_FILENO, data.data, sizeof data.data); - if (data.size < 0 && (errno == EINTR || errno == EAGAIN)) + if (data.size == -1 && (errno == EINTR || errno == EAGAIN)) return; proc_send(client_peer, MSG_STDIN, -1, &data, sizeof data); @@ -449,6 +450,7 @@ client_write(int fd, const char *data, size_t size) { ssize_t used; + log_debug("%s: %.*s", __func__, (int)size, data); while (size != 0) { used = write(fd, data, size); if (used == -1) { diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 38d9f8a2af..477d351740 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -37,8 +37,8 @@ const struct cmd_entry cmd_attach_session_entry = { .name = "attach-session", .alias = "attach", - .args = { "c:dErt:", 0, 0 }, - .usage = "[-dEr] [-c working-directory] " CMD_TARGET_SESSION_USAGE, + .args = { "c:dErt:x", 0, 0 }, + .usage = "[-dErx] [-c working-directory] " CMD_TARGET_SESSION_USAGE, /* -t is special */ @@ -48,7 +48,7 @@ const struct cmd_entry cmd_attach_session_entry = { enum cmd_retval cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, - int rflag, const char *cflag, int Eflag) + int xflag, int rflag, const char *cflag, int Eflag) { struct cmd_find_state *current = &item->shared->current; enum cmd_find_type type; @@ -58,6 +58,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, struct winlink *wl; struct window_pane *wp; char *cause; + enum msgtype msgtype; if (RB_EMPTY(&sessions)) { cmdq_error(item, "no sessions"); @@ -87,7 +88,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (wl != NULL) { if (wp != NULL) - window_set_active_pane(wp->window, wp); + window_set_active_pane(wp->window, wp, 1); session_set_current(s, wl); if (wp != NULL) cmd_find_from_winlink_pane(current, wl, wp, 0); @@ -100,12 +101,17 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, s->cwd = format_single(item, cflag, c, s, wl, wp); } + c->last_session = c->session; if (c->session != NULL) { - if (dflag) { + if (dflag || xflag) { + if (xflag) + msgtype = MSG_DETACHKILL; + else + msgtype = MSG_DETACH; TAILQ_FOREACH(c_loop, &clients, entry) { if (c_loop->session != s || c == c_loop) continue; - server_client_detach(c_loop, MSG_DETACH); + server_client_detach(c_loop, msgtype); } } if (!Eflag) @@ -114,12 +120,14 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, 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); 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); @@ -129,11 +137,15 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (rflag) c->flags |= CLIENT_READONLY; - if (dflag) { + if (dflag || xflag) { + if (xflag) + msgtype = MSG_DETACHKILL; + else + msgtype = MSG_DETACH; TAILQ_FOREACH(c_loop, &clients, entry) { if (c_loop->session != s || c == c_loop) continue; - server_client_detach(c_loop, MSG_DETACH); + server_client_detach(c_loop, msgtype); } } if (!Eflag) @@ -141,12 +153,14 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, c->session = 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); @@ -166,6 +180,6 @@ cmd_attach_session_exec(struct cmd *self, struct cmdq_item *item) struct args *args = self->args; return (cmd_attach_session(item, args_get(args, 't'), - args_has(args, 'd'), args_has(args, 'r'), args_get(args, 'c'), - args_has(args, 'E'))); + args_has(args, 'd'), args_has(args, 'x'), args_has(args, 'r'), + args_get(args, 'c'), args_has(args, 'E'))); } diff --git a/cmd-bind-key.c b/cmd-bind-key.c index 97af9f57ca..2af15d2928 100644 --- a/cmd-bind-key.c +++ b/cmd-bind-key.c @@ -44,15 +44,16 @@ const struct cmd_entry cmd_bind_key_entry = { static enum cmd_retval cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = self->args; - char *cause; - struct cmd_list *cmdlist; - key_code key; - const char *tablename; + struct args *args = self->args; + key_code key; + const char *tablename; + struct cmd_parse_result *pr; + char **argv = args->argv; + int argc = args->argc; - key = key_string_lookup_string(args->argv[0]); + key = key_string_lookup_string(argv[0]); if (key == KEYC_NONE || key == KEYC_UNKNOWN) { - cmdq_error(item, "unknown key: %s", args->argv[0]); + cmdq_error(item, "unknown key: %s", argv[0]); return (CMD_RETURN_ERROR); } @@ -63,14 +64,21 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item) else tablename = "prefix"; - cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0, - &cause); - if (cmdlist == NULL) { - cmdq_error(item, "%s", cause); - free(cause); + if (argc == 2) + pr = cmd_parse_from_string(argv[1], NULL); + else + pr = cmd_parse_from_arguments(argc - 1, argv + 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); return (CMD_RETURN_ERROR); + case CMD_PARSE_SUCCESS: + break; } - - key_bindings_add(tablename, key, args_has(args, 'r'), cmdlist); + key_bindings_add(tablename, key, args_has(args, 'r'), pr->cmdlist); return (CMD_RETURN_NORMAL); } diff --git a/cmd-break-pane.c b/cmd-break-pane.c index 74ecce6f7b..6c63810372 100644 --- a/cmd-break-pane.c +++ b/cmd-break-pane.c @@ -76,9 +76,12 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item) window_lost_pane(w, wp); layout_close_pane(wp); - w = wp->window = window_create(dst_s->sx, dst_s->sy); + w = wp->window = window_create(w->sx, w->sy, w->xpixel, w->ypixel); + options_set_parent(wp->options, w->options); + wp->flags |= PANE_STYLECHANGED; TAILQ_INSERT_HEAD(&w->panes, wp, entry); w->active = wp; + w->latest = c; if (!args_has(args, 'n')) { name = default_window_name(w); diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c index d3d7e8f86e..fc6c26e4e0 100644 --- a/cmd-capture-pane.c +++ b/cmd-capture-pane.c @@ -39,9 +39,9 @@ const struct cmd_entry cmd_capture_pane_entry = { .name = "capture-pane", .alias = "capturep", - .args = { "ab:CeE:JpPqS:t:", 0, 0 }, - .usage = "[-aCeJpPq] " CMD_BUFFER_USAGE " [-E end-line] " - "[-S start-line]" CMD_TARGET_PANE_USAGE, + .args = { "ab:CeE:JNpPqS:t:", 0, 0 }, + .usage = "[-aCeJNpPq] " CMD_BUFFER_USAGE " [-E end-line] " + "[-S start-line] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -110,7 +110,7 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, struct grid *gd; const struct grid_line *gl; struct grid_cell *gc = NULL; - int n, with_codes, escape_c0, join_lines; + int n, with_codes, escape_c0, join_lines, no_trim; u_int i, sx, top, bottom, tmp; char *cause, *buf, *line; const char *Sflag, *Eflag; @@ -170,11 +170,12 @@ cmd_capture_pane_history(struct args *args, struct cmdq_item *item, with_codes = args_has(args, 'e'); escape_c0 = args_has(args, 'C'); join_lines = args_has(args, 'J'); + no_trim = args_has(args, 'N'); buf = NULL; for (i = top; i <= bottom; i++) { line = grid_string_cells(gd, 0, i, sx, &gc, with_codes, - escape_c0, !join_lines); + escape_c0, !join_lines && !no_trim); linelen = strlen(line); buf = cmd_capture_pane_append(buf, len, line, linelen); @@ -199,8 +200,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item) size_t len; if (self->entry == &cmd_clear_history_entry) { - if (wp->mode == &window_copy_mode) - window_pane_reset_mode(wp); + window_pane_reset_mode_all(wp); grid_clear_history(wp->base.grid); return (CMD_RETURN_NORMAL); } diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 65c3ae0afc..8178ec9f55 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -30,9 +30,9 @@ const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", .alias = NULL, - .args = { "F:f:NO:st:w", 0, 1 }, - .usage = "[-Nsw] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE, + .args = { "F:Gf:NO:rst:wZ", 0, 1 }, + .usage = "[-GNrswZ] [-F format] [-f filter] [-O sort-order] " + CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -44,9 +44,9 @@ const struct cmd_entry cmd_choose_client_entry = { .name = "choose-client", .alias = NULL, - .args = { "F:f:NO:t:", 0, 1 }, - .usage = "[-N] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE, + .args = { "F:f:NO:rt:Z", 0, 1 }, + .usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] " + CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -58,9 +58,9 @@ const struct cmd_entry cmd_choose_buffer_entry = { .name = "choose-buffer", .alias = NULL, - .args = { "F:f:NO:t:", 0, 1 }, - .usage = "[-N] [-F format] [-f filter] [-O sort-order] " - CMD_TARGET_PANE_USAGE, + .args = { "F:f:NO:rt:Z", 0, 1 }, + .usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] " + CMD_TARGET_PANE_USAGE " [template]", .target = { 't', CMD_FIND_PANE, 0 }, diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c index d7159ad0e9..603ddb0a0e 100644 --- a/cmd-command-prompt.c +++ b/cmd-command-prompt.c @@ -129,26 +129,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } -static enum cmd_retval -cmd_command_prompt_error(struct cmdq_item *item, void *data) -{ - char *error = data; - - cmdq_error(item, "%s", error); - free(error); - - return (CMD_RETURN_NORMAL); -} - static int cmd_command_prompt_callback(struct client *c, void *data, const char *s, int done) { struct cmd_command_prompt_cdata *cdata = data; - struct cmd_list *cmdlist; struct cmdq_item *new_item; - char *cause, *new_template, *prompt, *ptr; + char *new_template, *prompt, *ptr; char *input = NULL; + struct cmd_parse_result *pr; if (s == NULL) return (0); @@ -175,20 +164,22 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s, return (1); } - cmdlist = cmd_string_parse(new_template, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - new_item = cmdq_get_callback(cmd_command_prompt_error, - cause); - } else - new_item = NULL; - } else { - new_item = cmdq_get_command(cmdlist, NULL, NULL, 0); - cmd_list_free(cmdlist); - } - - if (new_item != NULL) + 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); 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; + } if (!done) free(new_template); diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c index 7036d34b21..be21a78bf7 100644 --- a/cmd-confirm-before.c +++ b/cmd-confirm-before.c @@ -82,48 +82,38 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } -static enum cmd_retval -cmd_confirm_before_error(struct cmdq_item *item, void *data) -{ - char *error = data; - - cmdq_error(item, "%s", error); - free(error); - - return (CMD_RETURN_NORMAL); -} - static int cmd_confirm_before_callback(struct client *c, void *data, const char *s, __unused int done) { struct cmd_confirm_before_data *cdata = data; - struct cmd_list *cmdlist; struct cmdq_item *new_item; - char *cause; + struct cmd_parse_result *pr; if (c->flags & CLIENT_DEAD) return (0); if (s == NULL || *s == '\0') return (0); - if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + if (tolower((u_char)s[0]) != 'y' || s[1] != '\0') return (0); - cmdlist = cmd_string_parse(cdata->cmd, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - new_item = cmdq_get_callback(cmd_confirm_before_error, - cause); - } else - new_item = NULL; - } else { - new_item = cmdq_get_command(cmdlist, NULL, NULL, 0); - cmd_list_free(cmdlist); - } - - if (new_item != NULL) + 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); 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; + } return (0); } diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c index 699a868da4..b35d0af1f8 100644 --- a/cmd-copy-mode.c +++ b/cmd-copy-mode.c @@ -60,7 +60,6 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) struct client *c = item->client; struct session *s; struct window_pane *wp = item->target.wp; - int flag; if (args_has(args, 'M')) { if ((wp = cmd_mouse_pane(&shared->mouse, &s, NULL)) == NULL) @@ -74,18 +73,11 @@ cmd_copy_mode_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (wp->mode != &window_copy_mode) { - flag = window_pane_set_mode(wp, &window_copy_mode, NULL, NULL); - if (flag != 0) - return (CMD_RETURN_NORMAL); - window_copy_init_from_pane(wp, args_has(self->args, 'e')); - } - if (args_has(args, 'M')) { - if (wp->mode != NULL && wp->mode != &window_copy_mode) - return (CMD_RETURN_NORMAL); - window_copy_start_drag(c, &shared->mouse); + if (!window_pane_set_mode(wp, &window_copy_mode, NULL, args)) { + if (args_has(args, 'M')) + window_copy_start_drag(c, &shared->mouse); } - if (wp->mode == &window_copy_mode && args_has(self->args, 'u')) + if (args_has(self->args, 'u')) window_copy_pageup(wp, 0); return (CMD_RETURN_NORMAL); diff --git a/cmd-display-menu.c b/cmd-display-menu.c new file mode 100644 index 0000000000..ac7a4cfe33 --- /dev/null +++ b/cmd-display-menu.c @@ -0,0 +1,178 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* + * Display a menu on a client. + */ + +static enum cmd_retval cmd_display_menu_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] " + "[-x position] [-y position] name key command ...", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_display_menu_exec +}; + +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 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; + + if ((c = cmd_find_client(item, args_get(args, 'c'), 0)) == NULL) + return (CMD_RETURN_ERROR); + if (c->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); + else + title = xstrdup(""); + + menu = menu_create(title); + + for (i = 0; i != args->argc; /* nothing */) { + name = args->argv[i++]; + if (*name == '\0') { + menu_add_item(menu, NULL, item, c, fs); + continue; + } + + if (args->argc - i < 2) { + cmdq_error(item, "not enough arguments"); + free(title); + menu_free(menu); + return (CMD_RETURN_ERROR); + } + key = args->argv[i++]; + + menu_item.name = name; + menu_item.key = key_string_lookup_string(key); + menu_item.command = args->argv[i++]; + + menu_add_item(menu, &menu_item, item, c, fs); + } + free(title); + if (menu == NULL) { + cmdq_error(item, "invalid menu arguments"); + return (CMD_RETURN_ERROR); + } + if (menu->count == 0) { + 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; + } + 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) + flags |= MENU_NOMOUSE; + if (menu_display(menu, flags, item, px, py, c, fs, NULL, NULL) != 0) + return (CMD_RETURN_NORMAL); + return (CMD_RETURN_WAIT); +} diff --git a/cmd-display-message.c b/cmd-display-message.c index eef6ad84ae..4d9bccb60a 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = { .name = "display-message", .alias = "display", - .args = { "c:pt:F:", 0, 1 }, - .usage = "[-p] [-c target-client] [-F format] " + .args = { "ac:Ipt:F:v", 0, 1 }, + .usage = "[-aIpv] [-c target-client] [-F format] " CMD_TARGET_PANE_USAGE " [message]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -49,23 +49,40 @@ const struct cmd_entry cmd_display_message_entry = { .exec = cmd_display_message_exec }; +static void +cmd_display_message_each(const char *key, const char *value, void *arg) +{ + struct cmdq_item *item = arg; + + cmdq_print(item, "%s=%s", key, value); +} + static enum cmd_retval cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct client *c; + struct client *c, *target_c; struct session *s = item->target.s; struct winlink *wl = item->target.wl; struct window_pane *wp = item->target.wp; const char *template; - char *msg; + char *msg, *cause; struct format_tree *ft; + int flags; + + if (args_has(args, 'I')) { + if (window_pane_start_input(wp, item, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + return (CMD_RETURN_WAIT); + } if (args_has(args, 'F') && args->argc != 0) { cmdq_error(item, "only one of -F or argument must be given"); return (CMD_RETURN_ERROR); } - c = cmd_find_client(item, args_get(args, 'c'), 1); template = args_get(args, 'F'); if (args->argc != 0) @@ -73,10 +90,30 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) if (template == NULL) template = DISPLAY_MESSAGE_TEMPLATE; - ft = format_create(item->client, item, FORMAT_NONE, 0); - format_defaults(ft, c, s, wl, wp); + /* + * -c is intended to be the client where the message should be + * displayed if -p is not given. But it makes sense to use it for the + * 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; + else + target_c = cmd_find_best_client(s); + if (args_has(self->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); + + if (args_has(args, 'a')) { + format_each(ft, cmd_display_message_each, item); + return (CMD_RETURN_NORMAL); + } - msg = format_expand_time(ft, template, time(NULL)); + msg = format_expand_time(ft, template); if (args_has(self->args, 'p')) cmdq_print(item, "%s", msg); else if (c != NULL) diff --git a/cmd-display-panes.c b/cmd-display-panes.c index c124a63133..df97819c0b 100644 --- a/cmd-display-panes.c +++ b/cmd-display-panes.c @@ -18,8 +18,8 @@ #include -#include #include +#include #include "tmux.h" @@ -30,100 +30,253 @@ static enum cmd_retval cmd_display_panes_exec(struct cmd *, struct cmdq_item *); -static void cmd_display_panes_callback(struct client *, - struct window_pane *); - const struct cmd_entry cmd_display_panes_entry = { .name = "display-panes", .alias = "displayp", - .args = { "d:t:", 0, 1 }, - .usage = "[-d duration] " CMD_TARGET_CLIENT_USAGE, + .args = { "bd:t:", 0, 1 }, + .usage = "[-b] [-d duration] " CMD_TARGET_CLIENT_USAGE " [template]", .flags = CMD_AFTERHOOK, .exec = cmd_display_panes_exec }; -static enum cmd_retval -cmd_display_panes_exec(struct cmd *self, struct cmdq_item *item) +struct cmd_display_panes_data { + struct cmdq_item *item; + char *command; +}; + +static void +cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx, + struct window_pane *wp) { - struct args *args = self->args; - struct client *c; - struct session *s; - u_int delay; - char *cause; + struct client *c = ctx->c; + struct tty *tty = &c->tty; + struct session *s = c->session; + struct options *oo = s->options; + struct window *w = wp->window; + struct grid_cell gc; + u_int idx, px, py, i, j, xoff, yoff, sx, sy; + int colour, active_colour; + char buf[16], *ptr; + size_t len; - if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) - return (CMD_RETURN_ERROR); + if (wp->xoff + wp->sx <= ctx->ox || + wp->xoff >= ctx->ox + ctx->sx || + wp->yoff + wp->sy <= ctx->oy || + wp->yoff >= ctx->oy + ctx->sy) + return; - if (c->identify_callback != NULL) - return (CMD_RETURN_NORMAL); + if (wp->xoff >= ctx->ox && wp->xoff + wp->sx <= ctx->ox + ctx->sx) { + /* All visible. */ + xoff = wp->xoff - ctx->ox; + sx = wp->sx; + } else if (wp->xoff < ctx->ox && + wp->xoff + wp->sx > ctx->ox + ctx->sx) { + /* Both left and right not visible. */ + xoff = 0; + sx = ctx->sx; + } else if (wp->xoff < ctx->ox) { + /* Left not visible. */ + xoff = 0; + sx = wp->sx - (ctx->ox - wp->xoff); + } else { + /* Right not visible. */ + xoff = wp->xoff - ctx->ox; + sx = wp->sx - xoff; + } + if (wp->yoff >= ctx->oy && wp->yoff + wp->sy <= ctx->oy + ctx->sy) { + /* All visible. */ + yoff = wp->yoff - ctx->oy; + sy = wp->sy; + } else if (wp->yoff < ctx->oy && + wp->yoff + wp->sy > ctx->oy + ctx->sy) { + /* Both top and bottom not visible. */ + yoff = 0; + sy = ctx->sy; + } else if (wp->yoff < ctx->oy) { + /* Top not visible. */ + yoff = 0; + sy = wp->sy - (ctx->oy - wp->yoff); + } else { + /* Bottom not visible. */ + yoff = wp->yoff - ctx->oy; + sy = wp->sy - yoff; + } - c->identify_callback = cmd_display_panes_callback; - if (args->argc != 0) - c->identify_callback_data = xstrdup(args->argv[0]); + if (ctx->statustop) + yoff += ctx->statuslines; + px = sx / 2; + py = sy / 2; + + if (window_pane_index(wp, &idx) != 0) + fatalx("index not found"); + len = xsnprintf(buf, sizeof buf, "%u", idx); + + if (sx < len) + return; + colour = options_get_number(oo, "display-panes-colour"); + active_colour = options_get_number(oo, "display-panes-active-colour"); + + if (sx < len * 6 || sy < 5) { + tty_cursor(tty, xoff + px - len / 2, yoff + py); + goto draw_text; + } + + px -= len * 3; + py -= 2; + + memcpy(&gc, &grid_default_cell, sizeof gc); + if (w->active == wp) + gc.bg = active_colour; else - c->identify_callback_data = xstrdup("select-pane -t '%%'"); - s = c->session; + gc.bg = colour; + gc.flags |= GRID_FLAG_NOPALETTE; - 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); + tty_attributes(tty, &gc, wp); + for (ptr = buf; *ptr != '\0'; ptr++) { + if (*ptr < '0' || *ptr > '9') + continue; + idx = *ptr - '0'; + + for (j = 0; j < 5; j++) { + for (i = px; i < px + 5; i++) { + tty_cursor(tty, xoff + i, yoff + py + j); + if (window_clock_table[idx][j][i - px]) + tty_putc(tty, ' '); + } } - } else - delay = options_get_number(s->options, "display-panes-time"); - server_client_set_identify(c, delay); + 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); - return (CMD_RETURN_NORMAL); + tty_cursor(tty, 0, 0); } -static enum cmd_retval -cmd_display_panes_error(struct cmdq_item *item, void *data) +static void +cmd_display_panes_draw(struct client *c, struct screen_redraw_ctx *ctx) { - char *error = data; + struct window *w = c->session->curw->window; + struct window_pane *wp; - cmdq_error(item, "%s", error); - free(error); + log_debug("%s: %s @%u", __func__, c->name, w->id); - return (CMD_RETURN_NORMAL); + TAILQ_FOREACH(wp, &w->panes, entry) { + if (window_pane_visible(wp)) + cmd_display_panes_draw_pane(ctx, wp); + } } static void -cmd_display_panes_callback(struct client *c, struct window_pane *wp) +cmd_display_panes_free(struct client *c) { - struct cmd_list *cmdlist; - struct cmdq_item *new_item; - char *template, *cmd, *expanded, *cause; + struct cmd_display_panes_data *cdata = c->overlay_data; - template = c->identify_callback_data; + if (cdata->item != NULL) + cmdq_continue(cdata->item); + free(cdata->command); + free(cdata); +} + +static int +cmd_display_panes_key(struct client *c, struct key_event *event) +{ + struct cmd_display_panes_data *cdata = c->overlay_data; + struct cmdq_item *new_item; + char *cmd, *expanded; + struct window *w = c->session->curw->window; + struct window_pane *wp; + struct cmd_parse_result *pr; + + if (event->key < '0' || event->key > '9') + return (-1); + + wp = window_pane_at_index(w, event->key - '0'); if (wp == NULL) - goto out; - xasprintf(&expanded, "%%%u", wp->id); - cmd = cmd_template_replace(template, expanded, 1); + return (1); + window_unzoom(w); - cmdlist = cmd_string_parse(cmd, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - new_item = cmdq_get_callback(cmd_display_panes_error, - cause); - } else - new_item = NULL; - } else { - new_item = cmdq_get_command(cmdlist, NULL, NULL, 0); - cmd_list_free(cmdlist); - } + xasprintf(&expanded, "%%%u", wp->id); + cmd = cmd_template_replace(cdata->command, expanded, 1); - if (new_item != NULL) + 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); + 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; + } free(cmd); free(expanded); + return (1); +} + +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; + char *cause; + struct cmd_display_panes_data *cdata; + + if ((c = cmd_find_client(item, args_get(args, 't'), 0)) == NULL) + return (CMD_RETURN_ERROR); + s = c->session; + + if (c->overlay_draw != NULL) + return (CMD_RETURN_NORMAL); -out: - free(c->identify_callback_data); - c->identify_callback_data = NULL; - c->identify_callback = NULL; + 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); + } + } 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->item = item; + + server_client_set_overlay(c, delay, cmd_display_panes_draw, + cmd_display_panes_key, cmd_display_panes_free, cdata); + + if (args_has(args, 'b')) + return (CMD_RETURN_NORMAL); + return (CMD_RETURN_WAIT); } diff --git a/cmd-find-window.c b/cmd-find-window.c index 22a541950a..c29878b526 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 = { "CNt:T", 1, 1 }, - .usage = "[-CNT] " CMD_TARGET_PANE_USAGE " match-string", + .args = { "CNrt:TZ", 1, 1 }, + .usage = "[-CNrTZ] " CMD_TARGET_PANE_USAGE " match-string", .target = { 't', CMD_FIND_PANE, 0 }, @@ -57,32 +57,63 @@ cmd_find_window_exec(struct cmd *self, struct cmdq_item *item) if (!C && !N && !T) C = N = T = 1; - 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); + 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); + } 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); + } new_args = args_parse("", 1, &argv); + 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); diff --git a/cmd-find.c b/cmd-find.c index 27d2a39413..154842abcb 100644 --- a/cmd-find.c +++ b/cmd-find.c @@ -34,6 +34,7 @@ static int cmd_find_best_winlink_with_window(struct cmd_find_state *); static const char *cmd_find_map_table(const char *[][2], const char *); +static void cmd_find_log_state(const char *, struct cmd_find_state *); static int cmd_find_get_session(struct cmd_find_state *, const char *); static int cmd_find_get_window(struct cmd_find_state *, const char *, int); static int cmd_find_get_window_with_session(struct cmd_find_state *, @@ -74,40 +75,27 @@ static const char *cmd_find_pane_table[][2] = { { NULL, NULL } }; -/* Get session from TMUX if present. */ -static struct session * -cmd_find_try_TMUX(struct client *c) -{ - struct environ_entry *envent; - char tmp[256]; - long long pid; - u_int session; - - envent = environ_find(c->environ, "TMUX"); - if (envent == NULL) - return (NULL); - - if (sscanf(envent->value, "%255[^,],%lld,%d", tmp, &pid, &session) != 3) - return (NULL); - if (pid != getpid()) - return (NULL); - log_debug("client %p TMUX %s (session @%u)", c, envent->value, session); - return (session_find_by_id(session)); -} - /* Find pane containing client if any. */ static struct window_pane * cmd_find_inside_pane(struct client *c) { struct window_pane *wp; + struct environ_entry *envent; if (c == NULL) return (NULL); RB_FOREACH(wp, window_pane_tree, &all_window_panes) { - if (strcmp(wp->tty, c->ttyname) == 0) + if (wp->fd != -1 && strcmp(wp->tty, c->ttyname) == 0) break; } + if (wp == NULL) { + envent = environ_find(c->environ, "TMUX_PANE"); + if (envent != NULL) + wp = window_pane_find_by_id_str(envent->value); + } + if (wp != NULL) + log_debug("%s: got pane %%%u (%s)", __func__, wp->id, wp->tty); return (wp); } @@ -121,12 +109,12 @@ cmd_find_client_better(struct client *c, struct client *than) } /* Find best client for session. */ -static struct client * +struct client * cmd_find_best_client(struct session *s) { struct client *c_loop, *c; - if (s->flags & SESSION_UNATTACHED) + if (s->attached == 0) s = NULL; c = NULL; @@ -150,10 +138,10 @@ cmd_find_session_better(struct session *s, struct session *than, int flags) if (than == NULL) return (1); if (flags & CMD_FIND_PREFER_UNATTACHED) { - attached = (~than->flags & SESSION_UNATTACHED); - if (attached && (s->flags & SESSION_UNATTACHED)) + attached = (than->attached != 0); + if (attached && s->attached == 0) return (1); - else if (!attached && (~s->flags & SESSION_UNATTACHED)) + else if (!attached && s->attached != 0) return (0); } return (timercmp(&s->activity_time, &than->activity_time, >)); @@ -166,6 +154,8 @@ cmd_find_best_session(struct session **slist, u_int ssize, int flags) struct session *s_loop, *s; u_int i; + log_debug("%s: %u sessions to try", __func__, ssize); + s = NULL; if (slist != NULL) { for (i = 0; i < ssize; i++) { @@ -189,6 +179,8 @@ cmd_find_best_session_with_window(struct cmd_find_state *fs) u_int ssize; struct session *s; + log_debug("%s: window is @%u", __func__, fs->w->id); + ssize = 0; RB_FOREACH(s, sessions, &sessions) { if (!session_has(s, fs->w)) @@ -210,7 +202,7 @@ cmd_find_best_session_with_window(struct cmd_find_state *fs) } /* - * Find the best winlink for a window (the current if it contains the pane, + * Find the best winlink for a window (the current if it contains the window, * otherwise the first). */ static int @@ -218,6 +210,8 @@ cmd_find_best_winlink_with_window(struct cmd_find_state *fs) { struct winlink *wl, *wl_loop; + log_debug("%s: window is @%u", __func__, fs->w->id); + wl = NULL; if (fs->s->curw != NULL && fs->s->curw->window == fs->w) wl = fs->s->curw; @@ -389,7 +383,7 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window) return (-1); fs->idx = s->curw->idx + n; } else { - if (n < s->curw->idx) + if (n > s->curw->idx) return (-1); fs->idx = s->curw->idx - n; } @@ -436,15 +430,16 @@ cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window) if (window[0] != '+' && window[0] != '-') { idx = strtonum(window, 0, INT_MAX, &errstr); if (errstr == NULL) { - if (fs->flags & CMD_FIND_WINDOW_INDEX) { - fs->idx = idx; - return (0); - } fs->wl = winlink_find_by_index(&fs->s->windows, idx); if (fs->wl != NULL) { + fs->idx = fs->wl->idx; fs->w = fs->wl->window; return (0); } + if (fs->flags & CMD_FIND_WINDOW_INDEX) { + fs->idx = idx; + return (0); + } } } @@ -587,8 +582,6 @@ cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) /* Try special characters. */ if (strcmp(pane, "!") == 0) { - if (fs->w->last == NULL) - return (-1); fs->wp = fs->w->last; if (fs->wp == NULL) return (-1); @@ -703,11 +696,11 @@ cmd_find_copy_state(struct cmd_find_state *dst, struct cmd_find_state *src) } /* Log the result. */ -void +static void cmd_find_log_state(const char *prefix, struct cmd_find_state *fs) { if (fs->s != NULL) - log_debug("%s: s=$%u", prefix, fs->s->id); + log_debug("%s: s=$%u %s", prefix, fs->s->id, fs->s->name); else log_debug("%s: s=none", prefix); if (fs->wl != NULL) { @@ -865,8 +858,6 @@ cmd_find_from_mouse(struct cmd_find_state *fs, struct mouse_event *m, int flags) int cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) { - struct session *s; - struct winlink *wl; struct window_pane *wp; /* If no client, treat as from nothing. */ @@ -888,40 +879,18 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) if (wp == NULL) goto unknown_pane; - /* If we have a session in TMUX, see if it has this pane. */ - s = cmd_find_try_TMUX(c); - if (s != NULL) { - RB_FOREACH(wl, winlinks, &s->windows) { - if (window_has_pane(wl->window, wp)) - break; - } - if (wl != NULL) { - fs->s = s; - fs->wl = s->curw; /* use current session */ - fs->w = fs->wl->window; - fs->wp = fs->w->active; /* use active pane */ - - cmd_find_log_state(__func__, fs); - return (0); - } - } - /* * Don't have a session, or it doesn't have this pane. Try all * sessions. */ fs->w = wp->window; if (cmd_find_best_session_with_window(fs) != 0) { - if (wp != NULL) { - /* - * The window may have been destroyed but the pane - * still on all_window_panes due to something else - * holding a reference. - */ - goto unknown_pane; - } - cmd_find_clear_state(fs, 0); - return (-1); + /* + * The window may have been destroyed but the pane + * still on all_window_panes due to something else + * holding a reference. + */ + goto unknown_pane; } fs->wl = fs->s->curw; fs->w = fs->wl->window; @@ -931,17 +900,7 @@ cmd_find_from_client(struct cmd_find_state *fs, struct client *c, int flags) return (0); unknown_pane: - /* - * We're not running in a known pane, but maybe this client has TMUX - * in the environment. That'd give us a session. - */ - s = cmd_find_try_TMUX(c); - if (s != NULL) { - cmd_find_from_session(fs, s, flags); - return (0); - } - - /* Otherwise we need to guess. */ + /* We can't find the pane so need to guess. */ return (cmd_find_from_nothing(fs, flags)); } @@ -955,7 +914,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, { struct mouse_event *m; struct cmd_find_state current; - char *colon, *period, *copy = NULL; + char *colon, *period, *copy = NULL, tmp[256]; const char *session, *window, *pane, *s; int window_only = 0, pane_only = 0; @@ -972,11 +931,27 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, s = "session"; else s = "unknown"; - if (target == NULL) - log_debug("%s: target none, type %s", __func__, s); + *tmp = '\0'; + if (flags & CMD_FIND_PREFER_UNATTACHED) + strlcat(tmp, "PREFER_UNATTACHED,", sizeof tmp); + if (flags & CMD_FIND_QUIET) + strlcat(tmp, "QUIET,", sizeof tmp); + if (flags & CMD_FIND_WINDOW_INDEX) + strlcat(tmp, "WINDOW_INDEX,", sizeof tmp); + if (flags & CMD_FIND_DEFAULT_MARKED) + strlcat(tmp, "DEFAULT_MARKED,", sizeof tmp); + if (flags & CMD_FIND_EXACT_SESSION) + strlcat(tmp, "EXACT_SESSION,", sizeof tmp); + if (flags & CMD_FIND_EXACT_WINDOW) + strlcat(tmp, "EXACT_WINDOW,", sizeof tmp); + if (flags & CMD_FIND_CANFAIL) + strlcat(tmp, "CANFAIL,", sizeof tmp); + if (*tmp != '\0') + tmp[strlen(tmp) - 1] = '\0'; else - log_debug("%s: target %s, type %s", __func__, target, s); - log_debug("%s: item %p, flags %#x", __func__, item, flags); + strlcat(tmp, "NONE", sizeof tmp); + log_debug("%s: target %s, type %s, item %p, flags %s", __func__, + target == NULL ? "none" : target, s, item, tmp); /* Clear new state. */ cmd_find_clear_state(fs, flags); @@ -1009,12 +984,16 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, switch (type) { case CMD_FIND_PANE: fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl); - if (fs->wp != NULL) + if (fs->wp != NULL) { fs->w = fs->wl->window; - break; + break; + } + /* FALLTHROUGH */ case CMD_FIND_WINDOW: case CMD_FIND_SESSION: fs->wl = cmd_mouse_window(m, &fs->s); + if (fs->wl == NULL && fs->s != NULL) + fs->wl = fs->s->curw; if (fs->wl != NULL) { fs->w = fs->wl->window; fs->wp = fs->w->active; @@ -1116,9 +1095,16 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, if (pane != NULL) pane = cmd_find_map_table(cmd_find_pane_table, pane); - log_debug("%s: target %s (flags %#x): session=%s, window=%s, pane=%s", - __func__, target, flags, session == NULL ? "none" : session, - window == NULL ? "none" : window, pane == NULL ? "none" : pane); + if (session != NULL || window != NULL || pane != NULL) { + log_debug("%s: target %s is %s%s%s%s%s%s", + __func__, target, + session == NULL ? "" : "session ", + session == NULL ? "" : session, + window == NULL ? "" : "window ", + window == NULL ? "" : window, + pane == NULL ? "" : "pane ", + pane == NULL ? "" : pane); + } /* No pane is allowed if want an index. */ if (pane != NULL && (flags & CMD_FIND_WINDOW_INDEX)) { @@ -1147,7 +1133,8 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, /* This will fill in winlink and window. */ if (cmd_find_get_window_with_session(fs, window) != 0) goto no_window; - fs->wp = fs->wl->window->active; + if (fs->wl != NULL) /* can be NULL if index only */ + fs->wp = fs->wl->window->active; goto found; } @@ -1187,7 +1174,8 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, /* This will fill in session, winlink and window. */ if (cmd_find_get_window(fs, window, window_only) != 0) goto no_window; - fs->wp = fs->wl->window->active; + if (fs->wl != NULL) /* can be NULL if index only */ + fs->wp = fs->wl->window->active; goto found; } @@ -1224,17 +1212,17 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item, no_session: if (~flags & CMD_FIND_QUIET) - cmdq_error(item, "can't find session %s", session); + cmdq_error(item, "can't find session: %s", session); goto error; no_window: if (~flags & CMD_FIND_QUIET) - cmdq_error(item, "can't find window %s", window); + cmdq_error(item, "can't find window: %s", window); goto error; no_pane: if (~flags & CMD_FIND_QUIET) - cmdq_error(item, "can't find pane %s", pane); + cmdq_error(item, "can't find pane: %s", pane); goto error; } @@ -1304,7 +1292,7 @@ cmd_find_client(struct cmdq_item *item, const char *target, int quiet) /* If no client found, report an error. */ if (c == NULL && !quiet) - cmdq_error(item, "can't find client %s", copy); + cmdq_error(item, "can't find client: %s", copy); free(copy); log_debug("%s: target %s, return %p", __func__, target, c); diff --git a/cmd-if-shell.c b/cmd-if-shell.c index 2400b3b4ac..2befbc0c18 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -49,8 +49,7 @@ const struct cmd_entry cmd_if_shell_entry = { }; struct cmd_if_shell_data { - char *file; - u_int line; + struct cmd_parse_input input; char *cmd_if; char *cmd_else; @@ -64,61 +63,68 @@ static enum cmd_retval cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct cmdq_shared *shared = item->shared; + struct mouse_event *m = &item->shared->mouse; struct cmd_if_shell_data *cdata; - char *shellcmd, *cmd, *cause; - struct cmd_list *cmdlist; + 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 = item->target.s; - struct winlink *wl = item->target.wl; - struct window_pane *wp = item->target.wp; - const char *cwd; - - if (item->client != NULL && item->client->session == NULL) - cwd = item->client->cwd; - else if (s != NULL) - cwd = s->cwd; - else - cwd = NULL; + 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; shellcmd = format_single(item, args->argv[0], c, s, wl, wp); if (args_has(args, 'F')) { - cmd = NULL; 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) return (CMD_RETURN_NORMAL); - cmdlist = cmd_string_parse(cmd, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - cmdq_error(item, "%s", cause); - free(cause); - } + + 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; } - new_item = cmdq_get_command(cmdlist, NULL, &shared->mouse, 0); - cmdq_insert_after(item, new_item); - cmd_list_free(cmdlist); return (CMD_RETURN_NORMAL); } cdata = xcalloc(1, sizeof *cdata); - if (self->file != NULL) { - cdata->file = xstrdup(self->file); - cdata->line = self->line; - } 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->client = item->client; + if (!args_has(args, 'b')) + cdata->client = item->client; + else + cdata->client = c; if (cdata->client != NULL) cdata->client->references++; @@ -126,10 +132,24 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) cdata->item = item; else cdata->item = NULL; - memcpy(&cdata->mouse, &shared->mouse, sizeof cdata->mouse); - job_run(shellcmd, s, cwd, NULL, cmd_if_shell_callback, - cmd_if_shell_free, cdata); + 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) { + cmdq_error(item, "failed to run command: %s", shellcmd); + free(shellcmd); + free(cdata); + return (CMD_RETURN_ERROR); + } free(shellcmd); if (args_has(args, 'b')) @@ -140,31 +160,36 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) static void cmd_if_shell_callback(struct job *job) { - struct cmd_if_shell_data *cdata = job->data; + struct cmd_if_shell_data *cdata = job_get_data(job); struct client *c = cdata->client; - struct cmd_list *cmdlist; - struct cmdq_item *new_item; - char *cause, *cmd, *file = cdata->file; - u_int line = cdata->line; - - if (!WIFEXITED(job->status) || WEXITSTATUS(job->status) != 0) + struct mouse_event *m = &cdata->mouse; + struct cmdq_item *new_item = NULL; + char *cmd; + int status; + struct cmd_parse_result *pr; + + status = job_get_status(job); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) cmd = cdata->cmd_else; else cmd = cdata->cmd_if; if (cmd == NULL) goto out; - cmdlist = cmd_string_parse(cmd, file, line, &cause); - if (cmdlist == NULL) { - if (cause != NULL && cdata->item != NULL) - cmdq_error(cdata->item, "%s", cause); - free(cause); - new_item = NULL; - } else { - new_item = cmdq_get_command(cmdlist, NULL, &cdata->mouse, 0); - cmd_list_free(cmdlist); + 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); @@ -174,7 +199,7 @@ cmd_if_shell_callback(struct job *job) out: if (cdata->item != NULL) - cdata->item->flags &= ~CMDQ_WAITING; + cmdq_continue(cdata->item); } static void @@ -188,6 +213,9 @@ cmd_if_shell_free(void *data) free(cdata->cmd_else); free(cdata->cmd_if); - free(cdata->file); + if (cdata->input.c != NULL) + server_client_unref(cdata->input.c); + free((void *)cdata->input.file); + free(cdata); } diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 947b24998e..672072919e 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -20,6 +20,7 @@ #include #include +#include #include #include "tmux.h" @@ -34,8 +35,8 @@ const struct cmd_entry cmd_join_pane_entry = { .name = "join-pane", .alias = "joinp", - .args = { "bdhvp:l:s:t:", 0, 0 }, - .usage = "[-bdhv] [-p percentage|-l size] " CMD_SRCDST_PANE_USAGE, + .args = { "bdfhvp:l:s:t:", 0, 0 }, + .usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .target = { 't', CMD_FIND_PANE, 0 }, @@ -67,11 +68,13 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) struct winlink *src_wl, *dst_wl; struct window *src_w, *dst_w; struct window_pane *src_wp, *dst_wp; - char *cause; - int size, percentage, dst_idx; + char *cause, *copy; + const char *errstr, *p; + size_t plen; + int size, percentage, dst_idx, not_same_window; + int flags; enum layout_type type; struct layout_cell *lc; - int not_same_window; if (self->entry == &cmd_join_pane_entry) not_same_window = 1; @@ -104,12 +107,28 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) type = LAYOUT_LEFTRIGHT; size = -1; - if (args_has(args, 'l')) { - size = args_strtonum(args, 'l', 0, INT_MAX, &cause); - if (cause != NULL) { - cmdq_error(item, "size %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + 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 (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); @@ -123,7 +142,14 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) else size = (dst_wp->sx * percentage) / 100; } - lc = layout_split_pane(dst_wp, type, size, args_has(args, 'b'), 0); + + flags = 0; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + + lc = layout_split_pane(dst_wp, type, size, flags); if (lc == NULL) { cmdq_error(item, "create pane failed: pane too small"); return (CMD_RETURN_ERROR); @@ -135,6 +161,8 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) 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); @@ -144,7 +172,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_redraw_window(dst_w); if (!args_has(args, 'd')) { - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(dst_w, src_wp, 1); session_select(dst_s, dst_idx); cmd_find_from_session(current, dst_s, 0); server_redraw_session(dst_s); diff --git a/cmd-kill-pane.c b/cmd-kill-pane.c index a8a423d081..f0aacb2a62 100644 --- a/cmd-kill-pane.c +++ b/cmd-kill-pane.c @@ -37,7 +37,7 @@ const struct cmd_entry cmd_kill_pane_entry = { .target = { 't', CMD_FIND_PANE, 0 }, - .flags = 0, + .flags = CMD_AFTERHOOK, .exec = cmd_kill_pane_exec }; @@ -47,9 +47,8 @@ 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; - server_unzoom_window(wl->window); - if (args_has(self->args, 'a')) { + server_unzoom_window(wl->window); TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) { if (loopwp == wp) continue; @@ -60,13 +59,6 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); } - if (window_count_panes(wl->window) == 1) { - server_kill_window(wl->window); - recalculate_sizes(); - } else { - layout_close_pane(wp); - window_remove_pane(wl->window, wp); - server_redraw_window(wl->window); - } + server_kill_pane(wp); return (CMD_RETURN_NORMAL); } diff --git a/cmd-kill-session.c b/cmd-kill-session.c index 00ea7789c1..dcef809789 100644 --- a/cmd-kill-session.c +++ b/cmd-kill-session.c @@ -61,12 +61,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item) RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) { if (sloop != s) { server_destroy_session(sloop); - session_destroy(sloop, __func__); + session_destroy(sloop, 1, __func__); } } } else { server_destroy_session(s); - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } return (CMD_RETURN_NORMAL); } diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 9e0cac6291..8636b70a9a 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -60,9 +60,10 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) struct args *args = self->args; struct key_table *table; struct key_binding *bd; - const char *key, *tablename, *r; - char *cp, tmp[BUFSIZ]; + const char *tablename, *r; + char *key, *cp, *tmp; int repeat, width, tablewidth, keywidth; + size_t tmpsize, tmpused, cplen; if (self->entry == &cmd_list_commands_entry) return (cmd_list_keys_commands(self, item)); @@ -75,11 +76,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) repeat = 0; tablewidth = keywidth = 0; - RB_FOREACH(table, key_tables, &key_tables) { - if (tablename != NULL && strcmp(table->name, tablename) != 0) + table = key_bindings_first_table (); + while (table != NULL) { + if (tablename != NULL && strcmp(table->name, tablename) != 0) { + table = key_bindings_next_table(table); continue; - RB_FOREACH(bd, key_bindings, &table->key_bindings) { - key = key_string_lookup_key(bd->key); + } + bd = key_bindings_first(table); + while (bd != NULL) { + key = args_escape(key_string_lookup_key(bd->key)); if (bd->flags & KEY_BINDING_REPEAT) repeat = 1; @@ -90,14 +95,25 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) width = utf8_cstrwidth(key); if (width > keywidth) keywidth = width; + + free(key); + bd = key_bindings_next(table, bd); } + table = key_bindings_next_table(table); } - RB_FOREACH(table, key_tables, &key_tables) { - if (tablename != NULL && strcmp(table->name, tablename) != 0) + tmpsize = 256; + tmp = xmalloc(tmpsize); + + table = key_bindings_first_table (); + while (table != NULL) { + if (tablename != NULL && strcmp(table->name, tablename) != 0) { + table = key_bindings_next_table(table); continue; - RB_FOREACH(bd, key_bindings, &table->key_bindings) { - key = key_string_lookup_key(bd->key); + } + bd = key_bindings_first(table); + while (bd != NULL) { + key = args_escape(key_string_lookup_key(bd->key)); if (!repeat) r = ""; @@ -105,26 +121,47 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item) r = "-r "; else r = " "; - xsnprintf(tmp, sizeof tmp, "%s-T ", r); + tmpused = xsnprintf(tmp, tmpsize, "%s-T ", r); cp = utf8_padcstr(table->name, tablewidth); - strlcat(tmp, cp, sizeof tmp); - strlcat(tmp, " ", sizeof tmp); + cplen = strlen(cp) + 1; + while (tmpused + cplen + 1 >= tmpsize) { + tmpsize *= 2; + tmp = xrealloc(tmp, tmpsize); + } + tmpused = strlcat(tmp, cp, tmpsize); + tmpused = strlcat(tmp, " ", tmpsize); free(cp); cp = utf8_padcstr(key, keywidth); - strlcat(tmp, cp, sizeof tmp); - strlcat(tmp, " ", sizeof tmp); + cplen = strlen(cp) + 1; + while (tmpused + cplen + 1 >= tmpsize) { + tmpsize *= 2; + tmp = xrealloc(tmp, tmpsize); + } + tmpused = strlcat(tmp, cp, tmpsize); + tmpused = strlcat(tmp, " ", tmpsize); free(cp); - cp = cmd_list_print(bd->cmdlist); - strlcat(tmp, cp, sizeof tmp); + cp = cmd_list_print(bd->cmdlist, 1); + cplen = strlen(cp); + while (tmpused + cplen + 1 >= tmpsize) { + tmpsize *= 2; + tmp = xrealloc(tmp, tmpsize); + } + strlcat(tmp, cp, tmpsize); free(cp); cmdq_print(item, "bind-key %s", tmp); + + free(key); + bd = key_bindings_next(table, bd); } + table = key_bindings_next_table(table); } + free(tmp); + return (CMD_RETURN_NORMAL); } diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c index df8a25bc7f..72ff47e8bd 100644 --- a/cmd-list-sessions.c +++ b/cmd-list-sessions.c @@ -30,8 +30,7 @@ #define LIST_SESSIONS_TEMPLATE \ "#{session_name}: #{session_windows} windows " \ - "(created #{t:session_created}) " \ - "[#{session_width}x#{session_height}]" \ + "(created #{t:session_created})" \ "#{?session_grouped, (group ,}" \ "#{session_group}#{?session_grouped,),}" \ "#{?session_attached, (attached),}" diff --git a/cmd-list.c b/cmd-list.c deleted file mode 100644 index e999c37049..0000000000 --- a/cmd-list.c +++ /dev/null @@ -1,126 +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 - -#include "tmux.h" - -struct cmd_list * -cmd_list_parse(int argc, char **argv, const char *file, u_int line, - char **cause) -{ - struct cmd_list *cmdlist; - struct cmd *cmd; - int i, lastsplit; - size_t arglen, new_argc; - char **copy_argv, **new_argv; - - copy_argv = cmd_copy_argv(argc, argv); - - cmdlist = xcalloc(1, sizeof *cmdlist); - cmdlist->references = 1; - TAILQ_INIT(&cmdlist->list); - - lastsplit = 0; - for (i = 0; i < argc; i++) { - arglen = strlen(copy_argv[i]); - if (arglen == 0 || copy_argv[i][arglen - 1] != ';') - continue; - copy_argv[i][arglen - 1] = '\0'; - - if (arglen > 1 && copy_argv[i][arglen - 2] == '\\') { - copy_argv[i][arglen - 2] = ';'; - continue; - } - - new_argc = i - lastsplit; - new_argv = copy_argv + lastsplit; - if (arglen != 1) - new_argc++; - - cmd = cmd_parse(new_argc, new_argv, file, line, cause); - if (cmd == NULL) - goto bad; - TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry); - - lastsplit = i + 1; - } - - if (lastsplit != argc) { - cmd = cmd_parse(argc - lastsplit, copy_argv + lastsplit, - file, line, cause); - if (cmd == NULL) - goto bad; - TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry); - } - - cmd_free_argv(argc, copy_argv); - return (cmdlist); - -bad: - cmd_list_free(cmdlist); - cmd_free_argv(argc, copy_argv); - return (NULL); -} - -void -cmd_list_free(struct cmd_list *cmdlist) -{ - struct cmd *cmd, *cmd1; - - if (--cmdlist->references != 0) - return; - - TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) { - TAILQ_REMOVE(&cmdlist->list, cmd, qentry); - args_free(cmd->args); - free(cmd->file); - free(cmd); - } - - free(cmdlist); -} - -char * -cmd_list_print(struct cmd_list *cmdlist) -{ - struct cmd *cmd; - char *buf, *this; - size_t len; - - len = 1; - buf = xcalloc(1, len); - - TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { - this = cmd_print(cmd); - - len += strlen(this) + 3; - buf = xrealloc(buf, len); - - strlcat(buf, this, len); - if (TAILQ_NEXT(cmd, qentry) != NULL) - strlcat(buf, " ; ", len); - - free(this); - } - - return (buf); -} diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c index a3cabdc04c..cdf44bf7e1 100644 --- a/cmd-load-buffer.c +++ b/cmd-load-buffer.c @@ -56,10 +56,14 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; struct cmd_load_buffer_data *cdata; - struct client *c = item->client; + 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; FILE *f; - const char *path, *bufname; - char *pdata, *new_pdata, *cause, *file; + const char *bufname; + char *pdata = NULL, *new_pdata, *cause; + char *path, *file; size_t psize; int ch, error; @@ -67,8 +71,11 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'b')) bufname = args_get(args, 'b'); - path = args->argv[0]; + path = format_single(item, args->argv[0], c, s, wl, wp); if (strcmp(path, "-") == 0) { + free(path); + c = item->client; + cdata = xcalloc(1, sizeof *cdata); cdata->item = item; @@ -78,19 +85,21 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item) error = server_set_stdin_callback(c, cmd_load_buffer_callback, cdata, &cause); if (error != 0) { - cmdq_error(item, "%s: %s", path, cause); + cmdq_error(item, "-: %s", cause); free(cause); + free(cdata); return (CMD_RETURN_ERROR); } return (CMD_RETURN_WAIT); } - file = server_client_get_path(c, path); + file = server_client_get_path(item->client, path); + free(path); + f = fopen(file, "rb"); if (f == NULL) { cmdq_error(item, "%s: %s", file, strerror(errno)); - free(file); - return (CMD_RETURN_ERROR); + goto error; } pdata = NULL; @@ -167,7 +176,7 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data) free(cause); } out: - cdata->item->flags &= ~CMDQ_WAITING; + cmdq_continue(cdata->item); free(cdata->bufname); free(cdata); diff --git a/cmd-new-session.c b/cmd-new-session.c index 2d671275ed..c76b564e4d 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_new_session_entry = { .name = "new-session", .alias = "new", - .args = { "Ac:dDEF:n:Ps:t:x:y:", 0, -1 }, - .usage = "[-AdDEP] [-c start-directory] [-F format] [-n window-name] " + .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]", @@ -69,18 +69,17 @@ 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 window *w; struct environ *env; + struct options *oo; struct termios tio, *tiop; struct session_group *sg; - const char *newname, *errstr, *template, *group, *prefix; - const char *path, *cmd, *cwd; - char **argv, *cause, *cp, *to_free = NULL; - int detached, already_attached, idx, argc; - int is_control = 0; - u_int sx, sy; - struct environ_entry *envent; - struct cmd_find_state fs; + const char *errstr, *template, *group, *prefix, *tmp; + char *cause, *cwd = NULL, *cp, *newname = NULL; + int detached, already_attached, is_control = 0; + u_int sx, sy, dsx, dsy; + struct spawn_context sc; + enum cmd_retval retval; + struct cmd_find_state fs; if (self->entry == &cmd_has_session_entry) { /* @@ -95,22 +94,31 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - newname = args_get(args, 's'); - if (newname != NULL) { + 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); - return (CMD_RETURN_ERROR); + goto fail; } - if ((as = session_find(newname)) != NULL) { - if (args_has(args, 'A')) { - return (cmd_attach_session(item, - newname, args_has(args, 'D'), - 0, NULL, args_has(args, 'E'))); - } - cmdq_error(item, "duplicate session: %s", newname); - return (CMD_RETURN_ERROR); + } + if (args_has(args, 'A')) { + if (newname != NULL) + as = session_find(newname); + else + as = item->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')); + free(newname); + return (retval); } } + if (newname != NULL && session_find(newname) != NULL) { + cmdq_error(item, "duplicate session: %s", newname); + goto fail; + } /* Is this going to be part of a session group? */ group = args_get(args, 't'); @@ -119,7 +127,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (groupwith == NULL) { if (!session_check_name(group)) { cmdq_error(item, "bad group name: %s", group); - goto error; + goto fail; } sg = session_group_find(group); } else @@ -149,14 +157,10 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) already_attached = 1; /* Get the new session working directory. */ - if (args_has(args, 'c')) { - cwd = args_get(args, 'c'); - to_free = format_single(item, cwd, c, NULL, NULL, NULL); - cwd = to_free; - } else if (c != NULL && c->session == NULL && c->cwd != NULL) - cwd = c->cwd; + if ((tmp = args_get(args, 'c')) != NULL) + cwd = format_single(item, tmp, c, NULL, NULL, NULL); else - cwd = "."; + cwd = xstrdup(server_client_get_cwd(c, NULL)); /* * If this is a new client, check for nesting and save the termios @@ -171,7 +175,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (server_client_check_nested(item->client)) { cmdq_error(item, "sessions should be nested with care, " "unset $TMUX to force"); - return (CMD_RETURN_ERROR); + goto fail; } if (tcgetattr(c->tty.fd, &tio) != 0) fatal("tcgetattr failed"); @@ -184,87 +188,98 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (server_client_open(c, &cause) != 0) { cmdq_error(item, "open terminal failed: %s", cause); free(cause); - goto error; + goto fail; + } + } + + /* Get default session size. */ + if (args_has(args, 'x')) { + tmp = args_get(args, 'x'); + if (strcmp(tmp, "-") == 0) { + if (c != NULL) + dsx = c->tty.sx; + else + dsx = 80; + } else { + dsx = strtonum(tmp, 1, USHRT_MAX, &errstr); + if (errstr != NULL) { + cmdq_error(item, "width %s", errstr); + goto fail; + } + } + } + if (args_has(args, 'y')) { + tmp = args_get(args, 'y'); + if (strcmp(tmp, "-") == 0) { + if (c != NULL) + dsy = c->tty.sy; + else + dsy = 24; + } else { + dsy = strtonum(tmp, 1, USHRT_MAX, &errstr); + if (errstr != NULL) { + cmdq_error(item, "height %s", errstr); + goto fail; + } } } /* Find new session size. */ - if (!detached) { + if (!detached && !is_control) { sx = c->tty.sx; sy = c->tty.sy; - if (!is_control && - sy > 0 && - options_get_number(global_s_options, "status")) + if (sy > 0 && options_get_number(global_s_options, "status")) sy--; } else { - sx = 80; - sy = 24; - } - if ((is_control || detached) && args_has(args, 'x')) { - sx = strtonum(args_get(args, 'x'), 1, USHRT_MAX, &errstr); - if (errstr != NULL) { - cmdq_error(item, "width %s", errstr); - goto error; - } - } - if ((is_control || detached) && args_has(args, 'y')) { - sy = strtonum(args_get(args, 'y'), 1, USHRT_MAX, &errstr); - if (errstr != NULL) { - cmdq_error(item, "height %s", errstr); - goto error; + 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; } if (sx == 0) sx = 1; if (sy == 0) sy = 1; - /* Figure out the command for the new window. */ - argc = -1; - argv = NULL; - if (!args_has(args, 't') && args->argc != 0) { - argc = args->argc; - argv = args->argv; - } else if (sg == NULL && groupwith == NULL) { - cmd = options_get_string(global_s_options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } + /* Create the new session. */ + oo = options_create(global_s_options); + if (args_has(args, 'x') || args_has(args, 'y')) { + if (!args_has(args, 'x')) + dsx = sx; + if (!args_has(args, 'y')) + dsy = sy; + options_set_string(oo, "default-size", 0, "%ux%u", dsx, dsy); } - - path = NULL; - if (c != NULL && c->session == NULL) - envent = environ_find(c->environ, "PATH"); - else - envent = environ_find(global_environ, "PATH"); - if (envent != NULL) - path = envent->value; - - /* Construct the environment. */ env = environ_create(); if (c != NULL && !args_has(args, 'E')) environ_update(global_s_options, c->environ, env); + s = session_create(prefix, newname, cwd, env, oo, tiop); - /* Create the new session. */ - idx = -1 - options_get_number(global_s_options, "base-index"); - s = session_create(prefix, newname, argc, argv, path, cwd, env, tiop, - idx, sx, sy, &cause); - environ_free(env); - if (s == NULL) { - cmdq_error(item, "create session failed: %s", cause); - free(cause); - goto error; - } + /* Spawn the initial window. */ + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.c = c; + + sc.name = args_get(args, 'n'); + sc.argc = args->argc; + sc.argv = args->argv; - /* Set the initial window name if one given. */ - if (argc >= 0 && args_has(args, 'n')) { - w = s->curw->window; - window_set_name(w, args_get(args, 'n')); - options_set_number(w->options, "automatic-rename", 0); + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = 0; + + if (spawn_window(&sc, &cause) == NULL) { + session_destroy(s, 0, __func__); + cmdq_error(item, "create window failed: %s", cause); + free(cause); + goto fail; } /* @@ -298,6 +313,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) 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); @@ -329,12 +345,14 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) } cmd_find_from_session(&fs, s, 0); - hooks_insert(s->hooks, item, &fs, "after-new-session"); + cmdq_insert_hook(s, item, &fs, "after-new-session"); - free(to_free); + free(cwd); + free(newname); return (CMD_RETURN_NORMAL); -error: - free(to_free); +fail: + free(cwd); + free(newname); return (CMD_RETURN_ERROR); } diff --git a/cmd-new-window.c b/cmd-new-window.c index 4baf8f0cfb..9a1025e9a3 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:dF:kn:Pt:", 0, -1 }, - .usage = "[-adkP] [-c start-directory] [-F format] [-n window-name] " - CMD_TARGET_WINDOW_USAGE " [command]", + .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]", .target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX }, @@ -53,87 +53,54 @@ 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; - struct client *c = cmd_find_client(item, NULL, 1); int idx = item->target.idx; - const char *cmd, *path, *template, *cwd; - char **argv, *cause, *cp, *to_free = NULL; - int argc, detached; - struct environ_entry *envent; + struct winlink *new_wl; + char *cause = NULL, *cp; + const char *template, *add; struct cmd_find_state fs; + struct args_value *value; - if (args_has(args, 'a')) { - if ((idx = winlink_shuffle_up(s, wl)) == -1) { - cmdq_error(item, "no free window indexes"); - return (CMD_RETURN_ERROR); - } - } - detached = args_has(args, 'd'); - - if (args->argc == 0) { - cmd = options_get_string(s->options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } - } else { - argc = args->argc; - argv = args->argv; + 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); } - path = NULL; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; - - if (args_has(args, 'c')) { - cwd = args_get(args, 'c'); - to_free = format_single(item, cwd, c, s, NULL, NULL); - cwd = to_free; - } else if (item->client != NULL && item->client->session == NULL) - cwd = item->client->cwd; - else - cwd = s->cwd; - - wl = NULL; - if (idx != -1) - wl = winlink_find_by_index(&s->windows, idx); - if (wl != NULL && args_has(args, 'k')) { - /* - * Can't use session_detach as it will destroy session if this - * makes it empty. - */ - notify_session_window("window-unlinked", s, wl->window); - wl->flags &= ~WINLINK_ALERTFLAGS; - winlink_stack_remove(&s->lastw, wl); - winlink_remove(&s->windows, wl); - - /* Force select/redraw if current. */ - if (wl == s->curw) { - detached = 0; - s->curw = NULL; - } + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.c = c; + + sc.name = args_get(args, 'n'); + sc.argc = args->argc; + sc.argv = args->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); } - if (idx == -1) - idx = -1 - options_get_number(s->options, "base-index"); - wl = session_new(s, args_get(args, 'n'), argc, argv, path, cwd, idx, - &cause); - if (wl == NULL) { + sc.idx = idx; + sc.cwd = args_get(args, 'c'); + + sc.flags = 0; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; + + if ((new_wl = spawn_window(&sc, &cause)) == NULL) { cmdq_error(item, "create window failed: %s", cause); free(cause); - goto error; + return (CMD_RETURN_ERROR); } - if (!detached) { - session_select(s, wl->idx); - cmd_find_from_winlink(current, wl, 0); + if (!args_has(args, 'd') || new_wl == s->curw) { + cmd_find_from_winlink(current, new_wl, 0); server_redraw_session_group(s); } else server_status_session_group(s); @@ -141,18 +108,14 @@ 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, wl, NULL); + cp = format_single(item, template, c, s, new_wl, NULL); cmdq_print(item, "%s", cp); free(cp); } - cmd_find_from_winlink(&fs, wl, 0); - hooks_insert(s->hooks, item, &fs, "after-new-window"); + cmd_find_from_winlink(&fs, new_wl, 0); + cmdq_insert_hook(s, item, &fs, "after-new-window"); - free(to_free); + environ_free(sc.environ); return (CMD_RETURN_NORMAL); - -error: - free(to_free); - return (CMD_RETURN_ERROR); } diff --git a/cmd-parse.y b/cmd-parse.y new file mode 100644 index 0000000000..2c924010c4 --- /dev/null +++ b/cmd-parse.y @@ -0,0 +1,1549 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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 + +#include "tmux.h" + +static int yylex(void); +static int yyparse(void); +static int printflike(1,2) yyerror(const char *, ...); + +static char *yylex_token(int); +static char *yylex_format(void); + +struct cmd_parse_scope { + int flag; + TAILQ_ENTRY (cmd_parse_scope) entry; +}; + +struct cmd_parse_command { + char *name; + u_int line; + + int argc; + char **argv; + + TAILQ_ENTRY(cmd_parse_command) entry; +}; +TAILQ_HEAD(cmd_parse_commands, cmd_parse_command); + +struct cmd_parse_state { + FILE *f; + + const char *buf; + size_t len; + size_t off; + + int condition; + int eol; + int eof; + struct cmd_parse_input *input; + u_int escapes; + + char *error; + struct cmd_parse_commands *commands; + + struct cmd_parse_scope *scope; + TAILQ_HEAD(, cmd_parse_scope) stack; +}; +static struct cmd_parse_state parse_state; + +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, + struct cmd_list *); + +%} + +%union +{ + char *token; + struct { + int argc; + char **argv; + } arguments; + int flag; + struct { + int flag; + struct cmd_parse_commands *commands; + } elif; + struct cmd_parse_commands *commands; + struct cmd_parse_command *command; +} + +%token ERROR +%token IF +%token ELSE +%token ELIF +%token ENDIF +%token FORMAT TOKEN EQUALS + +%type argument expanded format +%type arguments +%type if_open if_elif +%type elif elif1 +%type statements statement commands condition condition1 +%type command + +%% + +lines : /* empty */ + | statements + { + struct cmd_parse_state *ps = &parse_state; + + ps->commands = $1; + } + +statements : statement '\n' + { + $$ = $1; + } + | statements statement '\n' + { + $$ = $1; + TAILQ_CONCAT($$, $2, entry); + free($2); + } + +statement : condition + { + struct cmd_parse_state *ps = &parse_state; + + if (ps->scope == NULL || ps->scope->flag) + $$ = $1; + else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($1); + } + } + | assignment + { + $$ = xmalloc (sizeof *$$); + TAILQ_INIT($$); + } + | commands + { + struct cmd_parse_state *ps = &parse_state; + + if (ps->scope == NULL || ps->scope->flag) + $$ = $1; + else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($1); + } + } + +format : FORMAT + { + $$ = $1; + } + | TOKEN + { + $$ = $1; + } + +expanded : format + { + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_input *pi = ps->input; + struct format_tree *ft; + struct client *c = pi->c; + struct cmd_find_state *fsp; + struct cmd_find_state fs; + int flags = FORMAT_NOJOBS; + + if (cmd_find_valid_state(&pi->fs)) + fsp = &pi->fs; + else { + cmd_find_from_client(&fs, c, 0); + fsp = &fs; + } + ft = format_create(NULL, pi->item, FORMAT_NONE, flags); + format_defaults(ft, c, fsp->s, fsp->wl, fsp->wp); + + $$ = format_expand(ft, $1); + format_free(ft); + free($1); + } + +assignment : /* empty */ + | 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, $1); + free($1); + } + +if_open : IF expanded + { + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_scope *scope; + + scope = xmalloc(sizeof *scope); + $$ = scope->flag = format_true($2); + free($2); + + if (ps->scope != NULL) + TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry); + ps->scope = scope; + } + +if_else : ELSE + { + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_scope *scope; + + scope = xmalloc(sizeof *scope); + scope->flag = !ps->scope->flag; + + free(ps->scope); + ps->scope = scope; + } + +if_elif : ELIF expanded + { + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_scope *scope; + + scope = xmalloc(sizeof *scope); + $$ = scope->flag = format_true($2); + free($2); + + free(ps->scope); + ps->scope = scope; + } + +if_close : ENDIF + { + struct cmd_parse_state *ps = &parse_state; + + free(ps->scope); + ps->scope = TAILQ_FIRST(&ps->stack); + if (ps->scope != NULL) + TAILQ_REMOVE(&ps->stack, ps->scope, entry); + } + +condition : if_open '\n' statements if_close + { + if ($1) + $$ = $3; + else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($3); + } + } + | if_open '\n' statements if_else '\n' statements if_close + { + if ($1) { + $$ = $3; + cmd_parse_free_commands($6); + } else { + $$ = $6; + cmd_parse_free_commands($3); + } + } + | if_open '\n' statements elif if_close + { + if ($1) { + $$ = $3; + cmd_parse_free_commands($4.commands); + } else if ($4.flag) { + $$ = $4.commands; + cmd_parse_free_commands($3); + } else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($3); + cmd_parse_free_commands($4.commands); + } + } + | if_open '\n' statements elif if_else '\n' statements if_close + { + if ($1) { + $$ = $3; + cmd_parse_free_commands($4.commands); + cmd_parse_free_commands($7); + } else if ($4.flag) { + $$ = $4.commands; + cmd_parse_free_commands($3); + cmd_parse_free_commands($7); + } else { + $$ = $7; + cmd_parse_free_commands($3); + cmd_parse_free_commands($4.commands); + } + } + +elif : if_elif '\n' statements + { + if ($1) { + $$.flag = 1; + $$.commands = $3; + } else { + $$.flag = 0; + $$.commands = cmd_parse_new_commands(); + cmd_parse_free_commands($3); + } + } + | if_elif '\n' statements elif + { + if ($1) { + $$.flag = 1; + $$.commands = $3; + cmd_parse_free_commands($4.commands); + } else if ($4.flag) { + $$.flag = 1; + $$.commands = $4.commands; + cmd_parse_free_commands($3); + } else { + $$.flag = 0; + $$.commands = cmd_parse_new_commands(); + cmd_parse_free_commands($3); + cmd_parse_free_commands($4.commands); + } + } + +commands : command + { + struct cmd_parse_state *ps = &parse_state; + + $$ = cmd_parse_new_commands(); + if (ps->scope == NULL || ps->scope->flag) + TAILQ_INSERT_TAIL($$, $1, entry); + else + cmd_parse_free_command($1); + } + | commands ';' + { + $$ = $1; + } + | commands ';' condition1 + { + $$ = $1; + TAILQ_CONCAT($$, $3, entry); + free($3); + } + | commands ';' command + { + struct cmd_parse_state *ps = &parse_state; + + if (ps->scope == NULL || ps->scope->flag) { + $$ = $1; + TAILQ_INSERT_TAIL($$, $3, entry); + } else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($1); + cmd_parse_free_command($3); + } + } + | condition1 + { + $$ = $1; + } + +command : assignment TOKEN + { + struct cmd_parse_state *ps = &parse_state; + + $$ = xcalloc(1, sizeof *$$); + $$->name = $2; + $$->line = ps->input->line; + + } + | assignment TOKEN arguments + { + struct cmd_parse_state *ps = &parse_state; + + $$ = xcalloc(1, sizeof *$$); + $$->name = $2; + $$->line = ps->input->line; + + $$->argc = $3.argc; + $$->argv = $3.argv; + } + +condition1 : if_open commands if_close + { + if ($1) + $$ = $2; + else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($2); + } + } + | if_open commands if_else commands if_close + { + if ($1) { + $$ = $2; + cmd_parse_free_commands($4); + } else { + $$ = $4; + cmd_parse_free_commands($2); + } + } + | if_open commands elif1 if_close + { + if ($1) { + $$ = $2; + cmd_parse_free_commands($3.commands); + } else if ($3.flag) { + $$ = $3.commands; + cmd_parse_free_commands($2); + } else { + $$ = cmd_parse_new_commands(); + cmd_parse_free_commands($2); + cmd_parse_free_commands($3.commands); + } + } + | if_open commands elif1 if_else commands if_close + { + if ($1) { + $$ = $2; + cmd_parse_free_commands($3.commands); + cmd_parse_free_commands($5); + } else if ($3.flag) { + $$ = $3.commands; + cmd_parse_free_commands($2); + cmd_parse_free_commands($5); + } else { + $$ = $5; + cmd_parse_free_commands($2); + cmd_parse_free_commands($3.commands); + } + } + +elif1 : if_elif commands + { + if ($1) { + $$.flag = 1; + $$.commands = $2; + } else { + $$.flag = 0; + $$.commands = cmd_parse_new_commands(); + cmd_parse_free_commands($2); + } + } + | if_elif commands elif1 + { + if ($1) { + $$.flag = 1; + $$.commands = $2; + cmd_parse_free_commands($3.commands); + } else if ($3.flag) { + $$.flag = 1; + $$.commands = $3.commands; + cmd_parse_free_commands($2); + } else { + $$.flag = 0; + $$.commands = cmd_parse_new_commands(); + cmd_parse_free_commands($2); + cmd_parse_free_commands($3.commands); + } + } + +arguments : argument + { + $$.argc = 1; + $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv); + + $$.argv[0] = $1; + } + | argument arguments + { + cmd_prepend_argv(&$2.argc, &$2.argv, $1); + free($1); + $$ = $2; + } + +argument : TOKEN + { + $$ = $1; + } + | EQUALS + { + $$ = $1; + } + +%% + +static char * +cmd_parse_get_error(const char *file, u_int line, const char *error) +{ + char *s; + + if (file == NULL) + s = xstrdup(error); + else + 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) +{ + 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); + } +} + +static void +cmd_parse_free_command(struct cmd_parse_command *cmd) +{ + free(cmd->name); + cmd_free_argv(cmd->argc, cmd->argv); + free(cmd); +} + +static struct cmd_parse_commands * +cmd_parse_new_commands(void) +{ + struct cmd_parse_commands *cmds; + + cmds = xmalloc(sizeof *cmds); + TAILQ_INIT (cmds); + return (cmds); +} + +static void +cmd_parse_free_commands(struct cmd_parse_commands *cmds) +{ + struct cmd_parse_command *cmd, *cmd1; + + TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) { + TAILQ_REMOVE(cmds, cmd, entry); + cmd_parse_free_command(cmd); + } + free(cmds); +} + +static struct cmd_parse_commands * +cmd_parse_run_parser(char **cause) +{ + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_scope *scope, *scope1; + int retval; + + ps->commands = NULL; + TAILQ_INIT(&ps->stack); + + retval = yyparse(); + TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) { + TAILQ_REMOVE(&ps->stack, scope, entry); + free(scope); + } + if (retval != 0) { + *cause = ps->error; + return (NULL); + } + + if (ps->commands == NULL) + return (cmd_parse_new_commands()); + return (ps->commands); +} + +static struct cmd_parse_commands * +cmd_parse_do_file(FILE *f, struct cmd_parse_input *pi, char **cause) +{ + struct cmd_parse_state *ps = &parse_state; + + memset(ps, 0, sizeof *ps); + ps->input = pi; + ps->f = f; + return (cmd_parse_run_parser(cause)); +} + +static struct cmd_parse_commands * +cmd_parse_do_buffer(const char *buf, size_t len, struct cmd_parse_input *pi, + char **cause) +{ + struct cmd_parse_state *ps = &parse_state; + + memset(ps, 0, sizeof *ps); + ps->input = pi; + ps->buf = buf; + ps->len = len; + 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 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 *add; + char *alias, *cause, *s; + + /* 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); + } + + /* + * 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). + */ + 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); + } + cmdlist = cmd_list_new(); + } + 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_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_append(cmdlist, add); + } + if (cmdlist != NULL) { + cmd_parse_print_commands(pi, line, cmdlist); + cmd_list_move(result, cmdlist); + cmd_list_free(cmdlist); + } + + 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); +} + +struct cmd_parse_result * +cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi) +{ + static struct cmd_parse_result pr; + struct cmd_parse_input input; + struct cmd_parse_commands *cmds; + char *cause; + + if (pi == NULL) { + memset(&input, 0, sizeof input); + pi = &input; + } + memset(&pr, 0, sizeof pr); + + cmds = cmd_parse_do_file(f, pi, &cause); + if (cmds == NULL) { + pr.status = CMD_PARSE_ERROR; + pr.error = cause; + return (&pr); + } + return (cmd_parse_build_commands(cmds, pi)); +} + +struct cmd_parse_result * +cmd_parse_from_string(const char *s, struct cmd_parse_input *pi) +{ + static struct cmd_parse_result pr; + struct cmd_parse_input input; + struct cmd_parse_commands *cmds; + char *cause; + + if (pi == NULL) { + memset(&input, 0, sizeof input); + pi = &input; + } + memset(&pr, 0, sizeof pr); + + if (*s == '\0') { + pr.status = CMD_PARSE_EMPTY; + pr.cmdlist = NULL; + pr.error = NULL; + return (&pr); + } + + cmds = cmd_parse_do_buffer(s, strlen(s), pi, &cause); + if (cmds == NULL) { + pr.status = CMD_PARSE_ERROR; + pr.error = cause; + return (&pr); + } + return (cmd_parse_build_commands(cmds, pi)); +} + +struct cmd_parse_result * +cmd_parse_from_arguments(int argc, char **argv, 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; + + /* + * The commands are already split up into arguments, so just separate + * into a set of commands by ';'. + */ + + if (pi == NULL) { + memset(&input, 0, sizeof input); + pi = &input; + } + cmd_log_argv(argc, argv, "%s", __func__); + + 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); + + 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); + } + } + + cmd_free_argv(argc, copy); + return (cmd_parse_build_commands(cmds, pi)); +} + +static int printflike(1, 2) +yyerror(const char *fmt, ...) +{ + struct cmd_parse_state *ps = &parse_state; + struct cmd_parse_input *pi = ps->input; + va_list ap; + char *error; + + if (ps->error != NULL) + return (0); + + va_start(ap, fmt); + xvasprintf(&error, fmt, ap); + va_end(ap); + + ps->error = cmd_parse_get_error(pi->file, pi->line, error); + free(error); + return (0); +} + +static int +yylex_is_var(char ch, int first) +{ + if (ch == '=') + return (0); + if (first && isdigit((u_char)ch)) + return (0); + return (isalnum((u_char)ch) || ch == '_'); +} + +static void +yylex_append(char **buf, size_t *len, const char *add, size_t addlen) +{ + if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen) + fatalx("buffer is too big"); + *buf = xrealloc(*buf, (*len) + 1 + addlen); + memcpy((*buf) + *len, add, addlen); + (*len) += addlen; +} + +static void +yylex_append1(char **buf, size_t *len, char add) +{ + yylex_append(buf, len, &add, 1); +} + +static int +yylex_getc1(void) +{ + struct cmd_parse_state *ps = &parse_state; + int ch; + + if (ps->f != NULL) + ch = getc(ps->f); + else { + if (ps->off == ps->len) + ch = EOF; + else + ch = ps->buf[ps->off++]; + } + return (ch); +} + +static void +yylex_ungetc(int ch) +{ + struct cmd_parse_state *ps = &parse_state; + + if (ps->f != NULL) + ungetc(ch, ps->f); + else if (ps->off > 0 && ch != EOF) + ps->off--; +} + +static int +yylex_getc(void) +{ + struct cmd_parse_state *ps = &parse_state; + int ch; + + if (ps->escapes != 0) { + ps->escapes--; + return ('\\'); + } + for (;;) { + ch = yylex_getc1(); + if (ch == '\\') { + ps->escapes++; + continue; + } + if (ch == '\n' && (ps->escapes % 2) == 1) { + ps->input->line++; + ps->escapes--; + continue; + } + + if (ps->escapes != 0) { + yylex_ungetc(ch); + ps->escapes--; + return ('\\'); + } + return (ch); + } +} + +static char * +yylex_get_word(int ch) +{ + char *buf; + size_t len; + + len = 0; + buf = xmalloc(1); + + do + yylex_append1(&buf, &len, ch); + while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL); + yylex_ungetc(ch); + + buf[len] = '\0'; + log_debug("%s: %s", __func__, buf); + return (buf); +} + +static int +yylex(void) +{ + struct cmd_parse_state *ps = &parse_state; + char *token, *cp; + int ch, next, condition; + + if (ps->eol) + ps->input->line++; + ps->eol = 0; + + condition = ps->condition; + ps->condition = 0; + + for (;;) { + ch = yylex_getc(); + + if (ch == EOF) { + /* + * Ensure every file or string is terminated by a + * newline. This keeps the parser simpler and avoids + * having to add a newline to each string. + */ + if (ps->eof) + break; + ps->eof = 1; + return ('\n'); + } + + if (ch == ' ' || ch == '\t') { + /* + * Ignore whitespace. + */ + continue; + } + + if (ch == '\n') { + /* + * End of line. Update the line number. + */ + ps->eol = 1; + return ('\n'); + } + + if (ch == ';') { + /* + * A semicolon is itself. + */ + return (';'); + } + + if (ch == '#') { + /* + * #{ after a condition opens a format; anything else + * is a comment, ignore up to the end of the line. + */ + next = yylex_getc(); + if (condition && next == '{') { + yylval.token = yylex_format(); + if (yylval.token == NULL) + return (ERROR); + return (FORMAT); + } + while (next != '\n' && next != EOF) + next = yylex_getc(); + if (next == '\n') { + ps->input->line++; + return ('\n'); + } + continue; + } + + if (ch == '%') { + /* + * % is a condition unless it is all % or all numbers, + * then it is a token. + */ + yylval.token = yylex_get_word('%'); + for (cp = yylval.token; *cp != '\0'; cp++) { + if (*cp != '%' && !isdigit((u_char)*cp)) + break; + } + if (*cp == '\0') + return (TOKEN); + ps->condition = 1; + if (strcmp(yylval.token, "%if") == 0) { + free(yylval.token); + return (IF); + } + if (strcmp(yylval.token, "%else") == 0) { + free(yylval.token); + return (ELSE); + } + if (strcmp(yylval.token, "%elif") == 0) { + free(yylval.token); + return (ELIF); + } + if (strcmp(yylval.token, "%endif") == 0) { + free(yylval.token); + return (ENDIF); + } + free(yylval.token); + return (ERROR); + } + + /* + * Otherwise this is a token. + */ + token = yylex_token(ch); + if (token == NULL) + return (ERROR); + yylval.token = token; + + if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) { + for (cp = token + 1; *cp != '='; cp++) { + if (!yylex_is_var(*cp, 0)) + break; + } + if (*cp == '=') + return (EQUALS); + } + return (TOKEN); + } + return (0); +} + +static char * +yylex_format(void) +{ + char *buf; + size_t len; + int ch, brackets = 1; + + len = 0; + buf = xmalloc(1); + + yylex_append(&buf, &len, "#{", 2); + for (;;) { + if ((ch = yylex_getc()) == EOF || ch == '\n') + goto error; + if (ch == '#') { + if ((ch = yylex_getc()) == EOF || ch == '\n') + goto error; + if (ch == '{') + brackets++; + yylex_append1(&buf, &len, '#'); + } else if (ch == '}') { + if (brackets != 0 && --brackets == 0) { + yylex_append1(&buf, &len, ch); + break; + } + } + yylex_append1(&buf, &len, ch); + } + if (brackets != 0) + goto error; + + buf[len] = '\0'; + log_debug("%s: %s", __func__, buf); + return (buf); + +error: + free(buf); + return (NULL); +} + +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; + + ch = yylex_getc(); + + if (ch >= '4' && ch <= '7') { + yyerror("invalid octal escape"); + return (0); + } + if (ch >= '0' && ch <= '3') { + o2 = yylex_getc(); + if (o2 >= '0' && o2 <= '7') { + o3 = yylex_getc(); + if (o3 >= '0' && o3 <= '7') { + ch = 64 * (ch - '0') + + 8 * (o2 - '0') + + (o3 - '0'); + yylex_append1(buf, len, ch); + return (1); + } + } + yyerror("invalid octal escape"); + return (0); + } + + switch (ch) { + case EOF: + return (0); + case 'a': + ch = '\a'; + break; + case 'b': + ch = '\b'; + break; + case 'e': + ch = '\033'; + break; + case 'f': + ch = '\f'; + break; + case 's': + ch = ' '; + break; + case 'v': + ch = '\v'; + break; + case 'r': + ch = '\r'; + break; + case 'n': + ch = '\n'; + break; + case 't': + ch = '\t'; + break; + case 'u': + type = 'u'; + size = 4; + goto unicode; + case 'U': + type = 'U'; + size = 8; + goto unicode; + } + + yylex_append1(buf, len, ch); + return (1); + +unicode: + for (i = 0; i < size; i++) { + ch = yylex_getc(); + if (ch == EOF || ch == '\n') + return (0); + if (!isxdigit((u_char)ch)) { + yyerror("invalid \\%c argument", type); + return (0); + } + s[i] = ch; + } + s[i] = '\0'; + + if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) || + (size == 8 && sscanf(s, "%8x", &tmp) != 1)) { + yyerror("invalid \\%c argument", type); + return (0); + } + if (utf8_split(tmp, &ud) != UTF8_DONE) { + yyerror("invalid \\%c argument", type); + return (0); + } + yylex_append(buf, len, ud.data, ud.size); + return (1); +} + +static int +yylex_token_variable(char **buf, size_t *len) +{ + struct environ_entry *envent; + int ch, brackets = 0; + char name[1024]; + size_t namelen = 0; + const char *value; + + ch = yylex_getc(); + if (ch == EOF) + return (0); + if (ch == '{') + brackets = 1; + else { + if (!yylex_is_var(ch, 1)) { + yylex_append1(buf, len, '$'); + yylex_ungetc(ch); + return (1); + } + name[namelen++] = ch; + } + + for (;;) { + ch = yylex_getc(); + if (brackets && ch == '}') + break; + if (ch == EOF || !yylex_is_var(ch, 0)) { + if (!brackets) { + yylex_ungetc(ch); + break; + } + yyerror("invalid environment variable"); + return (0); + } + if (namelen == (sizeof name) - 2) { + yyerror("environment variable is too long"); + return (0); + } + name[namelen++] = ch; + } + name[namelen] = '\0'; + + envent = environ_find(global_environ, name); + if (envent != NULL) { + value = envent->value; + log_debug("%s: %s -> %s", __func__, name, value); + yylex_append(buf, len, value, strlen(value)); + } + return (1); +} + +static int +yylex_token_tilde(char **buf, size_t *len) +{ + struct environ_entry *envent; + int ch; + char name[1024]; + size_t namelen = 0; + struct passwd *pw; + const char *home = NULL; + + for (;;) { + ch = yylex_getc(); + if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) { + yylex_ungetc(ch); + break; + } + if (namelen == (sizeof name) - 2) { + yyerror("user name is too long"); + return (0); + } + name[namelen++] = ch; + } + name[namelen] = '\0'; + + if (*name == '\0') { + envent = environ_find(global_environ, "HOME"); + if (envent != NULL && *envent->value != '\0') + home = envent->value; + else if ((pw = getpwuid(getuid())) != NULL) + home = pw->pw_dir; + } else { + if ((pw = getpwnam(name)) != NULL) + home = pw->pw_dir; + } + if (home == NULL) + return (0); + + log_debug("%s: ~%s -> %s", __func__, name, home); + yylex_append(buf, len, home, strlen(home)); + 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) +{ + char *buf; + size_t len; + enum { START, + NONE, + DOUBLE_QUOTES, + SINGLE_QUOTES } state = NONE, last = START; + + len = 0; + 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; + break; + } + + /* Whitespace or ; ends a token unless inside quotes. */ + if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE) + break; + + /* + * \ ~ and $ are expanded except in single quotes. + */ + if (ch == '\\' && state != SINGLE_QUOTES) { + if (!yylex_token_escape(&buf, &len)) + goto error; + goto skip; + } + if (ch == '~' && last != state && state != SINGLE_QUOTES) { + if (!yylex_token_tilde(&buf, &len)) + goto error; + goto skip; + } + if (ch == '$' && state != SINGLE_QUOTES) { + if (!yylex_token_variable(&buf, &len)) + 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). + */ + if (ch == '\'') { + if (state == NONE) { + state = SINGLE_QUOTES; + goto next; + } + if (state == SINGLE_QUOTES) { + state = NONE; + goto next; + } + } + if (ch == '"') { + if (state == NONE) { + state = DOUBLE_QUOTES; + goto next; + } + if (state == DOUBLE_QUOTES) { + state = NONE; + goto next; + } + } + + /* + * Otherwise add the character to the buffer. + */ + yylex_append1(&buf, &len, ch); + + skip: + last = state; + + next: + ch = yylex_getc(); + } + yylex_ungetc(ch); + + buf[len] = '\0'; + log_debug("%s: %s", __func__, buf); + return (buf); + +error: + free(buf); + return (NULL); +} diff --git a/cmd-pipe-pane.c b/cmd-pipe-pane.c index 9c2290d6a1..ce1b3d379a 100644 --- a/cmd-pipe-pane.c +++ b/cmd-pipe-pane.c @@ -35,6 +35,7 @@ static enum cmd_retval cmd_pipe_pane_exec(struct cmd *, struct cmdq_item *); +static void cmd_pipe_pane_read_callback(struct bufferevent *, void *); static void cmd_pipe_pane_write_callback(struct bufferevent *, void *); static void cmd_pipe_pane_error_callback(struct bufferevent *, short, void *); @@ -42,8 +43,8 @@ const struct cmd_entry cmd_pipe_pane_entry = { .name = "pipe-pane", .alias = "pipep", - .args = { "ot:", 0, 1 }, - .usage = "[-o] " CMD_TARGET_PANE_USAGE " [command]", + .args = { "IOot:", 0, 1 }, + .usage = "[-IOo] " CMD_TARGET_PANE_USAGE " [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -60,7 +61,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) struct session *s = item->target.s; struct winlink *wl = item->target.wl; char *cmd; - int old_fd, pipe_fd[2], null_fd; + int old_fd, pipe_fd[2], null_fd, in, out; struct format_tree *ft; sigset_t set, oldset; @@ -90,6 +91,15 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) if (args_has(self->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')) { + in = 1; + out = args_has(self->args, 'O'); + } else { + in = 0; + out = 1; + } + /* Open the new pipe. */ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fd) != 0) { cmdq_error(item, "socketpair error: %s", strerror(errno)); @@ -99,7 +109,7 @@ 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], time(NULL)); + cmd = format_expand_time(ft, args->argv[0]); format_free(ft); /* Fork the child. */ @@ -118,19 +128,25 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) sigprocmask(SIG_SETMASK, &oldset, NULL); close(pipe_fd[0]); - if (dup2(pipe_fd[1], STDIN_FILENO) == -1) - _exit(1); - if (pipe_fd[1] != STDIN_FILENO) - close(pipe_fd[1]); - null_fd = open(_PATH_DEVNULL, O_WRONLY, 0); - if (dup2(null_fd, STDOUT_FILENO) == -1) - _exit(1); + if (out) { + if (dup2(pipe_fd[1], STDIN_FILENO) == -1) + _exit(1); + } else { + if (dup2(null_fd, STDIN_FILENO) == -1) + _exit(1); + } + if (in) { + if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) + _exit(1); + if (pipe_fd[1] != STDOUT_FILENO) + close(pipe_fd[1]); + } else { + if (dup2(null_fd, STDOUT_FILENO) == -1) + _exit(1); + } if (dup2(null_fd, STDERR_FILENO) == -1) _exit(1); - if (null_fd != STDOUT_FILENO && null_fd != STDERR_FILENO) - close(null_fd); - closefrom(STDERR_FILENO + 1); execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); @@ -141,26 +157,53 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) close(pipe_fd[1]); wp->pipe_fd = pipe_fd[0]; - wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); - - wp->pipe_event = bufferevent_new(wp->pipe_fd, NULL, - cmd_pipe_pane_write_callback, cmd_pipe_pane_error_callback, - wp); - bufferevent_enable(wp->pipe_event, EV_WRITE); + if (wp->fd != -1) + wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); + else + wp->pipe_off = 0; setblocking(wp->pipe_fd, 0); + wp->pipe_event = bufferevent_new(wp->pipe_fd, + cmd_pipe_pane_read_callback, + cmd_pipe_pane_write_callback, + cmd_pipe_pane_error_callback, + wp); + if (wp->pipe_event == NULL) + fatalx("out of memory"); + if (out) + bufferevent_enable(wp->pipe_event, EV_WRITE); + if (in) + bufferevent_enable(wp->pipe_event, EV_READ); free(cmd); return (CMD_RETURN_NORMAL); } } +static void +cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data) +{ + struct window_pane *wp = data; + struct evbuffer *evb = wp->pipe_event->input; + size_t available; + + available = EVBUFFER_LENGTH(evb); + log_debug("%%%u pipe read %zu", wp->id, available); + + bufferevent_write(wp->event, EVBUFFER_DATA(evb), available); + evbuffer_drain(evb, available); + + if (window_pane_destroy_ready(wp)) + server_destroy_pane(wp, 1); +} + static void cmd_pipe_pane_write_callback(__unused struct bufferevent *bufev, void *data) { struct window_pane *wp = data; log_debug("%%%u pipe empty", wp->id); + if (window_pane_destroy_ready(wp)) server_destroy_pane(wp, 1); } diff --git a/cmd-queue.c b/cmd-queue.c index ec65bd80a1..fa6999e82f 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -32,11 +32,14 @@ static struct cmdq_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue); static const char * cmdq_name(struct client *c) { - static char s[32]; + static char s[256]; if (c == NULL) return (""); - xsnprintf(s, sizeof s, "<%p>", c); + if (c->name != NULL) + xsnprintf(s, sizeof s, "<%s>", c->name); + else + xsnprintf(s, sizeof s, "<%p>", c); return (s); } @@ -66,6 +69,7 @@ cmdq_append(struct client *c, struct cmdq_item *item) item->queue = queue; TAILQ_INSERT_TAIL(queue, item, entry); + log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); item = next; } while (item != NULL); @@ -81,23 +85,83 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) do { next = item->next; - item->next = NULL; + item->next = after->next; + after->next = item; if (c != NULL) c->references++; item->client = c; item->queue = queue; - if (after->next != NULL) - TAILQ_INSERT_AFTER(queue, after->next, item, entry); - else - TAILQ_INSERT_AFTER(queue, after, item, entry); - after->next = item; + TAILQ_INSERT_AFTER(queue, after, item, entry); + log_debug("%s %s: %s after %s", __func__, cmdq_name(c), + item->name, after->name); + after = item; item = next; } while (item != NULL); } +/* Insert a hook. */ +void +cmdq_insert_hook(struct session *s, struct cmdq_item *item, + struct cmd_find_state *fs, const char *fmt, ...) +{ + struct options *oo; + va_list ap; + char *name; + struct cmdq_item *new_item; + struct options_entry *o; + struct options_array_item *a; + struct cmd_list *cmdlist; + + if (item->flags & CMDQ_NOHOOKS) + return; + if (s == NULL) + oo = global_s_options; + else + oo = s->options; + + va_start(ap, fmt); + xvasprintf(&name, fmt, ap); + va_end(ap); + + o = options_get(oo, name); + if (o == NULL) { + free(name); + return; + } + log_debug("running hook %s (parent %p)", name, item); + + a = options_array_first(o); + while (a != NULL) { + cmdlist = options_array_item_value(a)->cmdlist; + if (cmdlist == NULL) { + a = options_array_next(a); + continue; + } + + new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS); + cmdq_format(new_item, "hook", "%s", name); + if (item != NULL) { + cmdq_insert_after(item, new_item); + item = new_item; + } else + cmdq_append(NULL, new_item); + + a = options_array_next(a); + } + + free(name); +} + +/* Continue processing command queue. */ +void +cmdq_continue(struct cmdq_item *item) +{ + item->flags &= ~CMDQ_WAITING; +} + /* Remove an item. */ static void cmdq_remove(struct cmdq_item *item) @@ -111,30 +175,23 @@ cmdq_remove(struct cmdq_item *item) if (item->client != NULL) server_client_unref(item->client); - if (item->type == CMDQ_COMMAND) + if (item->cmdlist != NULL) cmd_list_free(item->cmdlist); TAILQ_REMOVE(item->queue, item, entry); - free((void *)item->name); + free(item->name); free(item); } -/* Set command group. */ -static u_int -cmdq_next_group(void) -{ - static u_int group; - - return (++group); -} - /* Remove all subsequent items that match this item's group. */ static void cmdq_remove_group(struct cmdq_item *item) { struct cmdq_item *this, *next; + if (item->group == 0) + return; this = TAILQ_NEXT(item, entry); while (this != NULL) { next = TAILQ_NEXT(this, entry); @@ -151,32 +208,34 @@ cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current, { struct cmdq_item *item, *first = NULL, *last = NULL; struct cmd *cmd; - u_int group = cmdq_next_group(); - char *tmp; - struct cmdq_shared *shared; - - 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); + struct cmdq_shared *shared = NULL; + u_int group = 0; TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { - xasprintf(&tmp, "command[%s]", cmd->entry->name); + 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; + } item = xcalloc(1, sizeof *item); - item->name = tmp; + xasprintf(&item->name, "[%s/%p]", cmd->entry->name, item); item->type = CMDQ_COMMAND; - item->group = group; + item->group = cmd->group; item->flags = flags; item->shared = shared; item->cmdlist = cmdlist; item->cmd = cmd; + log_debug("%s: %s group %u", __func__, item->name, item->group); + shared->references++; cmdlist->references++; @@ -214,13 +273,22 @@ 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; struct cmd *cmd = item->cmd; const struct cmd_entry *entry = cmd->entry; enum cmd_retval retval; struct cmd_find_state *fsp, fs; int flags; + char *tmp; - flags = !!(cmd->flags & CMD_CONTROL); + 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); cmdq_guard(item, "begin", flags); if (item->client == NULL) @@ -245,7 +313,7 @@ cmdq_fire_command(struct cmdq_item *item) fsp = &fs; else goto out; - hooks_insert(fsp->s->hooks, item, fsp, "after-%s", entry->name); + cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); } out: @@ -262,12 +330,9 @@ struct cmdq_item * cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) { struct cmdq_item *item; - char *tmp; - - xasprintf(&tmp, "callback[%s]", name); item = xcalloc(1, sizeof *item); - item->name = tmp; + xasprintf(&item->name, "[%s/%p]", name, item); item->type = CMDQ_CALLBACK; item->group = 0; @@ -279,6 +344,25 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) return (item); } +/* Generic error callback. */ +static enum cmd_retval +cmdq_error_callback(struct cmdq_item *item, void *data) +{ + char *error = data; + + cmdq_error(item, "%s", error); + free(error); + + return (CMD_RETURN_NORMAL); +} + +/* Get an error callback for the command queue. */ +struct cmdq_item * +cmdq_get_error(const char *error) +{ + return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); +} + /* Fire callback on callback queue. */ static enum cmd_retval cmdq_fire_callback(struct cmdq_item *item) @@ -404,10 +488,11 @@ cmdq_guard(struct cmdq_item *item, const char *guard, int flags) void cmdq_print(struct cmdq_item *item, const char *fmt, ...) { - struct client *c = item->client; - struct window *w; - va_list ap; - char *tmp, *msg; + struct client *c = item->client; + struct window_pane *wp; + struct window_mode_entry *wme; + va_list ap; + char *tmp, *msg; va_start(ap, fmt); @@ -425,14 +510,11 @@ cmdq_print(struct cmdq_item *item, const char *fmt, ...) evbuffer_add(c->stdout_data, "\n", 1); server_client_push_stdout(c); } else { - w = c->session->curw->window; - if (w->active->mode != &window_copy_mode) { - window_pane_reset_mode(w->active); - window_pane_set_mode(w->active, &window_copy_mode, NULL, - NULL); - window_copy_init_for_output(w->active); - } - window_copy_vadd(w->active, fmt, ap); + wp = c->session->curw->window->active; + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->mode != &window_view_mode) + window_pane_set_mode(wp, &window_view_mode, NULL, NULL); + window_copy_vadd(wp, fmt, ap); } va_end(ap); diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index df1d213564..b4c5e844fa 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -18,6 +18,9 @@ #include +#include +#include + #include "tmux.h" /* @@ -31,8 +34,9 @@ const struct cmd_entry cmd_refresh_client_entry = { .name = "refresh-client", .alias = "refresh", - .args = { "C:St:", 0, 0 }, - .usage = "[-S] [-C size] " CMD_TARGET_CLIENT_USAGE, + .args = { "cC:DF:lLRSt:U", 0, 1 }, + .usage = "[-cDlLRSU] [-C XxY] [-F flags] " CMD_TARGET_CLIENT_USAGE + " [adjustment]", .flags = CMD_AFTERHOOK, .exec = cmd_refresh_client_exec @@ -43,40 +47,115 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; struct client *c; - const char *size; - u_int w, h; + 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; - if (args_has(args, 'C')) { - if ((size = args_get(args, 'C')) == NULL) { - cmdq_error(item, "missing size"); - return (CMD_RETURN_ERROR); + if (args_has(args, 'c') || + args_has(args, 'L') || + args_has(args, 'R') || + args_has(args, 'U') || + args_has(args, 'D')) + { + if (args->argc == 0) + adjust = 1; + else { + adjust = strtonum(args->argv[0], 1, INT_MAX, &errstr); + if (errstr != NULL) { + cmdq_error(item, "adjustment %s", errstr); + return (CMD_RETURN_ERROR); + } } - if (sscanf(size, "%u,%u", &w, &h) != 2) { - cmdq_error(item, "bad size argument"); - return (CMD_RETURN_ERROR); + + if (args_has(args, 'c')) + c->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; + } + if (args_has(args, 'L')) { + if (c->pan_ox > adjust) + c->pan_ox -= adjust; + else + c->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; + } else if (args_has(args, 'U')) { + if (c->pan_oy > adjust) + c->pan_oy -= adjust; + else + c->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; + } } - if (w < PANE_MINIMUM || w > 5000 || - h < PANE_MINIMUM || h > 5000) { - cmdq_error(item, "size too small or too big"); - return (CMD_RETURN_ERROR); + tty_update_client_offset(c); + server_redraw_client(c); + return (CMD_RETURN_NORMAL); + } + + if (args_has(args, 'l')) { + if (c->session != NULL) + tty_putcode_ptr2(&c->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 (!(c->flags & CLIENT_CONTROL)) { - cmdq_error(item, "not a control client"); - return (CMD_RETURN_ERROR); + 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); } - tty_set_size(&c->tty, w, h); - c->flags |= CLIENT_SIZECHANGED; - recalculate_sizes(); - } else if (args_has(args, 'S')) { + return (CMD_RETURN_NORMAL); + } + + if (args_has(args, 'S')) { c->flags |= CLIENT_STATUSFORCE; server_status_client(c); } else { c->flags |= CLIENT_STATUSFORCE; server_redraw_client(c); } - return (CMD_RETURN_NORMAL); } diff --git a/cmd-rename-session.c b/cmd-rename-session.c index e7586e0bb0..8385434abb 100644 --- a/cmd-rename-session.c +++ b/cmd-rename-session.c @@ -46,26 +46,31 @@ 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 session *s = item->target.s; - const char *newname; - - newname = args->argv[0]; - if (strcmp(newname, s->name) == 0) + struct args *args = self->args; + struct client *c = cmd_find_client(item, NULL, 1); + struct session *s = item->target.s; + char *newname; + + newname = format_single(item, args->argv[0], c, s, NULL, NULL); + 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); return (CMD_RETURN_ERROR); } RB_REMOVE(sessions, &sessions, s); free(s->name); - s->name = xstrdup(newname); + s->name = newname; RB_INSERT(sessions, &sessions, s); server_status_session(s); diff --git a/cmd-rename-window.c b/cmd-rename-window.c index 802eab7d7c..4d2ebb756c 100644 --- a/cmd-rename-window.c +++ b/cmd-rename-window.c @@ -46,12 +46,17 @@ 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; - window_set_name(wl->window, args->argv[0]); + newname = format_single(item, args->argv[0], c, s, wl, NULL); + window_set_name(wl->window, newname); options_set_number(wl->window->options, "automatic-rename", 0); server_status_window(wl->window); + free(newname); return (CMD_RETURN_NORMAL); } diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index bbb78de73d..3962546d91 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -19,6 +19,7 @@ #include #include +#include #include "tmux.h" @@ -55,10 +56,11 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) struct window *w = wl->window; struct client *c = item->client; struct session *s = item->target.s; - const char *errstr; - char *cause; + const char *errstr, *p; + char *cause, *copy; u_int adjust; - int x, y; + int x, y, percentage; + size_t plen; if (args_has(args, 'M')) { if (cmd_mouse_window(&shared->mouse, &s) == NULL) @@ -91,34 +93,69 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item) } } - if (args_has(self->args, 'x')) { - x = args_strtonum(self->args, 'x', PANE_MINIMUM, INT_MAX, - &cause); - if (cause != NULL) { - cmdq_error(item, "width %s", cause); - free(cause); - 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); + } } layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x); } - if (args_has(self->args, 'y')) { - y = args_strtonum(self->args, 'y', PANE_MINIMUM, INT_MAX, - &cause); - if (cause != NULL) { - cmdq_error(item, "height %s", cause); - free(cause); - return (CMD_RETURN_ERROR); + 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); + } } layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y); } - if (args_has(self->args, 'L')) + if (args_has(args, 'L')) layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1); - else if (args_has(self->args, 'R')) + else if (args_has(args, 'R')) layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1); - else if (args_has(self->args, 'U')) + else if (args_has(args, 'U')) layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1); - else if (args_has(self->args, 'D')) + else if (args_has(args, 'D')) layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1); server_redraw_window(wl->window); @@ -129,57 +166,64 @@ static void cmd_resize_pane_mouse_update(struct client *c, struct mouse_event *m) { struct winlink *wl; - struct window_pane *loop, *wp_x, *wp_y; - u_int y, ly, x, lx, sx, sy, ex, ey; + struct window *w; + u_int y, ly, x, lx; + static const int offsets[][2] = { + { 0, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 }, + }; + struct layout_cell *cells[nitems(offsets)], *lc; + u_int ncells = 0, i, j, resizes = 0; + enum layout_type type; wl = cmd_mouse_window(m, NULL); if (wl == NULL) { c->tty.mouse_drag_update = NULL; return; } + w = wl->window; - y = m->y; x = m->x; - if (m->statusat == 0 && y > 0) - y--; + y = m->y + m->oy; x = m->x + m->ox; + if (m->statusat == 0 && y >= m->statuslines) + y -= m->statuslines; else if (m->statusat > 0 && y >= (u_int)m->statusat) y = m->statusat - 1; - ly = m->ly; lx = m->lx; - if (m->statusat == 0 && ly > 0) - ly--; + ly = m->ly + m->oy; lx = m->lx + m->ox; + if (m->statusat == 0 && ly >= m->statuslines) + ly -= m->statuslines; else if (m->statusat > 0 && ly >= (u_int)m->statusat) ly = m->statusat - 1; - wp_x = wp_y = NULL; - TAILQ_FOREACH(loop, &wl->window->panes, entry) { - if (!window_pane_visible(loop)) + for (i = 0; i < nitems(cells); i++) { + lc = layout_search_by_border(w->layout_root, lx + offsets[i][0], + ly + offsets[i][1]); + if (lc == NULL) + continue; + + for (j = 0; j < ncells; j++) { + if (cells[j] == lc) { + lc = NULL; + break; + } + } + if (lc == NULL) continue; - sx = loop->xoff; - if (sx != 0) - sx--; - ex = loop->xoff + loop->sx; - - sy = loop->yoff; - if (sy != 0) - sy--; - ey = loop->yoff + loop->sy; - - if ((lx == sx || lx == ex) && - (ly >= sy && ly <= ey) && - (wp_x == NULL || loop->sy > wp_x->sy)) - wp_x = loop; - if ((ly == sy || ly == ey) && - (lx >= sx && lx <= ex) && - (wp_y == NULL || loop->sx > wp_y->sx)) - wp_y = loop; + cells[ncells] = lc; + ncells++; } - if (wp_x == NULL && wp_y == NULL) { - c->tty.mouse_drag_update = NULL; + if (ncells == 0) return; + + for (i = 0; i < ncells; i++) { + type = cells[i]->parent->type; + if (y != ly && type == LAYOUT_TOPBOTTOM) { + layout_resize_layout(w, cells[i], type, y - ly, 0); + resizes++; + } else if (x != lx && type == LAYOUT_LEFTRIGHT) { + layout_resize_layout(w, cells[i], type, x - lx, 0); + resizes++; + } } - if (wp_x != NULL) - layout_resize_pane(wp_x, LAYOUT_LEFTRIGHT, x - lx, 0); - if (wp_y != NULL) - layout_resize_pane(wp_y, LAYOUT_TOPBOTTOM, y - ly, 0); - server_redraw_window(wl->window); + if (resizes != 0) + server_redraw_window(w); } diff --git a/cmd-resize-window.c b/cmd-resize-window.c new file mode 100644 index 0000000000..9cc74e829f --- /dev/null +++ b/cmd-resize-window.c @@ -0,0 +1,113 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2018 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" + +/* + * Increase or decrease window size. + */ + +static enum cmd_retval cmd_resize_window_exec(struct cmd *, + struct cmdq_item *); + +const struct cmd_entry cmd_resize_window_entry = { + .name = "resize-window", + .alias = "resizew", + + .args = { "aADLRt:Ux:y:", 0, 1 }, + .usage = "[-aADLRU] [-x width] [-y height] " CMD_TARGET_WINDOW_USAGE " " + "[adjustment]", + + .target = { 't', CMD_FIND_WINDOW, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_resize_window_exec +}; + +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 window *w = wl->window; + struct session *s = item->target.s; + const char *errstr; + char *cause; + u_int adjust, sx, sy; + int xpixel = -1, ypixel = -1; + + if (args->argc == 0) + adjust = 1; + else { + adjust = strtonum(args->argv[0], 1, INT_MAX, &errstr); + if (errstr != NULL) { + cmdq_error(item, "adjustment %s", errstr); + return (CMD_RETURN_ERROR); + } + } + + sx = w->sx; + sy = w->sy; + + if (args_has(args, 'x')) { + sx = args_strtonum(args, 'x', WINDOW_MINIMUM, WINDOW_MAXIMUM, + &cause); + if (cause != NULL) { + cmdq_error(item, "width %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + if (args_has(args, 'y')) { + sy = args_strtonum(args, 'y', WINDOW_MINIMUM, WINDOW_MAXIMUM, + &cause); + if (cause != NULL) { + cmdq_error(item, "height %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + } + + if (args_has(args, 'L')) { + if (sx >= adjust) + sx -= adjust; + } else if (args_has(args, 'R')) + sx += adjust; + else if (args_has(args, 'U')) { + if (sy >= adjust) + sy -= adjust; + } else if (args_has(args, 'D')) + sy += adjust; + + if (args_has(args, 'A')) { + default_window_size(NULL, s, w, &sx, &sy, &xpixel, &ypixel, + WINDOW_SIZE_LARGEST); + } else if (args_has(args, 'a')) { + default_window_size(NULL, s, w, &sx, &sy, &xpixel, &ypixel, + WINDOW_SIZE_SMALLEST); + } + + options_set_number(w->options, "window-size", WINDOW_SIZE_MANUAL); + resize_window(w, sx, sy, xpixel, ypixel); + + return (CMD_RETURN_NORMAL); +} diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index 3d78c495ed..5e23fa15c7 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -20,7 +20,7 @@ #include #include -#include +#include #include "tmux.h" @@ -34,9 +34,9 @@ const struct cmd_entry cmd_respawn_pane_entry = { .name = "respawn-pane", .alias = "respawnp", - .args = { "c:kt:", 0, -1 }, - .usage = "[-c start-directory] [-k] " CMD_TARGET_PANE_USAGE - " [command]", + .args = { "c:e:kt:", 0, -1 }, + .usage = "[-k] [-c start-directory] [-e environment] " + CMD_TARGET_PANE_USAGE " [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -48,53 +48,49 @@ 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 *w = wl->window; struct window_pane *wp = item->target.wp; - struct client *c = cmd_find_client(item, NULL, 1); - struct session *s = item->target.s; - struct environ *env; - const char *path = NULL, *cp; - char *cause, *cwd = NULL; - u_int idx; - struct environ_entry *envent; - - if (!args_has(self->args, 'k') && wp->fd != -1) { - if (window_pane_index(wp, &idx) != 0) - fatalx("index not found"); - cmdq_error(item, "pane still active: %s:%d.%u", - s->name, wl->idx, idx); - return (CMD_RETURN_ERROR); + char *cause = NULL; + const char *add; + struct args_value *value; + + 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; + sc.environ = environ_create(); + + add = args_first_value(args, 'e', &value); + while (add != NULL) { + environ_put(sc.environ, add); + add = args_next_value(&value); } - window_pane_reset_mode(wp); - screen_reinit(&wp->base); - input_init(wp); - - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; + sc.idx = -1; + sc.cwd = args_get(args, 'c'); - if ((cp = args_get(args, 'c')) != NULL) - cwd = format_single(item, cp, c, s, NULL, NULL); + sc.flags = SPAWN_RESPAWN; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; - env = environ_for_session(s, 0); - if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, cwd, env, - s->tio, &cause) != 0) { + if (spawn_pane(&sc, &cause) == NULL) { cmdq_error(item, "respawn pane failed: %s", cause); free(cause); - environ_free(env); - free(cwd); return (CMD_RETURN_ERROR); } - environ_free(env); - free(cwd); wp->flags |= PANE_REDRAW; - server_status_window(w); + server_status_window(wp->window); + environ_free(sc.environ); return (CMD_RETURN_NORMAL); } diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c index a1e2611779..497e401eea 100644 --- a/cmd-respawn-window.c +++ b/cmd-respawn-window.c @@ -19,7 +19,7 @@ #include #include -#include +#include #include "tmux.h" @@ -34,9 +34,9 @@ const struct cmd_entry cmd_respawn_window_entry = { .name = "respawn-window", .alias = "respawnw", - .args = { "c:kt:", 0, -1 }, - .usage = "[-c start-directory] [-k] " CMD_TARGET_WINDOW_USAGE - " [command]", + .args = { "c:e:kt:", 0, -1 }, + .usage = "[-k] [-c start-directory] [-e environment] " + CMD_TARGET_WINDOW_USAGE " [command]", .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -48,64 +48,45 @@ 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 window *w = wl->window; - struct window_pane *wp; - struct client *c = cmd_find_client(item, NULL, 1); - struct environ *env; - const char *path = NULL, *cp; - char *cause, *cwd = NULL; - struct environ_entry *envent; - - if (!args_has(self->args, 'k')) { - TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp->fd == -1) - continue; - cmdq_error(item, "window still active: %s:%d", s->name, - wl->idx); - return (CMD_RETURN_ERROR); - } + char *cause = NULL; + const char *add; + struct args_value *value; + + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.wl = wl; + sc.c = cmd_find_client(item, NULL, 1); + + sc.name = NULL; + sc.argc = args->argc; + sc.argv = args->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); } - wp = TAILQ_FIRST(&w->panes); - TAILQ_REMOVE(&w->panes, wp, entry); - layout_free(w); - window_destroy_panes(w); - TAILQ_INSERT_HEAD(&w->panes, wp, entry); - window_pane_resize(wp, w->sx, w->sy); - - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; - - if ((cp = args_get(args, 'c')) != NULL) - cwd = format_single(item, cp, c, s, NULL, NULL); - - env = environ_for_session(s, 0); - if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, cwd, env, - s->tio, &cause) != 0) { + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = SPAWN_RESPAWN; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; + + if (spawn_window(&sc, &cause) == NULL) { cmdq_error(item, "respawn window failed: %s", cause); free(cause); - environ_free(env); - free(cwd); - server_destroy_pane(wp, 0); return (CMD_RETURN_ERROR); } - environ_free(env); - free(cwd); - - layout_init(w, wp); - window_pane_reset_mode(wp); - screen_reinit(&wp->base); - input_init(wp); - window_set_active_pane(w, wp); - recalculate_sizes(); - server_redraw_window(w); + server_redraw_window(wl->window); + environ_free(sc.environ); return (CMD_RETURN_NORMAL); } diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index 5a900a1341..cc66116390 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -31,8 +31,8 @@ const struct cmd_entry cmd_rotate_window_entry = { .name = "rotate-window", .alias = "rotatew", - .args = { "Dt:U", 0, 0 }, - .usage = "[-DU] " CMD_TARGET_WINDOW_USAGE, + .args = { "Dt:UZ", 0, 0 }, + .usage = "[-DUZ] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -50,7 +50,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) struct layout_cell *lc; u_int sx, sy, xoff, yoff; - server_unzoom_window(w); + window_push_zoom(w, args_has(self->args, 'Z')); if (args_has(self->args, 'D')) { wp = TAILQ_LAST(&w->panes, window_panes); @@ -77,9 +77,6 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) if ((wp = TAILQ_PREV(w->active, window_panes, entry)) == NULL) wp = TAILQ_LAST(&w->panes, window_panes); - window_set_active_pane(w, wp); - cmd_find_from_winlink_pane(current, wl, wp, 0); - server_redraw_window(w); } else { wp = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, wp, entry); @@ -105,10 +102,12 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) if ((wp = TAILQ_NEXT(w->active, entry)) == NULL) wp = TAILQ_FIRST(&w->panes); - window_set_active_pane(w, wp); - cmd_find_from_winlink_pane(current, wl, wp, 0); - server_redraw_window(w); } + window_set_active_pane(w, wp, 1); + cmd_find_from_winlink_pane(current, wl, wp, 0); + window_pop_zoom(w); + server_redraw_window(w); + return (CMD_RETURN_NORMAL); } diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 0d73dd2b95..2f45f49256 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -57,9 +57,10 @@ struct cmd_run_shell_data { static void cmd_run_shell_print(struct job *job, const char *msg) { - struct cmd_run_shell_data *cdata = job->data; + struct cmd_run_shell_data *cdata = job_get_data(job); struct window_pane *wp = NULL; struct cmd_find_state fs; + struct window_mode_entry *wme; if (cdata->wp_id != -1) wp = window_pane_find_by_id(cdata->wp_id); @@ -75,10 +76,10 @@ cmd_run_shell_print(struct job *job, const char *msg) return; } - if (window_pane_set_mode(wp, &window_copy_mode, NULL, NULL) == 0) - window_copy_init_for_output(wp); - if (wp->mode == &window_copy_mode) - window_copy_add(wp, "%s", 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_copy_add(wp, "%s", msg); } static enum cmd_retval @@ -90,14 +91,6 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) struct session *s = item->target.s; struct winlink *wl = item->target.wl; struct window_pane *wp = item->target.wp; - const char *cwd; - - if (item->client != NULL && item->client->session == NULL) - cwd = item->client->cwd; - else if (s != NULL) - cwd = s->cwd; - else - cwd = NULL; cdata = xcalloc(1, sizeof *cdata); cdata->cmd = format_single(item, args->argv[0], c, s, wl, wp); @@ -110,8 +103,12 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) if (!args_has(args, 'b')) cdata->item = item; - job_run(cdata->cmd, s, cwd, NULL, cmd_run_shell_callback, - cmd_run_shell_free, cdata); + 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); + } if (args_has(args, 'b')) return (CMD_RETURN_NORMAL); @@ -121,22 +118,23 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) static void cmd_run_shell_callback(struct job *job) { - struct cmd_run_shell_data *cdata = job->data; - char *cmd = cdata->cmd, *msg, *line; + struct cmd_run_shell_data *cdata = job_get_data(job); + struct bufferevent *event = job_get_event(job); + char *cmd = cdata->cmd, *msg = NULL, *line; size_t size; - int retcode; + int retcode, status; do { - if ((line = evbuffer_readline(job->event->input)) != NULL) { + if ((line = evbuffer_readline(event->input)) != NULL) { cmd_run_shell_print(job, line); free(line); } } while (line != NULL); - size = EVBUFFER_LENGTH(job->event->input); + size = EVBUFFER_LENGTH(event->input); if (size != 0) { line = xmalloc(size + 1); - memcpy(line, EVBUFFER_DATA(job->event->input), size); + memcpy(line, EVBUFFER_DATA(event->input), size); line[size] = '\0'; cmd_run_shell_print(job, line); @@ -144,12 +142,12 @@ cmd_run_shell_callback(struct job *job) free(line); } - msg = NULL; - if (WIFEXITED(job->status)) { - if ((retcode = WEXITSTATUS(job->status)) != 0) + status = job_get_status(job); + if (WIFEXITED(status)) { + if ((retcode = WEXITSTATUS(status)) != 0) xasprintf(&msg, "'%s' returned %d", cmd, retcode); - } else if (WIFSIGNALED(job->status)) { - retcode = WTERMSIG(job->status); + } else if (WIFSIGNALED(status)) { + retcode = WTERMSIG(status); xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode); } if (msg != NULL) @@ -157,7 +155,7 @@ cmd_run_shell_callback(struct job *job) free(msg); if (cdata->item != NULL) - cdata->item->flags &= ~CMDQ_WAITING; + cmdq_continue(cdata->item); } static void diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c index 0a0dd52eef..3395612f40 100644 --- a/cmd-save-buffer.c +++ b/cmd-save-buffer.c @@ -59,11 +59,13 @@ static enum cmd_retval cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct client *c = item->client; + 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 paste_buffer *pb; - const char *path, *bufname, *bufdata, *start, *end; - const char *flags; - char *msg, *file; + const char *bufname, *bufdata, *start, *end, *flags; + char *msg, *path, *file; size_t size, used, msglen, bufsize; FILE *f; @@ -83,10 +85,12 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) bufdata = paste_buffer_data(pb, &bufsize); if (self->entry == &cmd_show_buffer_entry) - path = "-"; + path = xstrdup("-"); else - path = args->argv[0]; + path = format_single(item, args->argv[0], c, s, wl, wp); if (strcmp(path, "-") == 0) { + free(path); + c = item->client; if (c == NULL) { cmdq_error(item, "can't write to stdout"); return (CMD_RETURN_ERROR); @@ -100,7 +104,9 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item) if (args_has(self->args, 'a')) flags = "ab"; - file = server_client_get_path(c, path); + file = server_client_get_path(item->client, path); + free(path); + f = fopen(file, flags); if (f == NULL) { cmdq_error(item, "%s: %s", file, strerror(errno)); diff --git a/cmd-select-layout.c b/cmd-select-layout.c index e32f115de4..775d32c5f9 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -33,10 +33,10 @@ const struct cmd_entry cmd_select_layout_entry = { .name = "select-layout", .alias = "selectl", - .args = { "nopt:", 0, 1 }, - .usage = "[-nop] " CMD_TARGET_WINDOW_USAGE " [layout-name]", + .args = { "Enopt:", 0, 1 }, + .usage = "[-Enop] " CMD_TARGET_PANE_USAGE " [layout-name]", - .target = { 't', CMD_FIND_WINDOW, 0 }, + .target = { 't', CMD_FIND_PANE, 0 }, .flags = CMD_AFTERHOOK, .exec = cmd_select_layout_exec @@ -71,14 +71,14 @@ 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 window *w; - const char *layoutname; - char *oldlayout; - int next, previous, layout; - - w = wl->window; + struct args *args = self->args; + struct winlink *wl = item->target.wl; + struct window *w = wl->window; + struct window_pane *wp = item->target.wp; + const char *layoutname; + char *oldlayout; + int next, previous, layout; + server_unzoom_window(w); next = self->entry == &cmd_next_layout_entry; @@ -99,6 +99,11 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) goto changed; } + if (args_has(args, 'E')) { + layout_spread_out(wp); + goto changed; + } + if (!args_has(args, 'o')) { if (args->argc == 0) layout = w->lastlayout; @@ -130,7 +135,9 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) changed: free(oldlayout); + recalculate_sizes(); server_redraw_window(w); + notify_window("window-layout-changed", w); return (CMD_RETURN_NORMAL); error: diff --git a/cmd-select-pane.c b/cmd-select-pane.c index a7fb4428de..6542c919e4 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -18,6 +18,9 @@ #include +#include +#include + #include "tmux.h" /* @@ -30,8 +33,8 @@ const struct cmd_entry cmd_select_pane_entry = { .name = "select-pane", .alias = "selectp", - .args = { "DdegLlMmP:RT:t:U", 0, 0 }, - .usage = "[-DdegLlMmRU] [-P style] [-T title] " CMD_TARGET_PANE_USAGE, + .args = { "DdegLlMmP:RT:t:UZ", 0, 0 }, /* -P and -g deprecated */ + .usage = "[-DdeLlMmRUZ] [-T title] " CMD_TARGET_PANE_USAGE, .target = { 't', CMD_FIND_PANE, 0 }, @@ -43,8 +46,8 @@ const struct cmd_entry cmd_last_pane_entry = { .name = "last-pane", .alias = "lastp", - .args = { "det:", 0, 0 }, - .usage = "[-de] " CMD_TARGET_WINDOW_USAGE, + .args = { "det:Z", 0, 0 }, + .usage = "[-deZ] " CMD_TARGET_WINDOW_USAGE, .target = { 't', CMD_FIND_WINDOW, 0 }, @@ -52,19 +55,53 @@ const struct cmd_entry cmd_last_pane_entry = { .exec = cmd_select_pane_exec }; +static void +cmd_select_pane_redraw(struct window *w) +{ + struct client *c; + + /* + * Redraw entire window if it is bigger than the client (the + * offset may change), otherwise just draw borders. + */ + + TAILQ_FOREACH(c, &clients, entry) { + if (c->session == NULL || (c->flags & CLIENT_CONTROL)) + continue; + if (c->session->curw->window == w && tty_window_bigger(&c->tty)) + server_redraw_client(c); + else { + if (c->session->curw->window == w) + c->flags |= CLIENT_REDRAWBORDERS; + if (session_has(c->session, w)) + c->flags |= CLIENT_REDRAWSTATUS; + } + + } +} + 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 window *w = wl->window; struct session *s = item->target.s; struct window_pane *wp = item->target.wp, *lastwp, *markedwp; + char *pane_title; const char *style; + struct style *sy; + struct options_entry *o; if (self->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); + if (lastwp == NULL) + lastwp = TAILQ_NEXT(w->active, entry); + } if (lastwp == NULL) { cmdq_error(item, "no last pane"); return (CMD_RETURN_ERROR); @@ -74,13 +111,15 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) else if (args_has(self->args, 'd')) lastwp->flags |= PANE_INPUTOFF; else { - server_unzoom_window(w); + if (window_push_zoom(w, args_has(self->args, 'Z'))) + server_redraw_window(w); window_redraw_active_switch(w, lastwp); - if (window_set_active_pane(w, lastwp)) { + if (window_set_active_pane(w, lastwp, 1)) { cmd_find_from_winlink(current, wl, 0); - server_status_window(w); - server_redraw_window_borders(w); + cmd_select_pane_redraw(w); } + if (window_pop_zoom(w)) + server_redraw_window(w); } return (CMD_RETURN_NORMAL); } @@ -108,32 +147,40 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(self->args, 'P') || args_has(self->args, 'g')) { - if (args_has(args, 'P')) { - style = args_get(args, 'P'); - if (style_parse(&grid_default_cell, &wp->colgc, - style) == -1) { + 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); } - wp->flags |= PANE_REDRAW; + 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)); } - if (args_has(self->args, 'g')) - cmdq_print(item, "%s", style_tostring(&wp->colgc)); return (CMD_RETURN_NORMAL); } if (args_has(self->args, 'L')) { - server_unzoom_window(wp->window); + window_push_zoom(w, 1); wp = window_pane_find_left(wp); + window_pop_zoom(w); } else if (args_has(self->args, 'R')) { - server_unzoom_window(wp->window); + window_push_zoom(w, 1); wp = window_pane_find_right(wp); + window_pop_zoom(w); } else if (args_has(self->args, 'U')) { - server_unzoom_window(wp->window); + window_push_zoom(w, 1); wp = window_pane_find_up(wp); + window_pop_zoom(w); } else if (args_has(self->args, 'D')) { - server_unzoom_window(wp->window); + window_push_zoom(w, 1); wp = window_pane_find_down(wp); + window_pop_zoom(w); } if (wp == NULL) return (CMD_RETURN_NORMAL); @@ -148,24 +195,26 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) } if (args_has(self->args, 'T')) { - screen_set_title(&wp->base, args_get(self->args, 'T')); - server_status_window(wp->window); + pane_title = format_single(item, args_get(self->args, 'T'), + c, s, wl, wp); + screen_set_title(&wp->base, pane_title); + server_status_window(wp->window); + free(pane_title); + return (CMD_RETURN_NORMAL); } if (wp == w->active) return (CMD_RETURN_NORMAL); - server_unzoom_window(wp->window); - if (!window_pane_visible(wp)) { - cmdq_error(item, "pane not visible"); - return (CMD_RETURN_ERROR); - } + if (window_push_zoom(w, args_has(self->args, 'Z'))) + server_redraw_window(w); window_redraw_active_switch(w, wp); - if (window_set_active_pane(w, wp)) { + if (window_set_active_pane(w, wp, 1)) { cmd_find_from_winlink_pane(current, wl, wp, 0); - hooks_insert(s->hooks, item, current, "after-select-pane"); - server_status_window(w); - server_redraw_window_borders(w); + cmdq_insert_hook(s, item, current, "after-select-pane"); + cmd_select_pane_redraw(w); } + if (window_pop_zoom(w)) + server_redraw_window(w); return (CMD_RETURN_NORMAL); } diff --git a/cmd-select-window.c b/cmd-select-window.c index f35b82021f..54965e89e7 100644 --- a/cmd-select-window.c +++ b/cmd-select-window.c @@ -119,7 +119,7 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) } cmd_find_from_session(current, s, 0); server_redraw_session(s); - hooks_insert(s->hooks, item, current, "after-select-window"); + cmdq_insert_hook(s, item, current, "after-select-window"); } else { /* * If -T and select-window is invoked on same window as @@ -137,7 +137,7 @@ cmd_select_window_exec(struct cmd *self, struct cmdq_item *item) cmd_find_from_session(current, s, 0); server_redraw_session(s); } - hooks_insert(s->hooks, item, current, "after-select-window"); + cmdq_insert_hook(s, item, current, "after-select-window"); } recalculate_sizes(); diff --git a/cmd-send-keys.c b/cmd-send-keys.c index 8d2c26088a..ddbab6f781 100644 --- a/cmd-send-keys.c +++ b/cmd-send-keys.c @@ -33,8 +33,9 @@ const struct cmd_entry cmd_send_keys_entry = { .name = "send-keys", .alias = "send", - .args = { "lXRMN:t:", 0, -1 }, - .usage = "[-lXRM] [-N repeat-count] " CMD_TARGET_PANE_USAGE " key ...", + .args = { "FHlMN:Rt:X", 0, -1 }, + .usage = "[-FHlMRX] [-N repeat-count] " CMD_TARGET_PANE_USAGE + " key ...", .target = { 't', CMD_FIND_PANE, 0 }, @@ -55,45 +56,85 @@ const struct cmd_entry cmd_send_prefix_entry = { .exec = cmd_send_keys_exec }; -static void -cmd_send_keys_inject(struct client *c, struct cmdq_item *item, key_code key) +static struct cmdq_item * +cmd_send_keys_inject_key(struct client *c, struct cmd_find_state *fs, + struct cmdq_item *item, key_code key) { - struct window_pane *wp = item->target.wp; - struct session *s = item->target.s; - struct key_table *table; - struct key_binding *bd, bd_find; + struct window_mode_entry *wme; + struct key_table *table; + struct key_binding *bd; - if (wp->mode == NULL || wp->mode->key_table == NULL) { - if (options_get_number(wp->window->options, "xterm-keys")) + wme = TAILQ_FIRST(&fs->wp->modes); + if (wme == NULL || wme->mode->key_table == NULL) { + if (options_get_number(fs->wp->window->options, "xterm-keys")) key |= KEYC_XTERM; - window_pane_key(wp, NULL, s, key, NULL); - return; + window_pane_key(fs->wp, item->client, fs->s, fs->wl, key, NULL); + return (item); } - table = key_bindings_get_table(wp->mode->key_table(wp), 1); + table = key_bindings_get_table(wme->mode->key_table(wme), 1); - bd_find.key = (key & ~KEYC_XTERM); - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); + bd = key_bindings_get(table, key & ~KEYC_XTERM); if (bd != NULL) { table->references++; - key_bindings_dispatch(bd, item, c, NULL, &item->target); + item = key_bindings_dispatch(bd, item, c, NULL, &item->target); key_bindings_unref_table(table); } + return (item); } -static enum cmd_retval -cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) +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) { - 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 mouse_event *m = &item->shared->mouse; + const char *s = args->argv[i]; struct utf8_data *ud, *uc; wchar_t wc; - int i, literal; key_code key; - u_int np = 1; - char *cause = NULL; + char *endptr; + long n; + int literal; + + if (args_has(args, 'H')) { + 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)); + } + + literal = args_has(args, 'l'); + if (!literal) { + key = key_string_lookup_string(s); + if (key != KEYC_NONE && key != KEYC_UNKNOWN) + return (cmd_send_keys_inject_key(c, fs, item, key)); + 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); + } + free(ud); + } + return (item); +} + +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 window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + int i; + key_code key; + u_int np = 1; + char *cause = NULL; if (args_has(args, 'N')) { np = args_strtonum(args, 'N', 1, UINT_MAX, &cause); @@ -102,19 +143,23 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } - if (args_has(args, 'X') || args->argc == 0) - wp->modeprefix = np; + if (wme != NULL && (args_has(args, 'X') || args->argc == 0)) { + if (wme == NULL || wme->mode->command == NULL) { + cmdq_error(item, "not in a mode"); + return (CMD_RETURN_ERROR); + } + wme->prefix = np; + } } if (args_has(args, 'X')) { - if (wp->mode == NULL || wp->mode->command == NULL) { + if (wme == NULL || wme->mode->command == NULL) { cmdq_error(item, "not in a mode"); return (CMD_RETURN_ERROR); } if (!m->valid) - wp->mode->command(wp, c, s, args, NULL); - else - wp->mode->command(wp, c, s, args, m); + m = NULL; + wme->mode->command(wme, c, s, wl, args, m); return (CMD_RETURN_NORMAL); } @@ -124,7 +169,7 @@ 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, NULL, s, m->key, m); + window_pane_key(wp, item->client, s, wl, m->key, m); return (CMD_RETURN_NORMAL); } @@ -133,7 +178,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) key = options_get_number(s->options, "prefix2"); else key = options_get_number(s->options, "prefix"); - cmd_send_keys_inject(c, item, key); + cmd_send_keys_inject_key(c, fs, item, key); return (CMD_RETURN_NORMAL); } @@ -143,26 +188,8 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item) } for (; np != 0; np--) { - for (i = 0; i < args->argc; i++) { - literal = args_has(args, 'l'); - if (!literal) { - key = key_string_lookup_string(args->argv[i]); - if (key != KEYC_NONE && key != KEYC_UNKNOWN) - cmd_send_keys_inject(c, item, key); - else - literal = 1; - } - if (literal) { - ud = utf8_fromcstr(args->argv[i]); - for (uc = ud; uc->size != 0; uc++) { - if (utf8_combine(uc, &wc) != UTF8_DONE) - continue; - cmd_send_keys_inject(c, item, wc); - } - free(ud); - } - } - + for (i = 0; i < args->argc; i++) + item = cmd_send_keys_inject_string(c, fs, item, args, i); } return (CMD_RETURN_NORMAL); diff --git a/cmd-set-hook.c b/cmd-set-hook.c deleted file mode 100644 index d0cd98db5e..0000000000 --- a/cmd-set-hook.c +++ /dev/null @@ -1,130 +0,0 @@ -/* $OpenBSD$ */ - -/* - * Copyright (c) 2012 Thomas Adam - * - * 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" - -/* - * Set or show global or session hooks. - */ - -static enum cmd_retval cmd_set_hook_exec(struct cmd *, struct cmdq_item *); - -const struct cmd_entry cmd_set_hook_entry = { - .name = "set-hook", - .alias = NULL, - - .args = { "gt:u", 1, 2 }, - .usage = "[-gu] " CMD_TARGET_SESSION_USAGE " hook-name [command]", - - .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, - - .flags = CMD_AFTERHOOK, - .exec = cmd_set_hook_exec -}; - -const struct cmd_entry cmd_show_hooks_entry = { - .name = "show-hooks", - .alias = NULL, - - .args = { "gt:", 0, 1 }, - .usage = "[-g] " CMD_TARGET_SESSION_USAGE, - - .target = { 't', CMD_FIND_SESSION, 0 }, - - .flags = CMD_AFTERHOOK, - .exec = cmd_set_hook_exec -}; - -static enum cmd_retval -cmd_set_hook_exec(struct cmd *self, struct cmdq_item *item) -{ - struct args *args = self->args; - struct cmd_list *cmdlist; - struct hooks *hooks; - struct hook *hook; - char *cause, *tmp; - const char *name, *cmd, *target; - - if (args_has(args, 'g')) - hooks = global_hooks; - else { - if (item->target.s == NULL) { - target = args_get(args, 't'); - if (target != NULL) - cmdq_error(item, "no such session: %s", target); - else - cmdq_error(item, "no current session"); - return (CMD_RETURN_ERROR); - } - hooks = item->target.s->hooks; - } - - if (self->entry == &cmd_show_hooks_entry) { - hook = hooks_first(hooks); - while (hook != NULL) { - tmp = cmd_list_print(hook->cmdlist); - cmdq_print(item, "%s -> %s", hook->name, tmp); - free(tmp); - - hook = hooks_next(hook); - } - return (CMD_RETURN_NORMAL); - } - - name = args->argv[0]; - if (*name == '\0') { - cmdq_error(item, "invalid hook name"); - return (CMD_RETURN_ERROR); - } - if (args->argc < 2) - cmd = NULL; - else - cmd = args->argv[1]; - - if (args_has(args, 'u')) { - if (cmd != NULL) { - cmdq_error(item, "command passed to unset hook: %s", - name); - return (CMD_RETURN_ERROR); - } - hooks_remove(hooks, name); - return (CMD_RETURN_NORMAL); - } - - if (cmd == NULL) { - cmdq_error(item, "no command to set hook: %s", name); - return (CMD_RETURN_ERROR); - } - cmdlist = cmd_string_parse(cmd, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL) { - cmdq_error(item, "%s", cause); - free(cause); - } - return (CMD_RETURN_ERROR); - } - hooks_add(hooks, name, cmdlist); - cmd_list_free(cmdlist); - - return (CMD_RETURN_NORMAL); -} diff --git a/cmd-set-option.c b/cmd-set-option.c index d1ec6fcf31..23b45230ab 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -42,10 +43,10 @@ const struct cmd_entry cmd_set_option_entry = { .name = "set-option", .alias = "set", - .args = { "aFgoqst:uw", 1, 2 }, - .usage = "[-aFgosquw] [-t target-window] option [value]", + .args = { "aFgopqst:uw", 1, 2 }, + .usage = "[-aFgopqsuw] " CMD_TARGET_PANE_USAGE " option [value]", - .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = CMD_AFTERHOOK, .exec = cmd_set_option_exec @@ -64,6 +65,19 @@ const struct cmd_entry cmd_set_window_option_entry = { .exec = cmd_set_option_exec }; +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]", + + .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_set_option_exec +}; + static enum cmd_retval cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) { @@ -74,17 +88,27 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) struct session *s = fs->s; struct winlink *wl = fs->wl; struct window *w; - enum options_table_scope scope; + struct window_pane *wp; struct options *oo; struct options_entry *parent, *o; char *name, *argument, *value = NULL, *cause; - const char *target; int window, idx, already, error, ambiguous; + int scope; + struct style *sy; + + window = (self->entry == &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); + /* If set-hook -R, fire the hook straight away. */ + if (self->entry == &cmd_set_hook_entry && args_has(args, 'R')) { + notify_hook(item, argument); + free(argument); + return (CMD_RETURN_NORMAL); + } + /* Parse option name and index. */ name = options_match(argument, &idx, &ambiguous); if (name == NULL) { @@ -103,25 +127,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) else value = xstrdup(args->argv[1]); - /* - * Figure out the scope: for user options it comes from the arguments, - * otherwise from the option name. - */ - if (*name == '@') { - window = (self->entry == &cmd_set_window_option_entry); - scope = options_scope_from_flags(args, window, fs, &oo, &cause); - } else { - if (options_get_only(global_options, name) != NULL) - scope = OPTIONS_TABLE_SERVER; - else if (options_get_only(global_s_options, name) != NULL) - scope = OPTIONS_TABLE_SESSION; - else if (options_get_only(global_w_options, name) != NULL) - scope = OPTIONS_TABLE_WINDOW; - else { - scope = OPTIONS_TABLE_NONE; - xasprintf(&cause, "unknown option: %s", argument); - } - } + /* Get the scope and table for the option .*/ + scope = options_scope_from_name(args, window, name, fs, &oo, &cause); if (scope == OPTIONS_TABLE_NONE) { if (args_has(args, 'q')) goto out; @@ -129,44 +136,13 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) free(cause); goto fail; } - - /* Which table should this option go into? */ - if (scope == OPTIONS_TABLE_SERVER) - oo = global_options; - else if (scope == OPTIONS_TABLE_SESSION) { - if (args_has(self->args, 'g')) - oo = global_s_options; - else if (s == NULL) { - target = args_get(args, 't'); - if (target != NULL) - cmdq_error(item, "no such session: %s", target); - else - cmdq_error(item, "no current session"); - goto fail; - } else - oo = s->options; - } else if (scope == OPTIONS_TABLE_WINDOW) { - if (args_has(self->args, 'g')) - oo = global_w_options; - else if (wl == NULL) { - target = args_get(args, 't'); - if (target != NULL) - cmdq_error(item, "no such window: %s", target); - else - cmdq_error(item, "no current window"); - goto fail; - } else - oo = wl->window->options; - } o = options_get_only(oo, name); parent = options_get(oo, name); /* Check that array options and indexes match up. */ - if (idx != -1) { - if (*name == '@' || options_array_size(parent, NULL) == -1) { - cmdq_error(item, "not an array: %s", argument); - goto fail; - } + if (idx != -1 && (*name == '@' || !options_isarray(parent))) { + cmdq_error(item, "not an array: %s", argument); + goto fail; } /* With -o, check this option is not already set. */ @@ -192,21 +168,26 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) if (o == NULL) goto out; if (idx == -1) { - if (oo == global_options || + 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 - options_array_set(o, idx, NULL, 0); + } else if (options_array_set(o, idx, NULL, 0, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + goto fail; + } } else if (*name == '@') { if (value == NULL) { cmdq_error(item, "empty value"); goto fail; } options_set_string(oo, name, append, "%s", value); - } else if (idx == -1 && options_array_size(parent, NULL) == -1) { + } else if (idx == -1 && !options_isarray(parent)) { error = cmd_set_option_set(self, item, oo, parent, value); if (error != 0) goto fail; @@ -220,9 +201,15 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) if (idx == -1) { if (!append) options_array_clear(o); - options_array_assign(o, value); - } else if (options_array_set(o, idx, value, append) != 0) { - cmdq_error(item, "invalid index: %s", argument); + if (options_array_assign(o, value, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + goto fail; + } + } else if (options_array_set(o, idx, value, append, + &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); goto fail; } } @@ -246,6 +233,16 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) 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(); @@ -253,15 +250,15 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) alerts_reset_all(); if (strcmp(name, "window-style") == 0 || strcmp(name, "window-active-style") == 0) { - RB_FOREACH(w, windows, &windows) - w->flags |= WINDOW_STYLECHANGED; + 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, w->sx, w->sy); + layout_fix_panes(w); } RB_FOREACH(s, sessions, &sessions) - status_update_saved(s); + status_update_cache(s); /* * Update sizes and redraw. May not always be necessary but do it @@ -295,7 +292,8 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, int append = args_has(args, 'a'); struct options_entry *o; long long number; - const char *errstr; + const char *errstr, *new; + char *old; key_code key; oe = options_table_entry(parent); @@ -308,7 +306,16 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, 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); @@ -331,16 +338,7 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, cmdq_error(item, "bad colour: %s", value); return (-1); } - o = options_set_number(oo, oe->name, number); - options_style_update_new(oo, o); - return (0); - case OPTIONS_TABLE_ATTRIBUTES: - if ((number = attributes_fromstring(value)) == -1) { - cmdq_error(item, "bad attributes: %s", value); - return (-1); - } - o = options_set_number(oo, oe->name, number); - options_style_update_new(oo, o); + options_set_number(oo, oe->name, number); return (0); case OPTIONS_TABLE_FLAG: return (cmd_set_option_flag(item, oe, oo, value)); @@ -352,9 +350,8 @@ cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, cmdq_error(item, "bad style: %s", value); return (-1); } - options_style_update_old(oo, o); return (0); - case OPTIONS_TABLE_ARRAY: + case OPTIONS_TABLE_COMMAND: break; } return (-1); diff --git a/cmd-show-messages.c b/cmd-show-messages.c index 21511b48b7..8da12374cb 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -43,7 +43,6 @@ const struct cmd_entry cmd_show_messages_entry = { }; static int cmd_show_messages_terminals(struct cmdq_item *, int); -static int cmd_show_messages_jobs(struct cmdq_item *, int); static int cmd_show_messages_terminals(struct cmdq_item *item, int blank) @@ -66,25 +65,6 @@ cmd_show_messages_terminals(struct cmdq_item *item, int blank) return (n != 0); } -static int -cmd_show_messages_jobs(struct cmdq_item *item, int blank) -{ - struct job *job; - u_int n; - - n = 0; - LIST_FOREACH(job, &all_jobs, entry) { - if (blank) { - cmdq_print(item, "%s", ""); - blank = 0; - } - cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", - n, job->cmd, job->fd, (long)job->pid, job->status); - n++; - } - return (n != 0); -} - static enum cmd_retval cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) { @@ -103,7 +83,7 @@ cmd_show_messages_exec(struct cmd *self, struct cmdq_item *item) done = 1; } if (args_has(args, 'J')) { - cmd_show_messages_jobs(item, blank); + job_print_summary(item, blank); done = 1; } if (done) diff --git a/cmd-show-options.c b/cmd-show-options.c index 2dc3dee3b8..da4811397d 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -29,19 +29,19 @@ static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *); -static enum cmd_retval cmd_show_options_one(struct cmd *, struct cmdq_item *, - struct options *); +static void cmd_show_options_print(struct cmd *, struct cmdq_item *, + struct options_entry *, int, int); static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *, - struct options *); + int, struct options *); const struct cmd_entry cmd_show_options_entry = { .name = "show-options", .alias = "show", - .args = { "gqst:vw", 0, 1 }, - .usage = "[-gqsvw] [-t target-session|target-window] [option]", + .args = { "AgHpqst:vw", 0, 1 }, + .usage = "[-AgHpqsvw] " CMD_TARGET_PANE_USAGE " [option]", - .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, + .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = CMD_AFTERHOOK, .exec = cmd_show_options_exec @@ -60,131 +60,193 @@ const struct cmd_entry cmd_show_window_options_entry = { .exec = cmd_show_options_exec }; +const struct cmd_entry cmd_show_hooks_entry = { + .name = "show-hooks", + .alias = NULL, + + .args = { "gt:", 0, 1 }, + .usage = "[-g] " CMD_TARGET_SESSION_USAGE, + + .target = { 't', CMD_FIND_SESSION, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_show_options_exec +}; + 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 options *oo; - enum options_table_scope scope; - char *cause; - int window; + char *argument, *name = NULL, *cause; + int window, idx, ambiguous, parent, scope; + struct options_entry *o; window = (self->entry == &cmd_show_window_options_entry); - scope = options_scope_from_flags(args, window, fs, &oo, &cause); + + if (args->argc == 0) { + scope = options_scope_from_flags(args, window, fs, &oo, &cause); + if (scope == OPTIONS_TABLE_NONE) { + if (args_has(args, 'q')) + return (CMD_RETURN_NORMAL); + cmdq_error(item, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + return (cmd_show_options_all(self, item, scope, oo)); + } + argument = format_single(item, args->argv[0], c, s, wl, NULL); + + name = options_match(argument, &idx, &ambiguous); + if (name == NULL) { + if (args_has(args, 'q')) + goto fail; + if (ambiguous) + cmdq_error(item, "ambiguous option: %s", argument); + else + cmdq_error(item, "invalid option: %s", argument); + goto fail; + } + scope = options_scope_from_name(args, window, name, fs, &oo, &cause); if (scope == OPTIONS_TABLE_NONE) { + if (args_has(args, 'q')) + goto fail; cmdq_error(item, "%s", cause); free(cause); - return (CMD_RETURN_ERROR); + goto fail; } + o = options_get_only(oo, name); + if (args_has(args, 'A') && o == NULL) { + o = options_get(oo, name); + parent = 1; + } else + parent = 0; + if (o != NULL) + cmd_show_options_print(self, item, o, idx, parent); + + free(name); + free(argument); + return (CMD_RETURN_NORMAL); - if (args->argc == 0) - return (cmd_show_options_all(self, item, oo)); - else - return (cmd_show_options_one(self, item, oo)); +fail: + free(name); + free(argument); + return (CMD_RETURN_ERROR); } static void cmd_show_options_print(struct cmd *self, struct cmdq_item *item, - struct options_entry *o, int idx) + struct options_entry *o, int idx, int parent) { - const char *name; - const char *value; - char *tmp, *escaped; - u_int size, i; + struct options_array_item *a; + const char *name = options_name(o); + char *value, *tmp = NULL, *escaped; if (idx != -1) { - xasprintf(&tmp, "%s[%d]", options_name(o), idx); + xasprintf(&tmp, "%s[%d]", name, idx); name = tmp; } else { - if (options_array_size(o, &size) != -1) { - for (i = 0; i < size; i++) { - if (options_array_get(o, i) == NULL) - continue; - cmd_show_options_print(self, item, o, i); + if (options_isarray(o)) { + a = options_array_first(o); + if (a == NULL) { + if (!args_has(self->args, 'v')) + cmdq_print(item, "%s", name); + return; + } + while (a != NULL) { + idx = options_array_item_index(a); + cmd_show_options_print(self, item, o, idx, + parent); + a = options_array_next(a); } return; } - tmp = NULL; - name = options_name(o); } value = options_tostring(o, idx, 0); if (args_has(self->args, 'v')) cmdq_print(item, "%s", value); else if (options_isstring(o)) { - utf8_stravis(&escaped, value, VIS_OCTAL|VIS_TAB|VIS_NL|VIS_DQ); - cmdq_print(item, "%s \"%s\"", name, escaped); + escaped = args_escape(value); + if (parent) + cmdq_print(item, "%s* %s", name, escaped); + else + cmdq_print(item, "%s %s", name, escaped); free(escaped); - } else - cmdq_print(item, "%s %s", name, value); + } else { + if (parent) + cmdq_print(item, "%s* %s", name, value); + else + cmdq_print(item, "%s %s", name, value); + } + free(value); free(tmp); } static enum cmd_retval -cmd_show_options_one(struct cmd *self, struct cmdq_item *item, +cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, struct options *oo) { - 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 options_entry *o; - int idx, ambiguous; - char *name; - - name = format_single(item, args->argv[0], c, s, wl, NULL); - o = options_match_get(oo, name, &idx, 1, &ambiguous); - if (o == NULL) { - if (args_has(args, 'q')) { - free(name); - return (CMD_RETURN_NORMAL); - } - if (ambiguous) { - cmdq_error(item, "ambiguous option: %s", name); - free(name); - return (CMD_RETURN_ERROR); - } - if (*name != '@' && - options_match_get(oo, name, &idx, 0, &ambiguous) != NULL) { - free(name); - return (CMD_RETURN_NORMAL); - } - cmdq_error(item, "unknown option: %s", name); - free(name); - return (CMD_RETURN_ERROR); - } - cmd_show_options_print(self, item, o, idx); - free(name); - return (CMD_RETURN_NORMAL); -} - -static enum cmd_retval -cmd_show_options_all(struct cmd *self, struct cmdq_item *item, - struct options *oo) -{ - struct options_entry *o; const struct options_table_entry *oe; - u_int size, idx; + struct options_entry *o; + struct options_array_item *a; + const char *name; + u_int idx; + int parent; o = options_first(oo); while (o != NULL) { - oe = options_table_entry(o); - if (oe != NULL && oe->style != NULL) { - o = options_next(o); + 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 (options_array_size(o, &size) == -1) - cmd_show_options_print(self, item, o, -1); - else { - for (idx = 0; idx < size; idx++) { - if (options_array_get(o, idx) == NULL) - continue; - cmd_show_options_print(self, item, o, idx); + + if ((self->entry != &cmd_show_hooks_entry && + !args_has(self->args, 'H') && + oe != NULL && + (oe->flags & OPTIONS_TABLE_IS_HOOK)) || + (self->entry == &cmd_show_hooks_entry && + (oe == NULL || + (~oe->flags & OPTIONS_TABLE_IS_HOOK)))) + continue; + + o = options_get_only(oo, oe->name); + if (o == NULL) { + if (!args_has(self->args, 'A')) + continue; + o = options_get(oo, oe->name); + if (o == NULL) + continue; + parent = 1; + } else + parent = 0; + + if (!options_isarray(o)) + cmd_show_options_print(self, item, o, -1, parent); + else if ((a = options_array_first(o)) == NULL) { + if (!args_has(self->args, 'v')) { + name = options_name(o); + if (parent) + cmdq_print(item, "%s*", name); + else + cmdq_print(item, "%s", name); + } + } else { + while (a != NULL) { + idx = options_array_item_index(a); + cmd_show_options_print(self, item, o, idx, + parent); + a = options_array_next(a); } } - o = options_next(o); } return (CMD_RETURN_NORMAL); } diff --git a/cmd-source-file.c b/cmd-source-file.c index e96af5f1d9..2e01ae6725 100644 --- a/cmd-source-file.c +++ b/cmd-source-file.c @@ -37,8 +37,8 @@ const struct cmd_entry cmd_source_file_entry = { .name = "source-file", .alias = "source", - .args = { "q", 1, 1 }, - .usage = "[-q] path", + .args = { "nqv", 1, -1 }, + .usage = "[-nqv] path ...", .flags = 0, .exec = cmd_source_file_exec @@ -48,45 +48,62 @@ static enum cmd_retval cmd_source_file_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - int quiet = args_has(args, 'q'); + int flags = 0; struct client *c = item->client; - struct cmdq_item *new_item; + struct cmdq_item *new_item, *after; enum cmd_retval retval; - char *pattern, *tmp; - const char *path = args->argv[0]; + char *pattern, *cwd; + const char *path, *error; glob_t g; - u_int i; - - if (*path == '/') - pattern = xstrdup(path); - else { - utf8_stravis(&tmp, server_client_get_cwd(c), VIS_GLOB); - xasprintf(&pattern, "%s/%s", tmp, path); - free(tmp); - } - log_debug("%s: %s", __func__, pattern); + int i; + u_int j; + + if (args_has(args, 'q')) + flags |= CMD_PARSE_QUIET; + if (args_has(args, 'n')) + flags |= CMD_PARSE_PARSEONLY; + if (args_has(args, 'v')) + flags |= CMD_PARSE_VERBOSE; + utf8_stravis(&cwd, server_client_get_cwd(c, NULL), VIS_GLOB); retval = CMD_RETURN_NORMAL; - if (glob(pattern, 0, NULL, &g) != 0) { - if (!quiet || errno != ENOENT) { - cmdq_error(item, "%s: %s", path, strerror(errno)); - retval = CMD_RETURN_ERROR; + for (i = 0; i < args->argc; i++) { + path = args->argv[i]; + if (*path == '/') + pattern = xstrdup(path); + else + xasprintf(&pattern, "%s/%s", cwd, path); + log_debug("%s: %s", __func__, pattern); + + if (glob(pattern, 0, NULL, &g) != 0) { + error = strerror(errno); + if (errno != ENOENT || (~flags & CMD_PARSE_QUIET)) { + cmdq_error(item, "%s: %s", path, error); + retval = CMD_RETURN_ERROR; + } + free(pattern); + continue; } free(pattern); - return (retval); - } - free(pattern); - for (i = 0; i < (u_int)g.gl_pathc; i++) { - if (load_cfg(g.gl_pathv[i], c, item, quiet) < 0) - retval = CMD_RETURN_ERROR; + after = item; + for (j = 0; j < g.gl_pathc; j++) { + path = g.gl_pathv[j]; + if (load_cfg(path, c, after, flags, &new_item) < 0) + retval = CMD_RETURN_ERROR; + else if (new_item != NULL) + after = new_item; + } + globfree(&g); } if (cfg_finished) { + if (retval == CMD_RETURN_ERROR && c->session == NULL) + c->retval = 1; new_item = cmdq_get_callback(cmd_source_file_done, NULL); cmdq_insert_after(item, new_item); } - globfree(&g); + free(cwd); return (retval); } diff --git a/cmd-split-window.c b/cmd-split-window.c index 7c1668302d..eaf1f74cf7 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,9 +39,9 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:dfF:l:hp:Pt:v", 0, -1 }, - .usage = "[-bdfhvP] [-c start-directory] [-F format] " - "[-p percentage|-l size] " CMD_TARGET_PANE_USAGE " [command]", + .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]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -52,115 +52,123 @@ const struct cmd_entry cmd_split_window_entry = { static enum cmd_retval cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { - struct cmd_find_state *current = &item->shared->current; 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 *w = wl->window; - struct window_pane *wp = item->target.wp, *new_wp = NULL; - struct environ *env; - const char *cmd, *path, *shell, *template, *cwd; - char **argv, *cause, *new_cause, *cp, *to_free = NULL; - u_int hlimit; - int argc, size, percentage; + struct window_pane *wp = item->target.wp, *new_wp; enum layout_type type; struct layout_cell *lc; - struct environ_entry *envent; - struct cmd_find_state fs; - - server_unzoom_window(w); - - if (args->argc == 0) { - cmd = options_get_string(s->options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } - } else { - argc = args->argc; - argv = args->argv; - } - - if (args_has(args, 'c')) { - cwd = args_get(args, 'c'); - to_free = format_single(item, cwd, c, s, NULL, NULL); - cwd = to_free; - } else if (item->client != NULL && item->client->session == NULL) - cwd = item->client->cwd; - else - cwd = s->cwd; + struct cmd_find_state fs; + int size, percentage, flags, input; + const char *template, *add, *errstr, *p; + char *cause, *cp, *copy; + size_t plen; + struct args_value *value; - type = LAYOUT_TOPBOTTOM; if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - - size = -1; - if (args_has(args, 'l')) { - size = args_strtonum(args, 'l', 0, INT_MAX, &cause); - if (cause != NULL) { - xasprintf(&new_cause, "size %s", cause); - free(cause); - cause = new_cause; - goto error; + else + type = LAYOUT_TOPBOTTOM; + 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 (type == LAYOUT_TOPBOTTOM) + size = (wp->sy * percentage) / 100; + else + size = (wp->sx * percentage) / 100; + } else { + size = args_strtonum(args, 'l', 0, INT_MAX, &cause); + if (cause != NULL) { + cmdq_error(item, "lines %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } } } else if (args_has(args, 'p')) { percentage = args_strtonum(args, 'p', 0, INT_MAX, &cause); if (cause != NULL) { - xasprintf(&new_cause, "percentage %s", cause); + cmdq_error(item, "create pane failed: -p %s", cause); free(cause); - cause = new_cause; - goto error; + return (CMD_RETURN_ERROR); } if (type == LAYOUT_TOPBOTTOM) size = (wp->sy * percentage) / 100; else size = (wp->sx * percentage) / 100; - } - hlimit = options_get_number(s->options, "history-limit"); + } else + size = -1; - shell = options_get_string(s->options, "default-shell"); - if (*shell == '\0' || areshell(shell)) - shell = _PATH_BSHELL; + server_unzoom_window(wp->window); + input = (args_has(args, 'I') && args->argc == 0); - lc = layout_split_pane(wp, type, size, args_has(args, 'b'), - args_has(args, 'f')); + 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')) + flags |= SPAWN_EMPTY; + + lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) { - cause = xstrdup("pane too small"); - goto error; + cmdq_error(item, "no space for new pane"); + return (CMD_RETURN_ERROR); } - new_wp = window_add_pane(w, wp, args_has(args, 'b'), hlimit); - layout_make_leaf(lc, new_wp); - path = NULL; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; - - env = environ_for_session(s, 0); - if (window_pane_spawn(new_wp, argc, argv, path, shell, cwd, env, - s->tio, &cause) != 0) { - environ_free(env); - goto error; + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.wl = wl; + + sc.wp0 = wp; + sc.lc = lc; + + sc.name = NULL; + sc.argc = args->argc; + sc.argv = args->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); } - environ_free(env); - layout_fix_panes(w, w->sx, w->sy); - server_redraw_window(w); + sc.idx = -1; + sc.cwd = args_get(args, 'c'); - if (!args_has(args, 'd')) { - window_set_active_pane(w, new_wp); - session_select(s, wl->idx); - cmd_find_from_session(current, s, 0); - server_redraw_session(s); - } else - server_status_session(s); + sc.flags = flags; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + + if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + layout_close_pane(new_wp); + cmdq_error(item, "create pane failed: %s", cause); + free(cause); + 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 (!args_has(args, 'd')) + cmd_find_from_winlink_pane(current, wl, new_wp, 0); + server_redraw_window(wp->window); + server_status_session(s); if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) @@ -169,22 +177,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) cmdq_print(item, "%s", cp); free(cp); } - notify_window("window-layout-changed", w); cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); - hooks_insert(s->hooks, item, &fs, "after-split-window"); + cmdq_insert_hook(s, item, &fs, "after-split-window"); - free(to_free); + environ_free(sc.environ); + if (input) + return (CMD_RETURN_WAIT); return (CMD_RETURN_NORMAL); - -error: - if (new_wp != NULL) { - layout_close_pane(new_wp); - window_remove_pane(w, new_wp); - } - cmdq_error(item, "create pane failed: %s", cause); - free(cause); - - free(to_free); - return (CMD_RETURN_ERROR); } diff --git a/cmd-string.c b/cmd-string.c deleted file mode 100644 index 9c8f10c15e..0000000000 --- a/cmd-string.c +++ /dev/null @@ -1,363 +0,0 @@ -/* $OpenBSD$ */ - -/* - * 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 -#include - -#include "tmux.h" - -/* - * Parse a command from a string. - */ - -static int cmd_string_getc(const char *, size_t *); -static void cmd_string_ungetc(size_t *); -static void cmd_string_copy(char **, char *, size_t *); -static char *cmd_string_string(const char *, size_t *, char, int); -static char *cmd_string_variable(const char *, size_t *); -static char *cmd_string_expand_tilde(const char *, size_t *); - -static int -cmd_string_getc(const char *s, size_t *p) -{ - const u_char *ucs = s; - - if (ucs[*p] == '\0') - return (EOF); - return (ucs[(*p)++]); -} - -static void -cmd_string_ungetc(size_t *p) -{ - (*p)--; -} - -int -cmd_string_split(const char *s, int *rargc, char ***rargv) -{ - size_t p = 0; - int ch, argc = 0, append = 0; - char **argv = NULL, *buf = NULL, *t; - const char *whitespace, *equals; - size_t len = 0; - - for (;;) { - ch = cmd_string_getc(s, &p); - switch (ch) { - case '\'': - if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL) - goto error; - cmd_string_copy(&buf, t, &len); - break; - case '"': - if ((t = cmd_string_string(s, &p, '"', 1)) == NULL) - goto error; - cmd_string_copy(&buf, t, &len); - break; - case '$': - if ((t = cmd_string_variable(s, &p)) == NULL) - goto error; - cmd_string_copy(&buf, t, &len); - break; - case '#': - /* Comment: discard rest of line. */ - while ((ch = cmd_string_getc(s, &p)) != EOF) - ; - /* FALLTHROUGH */ - case EOF: - case ' ': - case '\t': - if (buf != NULL) { - buf = xrealloc(buf, len + 1); - buf[len] = '\0'; - - argv = xreallocarray(argv, argc + 1, - sizeof *argv); - argv[argc++] = buf; - - buf = NULL; - len = 0; - } - - if (ch != EOF) - break; - - while (argc != 0) { - equals = strchr(argv[0], '='); - whitespace = argv[0] + strcspn(argv[0], " \t"); - if (equals == NULL || equals > whitespace) - break; - environ_put(global_environ, argv[0]); - argc--; - memmove(argv, argv + 1, argc * (sizeof *argv)); - } - goto done; - case '~': - if (buf != NULL) { - append = 1; - break; - } - t = cmd_string_expand_tilde(s, &p); - if (t == NULL) - goto error; - cmd_string_copy(&buf, t, &len); - break; - default: - append = 1; - break; - } - if (append) { - if (len >= SIZE_MAX - 2) - goto error; - buf = xrealloc(buf, len + 1); - buf[len++] = ch; - } - append = 0; - } - -done: - *rargc = argc; - *rargv = argv; - - free(buf); - return (0); - -error: - if (argv != NULL) - cmd_free_argv(argc, argv); - free(buf); - return (-1); -} - -struct cmd_list * -cmd_string_parse(const char *s, const char *file, u_int line, char **cause) -{ - struct cmd_list *cmdlist = NULL; - int argc; - char **argv; - - *cause = NULL; - if (cmd_string_split(s, &argc, &argv) != 0) { - xasprintf(cause, "invalid or unknown command: %s", s); - return (NULL); - } - if (argc != 0) { - cmdlist = cmd_list_parse(argc, argv, file, line, cause); - if (cmdlist == NULL) { - cmd_free_argv(argc, argv); - return (NULL); - } - } - cmd_free_argv(argc, argv); - return (cmdlist); -} - -static void -cmd_string_copy(char **dst, char *src, size_t *len) -{ - size_t srclen; - - srclen = strlen(src); - - *dst = xrealloc(*dst, *len + srclen + 1); - strlcpy(*dst + *len, src, srclen + 1); - - *len += srclen; - free(src); -} - -static char * -cmd_string_string(const char *s, size_t *p, char endch, int esc) -{ - int ch; - char *buf, *t; - size_t len; - - buf = NULL; - len = 0; - - while ((ch = cmd_string_getc(s, p)) != endch) { - switch (ch) { - case EOF: - goto error; - case '\\': - if (!esc) - break; - switch (ch = cmd_string_getc(s, p)) { - case EOF: - goto error; - case 'e': - ch = '\033'; - break; - case 'r': - ch = '\r'; - break; - case 'n': - ch = '\n'; - break; - case 't': - ch = '\t'; - break; - } - break; - case '$': - if (!esc) - break; - if ((t = cmd_string_variable(s, p)) == NULL) - goto error; - cmd_string_copy(&buf, t, &len); - continue; - } - - if (len >= SIZE_MAX - 2) - goto error; - buf = xrealloc(buf, len + 1); - buf[len++] = ch; - } - - buf = xrealloc(buf, len + 1); - buf[len] = '\0'; - return (buf); - -error: - free(buf); - return (NULL); -} - -static char * -cmd_string_variable(const char *s, size_t *p) -{ - int ch, fch; - char *buf, *t; - size_t len; - struct environ_entry *envent; - -#define cmd_string_first(ch) ((ch) == '_' || \ - ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z')) -#define cmd_string_other(ch) ((ch) == '_' || \ - ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \ - ((ch) >= '0' && (ch) <= '9')) - - buf = NULL; - len = 0; - - fch = EOF; - switch (ch = cmd_string_getc(s, p)) { - case EOF: - goto error; - case '{': - fch = '{'; - - ch = cmd_string_getc(s, p); - if (!cmd_string_first(ch)) - goto error; - /* FALLTHROUGH */ - default: - if (!cmd_string_first(ch)) { - xasprintf(&t, "$%c", ch); - return (t); - } - - buf = xrealloc(buf, len + 1); - buf[len++] = ch; - - for (;;) { - ch = cmd_string_getc(s, p); - if (ch == EOF || !cmd_string_other(ch)) - break; - else { - if (len >= SIZE_MAX - 3) - goto error; - buf = xrealloc(buf, len + 1); - buf[len++] = ch; - } - } - } - - if (fch == '{' && ch != '}') - goto error; - if (ch != EOF && fch != '{') - cmd_string_ungetc(p); /* ch */ - - buf = xrealloc(buf, len + 1); - buf[len] = '\0'; - - envent = environ_find(global_environ, buf); - free(buf); - if (envent == NULL) - return (xstrdup("")); - return (xstrdup(envent->value)); - -error: - free(buf); - return (NULL); -} - -static char * -cmd_string_expand_tilde(const char *s, size_t *p) -{ - struct passwd *pw; - struct environ_entry *envent; - char *home, *path, *user, *cp; - int last; - - home = NULL; - - last = cmd_string_getc(s, p); - if (last == EOF || last == '/' || last == ' '|| last == '\t') { - envent = environ_find(global_environ, "HOME"); - if (envent != NULL && *envent->value != '\0') - home = envent->value; - else if ((pw = getpwuid(getuid())) != NULL) - home = pw->pw_dir; - } else { - cmd_string_ungetc(p); - - cp = user = xmalloc(strlen(s)); - for (;;) { - last = cmd_string_getc(s, p); - if (last == EOF || - last == '/' || - last == ' '|| - last == '\t') - break; - *cp++ = last; - } - *cp = '\0'; - - if ((pw = getpwnam(user)) != NULL) - home = pw->pw_dir; - free(user); - } - - if (home == NULL) - return (NULL); - - if (last != EOF) - xasprintf(&path, "%s%c", home, last); - else - xasprintf(&path, "%s", home); - return (path); -} diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 7283bf53ab..3e0e6e600b 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -32,8 +32,8 @@ const struct cmd_entry cmd_swap_pane_entry = { .name = "swap-pane", .alias = "swapp", - .args = { "dDs:t:U", 0, 0 }, - .usage = "[-dDU] " CMD_SRCDST_PANE_USAGE, + .args = { "dDs:t:UZ", 0, 0 }, + .usage = "[-dDUZ] " CMD_SRCDST_PANE_USAGE, .source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED }, .target = { 't', CMD_FIND_PANE, 0 }, @@ -45,6 +45,7 @@ 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 window *src_w, *dst_w; struct window_pane *tmp_wp, *src_wp, *dst_wp; struct layout_cell *src_lc, *dst_lc; @@ -54,23 +55,27 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) dst_wp = item->target.wp; src_w = item->source.wl->window; src_wp = item->source.wp; - server_unzoom_window(dst_w); - if (args_has(self->args, 'D')) { + if (window_push_zoom(dst_w, args_has(args, 'Z'))) + server_redraw_window(dst_w); + + if (args_has(args, 'D')) { src_w = dst_w; src_wp = TAILQ_NEXT(dst_wp, entry); if (src_wp == NULL) src_wp = TAILQ_FIRST(&dst_w->panes); - } else if (args_has(self->args, 'U')) { + } else if (args_has(args, 'U')) { src_w = dst_w; src_wp = TAILQ_PREV(dst_wp, window_panes, entry); if (src_wp == NULL) src_wp = TAILQ_LAST(&dst_w->panes, window_panes); } - server_unzoom_window(src_w); + + if (src_w != dst_w && window_push_zoom(src_w, args_has(args, 'Z'))) + server_redraw_window(src_w); if (src_wp == dst_wp) - return (CMD_RETURN_NORMAL); + goto out; tmp_wp = TAILQ_PREV(dst_wp, window_panes, entry); TAILQ_REMOVE(&dst_w->panes, dst_wp, entry); @@ -90,7 +95,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) src_wp->layout_cell = dst_lc; src_wp->window = dst_w; + options_set_parent(src_wp->options, dst_w->options); + src_wp->flags |= PANE_STYLECHANGED; dst_wp->window = src_w; + options_set_parent(dst_wp->options, src_w->options); + dst_wp->flags |= PANE_STYLECHANGED; sx = src_wp->sx; sy = src_wp->sy; xoff = src_wp->xoff; yoff = src_wp->yoff; @@ -99,21 +108,19 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) dst_wp->xoff = xoff; dst_wp->yoff = yoff; window_pane_resize(dst_wp, sx, sy); - if (!args_has(self->args, 'd')) { + if (!args_has(args, 'd')) { if (src_w != dst_w) { - window_set_active_pane(src_w, dst_wp); - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(src_w, dst_wp, 1); + window_set_active_pane(dst_w, src_wp, 1); } else { tmp_wp = dst_wp; - if (!window_pane_visible(tmp_wp)) - tmp_wp = src_wp; - window_set_active_pane(src_w, tmp_wp); + window_set_active_pane(src_w, tmp_wp, 1); } } else { if (src_w->active == src_wp) - window_set_active_pane(src_w, dst_wp); + window_set_active_pane(src_w, dst_wp, 1); if (dst_w->active == dst_wp) - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(dst_w, src_wp, 1); } if (src_w != dst_w) { if (src_w->last == src_wp) @@ -124,5 +131,10 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) server_redraw_window(src_w); server_redraw_window(dst_w); +out: + if (window_pop_zoom(src_w)) + server_redraw_window(src_w); + if (src_w != dst_w && window_pop_zoom(dst_w)) + server_redraw_window(dst_w); return (CMD_RETURN_NORMAL); } diff --git a/cmd-swap-window.c b/cmd-swap-window.c index 39670e3c53..0c15479d56 100644 --- a/cmd-swap-window.c +++ b/cmd-swap-window.c @@ -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(self->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 180635df46..309a7e7c58 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_switch_client_entry = { .name = "switch-client", .alias = "switchc", - .args = { "lc:Enpt:rT:", 0, 0 }, - .usage = "[-Elnpr] [-c target-client] [-t target-session] " + .args = { "lc:Enpt:rT:Z", 0, 0 }, + .usage = "[-ElnprZ] [-c target-client] [-t target-session] " "[-T key-table]", /* -t is special */ @@ -54,6 +54,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) struct client *c; struct session *s; struct winlink *wl; + struct window *w; struct window_pane *wp; const char *tablename; struct key_table *table; @@ -61,7 +62,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) if ((c = cmd_find_client(item, args_get(args, 'c'), 0)) == NULL) return (CMD_RETURN_ERROR); - if (tflag != NULL && tflag[strcspn(tflag, ":.")] != '\0') { + if (tflag != NULL && tflag[strcspn(tflag, ":.%")] != '\0') { type = CMD_FIND_PANE; flags = 0; } else { @@ -72,6 +73,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); s = item->target.s; wl = item->target.wl; + w = wl->window; wp = item->target.wp; if (args_has(args, 'r')) @@ -112,9 +114,15 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) } else { if (item->client == NULL) return (CMD_RETURN_NORMAL); + if (wl != NULL && wp != NULL) { + if (window_push_zoom(w, args_has(self->args, 'Z'))) + server_redraw_window(w); + window_redraw_active_switch(w, wp); + window_set_active_pane(w, wp, 1); + if (window_pop_zoom(w)) + server_redraw_window(w); + } if (wl != NULL) { - if (wp != NULL) - window_set_active_pane(wp->window, wp); session_set_current(s, wl); cmd_find_from_session(&item->shared->current, s, 0); } @@ -128,6 +136,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) 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); diff --git a/cmd-wait-for.c b/cmd-wait-for.c index 396548a7f5..4f438a7f27 100644 --- a/cmd-wait-for.c +++ b/cmd-wait-for.c @@ -153,7 +153,7 @@ cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name, log_debug("signal wait channel %s, with waiters", wc->name); TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) { - wi->item->flags &= ~CMDQ_WAITING; + cmdq_continue(wi->item); TAILQ_REMOVE(&wc->waiters, wi, entry); free(wi); @@ -170,7 +170,7 @@ cmd_wait_for_wait(struct cmdq_item *item, const char *name, struct client *c = item->client; struct wait_item *wi; - if (c == NULL || c->session != NULL) { + if (c == NULL) { cmdq_error(item, "not able to wait"); return (CMD_RETURN_ERROR); } @@ -198,7 +198,7 @@ cmd_wait_for_lock(struct cmdq_item *item, const char *name, { struct wait_item *wi; - if (item->client == NULL || item->client->session != NULL) { + if (item->client == NULL) { cmdq_error(item, "not able to lock"); return (CMD_RETURN_ERROR); } @@ -229,7 +229,7 @@ cmd_wait_for_unlock(struct cmdq_item *item, const char *name, } if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) { - wi->item->flags &= ~CMDQ_WAITING; + cmdq_continue(wi->item); TAILQ_REMOVE(&wc->lockers, wi, entry); free(wi); } else { @@ -248,13 +248,13 @@ cmd_wait_for_flush(void) RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) { TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) { - wi->item->flags &= ~CMDQ_WAITING; + cmdq_continue(wi->item); TAILQ_REMOVE(&wc->waiters, wi, entry); free(wi); } wc->woken = 1; TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) { - wi->item->flags &= ~CMDQ_WAITING; + cmdq_continue(wi->item); TAILQ_REMOVE(&wc->lockers, wi, entry); free(wi); } diff --git a/cmd.c b/cmd.c index 45f83c2ceb..f77176c952 100644 --- a/cmd.c +++ b/cmd.c @@ -41,6 +41,7 @@ extern const struct cmd_entry cmd_confirm_before_entry; extern const struct cmd_entry cmd_copy_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_panes_entry; extern const struct cmd_entry cmd_down_pane_entry; @@ -80,6 +81,7 @@ extern const struct cmd_entry cmd_refresh_client_entry; extern const struct cmd_entry cmd_rename_session_entry; extern const struct cmd_entry cmd_rename_window_entry; extern const struct cmd_entry cmd_resize_pane_entry; +extern const struct cmd_entry cmd_resize_window_entry; extern const struct cmd_entry cmd_respawn_pane_entry; extern const struct cmd_entry cmd_respawn_window_entry; extern const struct cmd_entry cmd_rotate_window_entry; @@ -128,6 +130,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_copy_mode_entry, &cmd_delete_buffer_entry, &cmd_detach_client_entry, + &cmd_display_menu_entry, &cmd_display_message_entry, &cmd_display_panes_entry, &cmd_find_window_entry, @@ -166,6 +169,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_rename_session_entry, &cmd_rename_window_entry, &cmd_resize_pane_entry, + &cmd_resize_window_entry, &cmd_respawn_pane_entry, &cmd_respawn_window_entry, &cmd_rotate_window_entry, @@ -200,6 +204,47 @@ const struct cmd_entry *cmd_table[] = { NULL }; +static u_int cmd_list_next_group = 1; + +void printflike(3, 4) +cmd_log_argv(int argc, char **argv, const char *fmt, ...) +{ + char *prefix; + va_list ap; + int i; + + va_start(ap, fmt); + xvasprintf(&prefix, fmt, ap); + va_end(ap); + + for (i = 0; i < argc; i++) + log_debug("%s: argv[%d]=%s", prefix, i, argv[i]); + free(prefix); +} + +void +cmd_prepend_argv(int *argc, char ***argv, char *arg) +{ + char **new_argv; + int i; + + new_argv = xreallocarray(NULL, (*argc) + 1, sizeof *new_argv); + new_argv[0] = xstrdup(arg); + for (i = 0; i < *argc; i++) + new_argv[1 + i] = (*argv)[i]; + + free(*argv); + *argv = new_argv; + (*argc)++; +} + +void +cmd_append_argv(int *argc, char ***argv, char *arg) +{ + *argv = xreallocarray(*argv, (*argc) + 1, sizeof **argv); + (*argv)[(*argc)++] = xstrdup(arg); +} + int cmd_pack_argv(int argc, char **argv, char *buf, size_t len) { @@ -208,6 +253,7 @@ cmd_pack_argv(int argc, char **argv, char *buf, size_t len) if (argc == 0) return (0); + cmd_log_argv(argc, argv, "%s", __func__); *buf = '\0'; for (i = 0; i < argc; i++) { @@ -240,9 +286,11 @@ cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv) arglen = strlen(buf) + 1; (*argv)[i] = xstrdup(buf); + buf += arglen; len -= arglen; } + cmd_log_argv(argc, *argv, "%s", __func__); return (0); } @@ -301,106 +349,103 @@ cmd_stringify_argv(int argc, char **argv) return (buf); } -static int -cmd_try_alias(int *argc, char ***argv) +char * +cmd_get_alias(const char *name) { - struct options_entry *o; - int old_argc = *argc, new_argc; - char **old_argv = *argv, **new_argv; - u_int size, idx; - int i; - size_t wanted; - const char *s, *cp = NULL; + struct options_entry *o; + struct options_array_item *a; + union options_value *ov; + size_t wanted, n; + const char *equals; o = options_get_only(global_options, "command-alias"); - if (o == NULL || options_array_size(o, &size) == -1 || size == 0) - return (-1); + if (o == NULL) + return (NULL); + wanted = strlen(name); - wanted = strlen(old_argv[0]); - for (idx = 0; idx < size; idx++) { - s = options_array_get(o, idx); - if (s == NULL) - continue; + a = options_array_first(o); + while (a != NULL) { + ov = options_array_item_value(a); - cp = strchr(s, '='); - if (cp == NULL || (size_t)(cp - s) != wanted) - continue; - if (strncmp(old_argv[0], s, wanted) == 0) - break; + equals = strchr(ov->string, '='); + if (equals != NULL) { + n = equals - ov->string; + if (n == wanted && strncmp(name, ov->string, n) == 0) + return (xstrdup(equals + 1)); + } + + a = options_array_next(a); } - if (idx == size) - return (-1); + return (NULL); +} - if (cmd_string_split(cp + 1, &new_argc, &new_argv) != 0) - return (-1); +static const struct cmd_entry * +cmd_find(const char *name, char **cause) +{ + const struct cmd_entry **loop, *entry, *found = NULL; + int ambiguous; + char s[8192]; - *argc = new_argc + old_argc - 1; - *argv = xcalloc((*argc) + 1, sizeof **argv); + ambiguous = 0; + for (loop = cmd_table; *loop != NULL; loop++) { + entry = *loop; + if (entry->alias != NULL && strcmp(entry->alias, name) == 0) { + ambiguous = 0; + found = entry; + break; + } - for (i = 0; i < new_argc; i++) - (*argv)[i] = xstrdup(new_argv[i]); - for (i = 1; i < old_argc; i++) - (*argv)[new_argc + i - 1] = xstrdup(old_argv[i]); + if (strncmp(entry->name, name, strlen(name)) != 0) + continue; + if (found != NULL) + ambiguous = 1; + found = entry; - log_debug("alias: %s=%s", old_argv[0], cp + 1); - for (i = 0; i < *argc; i++) - log_debug("alias: argv[%d] = %s", i, (*argv)[i]); + if (strcmp(entry->name, name) == 0) + break; + } + if (ambiguous) + goto ambiguous; + if (found == NULL) { + xasprintf(cause, "unknown command: %s", name); + return (NULL); + } + return (found); - cmd_free_argv(new_argc, new_argv); - return (0); +ambiguous: + *s = '\0'; + for (loop = cmd_table; *loop != NULL; loop++) { + entry = *loop; + if (strncmp(entry->name, name, strlen(name)) != 0) + continue; + if (strlcat(s, entry->name, sizeof s) >= sizeof s) + break; + if (strlcat(s, ", ", sizeof s) >= sizeof s) + break; + } + s[strlen(s) - 2] = '\0'; + xasprintf(cause, "ambiguous command: %s, could be: %s", name, s); + return (NULL); } struct cmd * cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) { + const struct cmd_entry *entry; const char *name; - const struct cmd_entry **entryp, *entry; struct cmd *cmd; struct args *args; - char s[BUFSIZ]; - int ambiguous, allocated = 0; - *cause = NULL; if (argc == 0) { xasprintf(cause, "no command"); return (NULL); } name = argv[0]; -retry: - ambiguous = 0; - entry = NULL; - for (entryp = cmd_table; *entryp != NULL; entryp++) { - if ((*entryp)->alias != NULL && - strcmp((*entryp)->alias, argv[0]) == 0) { - ambiguous = 0; - entry = *entryp; - break; - } - - if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) - continue; - if (entry != NULL) - ambiguous = 1; - entry = *entryp; - - /* Bail now if an exact match. */ - if (strcmp(entry->name, argv[0]) == 0) - break; - } - if ((ambiguous || entry == NULL) && - server_proc != NULL && - !allocated && - cmd_try_alias(&argc, &argv) == 0) { - allocated = 1; - goto retry; - } - if (ambiguous) - goto ambiguous; - if (entry == NULL) { - xasprintf(cause, "unknown command: %s", name); + entry = cmd_find(name, 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) @@ -418,23 +463,11 @@ cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) cmd->file = xstrdup(file); cmd->line = line; - if (allocated) - cmd_free_argv(argc, argv); - return (cmd); + cmd->alias = NULL; + cmd->argc = argc; + cmd->argv = cmd_copy_argv(argc, argv); -ambiguous: - *s = '\0'; - for (entryp = cmd_table; *entryp != NULL; entryp++) { - if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0) - continue; - if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s) - break; - if (strlcat(s, ", ", sizeof s) >= sizeof s) - break; - } - s[strlen(s) - 2] = '\0'; - xasprintf(cause, "ambiguous command: %s, could be: %s", name, s); - return (NULL); + return (cmd); usage: if (args != NULL) @@ -443,6 +476,18 @@ cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause) return (NULL); } +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); +} + char * cmd_print(struct cmd *cmd) { @@ -458,6 +503,83 @@ cmd_print(struct cmd *cmd) return (out); } +struct cmd_list * +cmd_list_new(void) +{ + struct cmd_list *cmdlist; + + cmdlist = xcalloc(1, sizeof *cmdlist); + cmdlist->references = 1; + cmdlist->group = cmd_list_next_group++; + TAILQ_INIT(&cmdlist->list); + return (cmdlist); +} + +void +cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd) +{ + cmd->group = cmdlist->group; + TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry); +} + +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); + } + cmdlist->group = cmd_list_next_group++; +} + +void +cmd_list_free(struct cmd_list *cmdlist) +{ + struct cmd *cmd, *cmd1; + + if (--cmdlist->references != 0) + return; + + TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) { + TAILQ_REMOVE(&cmdlist->list, cmd, qentry); + cmd_free(cmd); + } + + free(cmdlist); +} + +char * +cmd_list_print(struct cmd_list *cmdlist, int escaped) +{ + struct cmd *cmd; + char *buf, *this; + size_t len; + + len = 1; + buf = xcalloc(1, len); + + TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { + this = cmd_print(cmd); + + len += strlen(this) + 4; + buf = xrealloc(buf, len); + + strlcat(buf, this, len); + if (TAILQ_NEXT(cmd, qentry) != NULL) { + if (escaped) + strlcat(buf, " \\; ", len); + else + strlcat(buf, " ; ", len); + } + + free(this); + } + + return (buf); +} + /* Adjust current mouse position for a pane. */ int cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, @@ -466,17 +588,16 @@ cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp, u_int x, y; if (last) { - x = m->lx; - y = m->ly; + x = m->lx + m->ox; + y = m->ly + m->oy; } else { - x = m->x; - y = m->y; + x = m->x + m->ox; + y = m->y + m->oy; } + log_debug("%s: x=%u, y=%u%s", __func__, x, y, last ? " (last)" : ""); - if (m->statusat == 0 && y > 0) - y--; - else if (m->statusat > 0 && y >= (u_int)m->statusat) - y = m->statusat - 1; + if (m->statusat == 0 && y >= m->statuslines) + y -= m->statuslines; if (x < wp->xoff || x >= wp->xoff + wp->sx) return (-1); @@ -496,17 +617,22 @@ cmd_mouse_window(struct mouse_event *m, struct session **sp) { struct session *s; struct window *w; + struct winlink *wl; - if (!m->valid || m->s == -1 || m->w == -1) - return (NULL); - if ((s = session_find_by_id(m->s)) == NULL) + if (!m->valid) return (NULL); - if ((w = window_find_by_id(m->w)) == NULL) + if (m->s == -1 || (s = session_find_by_id(m->s)) == NULL) return (NULL); - + if (m->w == -1) + wl = s->curw; + else { + if ((w = window_find_by_id(m->w)) == NULL) + return (NULL); + wl = winlink_find_by_window(&s->windows, w); + } if (sp != NULL) *sp = s; - return (winlink_find_by_window(&s->windows, w)); + return (wl); } /* Get current mouse pane if any. */ @@ -534,7 +660,7 @@ char * cmd_template_replace(const char *template, const char *s, int idx) { char ch, *buf; - const char *ptr, *cp, quote[] = "\"\\$"; + const char *ptr, *cp, quote[] = "\"\\$;"; int replaced, quoted; size_t len; @@ -565,10 +691,6 @@ cmd_template_replace(const char *template, const char *s, int idx) for (cp = s; *cp != '\0'; cp++) { if (quoted && strchr(quote, *cp) != NULL) buf[len++] = '\\'; - if (quoted && *cp == ';') { - buf[len++] = '\\'; - buf[len++] = '\\'; - } buf[len++] = *cp; } buf[len] = '\0'; diff --git a/colour.c b/colour.c index 5be6044522..c797287837 100644 --- a/colour.c +++ b/colour.c @@ -141,6 +141,8 @@ colour_tostring(int c) return ("white"); case 8: return ("default"); + case 9: + return ("terminal"); case 90: return ("brightblack"); case 91: @@ -158,7 +160,7 @@ colour_tostring(int c) case 97: return ("brightwhite"); } - return (NULL); + return ("invalid"); } /* Convert colour from string. */ @@ -188,6 +190,11 @@ colour_fromstring(const char *s) return (n | COLOUR_FLAG_256); } + if (strcasecmp(s, "default") == 0) + return (8); + if (strcasecmp(s, "terminal") == 0) + return (9); + if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0) return (0); if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0) @@ -204,8 +211,6 @@ colour_fromstring(const char *s) return (6); if (strcasecmp(s, "white") == 0 || strcmp(s, "7") == 0) return (7); - if (strcasecmp(s, "default") == 0 || strcmp(s, "8") == 0) - return (8); if (strcasecmp(s, "brightblack") == 0 || strcmp(s, "90") == 0) return (90); if (strcasecmp(s, "brightred") == 0 || strcmp(s, "91") == 0) @@ -225,11 +230,85 @@ colour_fromstring(const char *s) return (-1); } -/* Convert 256 colour palette to 16. */ -u_char -colour_256to16(u_char c) +/* Convert 256 colour to RGB colour. */ +int +colour_256toRGB(int c) +{ + static const int table[256] = { + 0x000000, 0x800000, 0x008000, 0x808000, + 0x000080, 0x800080, 0x008080, 0xc0c0c0, + 0x808080, 0xff0000, 0x00ff00, 0xffff00, + 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, + 0x000000, 0x00005f, 0x000087, 0x0000af, + 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, + 0x005f87, 0x005faf, 0x005fd7, 0x005fff, + 0x008700, 0x00875f, 0x008787, 0x0087af, + 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, + 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, + 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, + 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, + 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, + 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, + 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, + 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, + 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, + 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, + 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, + 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, + 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, + 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, + 0x870000, 0x87005f, 0x870087, 0x8700af, + 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, + 0x875f87, 0x875faf, 0x875fd7, 0x875fff, + 0x878700, 0x87875f, 0x878787, 0x8787af, + 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, + 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, + 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, + 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, + 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, + 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, + 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, + 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, + 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, + 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, + 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, + 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, + 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, + 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, + 0xd70000, 0xd7005f, 0xd70087, 0xd700af, + 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, + 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, + 0xd78700, 0xd7875f, 0xd78787, 0xd787af, + 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, + 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, + 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, + 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, + 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, + 0xff0000, 0xff005f, 0xff0087, 0xff00af, + 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, + 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, + 0xff8700, 0xff875f, 0xff8787, 0xff87af, + 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, + 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, + 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, + 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, + 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, + 0x080808, 0x121212, 0x1c1c1c, 0x262626, + 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, + 0x585858, 0x626262, 0x6c6c6c, 0x767676, + 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, + 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, + 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee + }; + + return (table[c & 0xff] | COLOUR_FLAG_RGB); +} + +/* Convert 256 colour to 16 colour. */ +int +colour_256to16(int c) { - static const u_char table[256] = { + static const char table[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, @@ -248,5 +327,5 @@ colour_256to16(u_char c) 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15 }; - return (table[c]); + return (table[c & 0xff]); } diff --git a/compat.h b/compat.h index e06f104df4..d3617413b2 100644 --- a/compat.h +++ b/compat.h @@ -67,6 +67,7 @@ void warnx(const char *, ...); #define _PATH_DEVNULL "/dev/null" #define _PATH_TTY "/dev/tty" #define _PATH_DEV "/dev/" +#define _PATH_DEFPATH "/usr/bin:/bin" #endif #ifndef __OpenBSD__ @@ -101,17 +102,17 @@ void warnx(const char *, ...); #include #endif -#ifdef HAVE_FORKPTY #ifdef HAVE_LIBUTIL_H #include #endif + #ifdef HAVE_PTY_H #include #endif + #ifdef HAVE_UTIL_H #include #endif -#endif #ifdef HAVE_VIS #include @@ -312,10 +313,6 @@ int vasprintf(char **, const char *, va_list); char *fgetln(FILE *, size_t *); #endif -#ifndef HAVE_FPARSELN -char *fparseln(FILE *, size_t *, size_t *, const char *, int); -#endif - #ifndef HAVE_SETENV /* setenv.c */ int setenv(const char *, const char *, int); diff --git a/compat/asprintf.c b/compat/asprintf.c index 95d7843058..187c19f00f 100644 --- a/compat/asprintf.c +++ b/compat/asprintf.c @@ -19,8 +19,10 @@ #include #include #include +#include #include "compat.h" +#include "xmalloc.h" int asprintf(char **ret, const char *fmt, ...) diff --git a/compat/fparseln.c b/compat/fparseln.c deleted file mode 100644 index 5c06f49192..0000000000 --- a/compat/fparseln.c +++ /dev/null @@ -1,221 +0,0 @@ -/* $OpenBSD: fparseln.c,v 1.6 2005/08/02 21:46:23 espie Exp $ */ -/* $NetBSD: fparseln.c,v 1.7 1999/07/02 15:49:12 simonb Exp $ */ - -/* - * Copyright (c) 1997 Christos Zoulas. All rights reserved. - * - * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Christos Zoulas. - * 4. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. - */ - -/* OPENBSD ORIGINAL: lib/libutil/fparseln.c */ - -#include - -#include -#include -#include - -#include "compat.h" - -/* - * fparseln() specific operation flags. - */ -#define FPARSELN_UNESCESC 0x01 -#define FPARSELN_UNESCCONT 0x02 -#define FPARSELN_UNESCCOMM 0x04 -#define FPARSELN_UNESCREST 0x08 -#define FPARSELN_UNESCALL 0x0f - -static int isescaped(const char *, const char *, int); - -/* isescaped(): - * Return true if the character in *p that belongs to a string - * that starts in *sp, is escaped by the escape character esc. - */ -static int -isescaped(const char *sp, const char *p, int esc) -{ - const char *cp; - size_t ne; - - /* No escape character */ - if (esc == '\0') - return 1; - - /* Count the number of escape characters that precede ours */ - for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++) - continue; - - /* Return true if odd number of escape characters */ - return (ne & 1) != 0; -} - - -/* fparseln(): - * Read a line from a file parsing continuations ending in \ - * and eliminating trailing newlines, or comments starting with - * the comment char. - */ -char * -fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], - int flags) -{ - static const char dstr[3] = { '\\', '\\', '#' }; - char *buf = NULL, *ptr, *cp, esc, con, nl, com; - size_t s, len = 0; - int cnt = 1; - - if (str == NULL) - str = dstr; - - esc = str[0]; - con = str[1]; - com = str[2]; - - /* - * XXX: it would be cool to be able to specify the newline character, - * but unfortunately, fgetln does not let us - */ - nl = '\n'; - - while (cnt) { - cnt = 0; - - if (lineno) - (*lineno)++; - - if ((ptr = fgetln(fp, &s)) == NULL) - break; - - if (s && com) { /* Check and eliminate comments */ - for (cp = ptr; cp < ptr + s; cp++) - if (*cp == com && !isescaped(ptr, cp, esc)) { - s = cp - ptr; - cnt = s == 0 && buf == NULL; - break; - } - } - - if (s && nl) { /* Check and eliminate newlines */ - cp = &ptr[s - 1]; - - if (*cp == nl) - s--; /* forget newline */ - } - - if (s && con) { /* Check and eliminate continuations */ - cp = &ptr[s - 1]; - - if (*cp == con && !isescaped(ptr, cp, esc)) { - s--; /* forget escape */ - cnt = 1; - } - } - - if (s == 0 && buf != NULL) - continue; - - if ((cp = realloc(buf, len + s + 1)) == NULL) { - free(buf); - return NULL; - } - buf = cp; - - (void) memcpy(buf + len, ptr, s); - len += s; - buf[len] = '\0'; - } - - if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL && - strchr(buf, esc) != NULL) { - ptr = cp = buf; - while (cp[0] != '\0') { - int skipesc; - - while (cp[0] != '\0' && cp[0] != esc) - *ptr++ = *cp++; - if (cp[0] == '\0' || cp[1] == '\0') - break; - - skipesc = 0; - if (cp[1] == com) - skipesc += (flags & FPARSELN_UNESCCOMM); - if (cp[1] == con) - skipesc += (flags & FPARSELN_UNESCCONT); - if (cp[1] == esc) - skipesc += (flags & FPARSELN_UNESCESC); - if (cp[1] != com && cp[1] != con && cp[1] != esc) - skipesc = (flags & FPARSELN_UNESCREST); - - if (skipesc) - cp++; - else - *ptr++ = *cp++; - *ptr++ = *cp++; - } - *ptr = '\0'; - len = strlen(buf); - } - - if (size) - *size = len; - return buf; -} - -#ifdef TEST - -int main(int, char **); - -int -main(argc, argv) - int argc; - char **argv; -{ - char *ptr; - size_t size, line; - - line = 0; - while ((ptr = fparseln(stdin, &size, &line, NULL, - FPARSELN_UNESCALL)) != NULL) - printf("line %d (%d) |%s|\n", line, size, ptr); - return 0; -} - -/* - -# This is a test -line 1 -line 2 \ -line 3 # Comment -line 4 \# Not comment \\\\ - -# And a comment \ -line 5 \\\ -line 6 - -*/ - -#endif /* TEST */ diff --git a/compat/getdtablecount.c b/compat/getdtablecount.c index 3ccab6501a..1f9a0aa70e 100644 --- a/compat/getdtablecount.c +++ b/compat/getdtablecount.c @@ -30,19 +30,12 @@ getdtablecount(void) { char path[PATH_MAX]; glob_t g; - int n; + int n = 0; if (snprintf(path, sizeof path, "/proc/%ld/fd/*", (long)getpid()) < 0) fatal("snprintf overflow"); - switch (glob(path, 0, NULL, &g)) { - case GLOB_NOMATCH: - return (0); - case 0: - break; - default: - fatal("glob(\"%s\") failed", path); - } - n = g.gl_pathc; + if (glob(path, 0, NULL, &g) == 0) + n = g.gl_pathc; globfree(&g); return (n); } diff --git a/compat/imsg-buffer.c b/compat/imsg-buffer.c index a302fa2775..814591f45a 100644 --- a/compat/imsg-buffer.c +++ b/compat/imsg-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg-buffer.c,v 1.10 2017/04/11 09:57:19 reyk Exp $ */ +/* $OpenBSD: imsg-buffer.c,v 1.11 2017/12/14 09:27:44 kettenis Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -29,9 +29,9 @@ #include "compat.h" #include "imsg.h" -int ibuf_realloc(struct ibuf *, size_t); -void ibuf_enqueue(struct msgbuf *, struct ibuf *); -void ibuf_dequeue(struct msgbuf *, struct ibuf *); +static int ibuf_realloc(struct ibuf *, size_t); +static void ibuf_enqueue(struct msgbuf *, struct ibuf *); +static void ibuf_dequeue(struct msgbuf *, struct ibuf *); struct ibuf * ibuf_open(size_t len) @@ -67,7 +67,7 @@ ibuf_dynamic(size_t len, size_t max) return (buf); } -int +static int ibuf_realloc(struct ibuf *buf, size_t len) { u_char *b; @@ -289,14 +289,14 @@ msgbuf_write(struct msgbuf *msgbuf) return (1); } -void +static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) { TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); msgbuf->queued++; } -void +static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) { TAILQ_REMOVE(&msgbuf->bufs, buf, entry); diff --git a/compat/imsg.c b/compat/imsg.c index 85f13370a4..54ac7e566c 100644 --- a/compat/imsg.c +++ b/compat/imsg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg.c,v 1.15 2017/04/11 09:57:19 reyk Exp $ */ +/* $OpenBSD: imsg.c,v 1.16 2017/12/14 09:27:44 kettenis Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -30,7 +30,7 @@ int imsg_fd_overhead = 0; -int imsg_get_fd(struct imsgbuf *); +static int imsg_get_fd(struct imsgbuf *); void imsg_init(struct imsgbuf *ibuf, int fd) @@ -266,7 +266,7 @@ imsg_free(struct imsg *imsg) freezero(imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); } -int +static int imsg_get_fd(struct imsgbuf *ibuf) { int fd; diff --git a/config/.terminfo.src/capabilities.sh b/config/.terminfo.src/capabilities.sh new file mode 100644 index 0000000000..f36fe83599 Binary files /dev/null and b/config/.terminfo.src/capabilities.sh differ diff --git a/config/.terminfo.src/config.sh b/config/.terminfo.src/config.sh new file mode 100644 index 0000000000..da5ea79004 --- /dev/null +++ b/config/.terminfo.src/config.sh @@ -0,0 +1,7 @@ +#Add the terminfo to .terminfo and /etc/terminfo: +rm -fr $HOME/.terminfo/s $HOME/.terminfo/t $HOME/.terminfo/x + +for i in $HOME/.terminfo.src/*.terminfo ; do \ + tic -xo $HOME/.terminfo $i ; + tic -x $i ; done + diff --git a/config/.terminfo.src/sixel-tmux.terminfo b/config/.terminfo.src/sixel-tmux.terminfo new file mode 100644 index 0000000000..67cc79424e --- /dev/null +++ b/config/.terminfo.src/sixel-tmux.terminfo @@ -0,0 +1,61 @@ +# A tmux TERMINFO that adds the escape sequences for italics and sixel, +# Usage: export TERM=sixel-tmux +# Test: tput smglr|base64 +# Assert: GzcbWz82OWgbWyVpJXAxJWQ7JXAyJWRzGzg= +# Install: +# tic -x sixel-tmux.terminfo; tic -xo ~/.terminfo sixel-tmux.terminfo +# +# am, km, mir, msgr, xenl, +# am, bce, hs, km, mir, msgr, npc, xenl, +# ich=\E[%p1%d@, il=\E[%p1%dL, il1=\E[L, ind=^J, is2=\E)0, +# sgr=\E[0%?%p6%t;1%;%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\016%e\017%;, +# sgr=\E[0%?%p6%t;1%;%?%p1%t;3%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\016%e\017%;, +# rev=\E[7m, ri=\EM, ritm=\E[23m, rmacs=^O, rmcup=\E[?1049l, +# sgr0=\E[m\017, sitm=\E[3m, smacs=^N, smcup=\E[?1049h, +# smir=\E[4h, smkx=\E[?1h\E=, smso=\E[3m, +# rs2=\Ec\E[?1000l\E[?25h, sc=\E7, +# sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +# 16m color was Tc, now obsoleted: use setaf, setab +# https://github.com/jwilm/alacritty/issues/1485#issue-346291738 +# +# 256 colors was: colors#0x100, but colors#16777216 is not signed 16 bit so use colors#0x100 +# along with setaf setbg to get 256 as fallback for apps not knowing setaf/setab +# +# Also using -x for strike through and setrgb custom string (should be setaf, setab) +# rmxx=\E[29m, smxx=\E[9m, +# setrgbf=\E[38;2;#1%d;#2%d;#3%dm, setrgbb=\E[48;2;#1%d;#2%d;#3%dm, + +sixel-tmux|standalone sixel-tmux with 24 bit colors, BCE, italics and sixel support, + am, bce, ccc, km, mir, msgr, xenl, + colors#0x100, Tc, + cols#80, it#8, lines#24, pairs#0x010000, + bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l, + clear=\E[H\E[J, cnorm=\E[34h\E[?25h, cr=\r, + csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H, + cud=\E[%p1%dB, cud1=\n, cuf=\E[%p1%dC, cuf1=\E[C, + cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\EM, + cvvis=\E[34l, dch=\E[%p1%dP, dch1=\E[P, dim=\E[2m, + dl=\E[%p1%dM, dl1=\E[M, ed=\E[J, el=\E[K, el1=\E[1K, + enacs=\E(B\E)0, flash=\Eg, hpa=\E[%i%p1%dG, home=\E[H, ht=^I, + hts=\EH, ich=\E[%p1%d@, il=\E[%p1%dL, il1=\E[L, ind=\n, indn=\E[%p1%dS, + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + invis=\E[8m, is2=\E)0, kbs=^H, kcbt=\E[Z, kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, + kcuu1=\EOA, kdch1=\E[3~, kend=\E[4~, kf1=\EOP, kf10=\E[21~, + kf11=\E[23~, kf12=\E[24~, kf2=\EOQ, kf3=\EOR, kf4=\EOS, + kf5=\E[15~, kf6=\E[17~, kf7=\E[18~, kf8=\E[19~, kf9=\E[20~, + khome=\E[1~, kich1=\E[2~, kmous=\E[M, knp=\E[6~, kpp=\E[5~, + nel=\EE, oc=\E]104\007, op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, + ritm=\E[23m, rmacs=^O, rmcup=\E[?1049l, rmir=\E[4l, rmkx=\E[?1l\E>, + rmso=\E[23m\E[m\017, rmul=\E[24m, rs2=\Ec\E[?1000l\E[?25h, sc=\E7, + 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, + sgr=\E[0%?%p6%t;1%;%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;m%?%p9%t\016%e\017%;, + setrgbf=\E[38;2;#1%d;#2%d;#3%dm, setrgbb=\E[48;2;#1%d;#2%d;#3%dm, + sgr0=\E[m\017, sitm=\E[3m, smacs=^N, smcup=\E[?1049h, + smglr=\E7\E[?69h\E[%i%p1%d;%p2%ds\E8, mgc=\E7\E[?69l\E8, + rmxx=\E[29m, smxx=\E[9m, + smir=\E[4h, smkx=\E[?1h\E=, smso=\E[3m\E[7m, smul=\E[4m, + tbc=\E[3g, vpa=\E[%i%p1%dd, + +# vi:ft=terminfo: diff --git a/config/.terminfo.src/terminal.sh b/config/.terminfo.src/terminal.sh new file mode 100644 index 0000000000..b504d20937 --- /dev/null +++ b/config/.terminfo.src/terminal.sh @@ -0,0 +1,111 @@ +#! /bin/bash + +############################################################################# +# Detect and report terminal type, name, version. +# works with bash, dash, posh, zsh, ksh->mksh +# not with tcsh, fish + +case "$1" in +-h) echo "Usage: $0 [-t|-n|-v|-p]" + echo "Query and report the terminal type, using Secondary Device Attribute report." + echo + echo Arguments: + echo " -t report numeric type" + echo " -n report terminal name" + echo " -v report version" + echo " -p report 3rd parameter" + return + ;; +esac + +############################################################################# +# query functions + +query () { + echo -n "$*" > /dev/tty + dd < /dev/tty 2> /dev/null +} + +terminaltype () { + +case "$BASH" in +?*) # try simplified query as suggested by Brian Inglis: + # https://github.com/mintty/mintty/issues/776#issuecomment-475761566 + read -s -dc -p $'\E[>c' da < /dev/tty + da=${da##$'\E'[>} + set - ${da//;/ } + ;; +*) # other shells do not have flexible read and string operations + # so it's a bit more tricky... + test -t 0 && stty=`stty -g` + test -t 0 && stty raw -echo min 0 time 5 + da=`query "[>c"` + test -t 0 && stty "$stty" + da=${da#??} + type="${da%%;*}" + rest="${da#*;}" + vers="${rest%%;*}" + rest="${rest#*;}" + rest="${rest%%c}" + set - $type $vers $rest +;; +esac + +case "$1" in +0) t=VT100 + case "$2" in + 95) t=tmux;; + 115) t=KDE-konsole;; + 136) t=PuTTY;; + 304) t=VT125;; + esac;; +1) t=VT220 + case "$2" in + 2) t=openwin-xterm;; + 96) t=mlterm;; + 304) t=VT241/VT382;; + 1115) t=gnome-terminal;; + esac + if [ 0$2 -ge 2000 ] + then t=gnome-terminal + fi + ;; +2) t=VT240;; +24) t=VT320;; +18) t=VT330;; +19) t=VT340;; +41) t=VT420;; +61) t=VT510;; +64) t=VT520;; +65) t=VT525;; +28) t=DECterm;; +67) t=cygwin;; +77) t=mintty;; +82) t=rxvt;; +83) t=screen;; +84) t=tmux; set - $1 200;; +85) t=rxvt-unicode;; +*) t=unknown;; +esac + +tt=$1 +v=$2 +pc=$3 + +} + +############################################################################# +# invoke the query and report as requested + +terminaltype + +case "$1" in +-t) echo $tt;; +-n) echo $t;; +-v) echo $v;; +-p) echo $pc;; +*) echo $tt $t $v $pc;; +esac + +############################################################################# +# end diff --git a/config/.terminfo/73/sixel-tmux b/config/.terminfo/73/sixel-tmux new file mode 100644 index 0000000000..f834e8927e Binary files /dev/null and b/config/.terminfo/73/sixel-tmux differ diff --git a/config/.terminfo/74/tmux-256color b/config/.terminfo/74/tmux-256color new file mode 100644 index 0000000000..17aaf5e1ca Binary files /dev/null and b/config/.terminfo/74/tmux-256color differ diff --git a/config/.terminfo/74/tmux-256color-italic b/config/.terminfo/74/tmux-256color-italic new file mode 100644 index 0000000000..7e9523b248 Binary files /dev/null and b/config/.terminfo/74/tmux-256color-italic differ diff --git a/config/.terminfo/74/tmux-italic b/config/.terminfo/74/tmux-italic new file mode 100644 index 0000000000..d713bbe1c0 Binary files /dev/null and b/config/.terminfo/74/tmux-italic differ diff --git a/config/.terminfo/74/tmux-sixel b/config/.terminfo/74/tmux-sixel new file mode 100644 index 0000000000..71d03332c7 Binary files /dev/null and b/config/.terminfo/74/tmux-sixel differ diff --git a/config/.terminfo/t/tmux-256color b/config/.terminfo/t/tmux-256color new file mode 100644 index 0000000000..2f9d96540f Binary files /dev/null and b/config/.terminfo/t/tmux-256color differ diff --git a/config/.terminfo/t/tmux-256color-italic b/config/.terminfo/t/tmux-256color-italic new file mode 100644 index 0000000000..847176d184 Binary files /dev/null and b/config/.terminfo/t/tmux-256color-italic differ diff --git a/config/.terminfo/t/tmux-italic b/config/.terminfo/t/tmux-italic new file mode 100644 index 0000000000..338dd775ba Binary files /dev/null and b/config/.terminfo/t/tmux-italic differ diff --git a/config/.terminfo/t/tmux-sixel b/config/.terminfo/t/tmux-sixel new file mode 100644 index 0000000000..127e4efbc5 Binary files /dev/null and b/config/.terminfo/t/tmux-sixel differ diff --git a/configure.ac b/configure.ac index 83c104c33c..5fba1eaf88 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac -AC_INIT(tmux, master) +AC_INIT([tmux], next-3.1) AC_PREREQ([2.60]) AC_CONFIG_AUX_DIR(etc) @@ -28,6 +28,7 @@ AC_PROG_CC_C99 AC_PROG_CPP AC_PROG_EGREP AC_PROG_INSTALL +AC_PROG_YACC PKG_PROG_PKG_CONFIG AC_USE_SYSTEM_EXTENSIONS @@ -35,7 +36,7 @@ AC_USE_SYSTEM_EXTENSIONS test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc # Is this --enable-debug? -test "x$VERSION" = xmaster && enable_debug=yes +case "x$VERSION" in xnext*) enable_debug=yes;; esac AC_ARG_ENABLE( debug, AC_HELP_STRING(--enable-debug, enable debug build flags), @@ -285,6 +286,7 @@ 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( [ @@ -297,6 +299,7 @@ if test "x$found_b64_ntop" = xno; then found_b64_ntop=no ) if test "x$found_b64_ntop" = xno; then + LIBS="$OLD_LIBS" AC_MSG_RESULT(no) fi fi @@ -455,14 +458,6 @@ else AC_LIBOBJ(getopt) fi -# Look for fparseln in libutil. -AC_SEARCH_LIBS(fparseln, util, found_fparseln=yes, found_fparseln=no) -if test "x$found_fparseln" = xyes; then - AC_DEFINE(HAVE_FPARSELN) -else - AC_LIBOBJ(fparseln) -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 @@ -476,6 +471,9 @@ if test "x$found_forkpty" = xyes; then fi AM_CONDITIONAL(NEED_FORKPTY, test "x$found_forkpty" = xno) +# Look for kinfo_getfile in libutil. +AC_SEARCH_LIBS(kinfo_getfile, [util util-freebsd]) + # Look for a suitable queue.h. AC_CHECK_DECL( TAILQ_CONCAT, @@ -608,13 +606,22 @@ case "$host_os" in *solaris*) AC_MSG_RESULT(sunos) PLATFORM=sunos - MANFORMAT=man + case `/usr/bin/nroff --version 2>&1` in + *GNU*) + # Solaris 11.4 and later use GNU groff. + MANFORMAT=mdoc + ;; + *) + # Solaris 2.0 to 11.3 use AT&T nroff. + MANFORMAT=man + ;; + esac ;; *hpux*) AC_MSG_RESULT(hpux) PLATFORM=hpux ;; - *cygwin*) + *cygwin*|*msys*) AC_MSG_RESULT(cygwin) PLATFORM=cygwin ;; diff --git a/control-notify.c b/control-notify.c index 4929148300..a1e2b7bf73 100644 --- a/control-notify.c +++ b/control-notify.c @@ -28,18 +28,16 @@ void control_notify_input(struct client *c, struct window_pane *wp, - struct evbuffer *input) + const u_char *buf, size_t len) { - u_char *buf; - size_t len; struct evbuffer *message; u_int i; if (c->session == NULL) return; - buf = EVBUFFER_DATA(input); - len = EVBUFFER_LENGTH(input); + if (c->flags & CLIENT_CONTROL_NOOUTPUT) + return; /* * Only write input if the window pane is linked to a window belonging @@ -47,6 +45,8 @@ control_notify_input(struct client *c, struct window_pane *wp, */ 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] == '\\') diff --git a/control.c b/control.c index 64e9fec165..c4cf5338b0 100644 --- a/control.c +++ b/control.c @@ -68,10 +68,9 @@ control_error(struct cmdq_item *item, void *data) void control_callback(struct client *c, int closed, __unused void *data) { - char *line, *cause; - struct cmd_list *cmdlist; - struct cmd *cmd; + char *line; struct cmdq_item *item; + struct cmd_parse_result *pr; if (closed) c->flags |= CLIENT_EXIT; @@ -81,20 +80,25 @@ control_callback(struct client *c, int closed, __unused void *data) if (line == NULL) break; if (*line == '\0') { /* empty line exit */ + free(line); c->flags |= CLIENT_EXIT; break; } - cmdlist = cmd_string_parse(line, NULL, 0, &cause); - if (cmdlist == NULL) { - item = cmdq_get_callback(control_error, cause); + 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); - } else { - TAILQ_FOREACH(cmd, &cmdlist->list, qentry) - cmd->flags |= CMD_CONTROL; - item = cmdq_get_command(cmdlist, NULL, NULL, 0); + 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(cmdlist); + cmd_list_free(pr->cmdlist); + break; } free(line); diff --git a/environ.c b/environ.c index 29191ee572..25968f7bc6 100644 --- a/environ.c +++ b/environ.c @@ -174,22 +174,22 @@ environ_unset(struct environ *env, const char *name) void environ_update(struct options *oo, struct environ *src, struct environ *dst) { - struct environ_entry *envent; - struct options_entry *o; - u_int size, idx; - const char *value; + struct environ_entry *envent; + struct options_entry *o; + struct options_array_item *a; + union options_value *ov; o = options_get(oo, "update-environment"); - if (o == NULL || options_array_size(o, &size) == -1) + if (o == NULL) return; - for (idx = 0; idx < size; idx++) { - value = options_array_get(o, idx); - if (value == NULL) - continue; - if ((envent = environ_find(src, value)) == NULL) - environ_clear(dst, value); + a = options_array_first(o); + while (a != NULL) { + ov = options_array_item_value(a); + if ((envent = environ_find(src, ov->string)) == NULL) + environ_clear(dst, ov->string); else environ_set(dst, envent->name, "%s", envent->value); + a = options_array_next(a); } } diff --git a/example_tmux.conf b/example_tmux.conf index 54de4d6be0..11db9ec638 100644 --- a/example_tmux.conf +++ b/example_tmux.conf @@ -6,7 +6,7 @@ # Some tweaks to the status line set -g status-right "%H:%M" -set -g window-status-current-attr "underscore" +set -g window-status-current-style "underscore" # If running inside tmux ($TMUX is set), then change the status line to red %if #{TMUX} @@ -49,11 +49,15 @@ bind F10 selectw -t:19 bind F11 selectw -t:20 bind F12 selectw -t:21 -# Keys to toggle monitoring activity in a window, and synchronize-panes +# A key to toggle between smallest and largest sizes if a window is visible in +# multiple places +bind F set -w window-size + +# Keys to toggle monitoring activity in a window and the synchronize-panes option bind m set monitor-activity bind y set synchronize-panes\; display 'synchronize-panes #{?synchronize-panes,on,off}' -# Create a single default session, because a session is created here, tmux +# Create a single default session - because a session is created here, tmux # should be started with "tmux attach" rather than "tmux new" new -d -s0 -nirssi 'exec irssi' set -t0:0 monitor-activity on diff --git a/examples/ansi-vt52-inside-sixel-tmux-inside-mintty.jpg b/examples/ansi-vt52-inside-sixel-tmux-inside-mintty.jpg new file mode 100644 index 0000000000..b2e2283e9f Binary files /dev/null and b/examples/ansi-vt52-inside-sixel-tmux-inside-mintty.jpg differ diff --git a/examples/ansi-vt52-inside-sixel-tmux-inside-windows-terminal.jpg b/examples/ansi-vt52-inside-sixel-tmux-inside-windows-terminal.jpg new file mode 100644 index 0000000000..79bf6f158d Binary files /dev/null and b/examples/ansi-vt52-inside-sixel-tmux-inside-windows-terminal.jpg differ diff --git a/examples/cutexterm_sixel-tmux.png b/examples/cutexterm_sixel-tmux.png new file mode 100644 index 0000000000..2c21a75fc2 Binary files /dev/null and b/examples/cutexterm_sixel-tmux.png differ diff --git a/examples/cutexterm_sixel-tmux_derasterized.png b/examples/cutexterm_sixel-tmux_derasterized.png new file mode 100644 index 0000000000..52a40e9077 Binary files /dev/null and b/examples/cutexterm_sixel-tmux_derasterized.png differ diff --git a/examples/font-programming-ligatures.jpg b/examples/font-programming-ligatures.jpg new file mode 100644 index 0000000000..18e6c672f5 Binary files /dev/null and b/examples/font-programming-ligatures.jpg differ diff --git a/examples/font-test1.jpg b/examples/font-test1.jpg new file mode 100644 index 0000000000..3979c688d0 Binary files /dev/null and b/examples/font-test1.jpg differ diff --git a/examples/font-test2.jpg b/examples/font-test2.jpg new file mode 100644 index 0000000000..c9bad8c8ae Binary files /dev/null and b/examples/font-test2.jpg differ diff --git a/examples/font-test3.jpg b/examples/font-test3.jpg new file mode 100644 index 0000000000..c0bec872e9 Binary files /dev/null and b/examples/font-test3.jpg differ diff --git a/examples/font-test4.jpg b/examples/font-test4.jpg new file mode 100644 index 0000000000..ff296d7bc7 Binary files /dev/null and b/examples/font-test4.jpg differ diff --git a/examples/msys2-ncurses-libevent-installed.jpg b/examples/msys2-ncurses-libevent-installed.jpg new file mode 100644 index 0000000000..87de4ba447 Binary files /dev/null and b/examples/msys2-ncurses-libevent-installed.jpg differ diff --git a/examples/sixel-tmux-inside-both-mintty-and-windows-terminal.gif b/examples/sixel-tmux-inside-both-mintty-and-windows-terminal.gif new file mode 100644 index 0000000000..240b278da5 Binary files /dev/null and b/examples/sixel-tmux-inside-both-mintty-and-windows-terminal.gif differ diff --git a/examples/sixel-tmux-inside-windows-terminal.gif b/examples/sixel-tmux-inside-windows-terminal.gif new file mode 100644 index 0000000000..08bcbe8912 Binary files /dev/null and b/examples/sixel-tmux-inside-windows-terminal.gif differ diff --git a/examples/sixel-tmux-night.jpg b/examples/sixel-tmux-night.jpg new file mode 100644 index 0000000000..faaa33b903 Binary files /dev/null and b/examples/sixel-tmux-night.jpg differ diff --git a/examples/sixel-tmux.jpg b/examples/sixel-tmux.jpg new file mode 100644 index 0000000000..828e88334b Binary files /dev/null and b/examples/sixel-tmux.jpg differ diff --git a/examples/sixel-tmux_2-vertical-tabs.gif b/examples/sixel-tmux_2-vertical-tabs.gif new file mode 100644 index 0000000000..45822d4a4e Binary files /dev/null and b/examples/sixel-tmux_2-vertical-tabs.gif differ diff --git a/examples/sixel-tmux_mintty.gif b/examples/sixel-tmux_mintty.gif new file mode 100644 index 0000000000..9dd26c126c Binary files /dev/null and b/examples/sixel-tmux_mintty.gif differ diff --git a/examples/snake.six b/examples/snake.six new file mode 100644 index 0000000000..b6644d1e88 --- /dev/null +++ b/examples/snake.six @@ -0,0 +1 @@ +Pq"1;1;600;450#0;2;38;31;19#1;2;41;35;22#2;2;41;31;19#3;2;39;35;16#4;2;41;35;19#5;2;44;35;22#6;2;44;38;25#7;2;44;38;28#8;2;44;38;22#9;2;47;38;22#10;2;49;41;24#11;2;47;41;28#12;2;50;39;28#13;2;50;44;30#14;2;52;47;30#15;2;56;44;31#16;2;53;47;35#17;2;56;50;38#18;2;55;44;41#19;2;56;50;41#20;2;55;52;41#21;2;63;52;45#22;2;64;55;55#23;2;53;41;31#24;2;55;49;55#25;2;45;53;45#26;2;45;58;55#27;2;47;64;42#28;2;31;24;11#29;2;30;24;30#30;2;60;50;41#31;2;38;28;30#32;2;41;39;24#33;2;27;39;20#34;2;66;67;41#35;2;47;39;30#36;2;55;44;16#37;2;50;45;42#38;2;44;63;52#39;2;83;75;80#40;2;72;78;55#41;2;61;75;80#42;2;55;60;31#43;2;45;44;30#44;2;44;58;30#45;2;38;53;45#46;2;44;49;30#47;2;55;58;25#48;2;42;52;24#49;2;67;72;19#50;2;42;52;44#51;2;66;50;22#52;2;56;58;9#53;2;42;44;42#54;2;31;50;36#55;2;41;56;50#56;2;44;41;14#57;2;44;50;16#58;2;45;64;72#59;2;45;35;9#60;2;44;36;9#61;2;38;33;0#62;2;41;39;30#63;2;30;36;39#64;2;49;42;5#65;2;39;35;5#66;2;66;56;5#67;2;39;31;8#68;2;56;45;0#69;2;63;53;2#70;2;42;44;5#71;2;16;86;74#72;2;55;38;9#73;2;38;60;50#74;2;22;66;55#75;2;17;39;28#76;2;24;49;36#77;2;6;20;17#78;2;3;6;3#79;2;5;20;20#80;2;13;31;25#81;2;19;41;31#82;2;31;50;41#83;2;38;56;50#84;2;6;16;6#85;2;31;56;42#86;2;38;58;45#87;2;33;53;49#88;2;20;24;13#89;2;3;2;3#90;2;45;47;67#91;2;24;14;60#92;2;9;9;28#93;2;9;35;36#94;2;16;16;11#95;2;3;11;11#96;2;30;41;56#97;2;9;0;28#98;2;11;11;3#99;2;28;38;31#100;2;16;24;28#101;2;6;17;14#102;2;44;38;31#103;2;35;28;0#104;2;44;47;55#105;2;9;8;13#106;2;45;36;35#107;2;20;11;41#108;2;30;44;36#109;2;9;2;14#110;2;42;35;35#111;2;19;67;71#112;2;55;47;64#113;2;45;33;56#114;2;31;55;60#115;2;44;38;55#116;2;28;36;6#117;2;28;35;36#118;2;24;38;49#119;2;17;16;2#120;2;24;24;0#121;2;19;38;31#122;2;30;38;42#123;2;39;33;49#124;2;22;38;38#125;2;20;35;36#126;2;28;19;38#127;2;39;17;9#128;2;3;5;11#129;2;22;11;2#130;2;41;33;53#131;2;85;74;16#132;2;25;19;0#133;2;16;6;8#134;2;9;3;3#135;2;33;25;0#136;2;16;6;17#137;2;27;9;13#138;2;28;22;0#139;2;22;11;30#140;2;25;50;41#141;2;38;31;44#142;2;31;25;39#143;2;33;50;44#144;2;42;55;53#145;2;27;41;33#146;2;22;22;2#147;2;49;39;33#148;2;5;25;19#149;2;28;44;38#150;2;31;47;41#151;2;35;53;47#152;2;22;41;35#153;2;25;47;41#154;2;20;14;0#155;2;45;17;33#156;2;64;31;33#157;2;28;47;41#158;2;61;44;42#159;2;0;28;20#160;2;9;39;28#161;2;72;66;2#162;2;72;58;2#163;2;86;72;0#164;2;80;45;61#165;2;30;14;30#166;2;39;30;41#0^~j~v~~^^~Vvzn~v~nznZnzV~zn~znnZ~v!14~dItJsJShTAKPl?LP??@B?B@bKPdIThEhDIDBODGO@A@A@?A???@?@C#32!8?O#6?A@M@]qSISnXVW^VBGqCyA{AS!8~w|XM|VGxA}?IsAG??@GcOAG#8!9?OI?O_CG??OIcRm|J~ExNqDi@oO_O#10!4?GYKI|]G^uRRy~Qz}~~n|r[qkdQlOeGu?Hu@Q@Q`Q?GcOASg?J`AHb?JPcYUgQkQcGqCOcGOeGQcIOAKaC_I_CWa[#23?_?_O!7?AgA?O??G???G#17!19?GACI?FADCFBDDFAABaBFDN~^MjjjZ^TnPFDB~SiSgO_G?eGU_IObRG_DBApI?SaGCQgCONRnV~^!25~j!24~v~v~~~z}!6~?~NBJOGo?oDY@IDaHu???O?O_#21!9?GE!9?_!4?yATi?VoEhShQdQ`epdQdq`HePDO@@`??DD@tHD@@@??@`@?D!5?g@CTAD@OH@hCOO?g@h_$#1_?S#31?G???_??G!5?O#28!7?C??CO?c#1!16?ITIcAOgOgGoG?gOg?_???CI?RcIpIChQ?_!4?B@U@_?_ODZfVCwgoj^@?WhGsNnD]`]`LJTI?eGb?_O!16?F?A_?Go?|@~tJ|v^@{eZJxf??gQCgQCOfPGAO@c?A#4!6?_#9?OC_GQCyDiSJsiTivc`pA_b_@?gD?l?@#11!5?bLQG#13??aGc?iSHu@U`I`]tYf{jUlsC|uC~smXc`UDRHRULilYTeXulJtntrPzJDZJbD@B^TMfTIDDyk???G#16???GC_GaCOiCY_IuHa]!9~v|zt~_\ojOSgQo?pCO_O?o?_@OCO!27?GOG#20A#19!41?S#16!24?G?G#19???C@!6?~?o{cjVFzNydE@IDAHDZtisjY~^!4~}|yfppnHRBBBFBFFBFBD@G@T?H@#22!4?G??G?G?G?CO?GaGeWCYdW_WAcQgUgUhUWeXqLylR}@]pItgUhUgUHiDiTiS^$#28!7?_??G?C#31!17?O#2!17?O_?OHcBAAtAeQUaEsZ}{~ws[_IOC_QOCwOwwLySmg{]|[mwCWgAC??O_#8!8?G_O_???_#7`O?_C_GkvLzD|B|j!9?AC@A?C#8!8?_O#6!16?c@GAO@cG_?G!5?hAODgPCIDJcJTiT?@AC?@C??g#11!25?c?C?C#14!9?A#15!8?G?g?cG_?O??_G???O??G?C??_COCW_[?G!4?A?COG???lAT?PA!9?@#19!17?W?G?GG?_GkKGKKgi???oCOCc_a?mwy{?`@BFN^v~Xvh^tn[kv^qktKt~j\vzlVznokOg?_#30!68?OC_GC???_oOW??_!19?_GOgOgO?W_GO#27!14?O??O???O??_#24!4?A???A#26C#25?A!6?_!6?OC!14?G$#29!10?_??O?G??COcOCg??O!6?G#4!21?C#3??@??@??JC!14?ADA!9?A!6?@A#5!45?_O?_BE!8?mAP?cCO~~VlzVlznG?udLiQZtNtRkPAS#7!18?O#12???O?GCC???C???OAK??@QlQLPRHTa?GGgGSG`A@G@?@Q?W??W???A@?@!4?@?@A@A@?@#14!6?G?SO?_??A_?aPGitwy@Bv|VtQliva\v\rnTzd^tGu\`#20!15?_?O__OG?O?o?O?O!9?GO!5?ISgO_!109?WC_?s?YcIDJCD?_!4?@ADOGMOUckSkOkW_[ok?{aSigEwUjUlQlM@MAlQLIeXEWfWeWdYaWaGOgUgUhUGEWe?oDAG@A_?_GO_E?Q?_DaDAOA$#4!83?Ca?_!6?@?_???A?@??QDAO??_#18!235?A???A??@??@#30!186?O#27!31?G???_?O?I???G?_C?Q??O?C$#5!98?w?SIC?kydEvJ?Oo!5?_#26!457?A!4?C!4?A-#0t~n|~~n~jpV~ynTnj~}V}zJn||~]v|nznz~~v!9~^v[iTmPnSiSHaCg@_@A@A@B_JATcGdIcIS_[?[_gP??R?JQ?A?g?U_?D??CG#8!8?eXAC?B#7???gO??sCC?CcS?CsKOg!7?O#8!27?O?AT?iO@?a?HATAPH}ZlIBsE?FFGF?@#10AD?aOYcYcyB|~Vj\~~~m~~~NfVjSj?paO`WdY_@??AGQCHs@iOdWcSgTgUhUhUhYa]`Q_RgPiOIcWa[c[sITaSHQCG#23?@??CGO?C?O??Q?A!5?@#17!17?G_SGCCC?IDw?}QMAmC?CCCDCEE]WXX@AB?gCOGqL?k?X?@A@S|~[_LQgBSaJpBES|}{|MT{}|W]w]Xz[XV!6~j~z!12~}!18~|!8~}~~~|!7~{~C{?KPiUj\oH?@qCSgS@A@#21!13?A@!6?@O?s@KoM`KXAkPmPe`QXR_WdM_NOXKIIDDA?CEce_O?cGW?g?GOQGo?IAG@E_W_?O?QBP???gCGAEG$#1I#31?OA#29??O?CA_?C?a???@_?CO?A!4?A??O#5!13?_#1G_?_?a?b@aO@A@A@AKAKOcWoO_B_A`APaD?Q???cGcGsCHtKw?R@NZyiRa?faw???|}We\yC{BUiTA@#12!4?C???C#1!9?GCGCD?LWfOjiBIbBSIRmPnSgAOdIOC`GQ?A@?A#4??GO_G?dA#11!18?G#12!9?gS_???@???oWGObO^CLiSB??DWeWdOC_Q?COC!4?C#23?A@#12!9?CGC#14!10?_G_G?aO?J?H?F^yvk}j{g|~l~xsIy@z]PrlZUhVA@AR#19!13?C??B?@?_P?wKxgZgWw`?bEamWsnTzluLQnR~e~}{}jA?b^qlV{j\sM{PjA@BApiB@Af`D`cC`e!22?@!18?A!8?@#30???A#19!7?B?zA~PePgSa?o??@O_Vj}|}nRBt{^w~[~|RV\oIOoO!6?Kq??O??_O#22!4?G??C?A??Q?_A_PcOiOlQhOHQLaXaDiTiTaLqDjShTshZcNyLulSkjUxFwb[hv$#28!8?O?G?@O?OC??G@?cO?A?`G?OC?C??G#2!11?@SIPKOGSHe[xUkQS`[`mWFClAGFGCHCHABdBREGsJcJocIpD@#6!19?_@y?{hTAl}^JzzzZJjzzJrnV!4~vxpioxoDWf?TkSCcHSg?A?h!13?@?A?@??@!6?O`SGPiOgO_WcWaS?g?OC???A#13!7?O!5?_CGC_IODIcYdYcXeWfhZcJyDjYFZJRITGEgAgADH?YhTgUmTltZf\BRBB?_G@ac_re}W_@?A@OADA???CGS?[??G#20!52?A??_!6?@#16!15?g#20!12?A?A#16?A?g!6?S?C#30!55?@?aGC@??EAuGChA!8?G?_C?a?Ag?_GcGFGtCWCa???A#27!11?G???_A?@!4?C?A??A???O?_??C?O?C??_@??`C??OC?OC_H?gA??CO$#33!9?G!4?G#31?O#4!31?A#3@#4!11?OG?O_O!5?G!5?_??_??C@!9?A?G!5?G!6?B#9!20?_O!12?A#5A@AA?_?GC?O_WWa`CPkOAV|nYtnz]fl|g}Sl{~SnUSiWGu?C?C#11!48?A?@#15!10?_?_?_?_O@S@S_S`C?A?@??A!4?_G??TATGCHOCO?_???@??@A!5?B_@?C_CCO!7?CGC#20!158?HCHuGAH!5?Ok{AB?B?@??Cg?EPfGfIzej\Jq@LpMre\BmPmXUdeg^DWpKoFcR_PgOlOhOH?LA\APaDAPaD?CAS?OaG??A?@A!4?ChA?BO`O$#3!9?C!52?Cg#5!9?O?O?O??O?G_GO??O!7?AcGOc?T_XvW\F~{~A@@???@!7?_#8!19?C?GCAA??O??@#9!29?_??c?@_AOgBgTgOfWfYdWjTFdJ`ZD{!4?A#16!91?@Ca??aGAchUg|}|gvz!4~v^jv!4ztyC~?lPkPBrAR_Qb@W`C_COdGOA??@??O#26!173?G!10?A!11?G??G!4?A??@!7?O!4?C$#3!74?O?O???G!5?AB!8?S?_!4?C-#0Zd|v^z^~|~~z}~|v~^~z^l~v~v|v~m~znu~v|!5~vtItN{BsJOjCRc@??I?ICI_Q?TaCJeGcHkBwDy???@???CWa?hA_G_QDO???_G#6!6?G??O?I_YdITiFaY^VPlVzfZvjVxcZ|!4~pvTym`QvkiD\aTA?ADO?ASG??_?_!7?_?O???__!7?`?I`A@e@AdQdAdOdY?S`Oa#12??GADG???G???GGEICAILIcK?_??N?E?DQDI@#14??_#15?A@?A?@?AG@CAGAC!5?C_?a?P?GA!6?@?H_?i?S_?O_!5?G?A]`G?C!5?_?O#17!12?@?H?OiG@M@MH?GAE?K?G_cG!7?@?_`G@c`_SwZOoO_d~_OdGDqgD?JSQ?Oo\]OANY^NDYtadQdZ|!6~u|}!30~zv~z~~zvn!12~votytitwQ|y?A?P_OPqooToDG@!7?O_#21???A!7?OIIOdGaL?UHePm@}@zCPuHU@EKaEdSXIC??O?YCJIgSQIHOCaO@`Qa?PA_A`GAO??_S@G??Og?CCq$#1cY??_#29???A!6?G???C_A#33!11?G#2!8?GISGoAW?_@CO?Ho}otpsjtUCtiLySWDIAOC?I?UlQui\jq@D\EHDEH#8!16?gAGDO??O#7??OGd?_KQgCWcGSgEZcA!4?A!8?O#8!20?gA?PI?ID?_YCOIoE?Hx}YtC[CWGKOG?O#10OOcY`UhMe\~~v|yv~~~v^~~vvxDjT`QPAPmPmPo?x?o?OOSze\a\aTkRkRkRKbKRkBcBwBsBSbOgAXEgDiO!4?@??O?_#23??@I?P?G?A#16!4?ACQhEwp}TYni^!11~}nu~nTv]pmpE|AlPkRkRKQDTiAK`EGCPCA?O?I?@!8?GA!5?O?O_?cHA???O?_?O_??G?G?_A!6?HA@#19!30?CG?C??CG#16O#19!12?GFIDIDADLADF\BmRneLNNiNqrs~zr\RZtiR[wg[{gwG!5?@D?QT?qH_O#27???@???C!5?A??A??O???G??@C??G@C?A???G?@??_?@OC@?i?OI_?@?g@??G$#31??AG?C_#28!4?C@?A??_???O?G?GAG?P?CO@?GA#1!7?`A?@cJSmOjkQM@N?M???GgG?O@?@a@O??A??`Qd?TaS@?O???O@?Dgjpz_MetIANN~F~VlvoNdYdiTgT?_?_!16?K?IC@UKGRDYaS?D?xQHyTItGqPsI@?S?D?_?O???_O#9@C?HOcA@?A`AhePqlQlIlIJ?ChAO@#11!11?_!6?O??__#36!33?O#12O#14!10?aO?iD_!6?@!4?BMDNM]v~|v~|`WolOxEM@iDOD_#19!12?O!5?_???_ATOgR_PcRHoa?O`S@FGCXGrcOOYiFcnNn^Y?FLYvyLVi~cJlZeL_`nloD__YdATYdQC#30!62?G???OGA_??w?{?K?G#20!5?GCI?CKaKcIDK`AT_BQCrkRkjtsiYdGqL_UHmP}?}CzaHuhuX`[hO@a?`]`EpCo?oD`?o?h?L?E?CGAC?S??O??C@???_CA??@_$#4!61?@!8?_!8?@??G!5?CGA?CI?A#5!30?GA#8!17?G???G#4!35?@C#35!27?G#13!17?o?gS?CXaPMPm?~?~IlidiCXA\_[iPkQkPcqWpcPgJ{F{JwJ[LVkC`TOON~~~}}u]nT^j[o_O_`!7?CB?A?@#20!27?O?O?_!4?A???AGTlQIwoqiASCIJCD!9?O_!13?A!9?G???G!74?_#30!17?_!4?ADA@?DBCRkRC#22!18?G???G_O@OGaC`Y`]`M`IoDQAk@uCjONoMgD|A|IlYE|DzmTJyEzTiFwRyL$#3!62?O?@@A!6?O?AOC!14?O!5?CICI@#5!43?_@O?`???_?Hiw~CgeDg`AvLMJT}~B|ymT^di~^DIIpNonQC?CGO?O#25!353?G?C#26!15?O?_???O?G!4?_!6?C!4?O#24C$#5!72?O?c@g@oCG??G???Ga?_O_?oSgQ?C?TOPIt|oo?o#24!463?O#34!16?O??C?A?@-#0ElZtn^Zv^!7~zz|~}~~~}}!12~r~jZvGVkR{B{BkPI_A[?[_Sg?gQdAGQ???aShShUhQlOiSkQkhAK@Ql?`_Ga?A?A#4???_GA#6!4?GG?Cm\iDAMJn]|Ui\julZulZulZ!4~^~N~^nI]jEZAtiTGAWC?AG`!7?AA@I?COAG`C@??@iOKGa?_?A_???jCZSJ[U@SHSAcHQ#12!8?JC???I?C~@k?@?C??A_!4?C#15?_?O?_!7?C?A#12??UiC#36??_#14O?O#15??Q?DwASgS?O?ACHaDATA_CPAC`IC@?@???G?NOaG!6?_#17!17?DA?Q`ITiwWceOG_AMOCGC_?O__!4?[gCB?_!8?@?@?SItk@?@?O?O_ARaAS?E?DzU~~T}?!8~N!22~|~^~^~^~~N!7~}!7~JvJzBkVeZADHEBHD!4?AHE@NEHu?S_?HA!5?O#21!12?IPCi??QHGaTGvGvGvG_ITg??O?{GA??C??C@EE?C!4?_@aAXBYE?BCt??PD`?@!4?G!4?cAGQ$#1xQCIO?cG_#29!7?C?A!5?@#2!13?K?C??A_O_@gAO?I_T??v_RGPVOdGOvGntZG?A???C?A@@A@@A?GOG!4?A#8!16?A@??@???P_O#7?@A@DaSHQcHQcHQc#8!4?_#5?o?_O`_OgcWA?_T?aWCCO?Z~lZ]n{sSqtyxltUYjmR}S@mPCG?MGGSGg#12?C#10!4?A@sJsHsHUhVlRn^~~~sxtysoix?}P}wnWt^_^hUhUhUHvGuGtISjShUhUhUh???~?^?^_\alZoFwHUgAGQCGCH?CA#14!4?GOCOwk{}~Z~vn_nKBFI?_~pE@@A`a#19!20?DAXHcB[`OfWbQKPgA???zH`Tz[~V`b`@@`bpABI~jTA@]tGxBLbD|k\tjVxfWCh??i@T!8?o!22?A?_?_?_??o#16!7?@#19!7?sGsC{RGHc[qsgsqw???_OAHM?HEH~j^~u|~^Gtph~A|nYtmYE!8?@?ceT?_#22!6?T??AOAcHA?tItIpMoCx?tIpMpMPMOLaGcPiShAYtMoMv[v^t^d~H}dYhUl$#31??_??_#28!11?C??@!4?@#1!14?Oc?pGBKAS@kRcTI|BGBKBEGFGQl?d???S@??A!4?gC`O?@AS?s???QDTT\|xw}~i?@O@}@]te^jOADQK@cO_?gO!21?S@CP?dGTIa\DbZxf]c?O__OAHHK?D?AG`CO!7?_#11!19?A!13?AIDJDSA!4?A?A#14!46?hA#23!21?c??O#16??Pswt~N?MX}}|]\!12~y|~l]tiTAdAOJsB[`GbShQk@[?qS?a?A?_?G??G??S?G!5?_GQ_IoCgaGQ???G?g?Wa!6?i#20!62?_O!8?ePwTgS_O_?o!8?_aICA?|AOdIPchZc^siZTzelQPGiVGvGvGvItiTn|Je@rGtGpMpIw?xGpMpMpMoCoCo?_Sg??cA?G???G!5?Q@G??_$#5!42?GC#3!12?_#4???_?_!6?OIc@a?G?`???C!5?C??Ac#7!67?_??_#8!5?A?@?@!5?A!5?Ok@iS@aPTwPo_?O!5?_?__#13!22?@???A@CO`I_\?UhUhQhUGfHVItjSjUhQhShUhTz?~?n_Na\?cI?Da@BSdlxrqSyxg|^zmtjYdBQB!6?O!6?O#20!41?@AE@^LjCSA!5?][Q}}I[E{{s!7?EACOCG#30!81?`GAPGCAXmFID_O_Oo!10?T?IC!7?@OcZ_@C_?CW#27!19?O?C#25??A#24???@#26A#27??A!7?H!5?@?Q?@G_A??a?_A?A?c?Q??@$#5!73?GS`SGQdOA?GACO?`A?HQlCWaGa?ED@?T^ul}@}`?O_O?_Og_O#4!47?C!4?_#9!16?A?F?FTJfV~OzcJs@GI_A_HQ_CgQkO_#37!157?C#26!194?C@G???G!4?OG???G?O!5?C$#3!75?A???G??O??g?O?`???QG#34!495?_!5?@O-#1s`?TcDibGTAtG?O?OcGOcOG_SG?O???C???AOg?qLQLwcw?oGTIVITJSjSiSiSiT?FI?S__G@B?B?B@AO?cGDP_?O_G?XaT@v~eqaa?iq}_\qx]lnzZlZlVgTgDIOAOd#8!16?@@!5?@!9?G?_O?_G?cA?HC?_??GaPGcPAOGO!4?|JzUG`ePP@_@#10??GCBcPIcZs~~|{}~}w{^~ju\uljVNfRmFLa^FLB!4?@TaLcJSB[R}PMpMpNOfGxAw?}?GAGqMSG_OaSHCJ?t?H?QC?A#23!5?O?C!5?@#16SI_ISiOiCyti\uzm\jzv~vn!7~z}zSiTheWpMxa\aGq?Pk@QCHQCQ??O??O?_?G?C??O??G!7?CI?GACG???DG#19!66?s?gPgSgUgvKGcYP_SH?QcZcYcYCWbePZS~~Yu]t~~}xu|y{tUuYOU|YSAHC!7?B#27?O?G?O???A!4?G??O??_???GA!6?P?G?@?G??A??OA?aG?G?C?G??a?@_$#0JUlIRiTKtI|Iv~n^nZvnZnv^jv~n~~~z}~~|NRK@aLqDZ?VGTaTGTaSJOjCiPISa~!5?Tu!8?@hAS?gAkBCbOA!6?GPCGO#6!6?_O?C_QcO_VaRgtI|FWvi\uZulZulZ!5~SyynKtiOnTaIOcG_??C?A#32!6?O??A#6!9?C?_?g?aGAc?HaSHCISJSjSRISHcPIc@???A#11@?@FB_?SHaHQCgOG?O?O???O#15!7?G?I?@?Ag@G`??AO??OCOCg?G?C?G`G??_?G?A?`???C_A?O?C_A!5?G??u?J!8?DA!4?O_O?G?GO#17!7?C@CjSiQHFA@EP?XA?AAAMkPA?G@!14?A!4?AS?jSO`?P_O_AoSooau~_\uZulZlZulY!52~J~VmVjVhVGRcQdGSj?OdZcZdYdY`KPiCj??dG`I#20??@EHADBIH?dI_A?aHsGxE|JuLzsKhEs@m?nW@[alQdQlIdYDYdK?_TgTiTAKC`EgQdAx?_AC#38!8?_#34?A?G#20O?C$#5?GQ_!5?_#31!22?@#2???_CqKO??A?FgBaG___G__C?P@C`@G?wt~j^G?MsN[f[UlEUHbYEXO?O?K_\aG??@C#7!17?AG?GCQ?d?gAGTaHcHQcHQc!7?C?Q#5!4?ITdIOsG_?HCCB??vHPkQaXV~~v\mvZmxlVnRlSo!8?_#12!16?A@!12?O?_OG?_?O?_?oGo?oe!6?_!16?@#36!6?`#14C@??_?GCAIsAHCwE?_?H?^n~BPb~~Hi_t^tjTnTz?GTaHC@ACC#18!15?@?C#19O_KO?KaCtL|kPo@is@b?\JjYJiZQkoMxE|_[Qtigj~SjnQsiThYtNjNITH?^aHcHQcQcHQd#30!62?_QH?aJ?SdG!6?`COGC_!4?@!11?_H?d#21H?dHsApExAoHoCGREhB}@vOAK@TAgAHO@A_G?A_rTGAA?GwaqQXSD?OCGU`G`!8?@C?@???O$#2!4?GO?O!7?_#5!22?@??_#4!5?C#3!22?A@!7?O!9?G#1!54?iC@O`ITmO_GOdJBV^vqZh{^vGQkBcXcG#9!9?A??CQHF?OC_TIOiCiSiSjcpgQIcP?I#13!20?c@WaL_WaKvN~NWiTqPsi[`C?eOMpK_nWfAlBV?vvxvDOAr]N\Bupo[?JsqH@xl^zU|_??wmS???S#20!40?_!13?kOkasCdsDcLRFpAxALblATT!5?G@CAC@!83?@??C??aI!5?@??A#25!30?A#22???C?A??_?O???G?dOaGODOcAcWDQdGQCGaTgTaD?HC_AgQlAt@[b[v\v~T~z]ux~Sn}Zn$#31!8?A#4!63?oGo_W_g!9?_!6?U#25!451?_#26!12?O@!18?G???G???a??@$#5!80?g?O?_?CBKBSbC?G_G?W@KXvDL@^aLE@AO?C-#1Tkq[uh[hAha??q@bKCdIO@aS_Oo_WoOqDjSktkZsyHwE_Baj?[_CEKDKmSxE{Jsl??gC?RA?mQIEIACGO?COGOCPWI?F?fC@BPBD`PeQgBj?nuY`?lRCjS`CXEWbCHQc#8!8?C!5?C?A!6?CAIOgG_[_G?A?C?aGP?SAOi?C??A?cOELO`UKojGTJzCzC?TAcO??G#10???y@iTcYd|hiSjszcRzVidvy|{}lo?`_aOoGogOaPiogOWCO@?dJQhFgRcWC?A?DA|AtGtGVgVGEhCGcGC`?IP?A@#23!7?A@#16!8?M_@AG@Q?ji~~n]n!8~vv!5~n~liULqLQdzExUd[dM_Cg@cG?S`OC@?A?_?A!4?A!17?C??g??a?OCOCOCOCOcG!8?cG#19???CG???_!11?C??CG??CG!8?S?{S?VgB[rMxFgC}TJCPAAo_SiTiDY`Q`S`YaTnV|nz}z~~^FfJ`]BJA?ABDSAgWGCOG???A#27!4?C??O!6?@C??H??O??A?_!4?OG!5?G??OAG?A?g?`G?a??OA?S?G$#0gRKbHU`UlSLYuLu[bRItNu\j^nN^fNfLySjRGRcGCoDwVgT?\a\aXaWbO@A?B?@AT??_C?Pb?@?@_`Q`ISA@A@?cA_?_GO?O_?GO?A??@#6!4?G?Q@?kPSj[XCXdWyshYtYvlZulZy|!4~z~x}{z^h}X?_CBOA@#4?_!9?_!4?_#6!5?G_OcG?`ISaGsCxCYtIsGDwCs@kO?{TiPCI??O@#35???GC#13!11?i?QKbJcBSjSiDNSjFxJsjWskAwRKRfzn\ug{?|IeItgFGtxQqvZrz]}dCks{{jTRI@?OQADE?C?KC#17!23?G!6?O?QDgO?oKWCWCgW?Go#37!9?G!5?O#17??OHOcG?G?CO@_A@xAiRiAyBx~FFR~X\nynylzlzmXv!8~Zv~~~zv~~~^!11~z~~zv~~zv!8~j~Bj~gV{bKpEgTi@IC@AP_LOiTiTyd[`]`]_TiOgAOC@C#30??_GO?A@??HS`#22!12?@!5?G?A`OCGShEgOaGOI_IDgTI@mOlOk@]`MPmPmPeTRKtjT~Yvn\vyn|~I~Vz$#5A?@!5?O??cH?G??_??_G!8?G!9?@?A@GOG!4?X_O_O#3!9?iS?OG?G!8?C!6?G?C@O!4?G?_?G?G#8!12?A???A?_#1!22?C@AC?E@?dTBOcH_KBB@~z~\vKLIKNSmH_???GcO#9!11?A?`I_HRiFzB}Rn@A??I`OA#12S@iSJCRgCgSOGC?A@OMT[KPKCRKBCHCO?BC_?cGS#15A?@S?C_G!4?@A@A??@?A???A?CG??C???OiPG???i?C??GG_O@C?A@?_!8?S???@!10?G#19!8?O@aL?_??`A@AbQ@^ZV]ZTvgKFwgZs]LqLaeI?Pvu}ym}^|}E|TkT|D{A?wOk?Ca?@?@A?A?@A#20!60?O!5?_KG[?D!24?oGs[_gSsjWkoiXSfcrkbSj]gojtIpOhEhYuiUxSaXuCsGDiT_PMOnOl@E`M@M@e@EH??`?O??C?O?G!6?_$#2!6?A??A?@!4?O?O!17?A??B?A???C?Sa@???@A?@iCx?sIOiSBZJcKCOksWSKGS_@wkskZ?@?I?B?@C?E?A#7!19?_A?AC@AC@IdGQcHQc@A!8?@#5?_O?aW?gCBSARSks!5?aQ@p?@PQ^~|~RBHaJUhQD?Ta#11!16?C!7?AC!8?@I?@A@?A@?A@#23!14?A?A#12!11?O_GO#14!4?O???O_???@!5?@??A@ABS?kp}~dc\iwzz|qzP^}|v}l~S@??O_O#20!22?A@A!9?_?_?aGBQ_BUcH`ALoLOdZcG!4@#30!91?AP?_oY_c@AI@!5?AK?I?DG#21!16?S_??COI@cB?RGBSjS_SJSItAlSGE`@@??AHC@a@T_OA?cO@?A?Ao?O__O?Og?gcQGC#26A!6?C#21???`??C$#31!10?O!6?G!23?C#4!8?A!14?@??_?_O#5@?@_@O`A@i@A@A_Ac@sGoGOgSgSgUkPlU{S~O@dK}Q?g??A?_#32!40?OG!6?_#3??_#25!364?@C!4?A!14?A???G#24!5?A#26!4?O#34!18?@$#4!89?O??C?iA-#1wsDGahIXXKCjGkMzm|u@CbCFLfAojDe?e{Jbmcja\b[rKDalbDFBEhU`NO^o~Oni?OiO_Po?AP_!5?A_Jc@Q?GP_@W?A??tywoqciHOiCQvxUl?LAoIpC?nOKOCPAS??_!5?O?O?O?O?gOg?g?G?OCIDO?Yp?@s\^MfZWkUKxKWm?MI??A!4?O#9!11?cAxsjSzYt{_OlIbGHCWGS!4?O_#13!4?G???COhOdW_MOlIcJWFoHUHu@rUhySstjQKytuF{NyCjGrgQEPvLJj`^b|rz}utop}NooWwOoOW!7?K??SgO!4?O#17!16?_??HC?HBGSjWKV?A??C?G_@`@@@GA??O?O!6?CIDI@IDI?@OGQOLQHEGVGABGCDBKFNIzYNyVm|J~y!8~Zun~nVnn~~~}|!5~n~~~v~~~^!7~z!4~iNOINIt}`EgEoc|QlbKbCJTIlJUjM^onoNpm@M`AD?G?G?o#21!4?@??sA@G?C@O_O?aH?I?VGTiDgTChUa\CACI_M?E_AG?Oop_oR{UOGcAOI?GAO??O?S?AG#34!4?G#21!5?KO?q$#5A?iOCOc?e?Y?U@O!4?[qGOG_OGC???O???S?HOC!4?_O??OgOgO???O_???_?O!8?G?AShQcHOC??G?c?EHIDaTkPI?DIDYPSnTzlGAg?nQ?L?C?A??@!14?@?@?@?@!4?AC@?N@_OId?EYsJ?__O?dA?_AAc?P_`\~HYe|OiDw@aC#10!14?A?DI?TKvQjbrj~}~~bZ~|vypSqMpaSgIfMpjQdZoFwJqHuHuKgQDi@IShqDAG??oCzCrKRkPmGQ_SI_GAGC?H?IC?_#23???C@?@?CAG#16!6?UgTgq?QyI~fYnj^tz!8~^~~uz~u{tjSdBgQdXePiDQOS_GcO@GA?C?_?C_!13?_?_???O_?_Og?QG_Q??D?D?DG@As?D!8?cHO?O_?O#30!14?G#20!18?AC!6?AGDGA!21?_!8?A?dWFII@NH{QVhIMDZkJSUZD~gviTyVijC`TaxkZtGpKoNwDzEDALE_@_D?H_C?P?C?HC@???O?A?H?C_!4?_?C$#0DJOfXEPe?p`S`Q`?@?Ha@S`oQGdISiXmXBsGPQCXa[bKPaOO?OgOhS`Y_?_!5?D??I?I?I?CHaScQchQ!4?HeGAC?DG@I???D#6!9?C@Q??LAtIjxOfolymtjV\Elze\zn}n}n}n}UnU~Tzu~_oOidO_?_??_?O?_?O_O?o?O!6?cH?aCw@iCHA\q|q\IdAHCjCDGBYdQ_O?_#11!8?GC?AGDAhL?ILAD#15!8?C_?C?_??G??C?@I??C@?G!6?O!4?_??_S?S?S?C?@?IDA@O?K!4?_!4?@!4?Q!6?_?D?O!4?IC#19!16?A??Ao?lWeXiTqLmIYeYBkElfh`\qzYdIp_o_S_OtnMNVlNqLuhVgVlSfHa[@wooC_o?_O!17?GO!4?@A!5?O!7?_!7?C!4?TolpotI@]xT_I??lQ[R?R_itQshSp_NONoMP]p]|y~v~v|CYaots}oA@C?O_oa?@??__O#27!8?OAG??A???A?@!5?_G?O?GAG?Q?G@?C?_?A??A?@C?g?Q??@C?hA?@$#31!9?A#2!5?COA!4?A#3???O??O#2!14?AGLAKA?C?AGC?N?N?N?DydOdOcD?tiSHAHHQC?sZulQO_Oo??_O_?D!5?_#8!10?_!7?AA@#9!19?@?@#8!6?ID??IDG@??A?@GCA?HAC@A@mPSa?s@OAL@AESXq|aLALaPWCAO??_#12!8?COCC??@??C!5?CA?p???AO?P?C?O#36!5?C#12!5?@!10?@oB?@#14??C?C@G!15?G??NBfBmNMfz|v}~~~rlhBAFL~LD_?GdOS_#20!34?CO?cOpO?OMAL?DYtITITiTi?Oo_!4?_!7?O_O?_#30!71?P?R!4?_[gS!24?J?DG!6?g_EP?GCAsH?C_!9?O?G#22??P_?T?qHODqCHACAHC?@idQTiTivOnsjm\b}HvT~c~rMz~U|RezL$#31!20?G?G!4?@???@#4!30?O???D?M?It#3!9?H!5?@?C?A!6?A?G@#7!16?OC?G!4?G?gaXQCXaC#4!24?C!8?@!4?@#23!92?G#37!101?GA#30!191?G#26!10?_??H??O??G?_!6?O#38!5?G$#3!65?I??@#4!22?_W?AC!6?CA!8?O_O#32!46?I#3!9?@#25!409?_-#1iBPLGVDZwdoACe~~@xTppFYTnzKuGVKTy{vwpdVnOgUhvDYDcucwwJADbdnQ`qJ|?TzEH~_A?@?OK_OG?IO_O?OGA_D?d?@LNFFDB!4?mPI~ZlYsgUhUhUGqDiT?T?T_AO`Q`?_?_?_?_?_I@QcGTgT??@S_DQgSJt?~`UDSMeTKcTG#8_?\aIsbK`!4?D~ULV?C?OBVg?A??C!9?_#11C!12?g!7?_?w#15!7?OA???_C@!9?G?O?C?@?@g@gAODOAS@_@?A!6?@?J??C?P?G?c?_Gs?g?i?@??AG??@_#17!14?AO@??O!4?_WcYC@OMDa???tg!7?UCAAaaQ!4?O!4?_C???@?A@ACAGQcCA?iOIto}]}|}v\vd]!9~Zun^~y|}!10~^z^!5~n^ux}z}x|~|Z^VZ|}}~~~}?i\~~vn~kmsxg~s~{z|~o~}jSjS??i#20!8?BC?jSCh?e@?AG?oJ?D?Js@s_UhjtxVczUlQ[AdAwExExAhQ_@R?G?~?@SB?G??OA??AP???OG?A!9?G$#5DWi?tGQ_E?IDo???_C?CI?C_?C?@C_???@?CIOG??B?S?G_?Y@GBC?Sg?O??GC!9?G?SGb?AC?`?C?DAC`?G?Y?YcO_OGOGCRK~Pmt?c?DB#8F@A#5!37?ISaHQgDBoGj?SGqI`PIaRIe^~a\oB[b]]PiTi!4?i?A?g#10!10?RM~lTJ@j^|Z~|~z!5~V|^oFIdA?GCf}@}@}TjSjSjSjSjS@S??~?^C^_NON?FwB}DwFwfOI_@OA?@!6?@??DG??aO#16!10?iODgTvlI]N}N}N~^!8~|n}~~n!4~^eZcJOm@qGTiDITaTG_O?H?@?O?G??c???_COgO_gAO??W_GO_???GPc!6?_!6?Y`!9?cHO_#19?DA@!10?_?_!7?@E@C@EA?A!4?A@@???@~Ta??G??BPHEU?J?BCA?N?@SjSj~~T!8~{z~SjZEvW[v{uJEs|ylo?IIB#27!6?G!4?`?G?@!4?C???G_?O???A??C_?G?_?g??_OC?AO?S@GA?a?OC?P$#0Oc?q?_gC@YDwJP!5?GCw`IO?rGrGriDAGBCI??nShAGqDy?GRCBC@A[IOGUGC??ACHE?I?DgS?`CIS?`GE?DACoEGCICQ??_O#6!11?Q_GOgChE`rLyTi~i~i\{n]k]|^~^~^~^~^t}lZviVi~tiHUgDQgCAS?I`G`OG_PG_P!4?DG?O?`iTiO!4?DRhF???@GOa?HQcGP?A_SG#13!14?A_COdGtJsAW@}@}@iChSjSJOiSj}j~~?~?B_Nojo{wES?QDgAGl`]]m|\}}~^LreMS}OarKGDDG?@!9?@??Q_?O#19!25?@?@om@oGTiTy?A\aS?D?cgy|l\SltITIDJoFTMPVlJum@]cHXeHQpkXSltGN@@@A@GaG#16!30?C!6?O_G!8?C?GC#20!13?O#30?O?A?@#17!30?_O??_#22!26?`?GAPCgAhC`OAK`?^?^CjCZUxsj\rE|iPfwFtj[b]RlvTzlYui~$#31??C?A#2!8?G??[AiA!18?_O!8?@!4?ogO???`?@oAzg?oo?TtyAbGQH``]SbPiwhQH@q@O`?_OG?aSB_A#7!11?O?OGC!8?A@??@?A#1!51?C#9!4?GqgOgSgSgV}tn\zulZc_?OG_SO_?_#15!15?A#36!27?_#14o!5?A!15?_?@?_QCGo?@iPKaTayR~]vJ~V~TTmyUg??T_?@o@o?_#20!34?Gb^i~Q@!4?@?IPitiSJgA@I?OcGPc?PaCPcHA?A@A?A#30!57?!4_!52?G?AG@!6?QCJs@K`U?A#25!19?C??C@???G!9?@!4?@$#31!16?A#3!42?C#4!4?C!10?C?O?A???G!4?CO!5?A??_G_wKp!87?`#12!21?A?A??AC?A?C!8?JgOOGSB@#23!51?_GO?_#21!225?@???@sH?A!6?OG?SGEgRChQlA[QtCgAPCOQKJs?Ye_?_O?g_?E@C?GO??KGAG?C`G_COG?CA_@C$#3!91?_??G#26!473?_?O?@?A??C??CA??_!5?_??G??@-#1|l}NuNFveuNw~nZ}{}w{}S}cv~}!4~^jn|jmt~|iRUkKm{W__~QHZlzmZuLqCh?S?C@dAC@{mWvykSwD?@YDa`IcId@?_@AFfFgOgcoNnVbsy\LfJPfuWdWA`?`?`A`!8?O_!4?@AcHQcHu?TICAwCqAew?C?S?cWAB???{SS?G?_?J?A#9!8?_ITgA|AlYc]hFoDYjvhD@aH?fHAC@??@#12@?A@?@?@@U@?@APB#15O?G?C@A@!10?A#12!4?@A#15!7?aGA!4?@#14?o@O`O`O?O_WCI_dw{}iiutz^HclmidGUiS@gUhQnTiV?__G#17!8?c??@OC!6?@?O_[?oDQg?R_oao__o_A?HDFA@A!8?EB?I?_EkUa@aAEDaCEO?E_?L]n~}n}fZlzmzmzmZulZ!13~}t~}!7~ul~~~n^!4~}^!6~}!4~qvztNVj]n]rjf}Kn{vFvD~iV|A|Js#20!9?IDYDIVI_G?O??@?UwFlYtIsZ|VYnUhZU\ULYTEdA{A\IsiT_?q?x?K?_@AO?A?_?CA@AG?CA!6?CQ?A?O$#0A?@?@?O?@H?A??c@??CA#31!4?G#0!7?S?AS@I?ACG?@aPAdIT!5?CPcHaGQ?i?QgQ?OG_B?B?@@ADaCi???Gt!9?GOA#6!10?_?GcIO@BAD|YxYx[|Y!8~NVj~~~}|ZulZuH~_SGCAGC???_!8?@???g?S@G@S_IW!6?C?OogOgA|AdY`EwDY_C#12???_#11!11?_IO_Cg?G#13???_O`m?lBsBwAwC}tj\uZtnYtkTynT}hT~Ti~TyLSpmZvnuNDmFMFMF~fFfRTJIDB@@T?I??_Q?O?O#19!32?A?C?G?`@?aHAgCBk?[?GCCY?EkQOYWKUGVMJHHLJH_{jTzUhQg\m\lxg\iXjUxUlQ_O??O?Oc#20!24?@A#19?@#16!7?HQ#19???O_#30!4?@!7?@!4?KG???_S@?@C?G?O#21!23?@#30G?IDGD?`!6?gEgQdGsGcA_@#22!4?_?G?c?gAhAt?TGTgCg?s?Z_TOaThu@}V|Jt]`uGqLyeP{jMyLzk~i^t~$#5?A?_GoG?O?_D?O??B?B??@?O??@!4?_?O!6?@ChAP?@ATI??c#4!10?S#5Ag??OG_??@!4?@A?yC_?WC?O?OGQCGOgO?TJEOIoOg[JDAqOOcG?C?A?C??A??C#7!8?_GS#8!12?I?AwDq?cGCO???_O_O?_G!6?_Si?TDiUlUn]j^NDAFT??O?@#10?O?I_DOGUy]\u~Wu|z}~~}]tl]zU~u}h}^m[?{A{B{B{Dy@ISaHcIOdIPiDOi?Si?iT?iDObKPcGOGoGOgOgOg?GO?g?O#23!4?S?G?C#16???O?P?UhTjiUhUlOiTgj]^V!8~Z~~}nz~\vz~v}]m^@uLQhSBkAGCHQCIP?dA??_??_?O?aOC_W?S_CHO@??O?O?O?P_Ch?HQ_@??@?@G?QCPCPCPcHQc#30!14?G#16!21?_!11?@#19?CIoG?_O_GO?@BOBGwGy?TgA|AsJ!9~sqdoo_o^U~n~~}~@@O??A@B#21??GcOhUcHA`q@IPOS?GA_A?APVHJCcAiD[?CHg???O?_G@T@O@Ge@O`???@???G$#31?O?O??_?G?O!25?O#2???O_?O!7?lQcQ!4?ODhU@hDRkIdR]?OcGCQg?W?ODyCA???IoGGcG#8!20?G_W_??ECCA#5!26?`p@?@HXPBNz~j^JFlk]v~BAjjuVI@S__ThQhO`O_#12!89?A#15!8?G??_COA!4?@??_UHA@CI`???S@!7?S@?_#20!15?_G!13?O?@FPAH@DGPGc__@gtGpcsSaoS@!7?@!5?A!87?CO?_#27!55?_???_?G?@!6?A?C?A?P?I?GA???G!4?O?_G_CPGACO@_CO?D_A$#2!7?G!9?@?@@i@J#4!57?@???@??@@?EdRQD??g?_PHD!65?O!4?@#37!198?O!5?A???A#25!171?_?@??G!9?_???@?A$#3!86?O?Is!5?_O???C?A#26!470?S???_G?C?A-#1^|~}~{z}[TVunQVk|~zxjzBb]z|{~~tzJnN~nNn^_uKPdnRRQDzaXBZEXF\a]hSkj?IWCG?c_UKDHpTP?_?aHA?QSBEgAC?`C?_?H`@jSZNHaP?B?KSAuhU?mGq?@Q_E#8!8?!7_??_???_?AeD_Z_QK`A?A!4?A???S?_W?VGa???[_GToDg@?_CGGCKK?CIS_?O?G#12!16?SA?A?AAyDGC???OC@?g??G??O!27?G?GA#14???@?OG_@?C_XlIWkfVyj}vX[}q~@qKiDtITgQlQCT{iT@?aA#17!8?CgC??@G?Gs?Y_?W_OJOGA`WFG?@AA@A??_C??_??KO??G???B@G@B@H?IEJ_ABAA`{KisKkwT[S!4{|}~}~}|~~}|!6~j~j!5~nv~nz^ZaZnVITFVn^^IT^NFDNZNN^NNVFFN~~~|zww}{uwys|l\qnVjUjRdJSJ?V?CO#21!7?C??C#30!7?_GOkOm?A@?@#22!4?G??Q?G??Ca?aCHQCI`ATGdIpMpMXeHYAlOjMd\bmTgUyTMWePmjU~Tf]zl~|$#5_??@?AC@?aG??H_A??CCSC[S`C?B??I?s?o?O_O_I?`IO?GcG_??C_?_?_!7?S_C`QcGR_@QCAGaCIPCOcWD?g?P?_@IOgQLaUkOjCoeOgfW|rAG@?_O#7!16?AC@C?C?C#5_!7?@?W?GC?Q?JPbiPI?||[jnZFD_??iT~_NPiDy@Sg@??O??_#10!5?`G@UgMOfIIuznzunV~~~jx~|~||Cytw{GsHoKoFgRCPeHaHQcHQcHQcHQCHaSHQcIt?Y`MOB?A@_[_TGQk?T_@A@?A#15!9?A@?K?m@p?i?cG@!5?@??S@#19!20?c?Hc?Gc?e@WE_O`QK@Ok@U?OGsgCQgPjulRulXcIdIoEOjcXsQlO{`]@bTI`RDA_B?B?B!25?O??O???KC?GC!6?`A!6?Oo_oo_wwo???A?FD@BHFDJAQaLOgShSkYCjSNGZzn!4~z~vrzvrz~~~nV~BFBB_@O#25???_#27!4?_?O!4?O!8?_??G?A!7?O?@?I?@GaG@g???I?c@?@C_?a?`CO$#2?A#0???@??bG_HOcGPA??A??_#2G#31??A!4?C!9?@#3!21?A?G??G?Q??G?_OC#4??@??G??@_!4?C@O??B?_O!7?KCOc#7???C#8?A@HO@#1!22?[@iCPEXCWaDcPhrKocGS?ePcAAb?O?_WGv\#9!5?A?A?AATIOdAGPATiTgVGeS_BPbGPdHCOCHOg!4?C#11!5?@?ABBB@AA#15???@?@A?A!9?O?G???_?G?c?O?G_?_!4?_??OG?c?OC?A#23?O_?OG?D?@G_#16??@??GAT?IPaUlQlRiATii}\|!8~zVz~~}v~vJZdUZfTJsHudWFgQlQcHQcHQI`IPICQ_CHQcHQcGoAoCOaCP_?LOk@[?AO?@Q?AgBgB?B?A@?@?@A??@A!6?S?S!6?G??C_c@?O_PIGG?_?C_?OOQ#20!35?o???_C!6?C?GGCGGC???Og?[okONONpmXMZulZU\FlYuHNaOjSzUlYTGtAtGpMpM@e@?CgO`!8?gC?O?OcO?H$#0!33?O???O??SGQcIOcG`!5?_@a?A?@A??S_S`OdGQK@qGaGAKQDiO?OC??O_CHOKO`?GQ??A#6!5?@A??A?hpGSGC@uL~}l^x!8~\Z]Z^Z^zB]TzmXex?WA?E???LOcGSGcX!7?_???Ti?BOc?G?SgASjQdraPiP_BGU@i@S_KOcO#13!22?sIcHqNOUkqkXc\ulZulZulJudzu\JudZPIndUPn[v|u\B^iedBRIMyx[e?TfBOg?S??Ca@??OC??O!7?g#20!31?A!8?C?_Wc?O_lDQ@EPhDA!6?AOCOCGgCO#30!55?O_??!4_O??OG??GG?C!5?G!7?C?A!19?_o?_#21!26?_KOeOcHQc@ag?d@u_XHSH?_?`?SG_AOC???_?Oc`SACSOQ?SOAT@@?`BGA!5?O!4?A$#2!40?@!7?CYC\a[CWCW_\_SjO?B@AA?@@!6?_?gOC@eHaG_???_A???C@#32!20?a!44?@!8?C#4?A#37!190?G#15!86?O#18???_#20??!4_o__#25!82?C!7?A!8?O?GA#26!5?_!5?A!6?G!5?G$#4!63?@#3!24?JCXAOGaCIOC?CGOC?_?O??G#34!473?_!5?O??G???A-#1n}~~|mulTnnY}~y~rv^R^BNz!4~n^^vzNzzZ~J\qLrTi|aSsyP{tgk?OO_Go_O??Xq?G??C?AK?@gosB???C_`tO_@?ACGAEC?GWOAmp_QhwcuqOSdWErMOChALQC??P?PAO??G??O?O?O??_@?O?P?GAPGcMThEOaDpAdSO?WSOXosLAN#9!5?G?G!5?PiOdOxQszszSRGCD@ID?K@O???O_#12!10?n??__gOoooOo_WAO_O?Ca#15!8?A?@???CP?GA?GA@!4?A??Q?Q?G?C_?_?O?_?O?_O???_QGOiT_Ap???O_T?Wa???D#17!15?@GAG@KADISJO?G?DA???A??_???_!4?C?C?CGAL?KACgGK?CW?BVK?_CAGCFWY^Oy|[BFFNfv]TnN!4n~NN!6^NN^NNNDNLJBDF!4B@O??@@#25GG?O!4?G#43C#17A???@!6?A?@R@BBF@VhFXvMJKBkB]_H_K`A`?WeBODOCO#20!9?@?_?@I`VOj_UdBej]DOcZ}TbUlZulTwExEz?rK_KpCGcZCB?H?LAG?GBC@C_??BO?@O?D??C?O$#5O@??AOHOiOOc@?C?KG?k?k?C!4?O_?G?_?C_?_AC?GaC?HAH?cA?APAl?DOD?DaS_?D?DA?@O`OkOB??CGBGPM!4?G!4?HQlAFlGPMVlUFZHHFjG?_#7C#8@EA!7?CCG?KCKC@A?@A?@AmRKaLMKIED?CI@IC?_?o?_?_?G?IC?@?ALo?_ShcOA_DIaIKkCKGK?KG?J?AGAo?_??_#11_!5?C!6?_?_?_#13??_QOTaK@M`MPe\iTnTiXvi\vYtnTxmczU|qIetXudOkWiZNg\ALG@LcbQ^]Q|h}OPKqOJ???C@?A??G?GA#19!29?C?A?G??_?AS@QgPMHE_?T?oaYN_DYaHqGtje\A^q\zSfonYdZsGruLzlujwaD?iD?bogoo?G@I!15?O#27!4?_!4?O??G??OGGG??G!5?O?_?AO??O?_!7?OG??O?O#16??__???_?O#21!28?_???c?G_OU`Ys@O@OdR_@?S_Q??AAEOAO?HGBIAGG_A??O?cOa?oB_OO?_ROk?G?C???_OAA$#0!5?@?A???@??@!5?_Oo!9?COC?C?S_HqCGPAShADI?ITA|A_O`?P_SaC??DADAo`O`OE?AG?EG@G!7?@???HAD?AC#6!8?Cg?QfXGogxU|qlz~~ize|bzrr}TNulvmTOKq\aPaPoWaPO_?OHEGICHI?cO_?_!6?DI?OI_T?OdWdQA@b?bC`BC?Ch_c@Y?DOM@YCi?G?C#14!12?O!28?O!12?A??_??_?_C_Q?H?@CAA@NE`LEc~l^JktlTiATAKpN?N?JED\ID?OI@#20!25?_???_?OG?i?NGD?Y_DSODQ?O?_o??_!19?_!9?O!4?o?O?O??_?___?!4_ooO?OOOGWWSO?CCAA?AA??H!7?`@B@?T@@BAEOEMCMGKW?GOO!4?Oo!5?L?E@OHEH?HG#30!17?@A?F#25!4?G???_I!4?G#27!6?@!4?C??P?A?P??_?OA!4?S?_GQ?GAO?cAG?AOGA??A??C$#2!18?_#31!11?_#2!20?@!5?LACICI@GALyQwGXKKAMA@KGsJP_QE?Id]yTwaDGO_O_??O??G#3!13?@#7!17?g_G?G?G#10!60?CPI_]oiPQcjT~rjZ!5~^~^~^O~NLNALBM@M@M@_DI?iPCGTaGdIOiCPIChAHcXAcHQlQ`O_?Ta\_VgQ@[_?_!5?_#16!9?A???TgCpMonOis`Watyynt}!12~}vxv{rtytJsljUlQkPuhT~?^?T_ODY_HaG`I?P??_@??BOBO`AcG_?HQ?O@O?D?_D?A?KOG?G?_?O?O?O??_O_???_??O!4?I?aC_A!7?@???@@??_!6?C??A??C?G?G#30!4?C??G!6?o???_??CAB?GKE@?K#22!29?C_I?GC?i?H?cHOg?hChCqCoCpCrCXcZcnON?|EgVKbkZClB{B|qN{iVLxtlzNz~~$#4!57?AGA?I?G@???_?o_A!8?__S!4?I?CaDWat_??O_?_#5!40?_@!4?_?c@_Ka@O_AoHT?ISOJJfF`JeMJoo?yTjEPFgViODO`?O?Q?A#12!19?C!56?CCCO#23!12?H?C??GA?H??Q_O#30!111?_!7?O#25!13?O_#44?_!4?__!5?O?__?_O!5?C?C?C?C?G#19!4@!6?E?EGEGpCBKB[`^qOoWsAo_Ooeqnzn!9~}^^~}O[_GCHG??W!27?A#25???G#26??_O?@!13?_???_!4?O$#3!79?@?O??_?O??@!4?O!7?@#23!172?@#21!161?_#30???C#42___?O?O?O!5?O??__?_?GIGMGIWYSSwCW?_o!5_#34!103?@!4?G?C_$#22!445?C?G?C?CG?AA?CGG@L@@?OO_O#47?___??___#34??O$#40!445?G!7?CCC?A?CGA#37C#26???_#14???A#49??_$#34!446?C???CG!9?OOo?_??_$#41!446?G!9?C?A?E$#47!447?_#46_?_o_oOO_#24@@@??A$#19!448?A@?@#38C#39???A?CA$#48!449?_#21?A!8?O??G$#45!457?G-#1YT]z{zU\z}t~v~V~vN^^~n}lANJ]znXqzJ~n~lk^SJsJ[rQhDlRTzddha_ptWw\nNVLylYgk_o_`_?_O!5?Y?Z!7?_O?_??_??lQm\jvzzWlQ?dQ?d??T?T#9!4?C?C??C#8?_wOo???@?qI@c@Wd!9?AHC?ICA?AG?G?C?MPMpIdISpCaPIOcG_???_?_??C#10_?PCJ?VkRkj{~]~z^v^n^nwjok`qwd[wmw?|Gy?s@K?A?@S_JShEWaHQcHQ?QlQCiOdGQgaTiOiPcGrCJ_@A??DQ?DO?DO?C#16!7?CGPiSaHtA|Jt~}|~{!14~^f]fmZnLv|z]tnYVsjUxVhuLqU`Y?T?oDY_ISaG`!5?A@???@!5?_@c@GC?K@@?D?D@A@!4?@?A???C#55?O#53C???C??C#39_?_?_#34?G#42?A@?@@#44@!7?HCGDABBH@#47???CAE???AA!7?@@`A_AAgQ#30@?A?G?o#20!18?{_isnoKa[oxKsSIGyVdTiTjUPxAl^oJPNySmPmXtGPE`EpM`IOC?aDA?`?AC??H?AG`S?G???R??@??HO?OA#26?@$#5di`CBChaC@I???g??_?_!4?T_S`?OaC?c?O?QA?`?I_AGC?Q?CgCOIOC^CIEDA?O_!5?Q??GOGO!5?@?d?c!6?O?CHCHUHUgQKPaSGCCDA?lQGdO?S?a#7!6?C!4?C#5G!7?_?@_?OCA?OcQGTAgD???A@?@^XTZDJH^pmpMtITjKzSKpeGQ?O_#32!14?@#12!5?_??_?_O_O??C@SG@?AA@?FA@@BA?rShUgA#15!23?O?O_O?O_?Q??_?A???A?D?A??A??A?CB???A??@?GS?O??@A#17!16?_W`W@cOqGA?`I?dG!8?LHCD_???A?@?@??C?Ww{Gw_Q@L_ISaKPD???A_B?MKMIC??C?A@@A?B@BB@@#58!5?_#40_?_!4?O?O_#43???C#54?C?A#48?@@#40_#34?_?_??_?_O!5?@?jSQ??CEEEA?ECCC?GOP@#15?A#16C??@??o#30!20?A?@#21??_HC_?i?OD?OiTiS@KAlO?I_K_?A@COA?CiOW!5?AIG?O?PCG`?qG?cx@C?A?eAW?cO?G_??A$#0!12?G???GO_???@AgO_?C?CHCO!4?P_Is@S`ChUgQgA?IOEX?I?@A!4?_???C?Q?O?OgOi???A@?D#59??_#3??G!6?C#0!5?`#6!6?aOlQGdYI~ji\i!4~!4z~zzVEnM~}|}]Lc[QKAO|nQLtAgT???Q`!7?_?o!6?O_?A?HaCHQdGFGVGTI@}hQcIP?BgQKR?@???C?G#11!4?FSJQGDAG@#15!7?_!9?_GS?COA!4?Q#14!12?C??A?DG@C@!5?DOigTgDoGSo|NZ{~hvkTj[uAhAcI!4?B#18!18?O#19!5?C??O?_G?@?G??O??Y?L_yH_CQsISvY{fFBsCSk}q]tj\rmYURm@RWBOOO?GGK?EDEADA!4?AA@@?@#41???_?_?_O_O_?!8_?_#55C#16!5?@?@#20??A#49_?ogOkWCGC??B?@@@A@?A?@A@#56CC???o#10O#25!35?_#30??gI#22!5?_??A_C?AODg?i@cGrC`EpMpMpkPa\a\eWb[jGVcZ?eWjWvGxfgJj[fZUmXml~Y~~$#2!21?O?O!36?_?_O_GQDQdR@K@E?DAD?SJOKS?y?JDyNAD_OgUHq@CHQ#12!28?C#1!4?@?@?@A?@?OAHa`GA?H_AgTAy|uh[sz{!4_Oo?_#9!14?@?VgVgViT}@QLZcis{??_?SA?@#13!12?A?CQ_DOFw?uC[Hu?jShUh^SbAxbLslZulllQlzTnYvlVLi@NDkJQCgo]]||~yglOAiOiNpjN?o_#23??Q#20!46?BSgE_UHa?_?_QIDCW`K@_H??B???B@I@!9?gGOsGco__@oRoAXGwGK?KCKCC??AA@A@A@@@?@?H!5?C?@#53?A#42!11?OGc[_]~NVjO`OAHH??_?_?`?a_QOD?GO@#19??@?EEJ?Go|ohyhwp~~}~}~}|B^THOMr\BEARJ@Tf!7?g#24A?O??@O!4?O!6?G#25!4?OC@_???_G#34!18?O??O??@_$#4!72?@M@EADIDjsnoi???sYDotyNNB_Q?gQ_D!65?CACACA#23!67?G#37!124?A#42!33?A???A#27!4?O!4?_!9?@!4?G??GGCCC?E?G?GC?K?[ASQOqGOW?C#22!8?`?O?WOGWGOGGG#57?_!5?cO#60?_#25!46?C!4?C_??@#27!4?A!4?G!6?@?G??AO?OD_Q??O??c?PC???Ca?C_?C@O?c$#3!75?G#25!326?_?_?o_o??OgOw???COO?A?AGIGGH?HCDB@??HGI??C?C?G?GG?G??C#41!12?_#40_o?GO?OGO#21?O?G#17OG_???BCCOwCKfNANUDUFM??@?@?@A#26!51?S!5?C!6?C?A?@!4?C$#22!410?_!9?_???_O???O?O!7?O!4?G_?_#21!6?_#14!14?C#30G!4?C#20C_???C!8?B?@?BO$#38!411?_!9?_OO??O???O!4?G?G??O???OWOO?O?_#17!18?C#27_?_?_??O?G#14??_??G?GGG_$#50!412?O_?OWWWGKKGKKC?ECAECA?C?B!4?C???AAA?C?C?C#51!16?@#52?@#36!10?C??cW_$#24!416?_#26___??__O??OO!8?OO??OWO!5?_#51!35?A$#46!428?A!4?A?A?B???A?A@@??B@B?B?A$#16!431?A#58!4?O-#1Sxn|~i}XxV|~~j\~ZvnzvnZu|^j|!4~Z|~|~}|y~T~j~]zu~}V!8i~~lZulZtntZnz_gB?@_AG??O`A?C!9?K??@GSaKBeHviXy[g_oCW_CI_CQc?C?C#5!13?C?C!8?QNil^IVKRkZd^b\fH@?@@@`_qmbaGReOdY@UgQ#9!6?A_H?TITJSJLBdIPCoJhB??@??G#13!4?O??_@_OdQ`Sb[pEhK}~jilRsJsRc[jEdzSBelJnVjZfZvLwSKyTAWbk{kynfEU~^ZLNdEd??}iCP#16?i@?HQcHQcIPehY~Vzl!18~^cZe\bVlvk^yvKrmXUliTMpChQdGQ_?Q?C???G??C?CAA@!4?@???@#62???A?A#58C#40!5?C?aHA?A#58!4?@??@#40!7?_?_O_Og?o_GG!5W!4OoGYEE#30_#42w?C!4?@aG?OCCC?NRkVTJ#36???AGTkVXERc`G_#30BLOA!10?_!7?GOiTA#25!7?O!4?O#27!6?G#22G?aGOHU?QgBcPASaH_U_XeGTICdYCjOdIcXcWe[b]he\iq]tZVsZlz^SR|T~~$#5gAOA?T?_Cg???S_?C??C??C@??OA!4?c??A?@AD?i?S?`CH??g?T?T?T?T#2??QcHQcIOIcOCZBcB?B@Bj`NOdRhfGaShdJOd?@#6!15?G?xEX?dAPcRdjtj!13~z|R!4~kznYL_DQ?DGR#4!6?A#6!6?_??G@?X@KXH!6?i?OAcGPIcH__gOgcQsGPcI@CO?_@O#12G?C??c?C_@OAA?G?GHCAGwO_???@!12?A!6?SCGCGA!8?O?O#23!7?_?_O??_!7?GA#15!9?OC#17!22?_XcHa[gQG@???_???_??_??I!6?c??@!4?CG!4?CA@?@@!6?@#63??A#38!7?BA?A@!25?GG#34o!8?__!4?XBF?@?@???GeyL?B?@??@#51g?cGOcO#66O#57A@GeXCHQ#61@?_#17Qmo!4~Pn?BC?I#21???__!6?OCOc?GAoLOb?TiOQdQCYOaOC@e_?gD?W?Kg?GCG@C??_?`O?@oCIIO@ABb?@G??P??@???G@!4?HC#26?A$#0BC!4?@E#6A!7?_GO?GO_G!9?A#0!15?@?T?T?T?T!14?C?OGo?o?S!4?G?O!9?GQc#8!16?_CyOWgGGWOGO!14?Ag!4?@??_?O??_O_?kRcY_[_WEOiCQCIPCOK?Q_?eYd}hVlT~n|ZvkTQuITACB#11?_!13?G_?`WW_HOB?@LAHC#23!5?A@#15???S?G???G?@?OG?_GO?O!8?@a@??@!9?_!9?A!4?P#19!37?A?O!5?A_C???O_?OO?oMpCGQ_?WXguOMFLFYCGEAH@?ABAA?@#30@#27!16?@#26!28?O#27?O#49_?___?_!9?@A@?PP?P??_?O???k??G?c#68!6?_#64?_?QKo#3B#14C#16?@#20@!8?__O_!4?_UomTilz_OsPgA_LSniDnlYlzdfTnXsHEhRgV_JaPjTqRgZeXFiSjIcHO_?dY?WCGaO@E?aCG?A_??CQ??_$#31!10?A???A!9?A_C#5!46?CGSMSKS?A?CG?O??@!6?rSlItaLqCPeGTeC`VVNA@A@?DA@?A?A#1!21?QCOD_?O??_?_!8?omTykYSM@?OCc#10!22?_?O?GQcIpMoE{^}mvvR~]BfJUmK{[aOcQaW`E@CQ@?S?QcJsJcZaShQCHsHQcOg?_O_?oEHqDiSbK@A@COGo#20!61?O?@GRK@EHADI@??QdGVlFADHipgqo@RRXHCGKCCG?C?CB?BA?@!5?@???@#42!31?_#52?_#47???_#58???@#22_@_#47???sos_I_KC???WGwmo?Q?aORNJ@F?A!9?_#19?K!4?mO~{Z^D^~~^^^hF@!4?NJJeTLAaGO#25!12?A?O?C#26??C#27O?C???C!4?O???O???C???c??O?C_OG?P?`GC?I_?C?AG?g$#3!73?O?_?G#4?_?[_IOcAGv[jUYsnY???O?HO?GG!4?@A#14!165?gCOA@A@?OHH?_CqOIxY^|@TzmmTu|ulZulZtmXE`?gCQ#37!53?O???_??O?O???C!8?A??@?@#38!48?P?G#44???G?G!8?_#56!19?g??COG#26!74?A?@!6?@#34!7?C???_!4?_?_A$#3!97?a??A??@o?O#25!270?_???_???WGOC?C?G???C?@???B??@!5?@#26!39?_#27@_[?A?A!5?@??A?@#65!20?AK#67O#25!77?O$#53!379?_!9?GG?!4C!4?AA#25!50?O#52!4?G?]_Mo!5?_?O!7?_Ok_gO$#50!380?_?O?O?O?G??G!8?@#57!59?C?A#20?O@???A$#22!382?_!5?O!5?G???C!6?A!6?@@!4?@#40!46?C?@$#26!383?_!9?G???C!5?AB!4?C!4?@@@#22!49?A$#41!384?_!4?O__oo_go_wsocscsc_cCO?A?A??_??`_`o`Oo_WWsWmSkO_F!6C?C@G@@G?C$#40!385?_#39!4_OO??OOGW?GGWGWG!4W_!4{}}]}}]]]N]nN^fFJF@JBF^?!6BFBMFMMEE$#21!389?AA??A??@$#46!391?A-#1x~vvN~|~~^]|bnJvpvy}~~}~Yvl{Zvnzx}|~~z|xtz~y~xzuvVfuM}QUEuUfF`NAtJu\j\G`?Q\uKWTYC_OA??A_CCGS?KOCGC?KaKT[~LR[z|wTi~PnOgA?kOKOcG_G#5???A??A#7???A#9@??@#8A???_!4?GOJCiPcGADAK?CGCPa?@A?Pi@_GdWswlTAhATBHVaF@BA@BgAA?A??G#12!10?_CScK_!7?@??O?QACGe?@A@I?iQ#15??C`Ga?g???_!7?C?C?SA?@_@C???O!12?A??_W?_?`S!11?@?_#17!16?G??A?@GQ@?O?_QGOK???A`?B@CCC?O!5?@?A#22G!4?A??@#83O#73O!4?C#50_#86?o#74C!5?C#39!8?@B??@BCHELDA?@?@#38??GSI?OC_?W_WAA#42W!8?D!6?@@m}]A???OCkuX_S#51??OaHC@SJ??C#64!4?C_Lwo_AA#59A_#17n^~PjB[kgS#21!7?OC?O?DOC@C?HaCHQc@iC@Q_@ULOCA???oIQ_I???Q?GsGEG`?_HQ?Aje`@??D@?G??QG@G_O`G@?C@??G_CA$#5E??GO?A???@A[?oGAGC!5?cGOBcGO?E@A??C?CAC?D?E?HGGW?p?h_H?HOgO_?G!5?ASID???_?_?X?gTgOS??C?k?CGBQ\r[rgB?akbC?B?@#1!31?O?O?t_HqCgOJDiXS_O?_O?_o?HB}CuA@A???@#9!11?OgSgSgS@{?OcOCOwACxCAWhC@#35?GGO#11?GCOgcQ_B?A?G?G@?@?A#12!18?@!6?_?_??_#23!5?_?_!7?_E@A?AO@?c!8?@?O#19!32?@?A?@??_?_OoM@?AgOI?@A@J[OaOBAB@#40O#41O?KgGIKKLMEFEB@@BF@@??@@?@A!6?E??@iSjUHQILFIBIBF@@?P?QDI@A#34_O?A@!8?@???AGOS??_!5?y@#36!9?o??_??_@a?BPJOA@KpG#9@#30AO_???_?OS@A?@!6?@I?O?`#25!6?C!9?G!9?@?_#26!17?C#25!8?@#34_!8?_?G!7?_$#7??G#6?_!4?_???OC?C?@!5?@!13?A#0!7?C!4?H?@?HoH_GO!9?cISG???CG?_?H?gDg?G!8?@A#6!4?_?O!8?IOlQhIRnrnZv^v~~~|~~|~~~|}~~}|n^n^I^uLrC_O??A@O_O?_O!6?w!4?A?A?BAAg?S?gSaG@?iShUgU@SlOlRcBkPEOhCAO??_#13!11?G?}@}@kZuXsipmTyTl]tiQEXSQFxE[B]pf}|YxQxy@G|E[YYT]lGP}|~~Iwm\tDiSH?G@mCpC#20!45?@A?@?oKOC?SoG__?adTIS?C?B@@#77O!7?_#58G??A!4?AAB#71?A#34!11?G#71!5?_???_!4?O?_?C#39!4?A#58?C#55_#46o#20C#14o??o#9oGGW#16_#39O_#22@EOR?_#47?@@|Zukn@OHa^j~~n\uJA#66??CA@A#57!5?AC#60A?K#65Pw#10O#19???mS[bBBg{{}}z}}NHS??A_#24!10?G?O?_#27???A!6?G?C!6?O??A??G???C_??P??_A?_C??H?Q_CH?C??A?I_COA??H_$#31!10?_!5?G??@??@???A!4?C!7?AG!8?_#2!4?C!6?MOlAsHaSaP?`_aHrBaDZEcSAOCJqopgPogpO_#4!11?A?A#5!33?_!7?BC_OC?aLIlRNjTJmS{?@HkS{\vWeGDOA|A|AgS_KO#10!7?hAH?g?CPi?jSbSj}^RbJrVznVZl^{u|@e@cO_??HSKO_D??`IPKpCjDwExA{`MW@ADALADItAwBc@I@QdI@A??@#16!10?_!4?@?GPAShQCxEWeXVjt}!13~v~~|~}ul{~m~^LuLbMpMpKRk_JqXMc@IGDGC#39__??G?CcCAAA@@?@#76G#38W][GW{[WMW?iG!4?C?O#34!12?_#44!17?_#47KfG#68A#64A#15_O#5A#17W#41??O#27?@CiAP#49???cGA??A?C!9?@#68??_??OkoiS_#56@GQ?CC#14G#20!9?A@B?@C@@_qid~gNY}z~u\zqlZeTjuLZu@oLrlYtmBs_YDYdYdIdIpGv?vLQ`IT??KQDW_IO_A!6?A?AG?_?O@??g$#4!63?O!18?A@?A@?@JABABBA_?_#32!11?C#8gS?c?ADSt#3!46?A#11!37?A?B#14!71?_??Ac!4?S@O_IwDguZVEPZMYi~umljUlzExfWeGSI@#25!41?O!6?_?C?C??A?@#81O#92__#93_#40G!7?A?@C?CBC@HV@^B~gC@}SGO?O_OoWSKScGuItInhYtc\BD@@!9?m^mxC_#21C#20G#52!5?@P!13?{iSzDI?G@G#72!4?C#70@?_#15?C#19!34?O#22??G?CG_?aGOdIPC@GDOdYdGdQ@Cp?UGQcGTgOGQkYdYSjU\uld]rULYTudZsNyf\R\u^~~$#3!96?CG??@?A#43!264?_???G#46C#88_?_!9?_#85O#48!51?A#36GoKL@@@#26!5?G#69!27?WO{DQC#42!9?@#26!88?_???O$#53!368?_?O#58O#87_#38G#26C!4?@#82O#27??O??c_?_?_?__o_wOsug}_w??wu??_?_??_???o_W_???_!7?GK#57??O#52C@#10?O#30_?A#38!4?_$#37!369?OG??AAB?@#80O#91_#54!4?O#75G#51!54?A#72??E#56CC$#27!369?G#33!4?O#50?@#79O#90_#94!5?_#84O#66!54?@#59???A#11_$#89!374?_#78?O-#1oP{sZyxT{~f|R~vrVzn^n~^~|QvE|ZuP!6~nnxn^pbnvlu~|pg~yqevmEAqqoPG?__?O_H?@G?C???q?A@a@???_?_!7?_pOo?QEWVJHDvZMJg@KAGBK@GACH#9!15?_#8NHuwwoS?aCAhAcHS?gOGOGOGCI?_?O?uGoCQ|qnyT_y_TpgS_???Q?GAO?OO_#12!11?CA???@cB??G?OG?COq?G!4?A@?A#15!5?OCG?a?C!5?C??G?G?G?GC?A?AH?GC???_?O#16!11?G?C?_GS@GADgaTIo^ivXv|z~~~n!6~^~[~y|vZfzUxnrm\vMsJKalTYDYCiCCH@?@#41_??A?@?OC?oAM@GG??__!4?GCGOOG!8?OO?OOo?AC#10_#71@??A!6?@#38ICM@B?@?C@@?_!9?_???_G#42???_{^zS_soVDH@#36O_WlWvUQ?@E|a\YC_`???__JA?K#67p#10u#30Ms!6?_sWuga!7?PA@AGDG#24!5?A!6?C#26?O#27??O??G???O??_???O??A???a??OAG@?O?I?IO?Cg?AG?a?G?Q?GA?A?O?_#38O$#5NMAJ?@C???O?_???GC??O???A?GHAC@i!6?OOEO_M[?G?G?AKR?DHWGOh[@K@CaT?P?@???S?@O@_L?K?K?KO?@O@IPCP?C??PCjF~lxf_ssqG?__CA_?_?_?_?_#1!19?@A@GbCHACAHJeJ@?a@@A`???DGaC??o?o#9!13?AlJChU`[HsbgEpgSBgaDOP?O#11!7?cLIFUK@c?A@GD?H?G?G#14!21?_?_!5?@!9?A!6?aP?^_VkA}adjkITbmUlYVLitN_TGeGAC#15???O#17!6?_?b?DAGcWC@A#37!6?A?_@!7?_???A#108O?G!4?_#26__#111?G#90[O_?oOSO#29O_#88o_G#40C?c_kOo?o!4?__`G_c?p`ADIDEHUHELALICI@M?f__B??_!7?gE??T~mZc#47!5?_?@IB??wu}n^DOc???@#66?@#69?@#56_?O??_!5?cIq#103M#14G#17PJ^M?o`A#21!6?A!9?C??Q?T?EG_ICWdI_GCgSs_CWS?G@@_??@cOAGQL?AQTChHKCQC`aOQcAC??@?@S?CA!4?C@$#6?_??c?Ag???AK?GC_?O_??_??d?o?_#4G#3C#31!13?O?A???A#0C???@?@O`!5?AS?@!7?@_?A@_S?D?G#6!20?G?AG?_OSRoPsR{R}V|Zu!15~^ouGDAFGzSxOCO??_A@?a?@A`??IDLi!5?_!4?I?D?ICAHOOcZChU`cJGEPITIoDOGa?_I?CA#13!11?Q`GC@alQlqlq}p^q|r}Lq`^GVxr{zSz`YDFFTaTe_Av{dscQuRh\i^Nnn~HMn_FgAx@@O?@@#20!38?_?OCPaGOGSQ[OIcQDBORXoCTC!7?G#107???_!6?C#77CO#84AC#27@??@@BAFZFNzM^CNEFB@@CC_??AXCHUHALQC?P??K?Q@_WgSGOO??O_!9?C_I@#49??C#51iT!8?A?A??@?A#9!5?_?O!4?O#59o??@#15?@#42_#19?_p~F]|FJ@@DH\^{}{~t_soO_???Gi__C?o#25!28?O@G!6?G!7?@#34!9?G?@??G??C??G$#7??@#31??C?A@!6?G!9?G#6!21?O@#2!6?C!5?K?M_P?HIui^qdITiGM\?KBGaWAF`I@G@?G?@#8!16?C@??KAHC#5!27?C!5?hOcOO?{ULSmsKUytoQO@~HFNJLALOD_^?^_ITaLAO_#10!4?A?C@GCA`KQLqLm^dzx|~~}ZWqtohbuHYdGuOQcQDOC@K_LAK@aHU_TgAKBCjCYdYoW_K?WSw?@AHOk??U_T?o?O#15!11?IO?O??O_O_?O#19!30?GC?G???`@_@?A?@g_wCG?C#22O?GK_#95_?C??@@#114???_#113O#96O?@AG#79G#78BCGo_#34A!7?O???R?G??A!6?O?O??OO??OP???_G?A?C???_???_?A!7?Qh]DY@!4?GN?A#52!4?_??G`CA#60!15?@#20!9?G??W?eGQS__B@B?IMHMhVyd~aTXVXtAfYt^PzDgHFadbUdYeHuHUXeHeHoDk??OA?`?_A?C!4?_?AO?_??G`?@!5?CA$#32!8?A?G#3!51?@??C_!13?o?O@!5?o?oO_o_O#4!71?@#12!97?APa@B???O?A@?_#46!73?_!8?G?A#88O??_#39@???WmF@@EFFE!8?WOG#25!6?C#22O#42?G#39??OGGG#33?G!4?_!8?_#42?o??O!5?HA?G_kC#21?@#41O!4?@#17!13?g#57!6?A#14@#72?GgcOO???_?OGS_P???Os#22!38?@!4?a?A@AGPA?hQcGUHUgAHcPc?y@k?jOeOrKpKXDlPlXyTm|Uj\rS~c~Uxmtzlz\nv~$#4!64?I?GACGC?CY_aSaOA???_O?o_]CMECMBMmz~~MICG#23!168?G?G???@!9?S_O?W?PC?S??QCaG?@#17!52?@?AAA#27G??A#100G???CA#115!6?_#104?_#97@A#98@#100K#94GO#26A!8?G???_#86!6?AG?G#32O#116__?_???_#49O#32?_?_!4?G#44G???A?DAS#47B#10@@#58?_#43@#39?i#64!27?Gg_?O?CGMCIYc^NCG@#25!38?G!7?A??G#26!21?C!5?A?_???A!12?_?O!4?A$#36!279?G#25!80?_??_?A?@#99C_O#78A#106C#92!10?@#101A#99?@#38@?B@AA?dGc?_?`!4?_C?CAO@IC#58G#48??_???_#20!5?_???C?_A?O!5?W??OE!7?oCA#68!18?OCGAKA@B@A@DI$#50!362?_??@#58C??A!4?O???G??Gg!9?C#62!18?O#85?O#57!4?_???_#47!4?O#14O??O!6?@A?A#112??G#50G$#55!364?_#53?@!6?A@#62!12?A#25C#33O#54_#70!28?_#46!10?C!5?CIC???A#53??C$#110!366?O!4?G#16!60?O!5?_G?GC???@$#29!367?O?OGO#21!60?G#26A#22O!8?S@_W??OC@Q@$#40!367?A#38@#105GC#87@#25!62?C?@???O#102!4?A$#104!367?C#84O#80?A#48!68?@$#109!368?G#94?_-#5zG_Qg?_SPg@!4?_C!5?A??O?_??@??C_G!4?Q?_?HQ_O__oaOACGO?O?O_GCOi?IC???G?ISG?GCPG_?GA?`C@???@??A?AWeITAfTJ]!4~wcZcGD???A??A??D#9!13?OCjO#8?hFfA@?COgAhCQC?i?W@eO_?gQC@S?IoEOlinY~tI|iv\vlOGcO_I??o_?OIWEXCH#11!9?G?GOG?T?Ta?O!4?O#14!12?_O#15@#11?_??_#14!6?OGC???_!12?CAGBGAC@?@CAPvlG|enY|{wV|u?mXeiGul_?_#17!9?oCAG@CAAOhOe??@??gC?AC?DCAC#26__O#41OG_!4?hI!5?CA@@??CCAA@@#88_??@MuA_??S#34G?C??COEC!5?__oGKKICAC???AO?U@`_!8?@@A@BA!4?GITnqR??@#36W_o[oOogO_[p}T^l~lsriA??@???C!5?DGE?mO#15A#17CoF??n`???Og?_W_#21!9?H?A?EPEgASHqCXaS@E@u?PG_GOIWG?^S@S@OO?gSB@?A?P?AC??@!4?OGC?_A$#1Ct]lVn\iEV}~~~VJOvYn}Zt{Fjt^yvUl~Z^vz~v|L~^~ul^j^]NWnsxrf\dJLFTjDPDo?hQc!5?A!4?A??_CG??A@???@C?D?OOgOWgs!5?B??AOi?I??CG?S#7!16?OC#5C???O?gOI@EXCO@gjP\DuOLLnRDOshyo??H?@Od?ItATGaGQ?dOIS_#10!7?CH_JS`]`]`]|}~u~vmv~i~iXSNgVgVg`oHO?@!7?AC@?A!6?hUgDA_?oO_O?CHCH_AO`SaHQg@S_#23A?A@?A?Q@?@O?A?@#13!7?T???AO#15!11?_#19??A?OCA!4?g!4?O_?@A?@?B@A@#39_oW!4?O@!6?D#104A!4?@!4?C#105OOO#33@!5?A#38@Gw@_q@`ADadXC#48OG#46C#57A@?@!7?C#3@#27_GOO?O_!7?@??@#26E#24C#58A#21??O#49G?G?C#27@?@#56??_#14?GC??G?C!8?O#72@@??A?@???I@A?dPA@#70A#47o#30pN??MOEKQjDAQDBD#25!14?A!9?_??A#27G!4?_??C_??I???G?A?H??C@??C??G?CA@?@??G?P?C?Q@?AGO??aGOA$#31?A!5?@!9?G??@??A??G???G#7??_#0!6?_!6?C?@?D?HACG???_?_?_?i?I???@?@?@??@???_?GA!6?A??@A???_?_#6!8?CZcXfOB`ywhuwitq!13~njOj~UwGxUnpmPCOBkBOC?a?G?AO??_GADD?H_AO!11?nOJcJPkRMDZc@?_?_?Q?C_I??@?@??@#12!5?Ca?@!4?QCOaC{?yCgQCO?OO?OG?C_HQ!4?g?IC?HAc!4?O#15!6?_??_!9?CA??@?ACgAHQ!6?O?C#20!23?AS?H???O_W?G!4?AGB#22C@G#40Cb?C!5?G#26G#122C?G?A_#115C?A#136_?_#98_!4?EQO#116_#50C#27@BQK?KWWOK?aO@A#20?@#49oAsQkWSIc!8?_#41?C?I??O???_?_`G_#51???O?GGo??GC@?E?@C#10A!4?_!6?G??A!4?C!9?_H#42I#19?wv`?Oag??OG?_Oz!7~DcAxPP?_?O@#22!6?g?O?G_AOAc?`CP?_I?iCjCR?gSiSjkI|Ye\yl}jUHvO~I|j}luz\vmX}\un|vT~$#6??@???A?g!5?gSj?dO?cG@wCA?DG_Q!4?C?GA#4!17?_???O?O??O!6?T?HaT??i?T?aOHQgWs[ukYugpg_@@AL???`#8!10?{SDDQ@D@IG#1!21?C!5?_Ag?OC?a?G@aO?CgJA???NoEOC#9!12?A?@?CRk@YcJsbOEOaK`Y@S`A#13!12?H_UgVgFKJeLzA~DzVlzNlim^lv^z^ulUhFqP^tJNUlZzuzuN|n]jXsDSuHZ{~{y|kG?A#50!52?_O?O!4?E#108??E@#58A?C#76_A#100OK!4?_!5?___O!8?G#40@@oo?gOGa?_IPYCJE@@o?o?gO_GsG{_?GuMN~M[wxpyqcw?a??@??GVda#42?CCaEA!6?B?B#64!8?OIGCC#56C@?@???@?@AOQAG@?G#51C#20???GO?GPDSiDdYCIC!7?yQ|CmgkXVliuLze\JUxk@vMtNTJtARM_@sjSbCxCaSIPgCAO?`?A???C?A?@?C?O@!7?@$#7!5?O#2!51?AISAGA?I??DpUlZuisaSgvyCvG^T@o?QcGaGOcGOKOD#16!196?o?W?c?@A???lPeX?vHA\j^!9~NZ|v{zlxlUnX~Ti~uVj^lYDiQD?E@#38_???oOw[A?@#117C@!6?o!9?@#84_??H@#54@??AA#22?C?@#58???_#62???_#54G#42OGC!8?A???@#39!4?@BBECCKG???WwWWoV_#30!4?_#47OGseB@AN@GE?DBI@A#68???A@C@#9O!4?BABGK?O?gO#65?O#30!57?_#25!11?_??O!7?O#34??_??_!7?C_?@c??@??Ga$#3!59?_!5?C!19?C?C@A@?@?@!4?AGC???A#25!245?__WO[K?C@#46?A@#88???G??y#101@?OO#92IA!5?O?WG#79G#77C??_#80??A#25??C#26??B???@#44!6?CA#47?A!9?G#20A#50!10?@#25@??G#33G#53C#22C??D!5?@?@#52?G?SI!6?O!5?A#28!5?_!6?_#135O#67?__!4?_#26!74?`??@_???O?c?A!6?G!4?A!6?G$#37!352?G?G??@#86???_#80!4?o@_#33?G_#78_#128G#79C#118@@!7?C#124A#78G_#29G!4?G#56!19?@?@#61@???@#60?A#33W#38!12?ACo#43C#57!20?_?Og?_??g?O#59!6?AE??C?GARCC_???G?@#24!75?O#38!5?G$#27!353?_!5?AGC???O#75C#62_???O#125???C#58G!7?@#99?A#94O?SWcG#52!24?AA#120???@#48C#20!13?A?O???A#4!33?G!8?O!4?_$#99!367?G?C#42_#53B??_#123?O_!6?A#119???@???_#103!24?@#116@@?@?_#37!14?C#134!38?__O#5??C#138?_#8??G#60C???C?C$#121!367?A#29A@!4?O#96?APGWKGEA#89!8?C#131!25?_?O#132!56?O#127G?C_gO?_#64??H!4?O$#0!368?O#48?O#112!5?CG??@??@#133!94?O?O$#77!370?A#63!5?_#90?A??C#129!97?GgWO$#94!370?C#130!7?O#114@#126O?G??C#2!93?C$#137!479?_-#1yunYjXpnkzTyUc\AJPLuZllv[j\JuTkVbar~knev@i!4w{{t\xt\r~ZZw}gpswoO!4?SCHfBBC@@??BBB?C???O?C???G?_O!4?@JlZsww{SYtA?cG?P?JSbS`CHO!8?@?@?@??@#5!5?Ag@V_\eLRHE??B??APhoow[~[y[kUkEmSMCn`]DiODH!6?Q#10!5?GTaSHoE_UI~FZy|s}yt}doO~_^pISgSA??@@@?_?_??`???C?C??iSHqChQdACPitIsIPeXA\aHU?AHAC@A?@C#23!8?A#16!5?GoA_?~?ln^~}~~^n!6~^o^jVhQg~I@@@DDFA???A#1O#25CA@@@#58S?D@SG#80C#29O???O?K!9?A!5?C!6?@#119???_#40AB`a`CAPA@`cm]TI_Q@I_?iSb[bn|??A?BShFWEoJPBm??E?BA?FBB@@#47G???kAF#59?`U}?c___?_OGgGK!6?CC!4?A??@@??A#17_?oO@!8?@??C@#21!11?@??PH?IdQK_VGuGTADOCDKPnAlbD@SPcg@BSWOOGG@QOY@GE??D???AG@C?A?O?A_??_#38!6?_$#5DG???C!6?@???OC!11?G@?W\K?AOXG}T!4F@BA_A?aG?_CD@RCI@J?gOcHA@A?O?@A??@!7?G??P?Da?_Yc??In}sQcGCC@``?GpI?S_C_#9!8?@!22?GA#8??o?qH{kWvKgACBD?_?_?_OgOgOgOg?A_ODIqCv~l^Y_C?]jS??G#11!9?_!5?@??G!5?C_??GO#14!21?GOG?C_A???@AOG???_???_???O?GC?A?H?_~?RfV~@|?~hvFN\^~?~Q?_?@??_O#17!6?_N?OgUD!4?_AAA?DE#31OOG#101_#98_#22C?CAG`!4?_?C#88G??S_Sg`#44@#78O??_!8?O?_S^#27!6?@??OS?@??_??O_#131!9?G#44!5?_Bo#34!11?OO@??G??GOGqIB@BB@#36_g~??@@@BPFAFMBD?R@W_?gG?WcMSCO!6?@?A#42C#19g}!6~owopw_owxf^xf~H~flCeDCCe??G?O#27!9?A??O!5?_?O@??@?G?C??G@_C_??a???@_?G_CO@CO?Ag?GA_?AG$#31?@??C?A?P!5?A_??a???Q?@??_??A#2!15?A#0?GACI?C?C_???I?AC??_O??A???O#8!33?A?IC!9?_!6?_wsWY[M[!5?A#1!15?c?oAOcGrScAKGB??@C?@#4??O??_O#9OG!10?_^`]`SjTeaX@uFHVHd?O_#35?A#13!6?@H?V_IPiSaDzdIqMxEZE|IS~U|j~yn|TbeDzQLW|rmSGdBtmXEta\U`|lctrw|lyQz^?~kWg?{A~?UGo?_#15!5?O#18!15?_C#19??gV?sEIoG???@?@#94_???_!8?G!4?GA?_???GE?OGG?C?C?G!5?BkU?OO#33C??GGG?G??C#48A@#33!16?AG#27Kv{jQwEoHScW@nAO__O{oKc#17!6?A#51T#56O?C!4?OI!6?D___O_?_G?GO_ACG!5?o@#30wJF!7?FCCEBOCE!9?O?O?G??_#22??@???@??_Ga?Oa??O??gQ?i@QCwADaLQdYDiDyV_^TyEzmXvsZylYjmW\V~U|Zv\v}^x~~$#6??OdOaGO?CiDgZ_\CiOHcQ?G_SaSHaOgC???P#4!20?A?C??C?DDJAO_go?GcWskM{GgO{aXe?eGaEW?SQ@@~~tO!4?BB@A#6??ItMPvjMzSJ[j]zun^EJfdbpb}~}~}|~}!5~|V}_\aXAGC?@B???@???ACB?A@BA@B@@B@B?S@iOdGqG?Q_D?G`???iP?Ci?GoG_O?G?C?A#15!55?G???GAOAG?A?OC_C#20!44?@G?GOGG??A?@!6?_#41???B@?_?_B@#33@?@AG?A#84`!8?G!7?Og?BE#38?CC@?@CC@#25OG#39G#116!17?DO#34@G#44!12?c#41@@?@@#42??OKss}{{O#52G#9??A@!5?OG!6?W???CC!4?A#48_???O_#2E#103C#47?O#15C#20!9?GBIGCJI@EW_EW?u?WAyHyaqXTYdqNgvGvi\qLzgPm?lQ[OkACICyC_ADa`Qc?C???O_G?OCO!6?c#34?@c!5?C?_?@?C$#8!6?C?A!7?_!7?A#3!36?@??A???C!9?O?O!6?P#32!78?GO#12!48?C@?H@CI@QMe?G??C@B@gCYsKoEXcXAtI?hAO?@OA!9?G#11!70?O!6?G#88_#43C#102??G#105_#38C??A]wM?C@BAD#105G!9?wE?ooWkw_ph}]J?O#42!6?_???O??_#58A#54A@#57!17?O#48C!14?W#25_#38MSKA#72!13?W??QOGC?OG???A#1??@#10GGO!4?_??Go_gWw_?G@#25!54?A?@!5?G#34!15?_$#2!64?gQCHaHOCWcGaGQ`AcSkBXeXeHvGxa\bLCI#42!238?_#37C??OOG!5?@#27?A!7?O#140A#37C#62G!4?A#0G@#32E_#119`#136?@?D?G?A??A??@#41!10?ACAA?A???O#70!17?G#84_#26!15?G#22O#20?_??_#61!12?_#64?kGC?GC?@C!9?O_#4?@!4?OAC#135?B#72?A#26!57?G!6?_!5?_!4?H???H??C?A?@!4?@???@??G!4?A$#46!337?O!6?@??@A#26?GAG@??o#125?_#73??@#53?C#95C!7?W#98??o???O!7?_k!5?o#44?O#34??_O_O?O??@iD@#103!48?G#70??A!7?A#138???A#129AA??@#135A#46???O#13_#3CG???G$#14!338?C#50_!4?CA?E?H#53O#40GS??A?gA??_A_#54A#142G#101O!8?A!8?@!9?G?G#49???_!8?o]l}t^~Tb[b[OA!6?C?`HE_Ic#67!29?_!6?CC!6?@?@@?AE?A@?K$#62!340?_???G?C#24!4?O#108_#121!10?O#143_#31??A#28C#116O#46S#77!5?C???A#88A?C!5?A@~cH?W#80!4?G#99?GG#57!62?@!9?O?O_?O?GG#8???C??O?C$#100!341?_???_!5?_!8?G!5?@!6?EH?@CB??D?A!8?G#86!9?C#75C#60!63?O???_?A#0?C#5C!4?A$#99!342?O??GOG?O!9?O?OO??_!5?C#122!5?@#128_??O!4?_#65!81?_oOO#88???@#134@@#127AA@$#29!342?_O!4?O#120!19?O#48?I#141!6?A#92?@#139@#28!96?A!7?@@@$#80!343?_#133!134?@-#5a???C??GDgWCCc@OG!4?A??`CO?CO_A!9?@COAQRr?AAaIQIQOGC??_Wo@A@ShOGG@G?_??aCiCw!5?t?sCWCyCrCB?sguzOl]`?F{g\dE?DA_T??A??@!5?A!5?A?A??A?C???GCSg?QCOkSWkCAPIIDBA^F@B?ACWFM`FY`FHbITgQG!8?@#10!5?@QChEHCQHul}vl~vn~~j\vQXkVIJCgSh?AA@WcW@!5?_??O?O?gDiPeGTi?OHaTBKQcAhQCiPI_Cg#23!4?_AC??C#16!6?_??OcG?@GPihU{zvz~zt}!4~^~n|FLiZJB#27O#40_?GGoCgES?G!8?_!4?TGBYC#99_@@#98@C???@EGOO?C_GCa?O?G!5?GB#27_?yCi?O_QT|CyEOG!6?_??O?cG_O?@FH~|Yt^tmxV{jg??@??@CG#15?O#51OAC@#65?G!5?@#0A???@#60@#36!5?OhATgjOdIO`?W@ykoC#42?`#17z_!6?CG!8?gO#21!8?sOCgSChQCRcHRcHo?xAxCXE@RAo_GoC`S{_iGSC?AEd`QTGToKhS?I?H?O@Gc!7?O$#1\GvhZsjSqCBxZG}deXd]DwjMOjCzhEXk~~n|~~f\~izn|LkK}||\tltlnvz~~BBBEOI?CCcoabRBi?O?O?C?_!4?G??_!4?roO!6?`]~W??AOhUgPI?i@gS?S?T#9!6?AA??@??@!9?A!38?A_???A_J_B{OfHqK`Qocqhc?Q??A?GO#12??SA?HcA??S_AB?RgOk?@ACY`IS_CG?C??C#14?GCa@?G!5?A?@?@?AgPCaO?_O???_O?B`vW{Ni\v]\]|iXB|uueTAhBCGC?CI@!8?_O#15CC#50?O???@!7?@#26@!8?@#25@?C#149_#27a??_O#148G#100A!8?C?C#95@!6?G#146?_!4?_C#40OB?oCwg]g??J@@?dOiTyti[RmHuHrM_!6?dI_IPAgBO#42O@A??@IaTTNM@@#67_o?WIKCAAOB??ECO#9!8?C#64?A!6?_#14?A???C#30C!5?_#20!11?A@?IDI?HaEHaySizSlykZukZuLzEpEweXUKxFKbEpM`?L?rGpKOG?C_!7?cO??_A???AO??A!4?G$#6?PGU_JCBG@cA_R?IPeY`YDOpMOjCQhEP!6?W_?S???_#0??@#2!12?[CGGCSi?H?DOCcODyDy@O?N?KOk?`JQDj?j???`AO#8??C!6?S_GOgOK?_PSCBAA@?GKCCGGCC!8?GKC!5?LkWDAiDRG_Co?w?{?_UGThRAgPEg@IOa[siTktk\Zun|T_]gBB#11!22?_GcA?_T?O#12!36?_?@!9?A??C?C#15??G?G!10?C??A!4?S#17!13?_??A?A??C#26__O#88C??@?@@!9?@??A?AO!6?S?GRq??C_??_a!7?_oO_zoJ#34?_KKDH?DA?@?AoC_#33!16?KA#44AwS#56!11?A??_#10?_!9?G???O?O??CSGo??E[_cGO!7?D?@???@I@C#19??[bVJFN^zv!8~Ti~pqPzeXp?L?@??A?@#22!6?AC?C?A?_G_CGBS@IOA@AT?bGrKpGYDgVaLbSbYdZsVlydZsnLzlyuL~Tf}Z~d~~$#31?a!7?A!10?_?C#8!11?O#32A???A#0!22?_??@??O_O?G?G!5?Ci?O#3@ABB#6!21?_???A?@A_TICiPg|g}ivrzxvvxx~|}|~}|~rqxzvzjTq@BiO@_?BPI?DA!6?A!6?O_Cg??@?A@APAcHO?IS?S?kWuK`YCHQHCQH?@GO#13!10?PG__JTgUkTlQfYdyd]tj^Zv~j~nzVqPKWvaT~nu\g[qkYhSDiPKd^XF~z~ZN|o]?bBoTaG`A`A@As?G@G#19!17?O???@???F@#53A???A???@!8?A#45@#58?CO#80_!6?_#62G#94Eg?_???OBHWo?A!6?CHCNSF#42OA!8?A#84!20?Bw#48[#34?A?A!5?C??C!9?A#9??_#4_??_?O???K!5?A@??@#47!17?@??Y#20?B[gswO_#30!11?C?CGcCOCGA?@A@#25!13?G#27?@??_???O?G!4?O?C?A?`?O?GA_GA?A?@?_?G??Q???aCO?G_???@c$#8?C#7!4?O_?O#4!53?C_g_@AABACO?KODG@?@B_]pkOIU?hAODOKGKMHFHCjQ!5?B#1!33?@AC!6?_?@?A?oK_Do?{@_WgsgSgdO_W?CO?S#46!89?S#20!54?W???o??AH#0A@A#39!5?AAEAAMMCCG?w!6?@#108??C#29_!8?A#78??E?O?HGAD@#101A#32!4?G#116@#49o?AOAD@C!4?WnQnTiDITBkPeHQCP#57?@#116_#42?_#32!11?@K#20D?@G?WA#36!4?OIAFB#32_???o_?G!4?O#9!20?Aw#26!61?G!9?A!6?O?G??CA??C??G!5?@??AO???G$#3!64?O!45?A#46!218?_O#25_!9?A#22!7?@#144@#117@#125?@#100A#29?A#38GAGSk_WK#110?_#101O#84?G??A?CG!7?_?O?G???_O#47!5?@#44!4?G#146!21?C#50!14?C#48OG!8?_!7?_O!4?_!6?GO#25!81?A#34!27?@?O??D?A?_G???Q$#32!110?@#30!222?C#62G#99G?C!5?@#54!13?C!6?B#121?O#146??CGa???_#136C@#128?@!4?F#85!18?_#47!37?_??o!4?_?@KACD!5?_!4?_$#147!333?G#1?C#58_O_G?O#50!15?@#145@!6?A#116???@CP_O_#134!6?_?S?D?A#44!53?A?IAC#14_???_?OG!6?_#56G?G!4?om`aSJ?EkIPCDY_a[MFIC???B$#22!336?O#34_#38S?G#33!17?G#74???C#119!7?O??G#139A#89!8?A#119_???S#3!53?_#46OKOO#30@?G#8!4?O!5?G??C#28?BA$#100!338?A?@#33!29?BCW#105@@???IWtJ\???A#57!55?O#16C?A#25C#17?_#60!7?OC#103C!7?@$#41!339?CoCwi}s{x|{ooywS{?_?A@O@A#3!7?G#57!85?_!6?_?O???G@A?f?P_AOg?TGAo_S@OCw$#59!458?@?@@#2?@$#61!459?@A#3C??W?CGG$#135!459?A-#1pJFMZCzGrwUgMNR|AS__Pm?za\HecZUb^NnN^n?\jZcI}Mw~~kjtmnnjVRPPBEMFeCSkX`BpGWKM[MMMKE___OO?G?D_I_?_!5@B??`_S|Nm???CICBAGDACID?D?D!18?AO!5?CI!5?GE@CGE`OlBE@GBDM@C?Q??_#9!8?o???_COaO~`sJuHg?uQRIkOOg#11A???O?POOa!5?_O?Q#14!15?O!4?_?DA?@I!9?G??`O?H_AP_S_AP_A`@B@eHq@}tn}o@bdhNJ{GdRg#18!8?O#17_?G?A#38_?wOO_??C??A!9?_OOGOIuG???G??kgWo#45G#54_#29AI?C#94c_w!5?@!6?o_WcU#34_?KGRW!9?`G#44!17?AO@AG?_#20_!5?_OQC!6?G_O#48??GG?ICGA`OGcOG#64???_?O?G!9?CH]?M@#10??OC#30[~@???@CR?`A?_?`_!5?aCbCa@#21DGcASh?YdGj?JS?tIsieXelY`HSASBOI_I_KC?uCc?@?GCa`PKCcG_HOCQG?U???G_D?A?O?C_#38??A$#5M_W_?_!4?@?@!4?`?H??O?C?aOH?_G!6?A?CCAP@`D??RSIPOOSggkm{ppoOxaPa?o?udqpap_@qpKPM?_ofOyOtO~OuwqwqwvzS]JA_P^YjqP_Wc?o!27?C_DICO@?OP}PyT_gQ`OKA?Og[eW_O_iPchQDQDGDADG#10!11?E??GEQjHcCpBnfUt~N~N|gGnX{vysJSJ?Hc!4?LADG???_?QGPcPiODi?TI`MPCi?IP?HCQ`Y?l?IPcWaOO_O_#15??G#16!9?DGSAO?oAdYgV~n!6~n^~v~LNN@#58?_KOKO?I?C@@!6?O?G_?_#73??_#39@F#100_!9?@S_O#119C??O!7?@?D?A?G?G#27a?VKd]GZmlI?@BUuLWj?S`?T_ChQGScHwC_???FFHp@cH`Ig?A!4?O__O?O#33?A#57!4?@!5?@eXFtAW?_OC@CQG@a?gHQ?i@S`??A#15@#19?}O{wsOk[Y[~N]]N~~Zv^LGWGLSiERO#25!4?G??@!4?O#27?@O?@OC?_?@?C??A_?A_??a???c?P?G?A?@?S?aG_CP?C?COAG`?_I?P?S$#6?C_PcZCrGFgVoO?A|IXCmPnCXaCHQCHS??O__OL_O_Xc?OA#4!10?C!6?@!4?KGC!6?@_??AC?lNLOd?I?D?DGCKCKCGCI@!5?C#32?@#12!87?G!4?C??G?C??G@G?o??AEF??@??H??_cCQhUhU?_??PaS?G?C!14?P???c?O#15?I?C#23!9?A??A??A??G#15!6?A?G?_?@O?C??O#20!11?_O?AF@A#41__Ioshyi{v~z~~nNfB@J?HQV]GE#62O!7?G#63O#146???@BCGC?GI?CIAG#101??O#78_O#48_O@O#44?_!8?EwG#116!18?@CSO#26??G!6?C@!7?O#32AA!4C?G_`#47Q?S??A#3!7?@#60G#72_#10??OgO_S#72!7?G#48??CO#42a#20??NBFIj?b?`?O`?O??_?_OrCrOiOpGljU~dQvS}sj~IdJSGeWA`YUHsIoJsHSJoHU@OHuGR?GCA?P?GA?A!6?oAP!4?G$#7?O#31!5?C!5?_!4?AO#7!6?O??_??_O#8!4?_#2!19?A!4?GGA@?CQCA@?@???OO?GPIPA?A?I?D?I?I?A?A#6!6?_?O?__!4?C?AItyty~y~y^~^~^~^~!5N~n^`DliTyTyHwCnM@MD_GO`OGA?O_!6?YPcGQLQl??_?O?d?I?D?HaHA??Js@o@S?@g?O#13!7?_??_?CAGDAsJCZ_HUhUhq\yvm\j^vlbmZmTNyO|isSpmzC~tmZublSCjQuTkIFHNlKJ\[}s{XuD}@IO@IsGOEOC?A#46!16?O#40?_?_GOKQ_M@S@SAG?C??O?GcQ_T?dG?O!4?O#25C???_#84!6?o_?_??@_?G_O?G@?HD#32O?_#49?A?T!9?a??aG?I?AO?c@i@U#33??G??_#22??C?OOcW?C!6?_#43G!7?_#36!6?_OGOeWAScG??@YhCaUHjCqc@Toa]\jG#17???_!6?C!8?CG#30!26?@#22!4?C?aG`Gc@S@S@Qh?HQHuGvaXSM_zAtJsLrKrmhI|If\q]p^d~i^j~t~~$#8!8?C!5?k???CA#31!13?_?O??OA#0!21?G!4?GA!5?A??@???@#8!30?@SGcZ_Xt?G@!6?_?_?_?_?!5o?O_]yO@I?_@eAp?_?_?APEGE`OlAKPaPcY`C?I@C_G?yvY|ivYNt~y^qLSl?O#12!84?C#22!40?O?G#26C?B@@@#71!14?C#27C#81!4?_#80_#26@#33_?@#50A??CW?_#98!5?G!6?@???_!5?A#33G#40B???`acPQC!4?@OfS\b]ti\jUHu@Y_Fw!6?O?C@A#16???_G??_O!7?O#70!18?C#48!6?G#14!12?_#25!66?G#26!9?_??O!5?@???@#34?_!4?C!7?G$#3!71?G#50!256?C#25?A#27@#151!26?O#101@#88B?@@???CCBZJYCACwOvouYtcVINDEB#42??C#46_#54!8?_@#48G#42C#84!20?A#48_CO#38??AKAI?A#25??C@!6?O#46G?GG?oS?ICSGAE@$#143!359?C#53_???A!4?_#116!4?@ABM?CG??O#38!18?P#33Y#116O#98!21?@#146G_#34!4?Ag?OCPA@!4?_#47@@#17@???O#44_$#145!359?A#108O!5?C#77@?G#33!5?@#28?@#75!25?C#119_#32!22?A#42@FwO!8?G?C!7?___!8?@@G$#150!359?G#22CO#27C_Ko?O#94!35?_#57!24?G#14!12?O!7?@!5?AC??GAkO?E@$#55!360?G#31A!4?A#3!74?_?_???C??A???@$#43!363?A#28@#99A#117@#56!75?@?@??A!8?@!7?_??_GHBeRFycA@D??SR??_!4?a?@$#110!365?@#10!76?I!7?@???C@OO_`$#28!442?_O!7?AB$#0!442?O!5?CC?@$#67!443?E!9?@$#65!443?@#60A?C#50???G$#138!443?G#4O#8C!8?A$#103!444?C#2G#1G$#132!444?G#36A#9A$#57!445?@-#5EfP?g!8?A!4?_??AG!4?_O??_CO_W_GWC!9?YcWcWIQnUNTnYF\~n~Vn^uNNN\n^tN~Nzm\~lZN]NYNNVjV||K^ZVLlYcZMWvK|@_?_OA?_Q?H#8!14?A@CG_[oG_oTHCEMBEZFF@?C!4?jSHC?OLqDyDYsiOo?oOoun\~ivT?~^|vZIsL#12!18?A#11??_D?~!6?cO?_A#10!19?`?GQ?HCODO_Ag@S_@?_@??D?D#16!9?_??_?g?kO_S?@?A|T{z}|z|\@p~^^JNEAB#40O_GU?T_P?CP?CP?G?C!8?G?_?ogOgG?C?_?QC_O#44@GOGG!4?__?_?__??_?O_wCW?J?A!8?G?AGo_#34!11?OCGo_Sq_WO#32O!9?_!9?_!5?@A#4AC#9O??O_#64??AU@E#47??W!4?O#64!5?@??O#14?OE#30OA@!7?@?HwkVk[sg_DiSHQdG??_#21c?hQChQ?U`UHqCiCWCLodQSBO??GAoAOS_R?@hiOcOCHdZg?_?O?AIi?T?G!6?`??C#38!5?H$#1xGmUPmXq~tgFo@^~CZGmPcQsStSHaTiXGmCFYeBZzFZVJTnB@@A@A@@#2!15?@#12_!8?_??_#7???_!10?_??_?O#5!35?O?O???ACG?_!5?_?_Sbt]tjS?CaOLaLyDydJPbNuJbJ!7?g#10!6?G!6?_HExExExe|jRn|~~^y~?~ny~\aY?JOH???Ho#12!30?O#14@?A???@?@???@?_Hw[Zw^oVoRiZjx}{xAi@C@ACA_yC!8?@#26??@#41GAKE~rk~zM^FFBFB@@?@??__OoGOiOcG?O#150G#26E!5?@#88??@?@?w!7?G#89CCC#32c!6?@#56?@#57A?BA#40CE[o!5?DADHeHAT?JC?`AGAH?C#116BC??O!6?O?_#56_#48_O__?o?WSC_C@G!4?A#65!12?_w#48?@?A#67!6?_#60_#47?_#17_m\M?ChQcGO_?s!6?C#25!20?A_#27!4?A_?A?O?G!5?G???G?_??OC?G_?Q?CO!4?S`C?I_CO@?S?AG?Q?G?AOC_$#7?O?G#31!8?GC!4?C???@?A??A???C#8!8?C!4?A#12!5?_#9!21?__O_Go?O!5?_?_O_?o??_??O__?o?_?_#1!40?_?H!6?GA?G!5?jOHa?O#9!6?CG?G?K!6?GAT?_AGcPA_~}|ByDAOCpAhCXASKO#13!8?OD?a\?JSIShUhULzulZ~ul~}~~|~j]|vlZuznyn^|V}j^m}^{~~y}y}~~~}~^uFBCF?F?J?D??E?@C!9?C#46???_C??C#38O_O_GoGAG!6?_?OOGCEAB??@@@#54?@!9?O!7?O???_#98C???GG?A???HA#33?@!5?@?@B!4?@#84AK_#57!21?@?@A#44_!9?G?OGOGOMCB@?@#56???@C?Kgo`?DWdG_??d@B?K{A@?FCKu_R?A#15?O#16@#19_o}zUlZvn]}AF@?ABBBOA???_?AShAO#22!15?A_?A?G@gChU`SHChAHCgQCODQLqcY_F{Bkij[pDtIyfytj]\r]d~o^kny^s~~$#6???`EPeL?IVwFo_?zcRPmXcIhGJOLiTAb@Z?DPc_?WcgsGOK]C@E@ECD?@o?O@gA!10?A??A!4?O??O?O?_?o?gSG!4?CG?ACZCpfGrAgUi^It~^l~u!14~|}jvNbNv\JAuQxp{xcGo[jOI`IS??aOLa!8?C?@C?CHOa?T?gA!5?c@Q?@A{DYshAGCOA???_#12!14?@c_D_UhU_ACHQc?HQ?@??A?S?A??c#23!30?_#15??G?C??C???A???A!5?A?I?_#20?o?O#34_??C@#71!10?_#73?_???G???A#27@#108A#99A???A!5?@?O!8?@#46OO_#62G#100A#84@@H@@G?@#29G#48O??Q?G!5?A!8?@CO!21?CG#103GO!7?G#16???G#47_!4?_CIA?A?CG??@#59!13?O#10@?A?SG_!4?G??G#42G#20???@!7?@??QgP_GONwTjuLYtjU\J~UlzUl~h[HuLzTxDZoLIdaSjUhQ_ExE`UGVkA@i@AH!4?BS?D???O??@?CA?`?C???@_@$#8!13?G!9?@?A?C#1!53?C?A?AC!9?AAB???A?@!7?UHT?dG#7!37?O#27!163?O_GGAA#153!14?_#79_#89_#88O??G!4?E??@??@#50@??B!8?AC?_#101?A#77?G#105CCC?E?AA#136A#116O#20!5?@!9?@#146?C?O#42!23?_?@@EF@A!5?G??CCM?B#57?_oKOwao@y??CGVgAO@IhQ?A!4?G_CW`@?G?GC?@#25!58?C@#34!19?G@!10?G??_??G?A?O?@?A$#7!26?_!5?O??_!5?_???_?o__W?W_ogOg?i?cO_?O?gO_GOoO#4!8?@!5?@?D#22!228?_?O?K#152!17?O#101_#29_???CG#25!5?A!6?FG!6?@?A#94@AD@EIAAA?H?@@#120_#49!7?O?GoKOkGw_???ACOI?O?O?T?i?gA??@#61!5?A#17_!6?@#132C#22!5?@#34??@#10!4?G???OCG@ADA#70!11?K#60C#26!95?Q$#25!320?G?C?@#98!18?OO#39__ooOWGKCCCEQuw#58_??G#27HENMukacG[O?O?_O!4?O??G??A@?_?OoGokO!4?_@DJNo|iuHugiTsRlYtE\aLY_!4?@??@!6?FE@A?@#36!4?Og???o?_OHEGQdG_SOlYc?Ar?dGr?YqGVCFxF$#17!320?@@#58_?GU_F_P_?GA!4?OGG#75??G#22_!6?C!6?@!5?O!7?C#20!4?OO#42?_OO?_???G??aaOakC!4?A?A#119@#47!26?@AA#67O?G???@A$#80!344?G!9?@#33!4?A#124C#38?@DaX__#117!4?A#78!7?C??C??A@@#116!11?@@???QG_#65!23?C#36CC#154__#14C??@???P!6?G!4?@C$#85!344?C??A???@#151!8?G#144?_#63_#73???O#16!12?O#119@???G#135!46?G#3_!4?O??C@$#94!345?O??C!6?@#134!26?C#34O!4?CE@!6?C???o#60!31?G#51G#28O_OKIC@$#145!345?C#78GG?C?AA#47!30?_!6?oC[CK#8!40?G???O$#33!346?C#20!89?A!6?E??I@!5?@$#53!346?O#138!89?OG??A$#4!437?C???G$#10!437?_#46___?U!4?O_?O???A?A$#1!438?A??O-#5H?oG?cO_!6?@!6?_@Ol?iCAHOdRgDbZfZnQJ@BC!4?G!4?G?CPG@AC??I_H?AG@@IDADQDATACJ@?A@??C@?BGADICHSADYCSGuLI[qGhO_?PE@eCG@AG`?p#7!5?T_T#9!8?@?C!5?@??A#4!14?O_?_O#9!17?Vd^dULVcIOaLq?GTOcY@oA@@#35_#12!8?I?SE?AgAO?_?Q?C??P?@???@???@!5?g?P???G#23!16?COG??G!6?A??@??A??C???C#43??_#13_#46_O??CA?C#27CBKCKG#108OO#41?CADA?A@___o_W?K?CGAhA?I?A@?AOAA??EKOcW`O??_#25UC???H??GA#42DAADA??`?A?gSG#40!6?adI?bCO_#120_O#42??C_?O_OO_g_oO_{K?CA??_g_AsgQGC?G#116CEGO?gO#60O!9?_??LCS???O???os!9?_#64O?@#17_pnzF~~^gUxUg?`YdOC?CAr#21!11?Qc?C?RGrKpLrKPGC`A@CP_??O?G!7?@!6?a?`Si?S_Q?R_h?QG@???@#34?_?O?H?AO?A$#6ChAPgP_Ohr[vw|yl}po{MJCF?]Tj|UlYG?_O?O!7?A?G#35!4?@!7?@#106!11?_??O_?G_#9???ADCENBHEDC@!6?O???A@??@?DQCJSI?g??_#8!4?IrEo#5!19?A?@GE?DGB@R@dTiQaPdSyuK?}?GcmA\QeWqWc@Qc!8?@#10!7?GQdADIdMM|}}^vn~i\q|qsi_o\_Q?iCY@cH_}?IOaGPC!8?@!8?i@S@ACH#15!13?_?_@???@???A@C?A??@A#25!7?_?G!5?G#88_#22G#31??_#94??_???G!7?@#99@#27!15?_G?_!9?C@AB??C?GQDG[GaK!4?oySje\qNOjGTGPcGOg!4?@BF[jKjE\BTM@VA@B!5?OC?IA#50@?@#57?_O_!4?C??G@GAQADB?BC???C?A??A?A??@A?GO?ACA#47?_?A#51A#19?Cw??_VhEhTRG_Q!4?C?t~zjsC`EO!6?IgcC#24!5?G!6?_#27!4?C#26!13?_!5?O#25@#26!17?@??A$#1qELaTIDNQKbGDACQ?EHAPCI?A@!4?A?CQGC#12??_O???_YoS?{Tw}[us|hk`}sq{ws[o{{o{{Sy|iLytI{zswgxwowqgyWu\ytxsidycwGsHqC_G_O??o#1???@?A!4?G#8!23?OOGCQOKACY?_DcGaG`CGA[@]cXOg_gOcH_QkHQlQlQg!4?_?I@C#11!5?g???O!6?O?TaLAL@SJ?A?C#15!12?GO#14??O??O_O!4?O?GO?W??g?S_GOg_Og_Oc_uqg_o[_Pyp~rypzuSaXwKY?wdxJTIGG@KAD#58_!8?@??@?@A?D#54C#88C???A#26A#33!18?oO#38Q?O@A@CI@BA#33?G?@b#44QHADgA`DW@???qLDA#49?@ADOF?VGQcZC_!4?@A??@??C#20!5?_???K?@!4?OC?CC?A#88?@A#3GACC#28O_#65??__#36@??@??BSB?@`a\_N\PeD??m@dQcrKqTn[E#30OCO#20!9?A_?D?dGCA`?I?C?IPIoJudlRlZtCRGrMqKreTyUH]pMSjSjOb[b[b[b[a\a\aLQK@G@?GAC?c??A!4?_!5?A!6?G$#7?O?CA?I?C!8?GE@_OogO_?O?_??_DQGcGC?Lc][`LjV?aF@A@BAAAE??HBF@BEB@FAA@!7?@??CQ#35???C?O#147?_#106?_!9?_#6!4?B?DACBDmP}WZT}|vSCGN!4~i^i!8~}~z~|~mforgd_sgcWIOGPKOI@@@B??@A@TADHB?EHQcHQlQl?Y_YgQgPsj\qDlQ?iP?_@!4?G#13!8?@?H_\@|DzD}HuZ@~cnSfmzmn~n]nn~u~FnefnfvTUji\reV^nV^nZ^HHFVNBVMCM?KCGCG#19!16?A?A#39OG#100o?_??_#150!5?O??G??G#85A#101A#34C#79@#75!20?K!7?C#50GC???C!4?d?O_#20?O?O#32G?O#34???@?O_G_gS_!4?OG!8?ACR?hASI@MG@A!7?G#47!4?_O_O_O`#10_!4?A@AE!7?G!8?CG!4?KQDACOG!4?@#30!12?KU?GIrzxWK???S@iSHcHY?GQ_#22!10?a@GS_I?JSjCjSb[b[b[b[a\a\QlPmSi@uHQlQkJSUhf{V}^s~Z{j^sn|Iv|V|~~$#31!12?A???@#35!23?_O#9_!4?_B#156???_#106G??O?O#110?G#7!36?AA@??@B@A??o!4?_G!6?_#1!31?G??OG??A??@C?A!6?@A!7?C@#23!65?O!4?O?G??O?GO#16!30?C?@g[aDpdzEWEoitVV]@@#26O???A??@#89!7?_#80_#39__ooOWWKKEEAVBB@???@??O???K@??@BEG#99???C??G#62O??W#43_#54O#16?_#33!6?E_K#44!17?A?O@AAKOW_#46!11?O?W?A_?CA?@#25??B#48_QCG??A?B?F@A#64!5?AC#9???_?A!4?_??G#8?G!4?G#42!6?W?G#25!49?_#27!24?OA?C?_H?H?S?hCOAG@_I?C?C??O?d??gA$#155!100?G#9!69?_#40!142?__GCA@!6?AH?@???A??H???O_wpgws{U|~s~|m~|nPC@Cw_gOcW`qo?__#94GO#46C?eO??O?_?_???C#57!16?@?@??So#47G#14!13?_O??@!4?@#67!7?@???_#3!15?AO#48!6?G!8?@#15!6?_K#25!99?_$#20!313?W?A@@?G#29_??__??_!8?C#95A#54!21?A!6?A#32??G#88G??OA#116!11?@P#48AH#33!17?C?A?_#43!16?_#16A??@!5?@???C#56???O@ACGGSSOo|l{ww[goO_?Xa\_A_???VOoG_?G`@G?B$#10!313?C#15?@#38O??@?AA@ELEMIKGD?@A?@#46!22?_#55G#145???@A#45!4?P??_#78_#146!11?_C#84!19?@CO_#28!18?_???@#0A!8?A#120??CGO#65!16?GG!8?G#3_$#121!316?_#50G?A#62O?O?O#98!5?O#78O???C???@#86!20?@!11?@#84?G#88!11?O#119I#146!19?AG#119G#48CG?_#21!15?@#10GCA#44O??w?OgSGG#103!5?G??__#70!12?A!9?B$#41!317?C#53G??O!5?O!4?O!4?@#119!36?C#116!33?ACG#3!19?OG#32O??@!6?@??@!4?B?G?K#14!18?O$#99!318?_!4?O??_??O#84???G#52!76?@#11!19?C#120O??@#46!13?@#47!24?@$#145!318?O#110O#34C?@A#146!106?_#116_GE$#67!432?C#62G-#6|qo|mG}B}`B@@@?GAAB!6?@?A@??A#9GcC?oOcOG#10??_#23??_?COOOWOO!6_oow!4{]sMo]wkK{mS{iC{[{[Yl[k]{m\l[oWoooOo_#110?G#155!4?_?O#35!6?O#6A@VFFJN\BBFjnYlZ!8~|^z~|^N^htjqZb[rA???@??ASiCi?Rc!9?@A?@A`AH?p?aOgO`N]TkVSjSKk?T??@#35?A#12@???G?C!4?A???A?C!4?A!8?A??A???A#16???G#47???_??_#42??_??O_SgQguoGpoIO_?Oo__OGx?W`G_#44O#43!6?B#27?_?GAGC#58_!4?A??A#144?A#105@@#78@@#27a#71!15?O#38?_O#27?O_Go?WA??GYgQmGOkQCQkOg!4?OOgPo_!4?AP{la^shV_^gVkZdXQlQDo?_!5?@@?@???@#116_OGS#119C@J#44?DYGjWD?@#52OGCdG?A???C#56C?GGWKK?C?CAH_WOdYdxU_PgJPA@?B#57A@#30__??_!7?A!5?KEED?BBB?_!5?AcTISdPGACO`#22!7?@???`?OG?iDo@sHqDaDATI|A|bGr?xAlGfKbYoNsiXrc}D^u~|V}~Tz^a|\fY|vm~$#1ACJA?B@?@Q?A??C?@#5???PCH?Q?C?KHS_FPHQLAGAC?C!4?C#106!6?G@?@A?G?D???A??A?@??@??@???@?A!9?A?A!5?_?Q?Oa?C!7?_#8!10?A#9!16?A#8?C??___O??G?G??KSlSKYsG!8?\qmTyTg[qlxmX]xuNMRLFAFI#10??iPGj?ARAfi|n}z|{]LeVrJ~QZlKVN[@?RC@AC?B?@?a@QG_?_?_!4?C?C?C???A?@#44!4?_#16?G?GO???C?OCG??CAC?D??_?ASHIcRHDJCAPAB#19@#26@@@#39AAA?!4CkC[{[??A@@AC@C#70!18?_#33C?B#25@#38?@???A!5?@??A!8?O#56??G#42C_@?C?@AgC!4?_??_?ggKw?O!5?CJINDBA@A!7?DBCFA#64!4?_G?go?wogoO!4?O?g??o??_GO_??_SgCO?OA?gcYNB#16O_!5?C#20!7?AO@@?C?G?@!6?lYgtjYie|rl]YtnSzU|aPfxExmO~DY?wJsLADQ@ItA|??o???`OD?B?`?OA??GO?G#34!10?G??O?A?O$#7?GC?Oo?w?K{{}}zv{|{~mrqLdmzDqaJX?AA??@@@!8?A??C#147??_?O?O#7!5?A#16???_?_?_?O???g?O??_?__O#156!7?@!8?G#7!6?@???@CCA_gs?GO?_??c_SOdQc!9?_#5??A???AISDcS`KpjQIQDHtBTzTnKZTaLPiDiTbKOEOc?C#7!12?A#11??CG_OO!6?A@QH?C??D?A!8?O?O#15!5?@#46?_!9?O_G?o?_CW_GO_O_#15!6?C??O?C@?OA?GO?A#13??G!4?_!4?O#22!4?_?C!8?A??A#63?A#31A#110A#45C#38C#116!26?GG#34_SdS_?O#41???@???BC#33_???A???A?a?A#14_#49!8?P?DG@CQ?HQDQ@#116?A??g#56C#46?WO_OOWC!4?@_#98EO#27???C#70!9?o!7?___??_?O!4?O#65!7?G?@!5?OOC#47O??@#19?_??F@A?@A@A@?B??oj{cwu]t!4~O@A#21???CP?GA?dIOjChAWeWCWE?f?O?IE?A?OWgg_!4?SDKjCSAOOoGCI?@OAC@@_!9?_?A_G$#5?@??@C?C#12!13?G?q???w?S_CoGoLACQcr~Z^^~VxXnjJdNBMN]D^VNID@B@B@@@A@@A@@?@A@AB@BBDA@???@?A?ABBNDNNKVf\}zy^Ykn{uGPgD?G?_!4?gOG#1!19?O?C#9!5?A#1!4?`__A?g???O_?i#9!6?A!9?o?kOgTgSo`??_?O`?@G?AO?C#13??__O_GO?Gc?Pg_B{~gzm|j|w~}~[]Lf\^V\Z^^\N!8B@@CA@A@!5?A!4?@#30_#51??_#46!10?_??CPkOIs_pM@#40OgA_WcgGQo?A!8?|SgO?Wa\b\uZul~j~Zn~n^vN~F@!4?ALPflRlYlRmSzC!8?_#57??C#40!13?A!8?A#146@DGO#88_#20?AA?C?A#3?_OG?A?_?O#36!13?ACAC?AG?CO???s???KQ@?cIDYEH??OcmlSJC@_??B#15A#24!48?C??A#19??@#27!9?_??C!5?GA?OAG??G?S?C_G?_?A?A_H?Ag@?iC?P?A?c?G@$#35!22?C?GO#156!9?_?g?G??_?_?GA_?C_A_CO??G!4?A#147!5?GOK?E?AA?A@CG!6?AB@B??O?KCK?G???_#9!6?_@O@??A!6?Gs??C#14!94?_!4?_?O_!8?C!4?_?O?_?_?!4_WsoKw[Wc[Ui\M\}N^bVd@HFBI??B??@@?@?QEpF]TJEpI?e_?WK#38?O_OoGCO#99?@#29@#80@#88@#100@#94@@@#75???o#53@#146!26?_C#42Oa#49!8?_#88!5?@i???C!4?WM@#44!21?A@C???BFCG#14O???G!9?A#9!16?@??A#116@?@#103A??A#59g#72??_GC#59!6?A!6?_#17!4?O_[]^~~wy|~{|}|}|_wwIO?OCG@I#25!24?G!8?_D#26!4?G??O!8?C???a!4?@??@C???O#20!10?C???@$#18!75?o?O???_!6?_O_?O_?_?_#5!5?@???@?D?CA?AGRgTITAgO_?O_OGO#46!94?_?O#11!22?G?C#17!26?G_?G???S_?GOG_?A#20!14?K#25C#85?C#41O?GSskGwwwOw_?_???iUlzeXa[aHcHQ?C?C#84!8?O#44_G#76!15?O#146O!9?o#48!25?G?DGG__!4?O!8?EO__O?OI#65!11?@!5?@?@?Sw#70!16?GS#51!4?GC?@#25!71?A#34!15?C??G$#15!81?O#7!14?@???A#47!179?C?ACgFHMADId??C#55!18?A#50A?@#99!11?A#55O#48!27?PC#86!15?G#80C#44o_!9?WMAQ@#119!19?A#57@??_#25??C#10_?_?G??@#33_?O?g#61!19?@#67@?BA?AA?B#72!23?C#42G$#108!315?@#157!12?G#86G#88!27?A#101!17?@#46G!4?BH?@#16!27?_#32AOO!6?_O!4?O??@#3!21?A#60?_?@?@?B@C!8?@EA???_??G$#62!376?DD??A#119CC?C#33!27?Q#47!7?_??C?@!6?G?O?_gDAA?@??@?@@A#10!5?C!8?AA@$#84!376?A#94A!5?C#120!28?C#13!11?A#0_??H#84?_$#25!377?G?@#34_?O!8?W_IS?I_Q_O?GeC!4?CW_#16!12?C#8C#57_!5?_!5?o{lvYQFSH{AKOAKOC?o?G!5?C@A$#16!378?_??G!5?@#62!36?G#6?A#88GAG#146C$#20!378?GO#21G#50??@#28!42?O$#31!378?C#32A???A?@o#67!39?C$#54!378?@#98?C#99??G-#6ZP\VZ@\I?BD#102!4?@#106?_??_?O?Sa#155?G#23_!5?_???P__{oPXY[BwdRIN@VbNVFJRFXFLfJQkJqDkA`ODI@GQCA@QDQ`IDXEgV`UHAeWiHaKHRniOGgQ#155???A??_#7??@BCAoW__pqHUGaS?_!6?@#5_?_O??@?OP?oSQpCIhtiW@JGhI_LQt_^oDI@iCHEPEHAC@A#32!5?_??O??G??G#11?CA@?@#47O_O?O?O?_#13!7?_??FfBj!4BpGIC?A#42!6?_?WcgUwuktwkyq[rQkRIcdMRnAVaZezFtKbEO??C@I@A?CAG??I?K?A#50O#58O?GPk?G@#39!5?OG?I@~#38k??BoC??O_?_GSoLoNOdAS#44!4?OA?GC#20S?_!4?_#42!4?o?_C??ql[AGIH__???@?ACKZCI}y|cn{~}}y^mnvRKEC!7?_ogogGU?yo#98A#120C#32A#3G#70?O??_G_OxMdIQ?A?@@?A?SA@!5?C?_OC?A??_#65_OOC#15_???@#19?KA??A_O_?o?_{ZSId[q\q|uz}vlrPpo`q\n]MGG?OGCI???OA#24!4?O#27!5?G??G?_?C?G?@!5?C?Q??OA?h?S??GA_?h?S?A?PC_A??AG??P?C?h$#10!4_?_??_#12_?oi`g??OusYyn}bLlv^~Mnl@CZFMCHA?B?A@A?A?C@#18!9?_??o?O_As?yOdYDIdY?Da?IgA@GD_E@U?UHAtOdOqGo?_#12??@?Rd^j]lTmQ|?QsGP?e?]?CO_?O_#1_?_?O_O_?C?HaSis??_@A`CIpCA?CASoESPEogAC#8_NYDMTzeHmXELjU|SWCG#46??_?_??O??O?_?CG??C??AG?O!10?O!5?OG??A?@???@?HCjSGBIC@D?A??A@?@??A?C#15!9?@??A#17@???@#48??GCOGS?Wc?W_C?G#40_??GQkPmPmHUhU`K`Ycg?Q??_JqHtH]dZTjNoNOnQHbK@EOF@!7?DAA?D??@?C@#48G?oG#20QA!4?O#40G#119AK#44@?AG?O__!6?A?@#20??_!4?A?A#44C!5?_O??GC??c#34C#88CP?O#28_#64!5?DOmApYdLrLn}}~kSJ?IA_CfHYoH!7?I`i@LC@#42A?@#20?C@#30!21?@GACkGASG??@??QdI`A#22???_!6?@?cO?Qc@?ON?nODQgApiThMqCdY`MPiQliSLR{JyE|i^d^mz^tFVsV[vi~h~Sn~$#62CKAG??_#5@G#110!11?C#5C?@G?Q???@OA#15?O_??_!4?k_c?w?OgookgSogwskWeoAW_C!5?GC???cA?H_C???CO?_???G?CGG#106?@!6?D?PCG_S`Og@GAg#110??_#9CG@@??@A#8!12?G?G??@@??_?O!8?@#7???o#4!6?O!8?O#10!5?_?___ooWOsI}yE}^E|J\IVMnZjVHPDI@AHATAHACGHO?OCOCOC?@!4?O?G#44!5?_?O#46!29?O_?OO_?O_?_?D??A?I?FK#54G#26AC#41_?AOEOqhSh]bUdPU?@???C?EAa@AC#27!5?_?GSGb]PE!5?ihQggTgQgVx?La[B!4?@|QdqKQ_???@?BACQt?@AH!5?C?O??C@G#46@IG???@!7?@#119??A??_#52!7?@?C#65??O_GoO???O_!4?FA#9!10?_#61!4?G?@#14??G#20!28?GAELIDaO?`vdYdUHtQ~SaC|UlZelZC^cBaOdONObOdAsITiS??g?`I@g?CA??AC?S@?A@?G_!6?H?aG??A$#1?A???A#156!19?O!4?O?Oi?CgOASHAGAC?`C?I?C?Q?G#158!7?G??C!8?_O??_GOC??GCQ?G?g?g?_#147??@ACCTBuKOOkc#5!6?AOD?VkGRgE?]@CG_?_#9!49?O_!7?IDIDIDEJ?@?@??@#57!4?_#13???C??A#57!23?_?G?C#16!41?_?A@#44!4?A???@??@_#27O?Ec#38rDA?@#71_?C?A#84!9?O#27}[??_#34!13?_?O_ghG!5?SLAO_SHQgE}AXA!5?_?dOC@CO??A?@G@?H?@C?Q?@??@??@?G??@#32G!6?H@C#49!4?_#78??@Ko#60!15?C!5?@A?@!5?O!7?_?AO?C#47_O#17_sws}q`w~|^n^~N~^BcjtYbLaLAHC??O#21!8?_O!5?o?l?JCxAhQcGQ?J_H?[lA_O?Gi?TGC??A_HQGCO_CT?O@iO_@??O??_!5?GOG!4?C$#7!4?CKACVKiNTURKB@HJ@@#9!8?_??SJ??@?A#13O#10@!6?D#16!18?@??L?BO?I!6?P?@?G!5?@???_#156!11?A#35A#6!15?@???I?CHVLJ^^^~n^n^uZvULiSI~NMmLIhCIpSIOdiC@AcXADGJ??_oo!5?_ooOG?@APADAH?D?D?@??A#36!10?C???G#47!22?O_G_CoS?S?O!4?O?AOCG_CgOcoQWokOsgXCOC?GAG@HEHQ?_?o?o?OaCOOA#39_#25O@#21G#88!19?d#86@#58??G?G??W?_??A#46!10?_?_SG#38???DA?@C#116!7?OQ@#46D#22!6?A#108@#53G#88Q?_#17!19?O#14?G_#57_!6?_!4?A!9?E?HM]VQM@#36!12?@???@??O_?A?@_A_?A@CK!4?A@#25!49?H!8?_#19??O#26?A!13?O?@??C!6?@_#34???C!4?O!5?_??_@???O?AO$#11!4?_??_#36!28?O?G?C?C#17!24?GO!7?_O?T_?GoCo_?_Q?O!4?O#48!106?_??_?O?O?_???__?_??_?S?_?gO?C_?g!4?_G?C?a??_Q#16!55?O#86??_#34@#148!19?G#71???@O?C???A#116!14?gC#42_r@A#22?O?G#88!9?_#33DA!9?C#3@#48AOc#36!22?O#10_!4?@#48?OGGC?AF?@@G?@@@FA@#59!17?G_S?kOh!4?Og?C!7?AAA#25!79?@#26!27?G_$#35!5?O?O?OO??GCq{M#14!19?_#147!30?@#14!136?_???__?O???GC_Id]Tuhi\uTJvaGWCOkgcWEsPyTlJUDqJ?JbC@BgAH@ID@?DAGD@G@HA@??H?C_G?w?oCWEgcGu@MDHJ_B[`D_@?@#159!21?A#57!25?C#33G@#0!17?G#1C#98!9?@#14_#32C#47CGS_!8?O!4?@!4?_O?O!9?OCOoGY!9?@#72!18?G???OGE@?C?AO?C?O???O#30?OGCA?@WE$#88!354?O#48OA#28!17?_#32G#116!11?G#57GO_O_#120!21?@???CC#28?C#146!10?G#61!24?_o?G#51!18?G$#146!355?B!31?o#67!26?_ACA#116OGEAA@!7?_AGO_#56!22?C!5?_LAEXh\Z\K@A@$#8!414?O#0@!7?A#103!35?O$#146!415?_?G$#4!415?O@#56@!7?@!9?Gco_G_$#1!415?C#33A!7?@!8?C?O$#3!416?G?aQ??@$#94!416?O#119_G$#98!416?_#88C$#134!417?O-#47A?AAGAYGKCGS???O?_#35A#106?A@?@#156???C@?Ha@O?CA?@?C??@?A?A??@!6?G#18???_??I?G@?OGS?_??`??@?A@@??@!4?A?CAoBGRMxulOnGoI?O#12??BBEDifJrIfsRmTikZshcWisgO#6AM[Vn}jTshVl?mSVy|SjGsQ?nTi@YiC~SqKGJGHO!4?@#48_?O???GOS?E?AAP?B???D?A?O@A?A!6?OAO?OGC???OIlgCaO?GH?_Ck?KCWACI?A#42ACOBTEjTn^v^~^~~}~{v{z]}|~}|~}|~Yy{oyOCX_!9?@GBC#39O#58O???O?O#71c#39!8?@G???F#27A??EM[WKwOcwq_S_?h_Cf?BC@??_?S`YCPKb?HA?D???A!6?A@JAC@?@#34BC#120A?C??_#65_#27?@??@?A#65!4?_#116_?OGC?F#120@??G#27???C#49c?C@??C#48??@#33A???@#70@?@?NPs#61!4?A_??@_aAE?@#60?_OGA?c_PLC?A#16o?o?W#19??_!8?HsA|AtItIsNSJKB[ZV^SKP}yJA@CA?BA!7?@#22?_??A_?G?@??O?@O_O@SGAsHqChAsLoJolOjiDWRkQEXVa|fWfyu^wl~sJ|Ujs^Rq\tM~J^|$#51gSkWEwCs?w?_O?_#23??G?GKGUWUXQxK[?[KEB@???G!5?P??g?GO?@?O??g?YCK@?U`iDAdAPIC?A#30??G??_???O#23?@???A@CGCbGpEGOaO??`Q_n_gW#156?O#155@???_??_#7!5?BE@BDBAFlPa#8!10?@!8?@#7!10?@?ACAE#9_?O!5?A?@#14?_??oGgOCgAOAOHAICQAC@cPACA?ADHCHBLD?A_SJDO?_??OP?ICi?w@H?G?DaODaD??@?_?_???_#47??@?BGBC`@A?@A?@??C@AL?LwcW{w{q{pkokA#44OC#40O?L?OUGPeGv?\ahEWfWfWv{Og!5?@?DA@?HA@C@#22?C_?D_G#62_#33G@@#14_#16@G#17@!4?_???G#44_?G?@???SD??O?O???K!7?AA?G?G!8?A!6?_!4?XAGc??`#98!5?@CO#146CO#120_#65CO???HqlQlDGfQM@CG!7?C??O!5?A#17OCW?geEy!7~uA|A|ItItJoJcBKBCG!5?C#21!8?W_S_ItIsI@Is?LAdOGQCHaKAJEg@bXAAHQQgAADCIACSOicCPdWaGC?G`G@??A???O?@???GC!4?__A$#52C??CO#15C?A??CGGGO?oO_o_O_?___?o_s?oh{y|^}Vzn^}nK~lV^enVm~F~vFZdPaYdGS?oGOGc?pGC_ICPCQCPaGcQ?Dg!4?O#147!4?@AK?ENCLL?W???_#110O#5???COHCPiDRcGOGcOGTgO_?gO?SiHUgQ@?g?DA`SBHCO?iT}d???GCpOOC?F?@A#32?OGG!5?A??@??@#46??G?K#42?_#36!4?A?@#57?C?GAC!5?A!5?C!4?G?g?OCI#44!11?_HAk?wCAO??_#14!18?A?@C?ADA?AC@A!5?BOS#20_#27oG@_Q`#38!19?T??@@BAB?DI@CDI@MOIP???O#48?C?C#44?A#47!11?O#46???A??O__!9?A?O!6?COO!6?C#67??_G!4?A#146@?F#52!7?_?y?GOG#8C#116O?@IUxC_#60?m#64ALQlQwFG`OMP@O?CA@BA@?@??OW?GDA#61@#30caUXGD!8?H#20!9?_?oO_?_?hAc@?OcWbWvk\b[jStItIt]PJ|Q|Qlulzc\qkSGUaSc?sC_CDGoGO?O`???@!6?P??A!7?@C?GC@_??A?O$#66OG?_#13??@?@??AEAECC?W?O_?_!8?A#158!4?_?_?O_?O_?O?_O?gO?g??Oc?IPcO_ASGdA_ATGEGVcQcwCycXcZcTiShQhCgS_#156!4?@#106?p?O_AOFScxICWsKOG?G??O!4?O#1!7?@??@??A???}OBg??I?sAgnO!4?Tz?bGAD?@#10!4?_OgOskWuMBDJF?B@?A@C@@A???@!6?@?@!8?A@?@???@?DA@AC@?@QDA?A@#16!6?G?g?OG#20??G#34!21?_!4?_#46@?@!4?@MOG?gA?A#38??hMhdI@#33!16?C?O?_#34C??GOC?W?IoE@IOIW_]@!8?A???@!5?@#32{?a#48AI!6?_??_o!5?EG?O_?_!5?G???A???GGK??La#46!12?G#119G@O_#56?AIFO#59!7?OOK_OGs_MA@!6?@???AC?@#47?A@#20??O#30!20?O?_?_?_ApI?@cXeWdGO_CB?J#24!6?C!5?A???A#25??@#27?`?G??H!4?O@?A_@?G?DO?gAG`C??AO??CH_CQ?G_A_OI??@_G`?C$#36@A@#10@@@?@#14AAA??CGGG#17!48?@??A??@G?ApOGP_I@G?ICA?GiO@SgO#16B??C#31!21?@?A#9!8?A#8!40?__ooG~mLFEBB@B?@#36Og#47_?_O?w?{_{asWsxklw{Ykxyt{xyexE{?QxCX?_O?Q@OcAc?o?O@AOaD_JODG#44!34?@#48???A?AADBLA?BCB?D?@#86??C#84!21?G??_#58???A#42_#41?A#20??_O!7?B!4?C?S_yC`CHU_HQ#119!5?}#33C#14H?O!8?O!9?G?_O?O?G???ED@#70OGG#34?_#33A#119AO#78!15?BC#88A#67CG?_#3W_#72!14?G??CA#9?__#51!7?_G?G#15?C@?@#25!50?_#26!22?H!6?C?A???A!5?_G??CO???@#34??A!6?c??A??O$#49?_O?_?_#42?O?_?_O#147@A@A#56!161?Og?C??C??C#13!5?C!8?@#42!15?_?_??_#44?_#13??A#62?A#32??C#25!67?G#41_mA???AcGRG~a\UxfWeOfGBg#88?OO#46G??_#21!11?O#26?GO#42C???_WJgAC@iOWU_[u_N~v{!4?@ITkCgj[^{?Gw!5?@@AFLCJLNFBBB@!5?_O!5?ChRJBE!4?@#32??_#94_G#103!20?@@#56O?S_PG~IKA?@H#52O#42?g#38!118?G$#48?@#34!6?_#12@@!4?@ADDF@EHEHELAABA@#57!153?_Go???GOCg?C#46!25?_?WEI?_?CW?H??a?P???_O_So_WdWO_OA#116!73?AG#78_#32O#25!16?_???A#46O?O#146!17?@#56G#57c#25??gO#16_#47???_!8?@?C!4?o?OOK?C???__?O!5?XaO?OwW]DZbEA_#134?I_#36!21?_GOGCC??OACA_o@?CA@$#17!10?O!4?_#145!312?_#44@!4?o#88!17?_C#116!20?P#20!4?A?T?A?A???_!9?A#10?_?_O?W#3?O??A#52_#78???W#84_#70!39?O?_?G!5?g_AC$#11!11?@@@#148!314?@#54E#116!22?OA#33!33?@#98@??_#67?O#70O#17???A?C#36!10?@#103E#98???c#57!41?_G#10???O$#30!13?_#94!315?_#32!23?GA!33?C#56@A?CG?__!8?_?OGcEO@?C#134?@$#119!389?CGO_$#146!389?AOGO$#28!389?G#57@BB?CW!6?_?_O?G??@?O??O??qC!8?_SgsO$#0!390?_#3CG-#66~|quw}gwcwoow_wogWo_#15?A@BBFBDFIBFFNNiN\JvGVDIDRMdJdNQf?QHAC@CAGC?@?@?DCXA?JCaKz?iT_T?IO`KpiTgaWdW`#18?ASnYn~ZcXaT_I_#12!4?OiSHvHTInje~\q}y|~s^hKrW_#6`bgY@SBILGD??CC?BAD@AAFFAC?@B@?@#9?C??@#48CGA?A@!5?_P#42!13?S???TGQ?T?GCgwsw{~u~y}z}w~{s}{{{wGWwooowOgO_S?gC_A???G?Q?P?PBCBJFA]Fn^^~z}|yPBXD_G?J!4?B?OICBSKA#40oKP?GPb?JU?Ti\aTqlZuDRlaLB??AAC??G#16@#42g_pOO?`b?AEC?C!4?NA?T_[UTnZAd@ADGABW!6?A???W?@??@AA?C#116A??G!4?C?IC@B#49W_WCYaSGO#33_??_#52@AC?V?AC#36A?@??A#3@C!7?GC!9?A#36oAG_OGKCqOG?C@#42?@#19G!4?OGW{?IsAGu?iTiTiTi}~DZnJ~N^KNNC?@Emn?OC#21??OfGRkQ?kRkRkRiTjSjSgVguG[@}b?p???OSA`___W_@@OaGA?A?D???@?g!4?CAc@o?oO?Q@G!7?@@$#49?A@?D?A@ACGC?S?CO#36??D???O?_#23GAWskoW_O?_!6?_!8?OHcASgQhSr@~s^}yYhehS_O#30???P?ITGRCjS`IS?CO#23C??EWt?ODO??A???DOCPa@_#110??gOG#155_?CO?H??G#31!4?H#110?A?C#7eDYS!7?A#48??_?_?_O??O??_#36!4?_?_G_?o?CG_GO_?G??_#48!21?_#14??_!9?G?C???C!4?A@??A???C?AA?@A?S??CG?G?C?CHCICgGc?g?_???_#34?C@ADiSciYdYcydYoKqGs?w_O!8?C#88_#160_#74??O!5?c#58!4?D#76_G#48G!6?C?@G??AOA!7?AGq@O!9?_?G?G???B???AK_O_O_???OG?K@GO?G?@??AA?_#47o_O[?IFB@?@!5?D??B@?H#64O?WoKwTo?G_!8?@?OaG`!9?_O_??CAB#16O??_#20!22?@!7?_Ao?pMoHO?zExFyeWvkRlzRkRkRkTiSjSjVgUGv@}?KiKzCZdAtGDIPACGC!4?S?_?O_COA@?C?G!4?C?A?W?C?GK?A?B?AA$#51??C@A@PE@AFGFIFIFBMWf[oKO?O#156??@#16O#18G_O_O#30??o?OG#18!4?_?_O!5?_`Oc?_Cw?G_??_O??A??@#51!7?_#17!8?@?A#16!4?I?_#106??_?c@aYdZeT}PnPBE?U?O?C???D?@A???O#1!6?R?C@?DADA@?@?@?@!6?@@?A#56!6?CA?@A#44!34?_?O??A!6?C#48@??@A???A?@???@?@???@@?C??OC!5?OAOi?_Go?g#49!11?GAODOdODW_NoGc?W#27??_[F???@ACJCGw_?_OgG???g??GO?_c[?gCO_G#47?@?@`B?C_!7?C#16??C!4?G#47???TYs{qtlwc!9?I@uSJeYoC#56@?O?_O!4?@#42??OG_???o?HG@F#84FK#94O#57A][wwgulZdNQFICG#60_GO__@_suoAGIOOG@?I@?_??G#61?_O?A?@#18!4?C#30!31?P?oIpMo@OChAwDH!5?C#27!14?@!4?@O!9?O?C???_@?@_@??I?AG???I_C@?G?O@G?C??G?A!5?@#64__$#161??G#52G??C?W!8?_??W_?_#158!11?DOaCGf?YDQKPICIOdGQHCGBGAH?A!4?D?A?ShCh[rCmT_IaKpCIQC@iRKbWfWfG`#147!4?CXA[G???GG?M?C#5?_??I_!4?a?@C??A_CrG@YCGCDAAC#11!10?C#57__?_???_O_O_O_WoGOgO?S`KOdQLADI?DOH_C?O?_#46!24?@#34!12?_#57?@#44??C??A?G?G??@?@CHADHCGD?CA!6?O#51!16?A@??C??_#46??A#26_#58Ga?_#85C#54G??O#71???C?@#116!10?o!8?C???C!6?O#20@!7?@@IDA@I#32!11?_?MS_#10!7?_!4?_?ACOO??A@@A?@#34?_?_C??_C?a_#119G@#116F#48G???C#56!9?@AO?_!7?OQ??cCgsswJOc]?EBB@@@#17O__w}R!4~nvfB~tJ|vH~TiTiTiT??ycOs?o?_#22!35?@?a???SACZcI`GSITIdJuYeXe\it^on\rmgUtZzUjtXmIrLJFL?FDB@@@#53C#8O#3O#0G$#47!9?@?A?@?@?C@A??I?_G#17!15?__Og_?OO?_G?c?O???O!4?A!8?OA#7!47?_@#9?OO#10!17?_?_?o?o?OG_GsG_WGcWgWoW_[G[CSAGAKAAD@E@A!61?@#46C?AA@JADABEPI?A!4?O?C?O?g#14!24?@#44???@#41?OMXo_!4?DA@AGADA?HAGA#85???P#34W?SOGg?OOO?_!5?@??@#98_#33?@#3!21?O?O#57_!9?_!4?O?__???@???B?GC?A@#44?GC?_!7?@?A#48!10?C#119@AA#116@DAC@#59??GC?DB!8?@@#68!4?G#15_??GA#19!75?@#26??_!4?A!4?O??G?O??G#34!6?P#26!4?_O?A!7?A???C?A#56O#32G#33G#60O$#34!11?@#13!9?@??K?cW_#8!116?WGW???GEFABAC?AA?D??CAB?CAAA@B@@#14O?O??OC?DQC?C?aGcAP?G??C_#16!63?@#17???@#22!32?@#38_?A!4?@AG??C??O??OcOOAC???_#119@???A#1?A??C#10C#14C??C?G!4?GS?O??O_?_?cG?A@??OC!7?@UgPc@G?PC?W_?G#67D?_O!7?A#52?C#27O_??OASG#88O#98A_#56O_#70_!5?_??_?_Go?O!4?@A?IC?C??AA??C!7?GC?A#30_??C#25!81?G!10?C#38!10?C#34!10?C?_#30_#42_#17OOO#36_?o___$#10!22?C??O?_#32!117?_?_?o?ogOWOGOG?SG?O?G?K???G?C#47??_?_?_G?gQg_XyPmWfQ\i~f~^z^~~j~~~iVl~I~VzFFJDB?@??@??B?AH@@A@FSfENIJClSiSg`QoW_woo_o!4_?O?O?OC@O?_!6?C_???A!4?C??@B@ACJ@#39!4?E#76?C#116G#84O#88!18?C#46B#20_GA?CO_#32??A!6?_?O?G__#67!21?G#116T_#46@B]g??CAGAC!4?A#146@?A!5?GO#78!15?o#146G#88!16?A#120C#146@C#67A#65wWG@@_``!4?B@??@A!5?GC#52OC?A@#51!119?_?_#24?A???A$#5!161?@!9?@#157!135?O#151_#160!18?A#50?@#33@!5?A#88?C??G?O!5?OC#56O#120!21?F#88A#62@#56GO#36!10?_???_#120@?C?C!4?GCA#134!31?@#135G#103O?C#61A#72!6?G??S#8???C#9G#10O?E@#51!5?GG#47!122?_?O#13O#37CC#102?C$#62!332?A?@#67???G#46A!4?K?G?K!4?a??__!4?O#103!11?_#13!17?G#65??@?A!9?@#154!29?C#132GG#57!10?O?G???C@??O#16!132?G#43G#62GG?CC$#22!332?_#146A!5?GG???O?_???O!23?G#60!20?GA@#3CC??O?@#138!33?AO#47!19?_!4?_??OC#52!124?_#10?O$#25!332?O#94@A?CC#49???_#3W!8?C@#135!42?AC#138G!4?C$#98!332?@#0!10?G#44A?@A!5?G_g]?I@_!8?A!8?@CHF!7?@?C#28!5?_G#98_O#32_$#21!344?@!4?A#119!48?oO_G$#120!345?O!4?GA#132!47?G#5C$#57!345?G#119?__$#27!348?A@@$#62!348?O-#66~f^n~n~^}~^~~~^~z~\{||}{GgO#10@?AT#7CGG#23BA?A#17@?{ZIlS_DW`???@#147!4?_?_?GcG?C?O_#15`?A?UGtGbSc?O_GG@?WEgvKtMtMtAG#106!5?IChA|a\jtFHvB@QDIcA?P!6?C?C#110@#46???O?O_?G?_!5?OA_O!7?A#51???G??G???_!5?A#14!4?@!5?@?A!8?C?Q?A_G@g?G?I?C@?GCO?_#34!8?AC@SHeObGTiDgU?iCoCo?SOc?O?o?O#57??@??K_S_?_!7?_#49??A@?@GDGBGV_F?D@#41___A???A?A@BG#88@?A?C?C???OoOOSI#46W_?O!4?A#17C#47!6?@@#16???@!7?C?C#17?C#47dIDqKf|~]~VlztW!4?@?C@?@@A?G?_?O!9?^!6?__oOy^kC#32_!5?I#64???@?@@?BI@E?H?C?a???CA?@??_G?owWKI@?@#16O?CC?C#19A@#30?A#19!8?[LCbGqLoDI@A@ABB??sI|w{woG!7?O#21?A@C_?HQcXATi\aPIdIPiD?eGOkGWcZcyLZD_?oSKCA?T@_?OC@COAI!5?AD?DA@#19A#66!6?AA?B@J@BBB[G$#161?G?O?O?_??_???_???_A#52A?@AS???_#6?Gwo#16@??DGw_??O??C_?Cgwkqk_OCC?C?C?A?@#158???C`GTGvItSIZuhUdOeZchUGoIpIoAH#18CGvmT~DZE|ATaOA?A#12?KuHQsPlZglV}B~~JVHY@?B#48!5?o?Sga[Hu@OHCLBG??A!4?@?A?@#52??G?CGC?C?C?C@#51!14?@#48?A?A??hG@cQt?wsywIo_OaGO_O_??_#49!8?GCQ_T_T?yCpKy@}GiWQksCW_g?_#48?@AAHGsW]\gwog?O?_#34??@CBGV_VGVw^YEF???G?O!7?_???OO?__#99_#20?G!5?ACkOMg@O?@G#40!8?A#33C?I@#48Zs_!5?A#36!10?O#57??Co?@AO???CgO?HACBOAG!6?o@#27??YdIDACA?@#33???@O#78Jo#116Fys?G#60??O_O_OO?EGc?S?ECB@!6?H???@@??C#47_GA@A??@#20!34?C?C@epacjSzALqLiZU|ulZe|i@a\mtYtmTynXtlRr`XcICq?wFwF?PgSa?gCb?_G?_?OGoBWSGGC?C#34A@A?@?@#69!11?@A$#49?O_#52!5?@#51!7?C?A@?A??A@@G#13B@#12AB#147FC??G#15@E[@?c?i@WaO?A???A@#106!8?_#17!7?_#30???AG`??CHQDG_A?@?B#17???@??a#147!5?_!4?G???wsGo#110G_#9!5?CA!8?CCA?A@??@#32CA@A?@???GC#57??O?AC?A?C??C@??C@?@?@A?A@@!5?@???C@?AC?C@C?C?g?QCOqG?G#10c!4?_#42@?B@DLTNFNN^n^|X]IaO@OCA?QA@@PAB@I@A??G?IGCG#46!8?A?A_??C??O#51!11?_?G#20??_#38_GOA??AAWfPOgSiG#33@!4?C?GG!4?s#50@?_#22?@#34?OA_@_G?OCO_I?OAK_#116O_#14O???aH?j??_iC`OA?@?g?CG_!6?@??C?@?@??A#49_??w!7?QdGD?CAGAC@?@#48i#88C?@G#70@??O?YcZa]DkOGOAEB?@@!5?GCOA!4?A#51A!5?A#21C#30!37?ABBAPM\ZSjC|aLoS_HA#25!6?S#22!9?O??A??A???@?_AWFGB_RgTiSJ[dZSrMxDeNWFID?B?@#36!19?@@$!23?@@UKu[{#56_#62??_o_#53O#11O#158?B!5?A??A??A#51!28?HA??aOC@O#23!7?GsPvGPi?O_O!4?CG#5!5?Cg@IOc?Og?K!4?A!4?@#14!8?O?O?_???_GAS?C??AA?@#44!45?@!4?C@G???A??O#27!11?G#51!5?G??G??C?_CA_A?A_?O?O#44!6?@???A?A!6?_#26!12?O#39O#27K@??|k_?_??`?Os_X@BBFEBM!6?TjQn`TOaHcOAWCQ?C#88???A@#32Dec#46JZOaOO_?D#3!12?BC@C#61_!9?O!5?C#44?_!4?s_`???G??OGAC#98???BOG#120O#65CG?_?_??K?A?@?@@_?QGGCIC#72!8?C!6?A#15gC@@#27!73?A??C?A?P??C!4?gA?@G?AO?G?aG@C?@?C?@?A?A#49OG?GCGA?A?@?GA?AO?O?_$#69!24?_?_#106!6?AC#18@AC??AC@A@?ADGVDOKQTKrYtBWb?`CIOeGO??A_#155!50?A??@#7o!6?A@#47!12?_?GcOiOcohyx{tw|{q{|ux}t]xux}x{y|z}}|}~~~y}||z}z}ynxzTllOFCQDARA@DBSIU_KqAG_W_o?O_?a_`SHe?`G?G?_C!6?@@@D@@@BEF~M~{pKaT?D?ACBCSiKPWoOgoc??C#40!9?Cda?@DWCISASCGG!5?@C@AC#85A#44?EG#25G#0!19?@#65_#120O#10G#44??CGSHCG!4?A#116!10?@CO?_!6?_?_?O??C?B?G??A#48I#34!5?A??@#57?_?_O!6?A@?@#59!5?G?CO!7?O!4?@??O#57!5?_#52A@#61A@#14C#30!80?C#26!19?A!6?O#42??_?O??G?C!4?@#52!13?C]$#47!26?A#1!6?O#102GW_#43_#47???_#30?O?W#23!5?@?@GaG@IWfOZSZonHVI]tG@#6!57?_???O???C?@#36!15?C!4?G??A??A?A?A?A??@???A???A!9?@???O#46!12?C#14!47?@!4?A?P??A?@???G???_#55!18?G#58GD!5?_#46???A???G#119?G?O??__#98g#48?_#57!21?G#119E#20!9?A#13O#56!14?GA?GGOooO?_GS?HCD@?@#119??O?C#28!17?G#89C#119A_#56?@CF}DIC??_?___WOGG???O__o_wmcJFIA#17???_OOOgwyz}z|}~|!8~bqz[vLqNyt}|}|{{~~JtA@??K#25!63?_??_#47?_#51_?OOGOKC?EGA@@?@???C_CgAO_$#37!35?C#35!95?O#10_O?gKKCyfQyB|ITLaEHE@A@A?@?@?@?@!106?G??_O#82!24?O#33O#145O#71!4?GC#84???@?A?C#3??O#38???DB@#146!24?G#67!25?A#65WG??_!9?G??A??A#84_c#98B#94!18?_#84C#3???o#61!14?_?oO???@@#30!13?_#17!114?O#161_???_???SOoKKe??_gC??_$#147!132?G#11__?oog?GKC#42!131?@?@BDBMFLMUIOq_W?_??_?G?B!14?O_?_o!9?V!6?KGUQflbjKszn|oO!6?@?a?PGOOHOG??_??A?A#9??_#60_O#48AIAG@A#36EOA#70?_!5?C!4?K#36!38?@??__WWKUA@?SopKCDpOKC#52!114?_#30G!4?@B$#44!313?@?A#146??G#108!4?G#33!53?A#32DCCG#10E_OGC?CA???@???A#146Aw#103!39?_??GCAA#131!131?_?_O_O_?g?_O?_GO$#43!313?C#116A?G!8?@#46!54?@??A#103???_?O??C?A#32??C#78W#67!45?@#162!133?_??OgO_CKO_WSsCOGwC$#47!315?_#51!67?G#42!7?_?O??O!6?K?QooxXCDG??AO$#120!391?G!4?@???@$#60!392?G#34_O?K#33@#132@$#52!394?g-#66A??EDXh^Mn|~z~z^}~~~nneH???@#72@#57_#56IC#1pO!7?O?_#17@??@@#11?__#147OO?idqhsjk_!4?A[`G#158H_?Q@e\tBkcIkPHSAKRKA@A@#23@q|vexZCL@A@!5?G#46_??O??_OgO?GO_G!7?G@??@???G!4?_AHADA??A_#44!10?_#49???@??A?@G?@?G@?AO?DO?i??S?gI_CW_W_O#57@A?E@CGQKTSrWsqwPs?q??_#44??@???C??_?_#51!7?@??@??O!4?_C?A?CGO?G_?_#48?ADADI@iUDUni]c_Og#27?_W?CB`!6?OnxaC?GAu^DGA??@@AKiGs!6?B@?BLTGAcOICA?S#17O#47??E@_O!6?O_SOJHVFHfIB@DA???@!4?OGW?KA!4?@?_!7?@?A?@M@E@A!5?@#32B#60_!6?O?CEHMF@AG`?_A@??_?O?G!6?A#16_???HATGkQGC?G#19!7?_O!4?M@MHELI?pCqNm^m@oNo!4~vz]^NKOS\^MK??C#22!8?C?C?C?C??C_??C?a??Q?GP?C_W?Y?XAK@ADA@@#3O#64_O#66_?GCA@@#51!14?G_GS?Qk?JCB$cJSgOa?_!7?_#69!4?OOXu~NYC#64?C#9@#10B!8?CGKC?OO?oO#23?CG?D?GGCA?QKHtA[`ACRs@S???_#30?GQHO@e?A#156!8?_#16???OC!9?_?O?O#11KGKKC???GC???CCA?@#14!12?@@?@??_W_PG??P??O!4?_#34!13?G??g!4?G?C?Ga??O?G_#14!4?@!5?G#56!10?_GO?_#32_O_?_?_#49!6?@GAH?JcHEHUiTmd^lq|Jyj{uysiOo?O#36S?C_O#46!8?O_#44@???_#49B#40_Gg??EXcYk]lOC@O@?G@#20_A!8?CP?g!7?AC?AcOH?_O?G??_#56???A?EK!9?_!4?_?GG?GCC?@???@@@?A#61@#70@#27_?wCwAKo!8?@#10?_#56?_O_O_OwWs{!6?_IsO?E?_Ap??OG_OKmMRdMDF@!5?A#30!49?GC`!5?A?@qGQO?_#25!7?G#27???c??C???O??C!4?P?C???Q?_AO??C?@#32__??G#36GC??@#49!19?aWCQl_I$HCIPiCO?O#68!16?oc?I#59?_#32GM!5?C???OOo___#43!4?___O#37O?O#106?O?PeGTbG#15!6?@GPAIs@Q`Q?ugtpgr|}|}]LAG#18HACZACH?AP?@#43G_O#14?!4_?_?__#48_???o_O?w?oCoGQsiTItjU[`UgsW_We_W??o?_?O#51!26?I!5?C!4?C??G??_#48@??B@AI@KBJCBMBlC\UL]o]gKwOo#34???A??AG?B?GA@CAPG?QHAS?S@#47@@BDjDMHAC_O!7?@?D@ANnUL#41??o???@?A@R#39@#42!10?GfT]XSikoO!9?MKO_?@_AD@jxugn^tx}W!7?@??C#57???GS?OKOAGD?@#32A!4?G#34??g?O?GAD?@COH#33C??_O#48W{woKOG#52???@?@#70?_G!6?O?@AG@?@!5?_#3??_#57@!7?_A?@#15O??C?AC#20!48?_oRnj?_o@vLjnYtZknynqFrmZAZfYhVwFGvOrUlIC_R_W_GCX?La}`YKECBAB@@#131_?O_iKPIDCAI@ACPYsYsYsHeH?QC$#47Oo_#161???A?@?A?C?C?@#36!9?@ysZS#8o!8?G#13C!7?G#18???@GDA@?@C@AOAg?S`YcA]jgs#51!6?C?G?@GAC#147!11?__oycysIoOQFNNBC#35B@#10O?SGSOi[sAYkRE|JpETkATiDASgAS#42!7?C??A?KGAGC??G?GCGiCKCQiTiEvvUV|i~afpSrOg{?sAO?W_?_#36??@!5?_?G?_#14!5?A?A???A?C#52!34?CAOcGoO?_#14!10?O#42??@Q[F@@#58_???OC#44!12?_Og@cAC!8?BBEG?O_!10?@??G#3??@G?O?_#44!4?A@?C#36???G??C???@A#88G???G#42_o_?OcIQC?a?G!9?@A?@#36!5?G?CA#3??[!5?L?GAG#132??OG#103CACA@#10??_??CI?A#59?C#42??_!5?D#21!51?_??_!4?_?ODIcRODOHoGP_X_W`QGFgrGHKh?taMgZEFd`EC_P?W#46_#11_#43O?G?C#10C#162o???A???@??_@??_?`Gc??A???@??P_$??@???C?_O#0!23?jO???G?O#28_#15@?AAB??FG#17!23?CAG#37!28?OCGCAC#102!5?@#7??A@?A@#5?@#32!5?K?ACAGA@G??O!5?G#52!49?@!7?G??_O#42!20?A?@?@?@B?DHODCWc?sgOg?A_?C??@??G#57!5?@A@JGIGosi?H_G???GO#34!5?EA#26G#22O#74??G#47!13?O??_AgOO#34@@c?O_!6?@GQgQLOiS?C@A??A#57??C?_#10@AO?__??_!5?O?O??D!4?A#0C#44?_#49???O_GsCxAWEPaC#116A??T#57nc@??o?sWm[mUMBDA#116?A??AA@#4???_O#36???_?PO?SSAA?@??_?O?xpWGC#14C?A#26!94?@#19!4?_#50!5?O#24GA?@#69_OGC#163og[OomoyxTosgOc#34!8?_#66??O_[$#31!33?C???C?O#14BAAGC?KCC?UG#106!55?@?DAD#6!8?C?C??C?AG@#57?_!5?_!4?_#47???_I?TADADGRd^kNrF|fz~^v^vzvSzrxlSaToG??g?D?OG?@CLBBB@SJfBF^E}t]w]`sgO__!8?@?@A@D?PREjM~]{[qscqo?Qo#10!24?@???@?@?A#38!12?OSMwe@_?_A?A[j}vtG#25!8?@!5?O#32A?OO#47OO_#14!18?_!5?M?JHACQGOa?D?A??A#120O!5?A?AAA#44!9?@?@!4?AACKA?A@?@?@#64_#88!4?@G#67_?D??_??_O?GS!4?A@!4?_#52??_???G#51?_???@#37!107?GCC?A#13A#161OGC?CB?C?AGCITJICB@BD@?@$#99!34?BGEG!4?_#51@?C#13!62?_??_??_O?OOGG#9@?AA?@@??@#10!79?A?C@?A?C?HC???G?G??G#32!44?@??CS_W_O#46!37?A!5?@CCG#17C#57_#116!19?BG#67__#46@b@!4?O#116!6?_??_O?W??@O?ACC@@#40!9?A#84_O@#3A#65!19?_?_weQC@@!8?C??C!5?O?G!4?C#17_Ow{wq{ivQcvz~v!7~^n!4~p}puxqt~MzLoP_P}NoN#62!64?O?G#14???A@$#33!34?C!5?_#42??A#36G??G#53!61?G#12G!5?A?A@AA@?@?A?@#56!145?CI?O#40!44?AG#119AC?O#48G?_#120!20?C#48@ATMKOMS?G_??_!8?A?C??_O???C???@#88!7?W?E#120G#61!19?O???G#64!10?_?G?GG?@!4?O_GCEE!4@#60!109?_#70O#16???@#52A$#29!34?guGo?_#46!5?G?O?_#16!273?A!5?@#103!22?O#32?G?O???_???_#103!5?__!7?@#78!17?nO#119!20?CD#138G!7?_O???C#9G#61@#8???OOO#4G?@#68??O#61???A#30OG!4?@??@G$#62!35?@@B?K#47!5?@?A#33!275?@C#88AG#56_?_#8!41?O#60O!7?@#119!17?g#78!20?@#28O!9?_??G#135?A#70!12?A#47!4?G?A!7?@$#88!36?O?_#16@?@!6?I?@RFMQ??CAG#98!264?@#116@S#3G#146_#67!44?_!5?A#134!39?A#98I#103O$#100!36?_#43?@A#120!285?G#14_#4!47?G???A#120!42?@$#80!38?O#3!335?_#146_??O!42?C$#102!38?A#133!337?o#119g?KCC$#33!377?C#28_$#98!377?O#78G-#47@AFEDK?O#69???__?GOgKQMB@B@?A@#28__!4?G!7?g@#3_?G#32@@@?BA?A#43@A@@?A@#47O#102@#53A#15_O__?o!8?@?NSiLz]v}~^~^n@H#10GO?O!9?@#49!11?_???_#57D??A@A?CAD?I?EGB?ACDASGa?kQG_w_gSwO?_???_Og?O_?_GO_??_O?__O!4?_#34???A?A?@?ACG@CA#52!7?@?HAD?ICGO_O#32?@?@?@?AGEGS?o?GO_?O#44?AG?S#49@???ACRGf\iVzm~z}Xv}lzuYggO?_#48?BTB?RSj}VMA#40CA???OP??ADa@#49JsMp#25??CILQK#26_#34_O???G!6?@??CG!9?@#57!4?O!4?OKOAG??`??O?O?GG?C??A!5?_#33C@#49???C@E`A?@???@#32?_!5?C?O@?C#70GO?C?ICAHC!4?C#138_O?C??B#61G?C!5?C???O#67@#52A?@#30?O??A?@#19O!19?oo??@Ooo?OgO_W_OmPmPmPmPmPe\!5~`???Ca?}J_#22!17?O?G#27??@???@?C@!6?@???A?C#0_?GC#68_GCA@#162!13?G?@!4?@!8?O`?@$#32KCO#51@IAUGTAK#68???_!6?C?C@#2O??G#29_O_OCBI@??@??AA#56_OCGK!4C#16??@?A???CA!5?C?goG_s_?C#17??_?O_?_#16???_#14?_O_oo_s_OGogCKQeDKDGOFBJCBCB?F?E?A???@A???C??@C?A?A?@!7?G!8?@??@!7?A!4?A#44!5?C#49???@?@??AODODGQcHA[aTgVhUgQcWc?O_O?_#36?A??d#48??CJcA@A@b@MB\SnA|cswsw_O_#34??@!5?@#131!6?C#52!4?@C@ADWC?O#56A#32?FGBS#14@#44_?D!6?C!8?OA??O_???@A???_!7?O!9?CC?C#32!7?_#16@#65?__??_#103O?OG!8?A@@#38?_#22_#88_C?G#47!9?WKWIP?CG#46O#3C???X_?_???O_?_GCAGCAG?OO@CC?@?@#59GAC?A@!6?A#68_#17???_o_OKsvqJX^zn^yV}t~|!8~NN~~}nNN~nVn^f^nPmPmPmPmPmXa#20!5?SjSviX~@cX[vTIiFyD~ItMjt]dymxu|D{?~GkP_YTRD@DA??AA?@A#25B@#60_?C?@#51G#131OCITEtHQ?C???_ow{MuivsxIOt?oH?A$#48AG#10GO#36_O?_?_??O_O_?OKOkICGA@?B#4?CAA#100G_GC_@eGVC#33?@?A#1A#68_?_??O#66??___#147@@AB?DEDFL?G?AO#158!4?B?@OJDAC@G@#13!5?[CCMGKMEECA??@!4?D#36!14?A!8?@!4?C?GO?G??G?O#46?A?@#56!9?O#32!6?O??_#51!27?@!8?C#57!6?@@DFNI]~ooOhwcWSgo?a??S?AG#42!4?A@???_CQ?AOG#47!12?ADiCXs__G_!5?@#38W?@_?cCgACdy!7?@@@??O#49!4?_S_GO?_#48!4?O??_@AA???GO?o?_?_O??C#120??_??A#28C#4?@???A#27??__O_OoOG!8?[AE@A@A?@??A?@#88G?O@#56??XCOIwENUZfpppqgTEm!4?@!5?AA???@??@@@#10!6?G#16_qGGKce_CO_Dg@I?A#30!41?ISj!6?A@G_T?G!23?!4_OG?C@??G#56_#3_???GA#162oWcEB@#34!14?OGCGI??KGMKEL$#4O#56O__#52?@GCgWoW?G?GC#64_?_OO???CA?@#88?_O_OSPM?@?_RK#4?O??EAA#57G??GC?G#14G?G???W??G?_#18AC@FIBE\?}M?_?O#23!9?AAB@BA?@?@#43??@#42?G???G_S_QGa?C?o?_?_?_??_??_#52!6?_???_#32!5?A!4?AC???_#42!13?@???@??A?A@BEBIDFDHIDYELXAtaW_PGUg?_???O#56!12?ICIOC@_???G?_#51!14?@?@??C?CP?C@aG@QCG`SO?_#10!6?_#62???G#27_?A!4?JA|zW?[qsI@?BEACQla[Wg_??C??C??DC!9?@@??A@#116!11?_P?YG?GGKCCCEA@A?@!5?OA#52!9?_?_#48_?o_GB#78]@#116we!6?_?g#65?O!7?@J?@??_?O!8?G?EAO@!4?A@#15G#21!60?GPC??OCa?I_ToDy?tIpSI`YD@E@AyA~?vQmZCICICA?A?@C@C#32O???A@#67@#163_OgwsixIul~z~~~^FFA@@#161@???__!4?o_SAK$#33_#3_#57?GO__#66@ADBFNVFFBB@@#60?___#3__OG??C#33?A#139?_??I#116!6?I#36__OOW?GG?GW??W??_O#106?@???A@#147!28?@@?@?@#48!23?@!5?@?A?A?@?D@??@??DS?Dd[EGRiFIS^u}h^kRkmTg]eGP[sYkGOG_?o_#33!42?C?O#10??A?C#57!38?@AICGsWCg#16???O#39_??@@#58Q_#71!5?@#34K?@#42_?sG?O!4?DEW^JRVNZN^II^AG?OO?@@A?A@CAEBEF@B?@#67???@AC?E!4?G?A?@#34_!5?K!8?@#57!8?_!8?A?BEDA@?@#67???G!7?wG?C!5?@#70???@!8?@??@#42?C#34!98?GOi?KGAK?C#16?G??@#64_???@#40!17?_?O#51??C?A?`?OA@A@$#14?@#49!4?@A#72!8?O?_???GACG#0_OOWGKDB!8?o[DD#60G#52?_?_O?Oo?O??o???_#37?A?A#23HCRK_TGXaw?o#47!19?_??O_O_O_oqsaWgSgsW{zWNw^[Y|]{{\}xWy{sxwsWwsiWsitOi@_O@?T?@?`A?@?A?B?B@AD@@AE@JCBVLRL}KW{Sywwcog_o_?o???GE_?@VLZaZelYntYugW_O_!10?@??B?HAHBDBFJk]~~{YgcW_?_#36!15?A?@G#41!10?w{]}H#47!11?KGo!7?@F???___O_o__gO_?_`?A?G???G?SG??GUGBB@!5?_?_!5?G?CCCE#40O#32@#78oMB#119!18?o?G#33C?XeG_O#10@#59!9?O#5???_#28?OG??A#36g_C?GHcCA@@GCCAe?JOC??@@#42!99?O??C???G#33?O#62CC@#70?C#72A#49!23?AT@AOB??CG$#56!22?OOW?GCE#8?@@#94???_?_GqG#64!5?O#8??@#69O___#51???O_?o?_??_#46!34?oG?WaKGQAGB!4?@?@!4?@?@#10!6?@A!5?@!5?A?@A?@???A!5?CG?G?C??CO?G??O?G_???_#42!108?@#46!18?G#20?_?@A#17!14?C!8?A#30@#20A??@?@#60!10?G!9?@#61!5?@#70@#145O#84GO#98!20?_E#146A#4!4?G#60_!8?C?@ax?CAA???O!6?A???Q#47??GC?_OG?C#60@#49!101?__O_O_#36_!6?O#66!31?_?G?{q$#9!26?C??B#136!6?OCOC#11!11?@?@A?A?A???C??G#16!36?A?@#46!69?C#48!133?_#32!19?@A??A#56OgC?O_!9?__GOGOC?_?AP???G??C??A#43!4?A#119@C@#134!18?@#28_#8!24?BA#72O!5?@#22!119?@???@?A@#37C#28OO#65OGA$#77!37?O#10!14?AC?CCC??GG??O#14!264?_O!7?CG?COG??M??A??AC!5?_#46A?@#94!12?_#103!46?_#9_???C#52_?O#51!119?O?W_O#46???G#19A#108A$#62!52?@#13!5?CCC?G?G?OOO#120!260?@AC?GO#70GO#65_#3!17?`??S?C??A#135!59?O??C#51??O!8?G???O_?G?A#162!103?_#47??GOO#88??_$#46!330?C!9?G?G??G?C#32!11?A@A#42___OO?WgG?CA!5?AHAWoWKC@ECEN?AA#154!28?G#132GCA?@#99!130?G$#116!330?@#10@!4?G???o?_?o??OGOC!5?@!4?@?@#4!60?A#64_?OOg_sWy{ugoo_GWKCE?A$#146!331?AC#3C?O#61_#70!22?C#44?_?OO??G???G??@??O{lu`_G_O_O_OGOGTC#2!30?G$#16!331?G#67G#36?C???O_!6?_!5?CG#48!7?O??@!7?A!4?gAO#10!45?@$#103!335?_-#28gSMC??GO?___?_?O?OGCGACE@A@C?C!7?C??F@#64?A@#51__?c!4?@?@?@??A?D?EBUDI{G[_Wo_O_O_#18@#14!4?Oo?oQxBrRlD?K@aCB?C#49!7?O??GA?C@?Ga?SA?hCODO@S@CPAG@a?CAOCg?G?O_??O#48!6?@!6?@?A@_!5?@???OA?cJcAHgOc!4?O_#42@?@?@?@?DADALAM@UHU_#52!4?@AHUdKi[k[gSw?O#48@?_OAQWnyVurGRgO?_#42?OOGSQxGgAWO?_?_!6?C!6?GC#27AG!6?o??p?!5@??A??G!4?@@#60O!4?_!4?_??OGG!7?@#33__G??C??A??@#47A!5?O?_?G#34?A?@???@@#94G??_#116s???_?_??__Oo?_???@!4?D?CSGD?K?A#64?_???G!7?FA`b_@OpRg[EFA?@@#42_GC!5?@#19!25?_OgOgOOHADAJADA!5?CITf~f~FFBF?A?G@F?M@DA@!5?`?PQow#22A!5?@#16?OO#13O!7?A#100O?O_o?WOgO_S?O#5G#10WO#163AFFMMCMMFMFN!5FBE?!4A?A@??@#65GG???CC$#0S!4?G??O?O???O??G??ACH?C@??C?CCECC?KGGK#57???O#66?OK}XU~}mu~}Fw@f?bObGOG__?_#17?D?I?KO_WCIdID?@#13!5?GC#10?A#46O@A@???@#34!9?O?GOAGQ?H???Q?OC?cAG?Ag??G??G#51?@#52??@!8?C?W?gGOgGoGO_O?_#32!4?@!7?_#47???@CRDJbLVNFVm}m}]}y}o{gwoPog?_?HVjTJUdA@I@C???O_?o?g???_!6?GfGVj^^~lnqhC?c#51@!4?C???A??S??O?g?O#36C#41?_?Cr`@IH?CG#42?ABA?AEABCCU?O@??BA?B#52KIIMMEM?AC#116_?_O?GOGO?@__OG?A?A?!4@#65?@#70?@#33!12?___O?C???I__O???_!5?GGA?E!4?AA@B??A#70?A?_GS?wC?@E@!7?O?GB#57G?C#68?G?CA#16OG?oGCa`Hqi?A_LOdY`Y?QcH!8?D?@AC!4?@@#30!15?G!4?OWSWcHQcOg?O??OCIc?UCI?_!7?_QWG#15_#37?GCEAAA#88G?GG?G???_GOCKBP_#59A#68@#162BDG?O!4?O???!6G???C!5?@!4?@#28_?G#92_$#67B@!4?C?GG??O???C?COO?O#33G?G?A?A#100@!4?@??A@#68!5?@#49???g??O!4?C?GT?AG_K_GO?O_?_#23@A?@!4?A@?@#44!10?C!4?OC#17!34?O#57!13?@???@A@@B?ABDHFEQM|K]zt~iziYzvJ|^ZsZ{qCiO[og?G?O#49@?@?@C@I@QDAk@UhQhUgSiSgOsGOo?_?_#56?@?@#32??@?BG`CO#46?gG?O_#44??_#49??A?DAhEPE|BnjYvL~h]jiUwC{Og_O#40??_A?A?_C@@UG!9?@#20?G#32A?E#33C#116??O#70?O_o_Oo?_o#3??O???O#67?O?@A#120A?AA#44??_?oGW?G?CCA?`_`OKXC[KWGCCEC!8?@BAB?@???A@#120O!8?OG#32??@??@#36!4?O!6?_??G?CA??QKCcRApG??A?@#14C#20!59?g_g_ZslRmO}`[qLitR^hjO~?lHFdNnl[LEDMLEB@@@?@#19@#17@#42@#3C!4?C#57A?@#28A?E@#65@#72A#51KOo?_?_#66__?o?O_O!5_G_??C?C?C?!4A?@@@$#116?I??C!4?O?O_O_?g??GCGA???C#32GGG??G#9O?O#94@A@#4O#69!12?@#161G??wAg#47!5?@?@AC???Y?CO!4?O?O_!9?_AoHQcGRKJYf^VjVJAF?b???O?GaO@??_!4?GCAG?AG@AHBRHEjV]vNn^}~l~y|e}SqlOqCo_G_#44!24?A?_#51!19?C!5?_?G?_??O?O#10!7?C#34!23?AO?c?S@GQ?S_S?g?g??O?_oWC!9?@???A??C?B!5?@!5?@#61O!8?O!5?G#103?G#28G?@???C???A#42O_WoGo?{a]XEKBCB?@CAA?@#28_!4?O#32?O!5?O?O??G?C#60_???_!6?_??OOO?C?cBbRA_O!4?_xGCAC#15!6?_?O?C?BA@#21!67?@?aG_O?G_?OC?M?E?WoOQ@??A@A@#14_!7?A#51B?B?@#161@#136??_O#94g_#9???_#131G?W@@Z@@G@G!7?@B!5@#64OG_g??C?C#146G$#88?_OWWoo__#68?A??@?@#70!4?_#52???_!9?_!9?}GR?A@?@!6?A#14!9?@?@@#16???@@ICAgPGOiKE]FLAC!4?A#36!56?A!6?C?C???GC??O#52!24?O_G??O?_#36!24?A!4?C??G_??O#47!33?@?@@ABAFEMHE#38?O@COs@o[I`eG#44K!8?HC_?W_??A#49???D?@#64G?W#36C?C?E??@A#0??C#146COG?CA?A#27_?_?_?oGw?[?EGB?A?B?BC@B?AA#43A#84BAC#3???_?_!9?G??_!6?_AA?B?BGAAA#59?O??W??GAA!4?O??A!6?@#27!94?A#17_?O#36??_#11O!5?A#36???A???A?@!6?C!9?_??_!9?OO?OG#57OG!4?C$#3??@AAE!4?!4GCGOCOA@P?PG!5?GG?GG???O?GEB#162!15?SOg[gSO_?O#42?@C!5?_!7?O_G_G_CwCGOG?_?S?_o_W_gSgs|g~KvtnhudTC}j|LUjjyBw`uxCtesS{_sgO?_?o#10!15?A?@#56?@?A@CI?SCSdCGc#57!46?@!4BIFENUyNkTKb?D?@C?C?C#52!24?@?@#57?@?A@#20?G#22O#26GKG#71?A#20!4?C#55_?_#50_!8?W?@#65!7?O??O??O!4?O#48A?`_!5?O_wOWGC?CCC?A?@???_??_?o?OWWG!7?N^MW[WK[KFDC?BC@#10@#132OO??O#65__???O??GC!8?IBC#61?@#52!4?@__?G?`??G#17_?o[?oyTUuLS~|^qnYd]d~lZu!8~y~}|ZnVnVmmu|y|s|y|!5~ztaW?W#47!34?__?_#43?G#56_!8?C?C???A!5?C#47??_#52?_#161?o?O#49O#69??o?O_!4OG?G___!4?CC???AA$#29??_#33@???G#64A??A?A?A@@???__#88?AC?@@@?B@B@E?@CA#15!29?AAA?F?KACJDAc?C?A?@#48!8?_?W?g!181?@??B#25@#39_#58??AS?a??O#25?O_#57GGG_Go??@!4?GG?G_!6?C?IA@?`Ac!9?@!8?A!8?O_O_?__!9?@???C!4?@WI@E?@#56a_!9?!5_PpHHCGc{@?O_ksW?SGK#47!7?O?GCAA?@#62!97?OGC#33O?C!4?G?G???A!5?_#161!18?C?CC?C?E@EAB?@@?@$#94???_#56@?ACC?CCC!5?@@!4?O!9?O?O?_?OwKD#48!255?K?O_?_?O_A@A_Qs#68!11?G?G#32???_?_??_!5?O?H!5?A#46!18?OG#88_???@#47!5?@CBAA#28?_?O??O!4?i??SGGO#9!13?_?OO#19!14?_?_!5?GG#102!94?G#1O??__#49???@#67C!6?@#68!24?OOO?GOG_!9?AA$#139!4?_#8@#57@A?A#60???C?cAAa_#10!5?OO?O#51_??_#8!4?O#54!261?O#47CC??CGg!8?CCAD?@?@@B@@@E@E@?@#1??O#119w?C#154C#3???C@@A#0!23?O#86@#78WH#119I#65!8?_#103!4?_!8?_??O!16?CB@#51!14?OCG#53!102?C??A#80O#52!5?AA?@#51!28?_#52?G?G_?_???C#0o#88OO?OG$#36!7?@@D@@B?B!8?_?_!4?O_O_?___?__?og?E?@#46!252?O!7?oCC#10!20?C?CA@#4A#98?K#88OG??C#20!27?C#95O#89CC#146!16?O?GA?XOG#65!36?@#10!104?__#29G?G??W_OG!7?GC_#70!29?O?G!4?C$#65!14?G??_#29!8?A?A?A???A?A#56!265?O!9?G?og_!6?_!4?gG!4?CE!4?@@?@??C?CA???@#108!17?G#134_#98_o#88!17?G???CDCG#0!141?O?O?Cc!6?CC!4?IO#60!30?O#3O_#100?_?o_O$#1!26?G!8?G#70!267?OO#14!5?_???C#138!24?A#99!52?C#138O#119G?A#99!144?G???_?_#131??@#139o?_?G#56!36?_#116O??G$#47!26?___?_#52!272?G#94!90?C#98H#67_?@?A?@CGK?C!9?CG#31!123?CC?O#60!5?C#67!43?G$#48!27?O#57?O?O#134!363?E#8!146?_$#25!542?@-#28J!8?C@#52__W???A!4?@??@!7?@?@AACCw?GA_@@COiO!5?A#162???@A?@?@#52!6?G?@?G!4?G#16???@A#46?`Ocwc__C_?DG??A#57!12?_!4?O??_!4O?O_O!18?_#51!9?@#52??@?PA?D???A@#44_?_?_#32!9?G_#33?O??_#52!5?D?A?A?C?CQg?c?O#42???@?a?_cGo_O??go?_gooo__#46A#14?O#10???A?@#44!5?I@?A??O?o_O#34???@QCPcODyCyCXr\G@#39??{JV#38?aJZOP#42C#48__SM_??oUl|{_`P??A???O?SYVM\TIC_WaAFfF!5?AOGDG?GOOG??G???_??@#88A!7?A!9?@??@#60_?o_oWOwcGY?KYSIC_Wqe}huNqAWKoKH@!4?_?@?C@#69C#47_OGK#14C!9?@#19!33?A!15?A!6?RjFze@?BO_??A!8?_?W!5?@#34_G_#162???GoOgWCoOGo#13?@#46@?A#29@@IO#136C?Ca?FWB#28_WF#69_OK!5?@?@#52!17?@?GEO#0@BS#28g_$#88sN@!5?PZA#64OKC??!4@#163?_?_??_#36!9?@@@BCJQTOk_O_#161!4?A?A@A!6?COA?`?C_#47!4?@A??C@GOPmOcGC??A?@aC@I@OI?OLAdIteLQ{SkYgWSkMMmNKEMJEnLNS]N~giW_YgWms}\]{]^vX~x|}~}~{~|}~M|~y~|~guW}OsS_g?g?_!9?@???@A?AHCPBbJRLF^Z}n|zuktWLY@?I@M_U?CE[BENN\UhQg?G_!7?@@?VHNuM~MMG@??_?O?O_#131!4?A?C#40_SqJD!7?B?@#32CK??OO_?_!8?@!4?_??G???_???@!8?O_gOO?oo??OO??G?S#47?O??G?G?GGKGG?GKGAC#36G?C?G??C!9?@!7?@!8?C??A!6?CC_?wC?@AC?@A@#15?@#30!66?@GZ{?DIsHsHao`???A!4?@??EBB@@A#52??A???@?@A?C?K?O#32AG#100@F_H_?aGC?HC#64_?C#161o?_Oo?wOoOsWquQ}YYyiC_cOg#69?COoo#4@#32A#3C#88AAC$#94?_{nHBRfm#103_#60OCAA@@#68A???@#47!19?A?D?M?[_C?_#51!14?A?@??A?@@ABQDZMWuSg_#14?dWUgJ?PIGPGO_?C?G@?G#49!12?@!5?@!7?@!9?@!5?@???C??A@?@?A?A#42??_#57!4?A?OGF@FBABCBS@JSBCBItnA`CTyzypyXcXkoGO_#49!4?@?ACHQIDqDYuD]_NgTIgBOG#48??A?CLF^v^\~]N~J~?CSgSoG_#49?@OAGADICJcJJyDzDxeGAB#41GsyBsg~Wo___#62O#33O???A_#47!4?A?GO?PAO??@???@??A?D??_Sg!4?@#116S?C!8?_?_?O?O?G??A?CC???@#42CCC??AC#28A#64_G?W??C!7?A???A#57@#59!12?@P@!6?@#9i#65??_O?B#68_?C?A#20___o?_?_!65?Uc?NYtJsJu\@]t~y\hEPNKFE#49_??O?K?C?COA??G#69!4?__#9C#56KO#0C#88gYC!5?o?A#67GC#162_?WCGCGCAKCGACGC@CdCSoSW_#36???A?GG_#70_#100@CH$#137?OA#105OswkW#67??C?@#65@#162_O_O??COEI_S?_?_?g_W__O#57???@#49!6?@?CA?H??CO?O@g?@g??_??@???A??C#42??@A?ADA?GB?B?D??C!4?C@?D?A?A@A@ADBBBDFFAB@@@?A@@C@?A?A@??FDFNDFE@J@A@A@?GA?E#48!17?C???GGH[R{B}Sj{zsTI?|MYIDCCK?cO_???_#51!16?@??@?@A?@?C#57!4?@!6?_?_o?s?}oi?_#42?@@??@du|ySh_I?C#58!8?_!7?@#45C#50CG?_???@??A#36!5?@CA#10A#65C??A#33??Sg_?_#44?AOIYEGCG??C!7?A?F?D?CK!7?@#154C!9?@#34CA#49C#70_?OO__OgO?O?SI_K?JECOA_?_O??CO!5?G?P#72A#64@!4?C!4?G?GPOK?BA#42C?@#21!79?M?I?D?E@M?A?@#131??_?O_O_G_#36@!4?@?@??KGO#1C#3_#139?@a?@#146!4?O@#68?OG#36@AA#52B?@?@#68!19?@E?_#64O#33?GO#139O$#136!4?A#101C#70!4?_G#66O_GCKCCEIA?@?G!6?D?ECkwGo??_!5?B?DNu\~xk|n}U|}Uzk\}]}x^u{{{_wco_?_#48!7?OAGSQGAiOq_tocooWsGOog?_???__??_O??___g???___???_???_#36!29?@#56!20?O?_#44!7?A?KOCG_O_#34!16?O???O#44!6?GO_?_#22!39?C#86!6?C#27??CAA@?@??@#70!8?CC??G?Q#54!5?O#46?G?O???O??GIK!4?A@??G?G???G!5?A#33A??@@?@@#84?@#4?A#120?@#134??@#89@#94A#138??@??B#3_?A!7?g_?gOa[C?G#8!12?c#4I#10???@#3C#52_??GA@#17O?GGG[E|A^|A{B~K|mZsLzFzmT~m\un|j}j~j~j~j~j~j~|~z!13~|!6~kSwCW_??_!13?O__O?GG#66???G??CO?IA?ACASKwwO#68O_#57_#8_#33O#94??OXSWo`KE#61_O#0B#3A#9A#163_O_Go?_?_?_??_?_#56!11?@@CKW_#94@A$#3!10?G#0A#49??O?OG???G??!4AE?BSAC?G#15!44?@?@AKO#44!7?@??PC??I??I?D???G???GO?O?GO_O?O_G??O?O_G?O?OO?O_O?O??__?_??_#52!78?@???@#46!58?GG???oK?GA?Q!6?@#56GAGgk??C#42!6?C???OW??A#33@?GL@__O#27?AADBA@E@A@?@#36?O#119?A??A??A@?@?@@?A?@AA#146BA?@#116@#120@#103@@@#61@#70!32?O??_#30?_?O??A!7?A#47!81?_?o???C???A??@!6?@#48??A#6@#62@#99?A#77!4?A?@?A#65!4?G#5@#51?C@?A?A!6?@#10!16?A$#116!11?@#69??C???A??@#20!272?C?A@#56G#85?@#54CTCG#64!7?G?C#10!16?_???O#0A#88CB#54?CA!5?G#146_!7?CC?C#10G#98AA?AA!9?@A#67!5?AA#65???_?Oo_?O???G?AG??C#61!17?C#72?A?G?@#19?OO?A?A#51!87?_?_OGOCmOhBD?CDGBC?AAE#10?A#105!8?GC#47!9?@#49GC???CA?AC!8?G$#57!14?A#51G???O!6?@L@JCBGAPA?CQG?s?g@QAIGPGo?_#116!242?G#57O!9?BEIggo_ptCBJB?_@__?`@@?@???@#65?_#28G#108???C#85G#3_??_???o_OGGC#4GG#65!4?C#52?O???G???G?C#67!46?@#51?OA#34??_#57??@#16C_W?\_A|?{?rAPcJqCwCPi?PaHOAS@S?S?S?S?S?S???C#42!46?O?OGS?A?@#161!5?_oG__#66!27?@A???B@@@?@?@?@@BJBNV~yk$#131!15?_?_OgO?w?[_[?wCg?O#44!264?Aa??A?H@???O#60!4?C#14!19?OO#120?G#119B#50!5?E?C#42?A?@A?CD?@#103!65?A#131!147?C?G?G??G?G$#36!15?A!8?@#108!276?G#36!31?_#56__?o_O#86???@#25?@#16!4?C#34@#56_?_!8?C_!4?A_?O_?_??_SgWKKGCEAC?@?D?DBD@B_@@@QHoKcaaLou}ZsP~ZYI$#161!18?g?_C?S???O?OO??_GO??_#20!313?A?AA$#57!353?_?O_ogpooxoC!5oOo?W?CWCC-#94K]MLILKN#3_#70g@#66K~G#131A[b]b[bWACQ#66!10?AOjz^v~{}wo!8?@BCIBSdZEhnZnV~z\n~~j~v~~~}v}wSGO#14?C@!9?_??_O???G#44!5?GQC?OC@???@!5?_O_@q`?_Q?O_S@?AC?AC@CA?SHU\iUe{y{uwmwuhY|QdSxQdY_JSheGS_O??O#32???H#33??@G?@#47???_!4?cATIViNRnKEBBADIC?AC???A?c?A?GG?HBEFfM]Ys{wsw_[oGC#46_?O?_#20???oCO_#34DOI@qD?btJ?@#58?_!5?OA???GA#46@SKQlSO?O#116CG?G?G?G!7?C?C?CO??C!9?@!5?@A@#28@#61@#14A#3__O_??c??O!4?OOocOOO?GOKA@C?D???@???@??@GCG?C??c???A#36GCOAC?B??_o?GCgICAA@#57@#21?O??@#16?c?YdgrsWrSjCi@gAdIOiTGaGOcGpCpCpCpCPCPCPcHA#19!8?_?OGSiU|[]kMKMKzs|YTYOMq@?_o?WGCFFB#13C#66o?GC?A@!9?A!4?A?C?bAn~O#56?@?_#29@_C#94lGE#61_?A#162OKB@!6?Ac???C!6?@?HA?PDItIOG#68?BE__#28@$#88r@oOO?O#103?G#61A#36_#52A#162?SsAW_?aGDGPcO_Y`[@uhI|gk?C#51???A!9?@ACGQdWAG_OO??O#161???aO??S?G#51???@G@?@A#57A?AG#10s!5?AgU?H?H??A#42!37?O#46O??_#52???C!4?A#42!7?A?C?UdAkQaEkYd]sG#48?@@?@JlNdjoS?O?O??O??AObpeaQhisGS#42???OhO{\gdXm|H]MtlIJ\TQRR^U[GGGo?_!9?_?O??A@@I@HFHIBtML_DC?C#38?IC??_?_wmc_Q_?D_#48A@g?aG@G@P@@O@!4?_#4???@#67???G???C#48??oO_gG?WO_#36CGG?G?_??I??A!8?@#116_GO?c?O??GC?I?GC#59!5?_?oWO{C_?c#65!13?_??O?_!5?A@#47o???A#58G#25??W_Q`#42?B??O#30!15?G!4?G#20!34?_???_!6?_B?LQLEDe@BA???B#162?O??G#34o?k^kM~iP#162?gOG_\O[A`LG`O#36?@hMW#0ACG#137?O#136A?@#68_WE@#161???C?JCH?@L`J@QDHPjSiEOTjEitIs_#64???@MOo$#28?_??__?o#138C#65S#64]#68@#161?a@?C?[?O__??L@C?aiHUtA@B#52!5?@@AICK??@?EDIS_O__???A!19?F_??_#46@ABB@FHFWQ_!7?C#27!65?G#57!11?I?C?OISNJun~mv~e~~|N[MH[?S?@???G#49?@?C!5?@??`???O??_C_?_?_O?O?_#57?I@DJEB?D@?@#27??o?{AcKqgUok?o?OgOG?N!6?O??OG#71G#54???AH_#14?A#32@F__w_g??_??_#61?C!5?G?C#32O_OG?o#10?_O#14??G#67!5?@?B?@#60C??_!8?_?_#67??_!7?_O?_??I#64!30?@!6?Og?@#20_??COA@`Gc]Xw?OA#30!58?_!5?__?__?kqHIHE?@#46W#56_#52_O?GC?A#47@#40_O_Oo#161!5?cSSad`wAO?C#52??gAO#32@K#33G#88PwAOGA#72OC#163_o{}~z~sxu|Yq]sylyumSjSxegSgO#66??@Jvo_G#56@#3AC$#136??@#146_?O??A#67@#69?o#51?@?_?@#163?@CATiHa]`]@S#49!4?S?S?_G#36???@Dzb]|sMOgO_#49H?CHQChCOc?g?C#36!14?ITk\ws!9?O_O?_#14!81?A#56!13?G#44!6?O@G???_@ocOaOg?_O?_!4?_!12?@O#48?@@D@AA?@[BIUIEM#16?G??C#47!10?A??A#40ooShAOC_?C??O?WAg#85G#50_O!4?O#56B???Oa??O?a?ODFIRprQiII?`?D??A@@?@?bBA_!4?E?XX?_?`sGwGiGWqIeHLGNJFJFDBC??A??A!9?A_aooqoPowg[CBMLzaW\nNJ@B#52???JC@#26G?Oc?@#19???C@#21!74?O?O#16__??GC??@#51A?PC?A?@???CQC?@A!4?O?CO??C#10??C#3Oo#100CB?C@#67O#65G#69_GA#131!6?A#69!23?C?N[oO#70CG$#105???ADA#116_?O#49!5?G@#69!28?C??O_#57AGoGO_#69!30?_#47@A#11??GO!4?_#47??A?H??G_O_soxugvgxuhqgydw_Go?G!4?G?C?QChEHACA@?RCHA@H@BC@?D@HBDBHDPBH???@?H?@??@?bUWu`[_Q_???_#52!15?@#34!13?A?A??A?@I?D?AG@?C!4?_#44!10?_?_PW`nFHA?@A#49!8?GQG#41???_Q\nZ]NB@HFdFtO#86S#44?ADOG#33_C???O_??O#47?@??GG?O#3!6?_#33_#47!4?G?CCEeKW?SC#64_?SoO_#48??@ACAB?AD@C#70???G???_!4?_?@A@@?@?DSH?c?@??@!6?G!4?Q??O?C!6?C#16???O#27o?O??GACG?A#44?_#14!78?OG??G?AA#131_o_GS??A??TidQHaH?I?CG?O#69???AS_#11A#99@A#139A#92??@#28_CB@#64@#57!37?G#116A$#98!6?B#154?@#47!39?@?A@#56_#32!38?_??O!5?A#27!122?O?O?o?O?O_O_?gC_?_?_#25!19?S?wO#39!20?@#57!16?G??A?A@Q`bOOW_ow__??`!4@WHGHCSqq`GQ?s_OSs??GCCE[@B???@???BD@@A?A#65?__O??O___kym|IgEdJAqO_O#72!16?G#67_#59?OOC#68O_OQC#14?G#17C?A@!7?CIDKJfKjSzT}V|YtnTav\vnRvMzMzMzMzmzmzmZu|!8~^~nvjThAb@RpRPrCJADIDKP!7?C!5?@?@#15@#49C?I@??@???G@!6?@?a?G#64!5?_#129!5?_#132O#127G#103C#67!39?@$#62!91?KIwCw#48DDHLCAC@VH`BBNAHD?@?HA?SDIDUuNfuyVn]sKY~LhUhE`ylkzkWowyo}go__Og#3!144?ACA??CG#60C??C??@A#62!9?O#52!14?G?G#70GG?O#32???KWGG@CACAC?C??A@#60!4?_??OWOO?O?OA?A??GN]?ZTNJLNANFV``w@_??CA???M?@#51???@#22o?cGACO#10!84?__O?G#161?_OG$#43!92?C?A#57!5?OcQM_SISG?C?Q?CA?CHA?OAG@?W@DG?@A@!6?OG!5?@AC!4?AG???O#67!146?C#65C???GC?CA???C?CG??CC!4?AA@!8?A???A_#33!6?O?O?O?O!8?C#69!44?_??C#34???G#38_?c#24??A#37!84?O#36??O?KC?A$#6!92?o#99?_#9!212?A#70?C?C?GG@E???GA!5?AAEAC@#120!9?@#103?A#4!9?C#46?A#69!159?_$#36!311?A?A@-#66ooOoWO?oo???@@GEKwo__#163@A@?@A?@??A#66_o__Ysi|z}^~~zbr}#68PIs#56AFDk[Ww_#49@?A?D?CA@?A??C#69!4?_???DYdA?G#64OO#9P#32C@??@?@ECKGGG?O!4o_#44G?@!11?IcI|i}\m\myOnSsIH?E??A#33?O?OCO?G!4?O#44??A?DHA@JBFVD^TnU\jShUGdQ_gTggOGOcGO!6?O_Go?OCHQgY?IaZUjUlUl]l]j]z~gv[isgWS#27GCAdG_I??G?_?w?g?OG_O?_ZSEytMt~Q|iTyuGU!5?A?AA@??@ADEKKGGK#62??_#56GFA@?I?@o??@AAEEN@D?C!4@BAA@DEDFAAEBAA`_oo_owW{QqIGWG#59???A!6?___??C?OOP?co?_`O?@_W`OHDI@?H??_#9?O??C#10GC#64??o???@?C???@#38?O[BIXUHA@#37?_#16KCEhk|~XGyT?DGPIcW@uGRk@iTGTaSIPiCiPCPcHQcH#19!9?Ql?jSaGp__??SENKK#51___oO_[GC!7?G_???@#22!4?A?G?G#51??J?_!7?@#52???_OC#64KD#67C@???C#72C@#163o{^!7~|!4~nv]d^aHbGrCRcAC#66?_OgIRRBC#70_wE$#68C?C!5?C!6?_#161BF@RWkOgyUG\CYA\?JCPdGSA?@_#69??C[K?ko#64@GO_#57@ACCO?_#162!10?A??@#36!5?O?_?tgqLMC!9?@@A@?@A#46??GO_O_?_#48!12?A?@?@A@CB?BIPsiwl~|Vn~nzn^R|n~~n~^|~yu{}s{wgw__?_!8?@???@AC??@@A?G??CA?BEhBoc_EDyd[_gOgOgO_O?O_#49!4?A??OCGS?dIcOD?I?@?O#44???AGb[b\A_#34!7?H?TGD?vG@#41g?TipGoSwgsw#25C!4?O??K#116!4?KACC@EAGA?OcOgO_?O?O?A!5?G#103O#67G#48?@!4?@#36!10?G#67!8?@_#61!12?G#70!4?_?O??GW_?G?G@CG?C?GCC#4?D?_G#57???G!4?OGG#27oOkA?Oc???O?_O#46_O_?A#17A?APCi~yvmtZf}HvkR}Tivi\jtmTzTmzmZulZu!9~lQ~Sj\vK[][{IXO?O???DKE@?@#10OCAA#68_GCB#34?GwUoRyShO?oAtV{oo#162DGYDIxFTQ^qEA#69gA!8?oG#131??_!7?A#161!4?OG`Y_\uSvKzkZ|z^^dUpcg#52?GT#64C@$#69GG?G??G!4?@#131!8?A??C#49!13?@??C#36!7?@ADItgA?@A?KoG_#161!10?@???A#68!4?_?OGQD#72_#56_i#10J!6?@AACC?MGK?K#42???CGK[PkP_YocwooO_?O?a!4?C#54!7?O??_!6?cAO!4?_#47!4?@!5?A?A?G_SgQ`BQDEVIVULRMZUM\NE^KJk]sHUgAOC@_DO@C@C@A!5?C???O?_OGA??@G??@@!6?D?@#46!4?@??O?G#40!13?_}F@gTGpC???A?_??@??B#73A#3!6?GI?E?GDcSgGGO?Go!5?A?@?C!4?C!4?_!7?_?_?@_??O#56!30?O?__og?Q@gSXHZwx?olfEA?A#52YaGC?D#22_OkBkPEHQ#21@#42!4?A??@??c?@#30!49?AA@b@___p@YLE?B?AD#36OG!5?A#131_GoEhAc@_?_!5?BCJOqDI`C#68!8?OA!6?_GE@#162!21?G!7?_?I@#69CGC{rI#68B#65G$#116@@#52GC?KOG?w_gQcCW_#60!37?WO#67_#52@ABKSOo_?_?O??G???_??O!5?G?D?A#57@#8?OC#33O??CG???O?O#47@?@AADBAJEbBmRm^dNZFNDJT?D!5?@???@?A@#57!31?GO#42@A?BCGsGgW?_??___?___oo_oo!19?@A@A@?@C?FG@DBDbbarGOAIOdTbUZIF]V|fSAKAGC?_!8?a?H?@#39?O}A???GgCUG#85?G#46WGO??oOOoO??_??_#4?G#0C!5?_#70??CGc_ioGCkW??O!4?O?C?C?CABG@ECB@@?UCOGKG[?SGG?G#36!26?A_?O!7?C!4?OCC_P??FA#26!7?_?_C#44???G?CO#15???c#20!51?@??A@??AADA@A?@#52__!7?G#49OSE#161@#40?KGCJUD~E|Ag#49??CG!6?AG?@#57???_OA#88oWVM#59_W#103@#162oMB#116!39?o$#61A#64CAAEA??A?A#10!44?A#3?_#51@?A?CH?Y@GC`KQ_OcG?g?_#4!11?_#0_?I!4?GO?o??_#14!26?O#50_??_#57!55?@!5?@?B?@@???CHAHO#46!13?_#34!14?O?OC_Y_SgC_?_#20!6?_??_G@IPI?cA#38!11?CE@@A@@EPA@ABEC#86@#103!6?C#57@!7?@?@@?A?@A???W!5?_?_?@@?@?A@@!6?KCC#72!39?O#8@!4?___??OAA#68??_wGCKAB#20_?GA!6?_Ka\JP_XA#42!64?_?OWG?G#56??__OkCE@#66?CB#164!8?G?@#161!6?AD?oSAwgc?G#36???G@#65O!6?A$#59?A#67@???@#57C?A@???O#66!46?BHEYdYti]pd\iZU~TN^~nYDA#1!7?YC??A!7?O#14A?@??KO?O#46!20?O?GOg?c?S@A??G!5?_#25!106?C?@PC?_#54!24?_!6?A#60!11?_#65O!6?O?GW?K???CCG???GWKGGWWWGKKCE?B!5?@`B`B`iBvpnr^WV~zxzdbKyJNTJCB_A??GDCAC!9?A#67!7?@#69O??@#34_!5?_??_#25?C?WACA@#14?O!4?A#49!64?_#47O?QGD#14A@#64_OE@#66!21?_!6?_Cx\F@#70_#33_GA#132_OA#135A$#161??_#56@??AB?@#99!83?ACA???O#57?A@A?C??@#16!151?@?A#32!29?O?_???`DETG@_?@oo#67???g?O??_#64?AA@C?o_?_Oo_?_??_?_???A??@?@!7?CC?C?CG?C?C?EG?CACICaCO?ISAGCCA?C?A#3!5?A@@?A??AA?O?@#66??_#47??_???A@#15!88?AA#8_#59O#11@@#162_o??@#56!32?G#3B#94_G?@$#49!4?_!9?@???I!7?_#31!66?g?C?CG#48@!7?K?A?D_?O#44!177?_?O#33_?_??G?o?O_?G?@QHAC@#60!8?A!4?G!7?_?c?CO??QG!5?o@aA_Qo?O??AO??@!8?@!4?GcZoDFAaOO??UcgUAA?C??@@?WGDA@#14???O#42OGC?@#16!89?@#70?W#60G#69O#0!38?C#28?_G@$#3!4?@@??@#62!84?@@?@A#56!5?C#116_#48!188?_!5?I@a?O?OO#61!17?C!6?GOO??OO#48!170?G#146!44?@#61O$#162!5?_!8?A@??CKDQlQDgTayd|_^CZM?A@#29!55?OwgO_?__?_#58!191?@#36!30?O#32?@??@#59??_#137!219?C$#36!6?C?GC[UkW_?O#88!77?_??_O_#52!228?_#33A??A$#51!6?_!6?A#80!82?O#4!233?A-#161~~rg?C`?o#69O?OacOCoCA??G#161?@C!5?C@QA@@AC@??A#69!4?CD`}jTA#64ClW!6?A???GOO???O???G?GGCCCA??@#8?@#9@#100???OSBG@@B@#3_wGCCAA@#57C??CACAC?C?C#44A??@A?BEHNJVMRAHDA?A@!6?O_O_CO_#14_#33!7?A?_?G#32?O#46??A#44?C?@GAdZTdeku{v[u}xyQsc_Og_?OcGqCSBwB[bC@I?eGPcG`ugPlIt]vlZfYC@#49??AC?@??G??@#44!6?@?RJEJADRCGA#34!4?@?C@S@#41{`]oJCADANDNVNBG#27OwGCWOPP?_#33E_hE?Q@?A_@???@?D?@#103__OO__??_?O#67_#116!11?@?@@#59???C!16?_O??_AcOGRL?A!8?E@A!9?G#68__cAH@#26_?_!5?_?G?COG_#21Q_#16?_PhU@?@?@FlA?B@g?AkPeWbKpEWbSbGRcHuGY_IS_HaC`G!6?G!8?_#30LC?A@@#161G?oO?A!8?_GcVTgT?_?pGW_G?G#40@@B?F@U#161???_eeUSCC#52_OOC#116_#61G???GA#162Ok@#161!11?__OgpsX\ZGDIXELMDFFBFDADA@#116O?G?A#103@$#49??C@I?AGC#68!4?@AhI_oK_#49???@?cGPI_G??g?g?SG`S?G#36!6?SaTYQ_!6?@BECQKg?[ICGCICMDCB@B@@@?@#139!7?GQC?GC#33?C?@A?@#66_O?__O__#47A?AM?K]Kow@o_oo__#48PchuQwnxS]jv\nzfLnQzN^^~~n!4~|~^~v~n~~|~x}sPkO?_GO#14!4?_#27@#47!11?_OeLzBgD#57OAC@C?A#47!8?_O?G??A???@#34!5?i?YC`Q?S?A?C#16???G!8?@#38!9?_!6?w_o?__?_?_@IF@@?JGEG#32AGE?@@?CA#60??a#65OS@G??_g???G??O!4?GGgkWCLE?@bACAEQBvEqcSvtf^Ux\rxVxVZzLn|vRkBDACA@?@?@??C?C???@!7?O?C#69WO?A#49A?A#27G?@??G!7?E_R_C_#46A_?`C#13??_#17?hFw}V~|RmXf[rMxf[j[vkZuHvd^tj^u\z]v!6~v!4~^n~~ZqBF@#49g??CCAODA#57O#68C???_G?@#131???EgQLoMPEHfGUGKWW_O_OaDO?@??@#70??__WI#88_shS#59_C#51_#131O_??_?C?C?C???@#162!4?_??OOO!4?G??C?A#65_!4?G#94OOOg$#162??GSpG#52?@???BTAgO??HACO[o_!6?C!4?OG?_QGT?Oc__W@??G_#56?CS???@B??G??_??__?_??O???G!4?A?AA#94!8?IO?A#56??OOGK?E?AC#46?@@#14@@A??A???G??G???O?_#54???GCOCG`SGaOC?A?@#47?_!20?A@IePIcA??B#48!21?gSAk`Wyyt|XvmZv]HVMATA@#27!4?_?o?sCiDOdgQkXa~G~a[tiTgSoCWaG`UhUhUlQdZsJI#39???D#58???G???O?O?S??CAAC#22AG#20O#25G#48@?A!4?@?@#70??IO!5?@?@A@JFAHAIDA??C`QxSsS[_o?__?_?@!15?Q#61!4?@#3?G?G?O!9?G?KO?@SgCCSCA#66???GCC?@#38OOkEhUPEs@IdAPG#20G\R#42I??_A?C?OA#15?oC#19!43?_O??C#51?_WoCgEQJ?B?@#70_#60O?C?CA#163?_W_gO?_???__OOo_oo__#162!6?AXOHJA#68???KA!6?GE@#163]~~^~z~z~z~~^]nVMJEACFIDEHA@A#70_?_?_?OOG?CC@@$#66???ACr[uJn~kGW!5?@BFBMG{YomsXallU}DRIVC@iFnZZYE#68??G_@#70?@!7?C!5?_!7?O#67_#28?_?_?_??_?_c??_!6?_[?E#36_oOgW?cGOkGKWC?O?_#46!9?CG?GS?_@??a!6?GO?K!6?O#42!22?GQHOHBGBG@EDlJZ^nV^~NJP!22?_?_GOcWDyM~JxPOm?RC@eG?t?XbISaC_HodW_Y_OhSgQkW_I#40?sB]`IsB\A|?Y_G_[ac?A?C?C#108??@#116?G??o?g_?OOk?K?COY?U?K?SO_??O_?_#67!11?@!7?A!7?GEGEG?C???GK#56???O_O!4?caoQpR`q`avugEPA@??@#42???_!4?A#58?O!4?@#25!6?CAG#37C#44S@G#47!7?G#42!50?W?CAA@@#66?__WGBA!4?_?C@#49??@?DAD?A@C?C!5?O???HWL?G#66__WZFF#65?_O!4?O?@#66!20?!4_??OOO?!4G?DA@E#88?__GCC$#36!14?DA@P??G_#51??O!6?O?O???__?G_?o#59!11?A#3A{c?EC??___!9?__O_?O?GG?CC?AA#77!7?C#64!4?_!5?GG#10A@?A#51?_?W_GO?o??_#20!135?C??C?A@??A#71!13?O#26!6?@??G!4?C#62??P#56C??KAKDMK?_as__?A?FADA??KAC?A!5?_!4?@#70!35?A@?A?GC#36_#9?_O#8?O???B@A#4@A?@#20!5?_O#22O?gQ@C@I_?iP?T?D#14!5?GU[yz}GOS#47!52?_#34G?C???@#52?_?C@@#65?{#3A#69OKA#51!11?A#34?B@EACEGmHnSa#69!5?__WGB!8?GA!23?__?_?OOO??GGGC?BB#136?_?O$#57!16?C??_#162!4?AB@F?@A#60!25?_??@??CG?O!9?_???O???CC?A#136!11?A#0!4?@A@@#32??@@?A#48!4?@#42!5@BED]CXE#46!164?O_!4?`Co#3?OoG??Ow??B@?AZA@C?G??G?G??GC#67!62?G!4?GA#36_O?GcOG#47GCB#34C!5?@A???_G#131!69?O?Og?C#36??OGGA@???A#22!19?@#64!16?@D#3C#28B?_A#36_#68!27?_?__??O!8?@#67C#77_$#64!17?ICOO?_#4!33?@#67BY#28C?oO_#66@?@B?@?@A?@?A@#0??O?OO?GGGCK?Bb#60!11?G??C#4?A#62?@#49!8?_?_#42!171?_O!5?O#4???GC#0_#67O!9?GCOG#135O?_???_#64??@?@BBBA??GGG_WGWKOGWGOgGGW_G?_??_?__!8?A@#70!22?_?C?A#52???O#51E#40_???gQ_CW?S_I#162!72?O_??GK?C!7?OOAGA?AGOI?C#56!27?A#146GUH#135C#72O#64!32?_?oOO???G#146O???A$#9!56?G#33?@?O?gO#65O#57G!8?O!9?A#29_OO_OO_G@b?_?_?O?A#52???_O?_OOG?OO?w_?O#50!175?@???A#57!5?A@#120!15?_#60??C??C???CC?_???_A??AC??G?@@G!5?@?AC!4?C???A??OW_c?_k{}|YTJHMG?K?KG??Ogw?G?A@#161!4?@#17GC@#64!89?_???A?WC@#60!38?@#94??A#138@#103@#61!37?_?O???A#100G$#116!59?AGG#68???A???G?O?CG!4?A?A#4G??C!4?@#44!205?!4_#61!28?OO_?!4O?W??B!5?_?C???A@?A??_E#64!40?_OGCBP#116!97?_?_#5?@#4@#120!39?O#52!43?G?C?A$#88!59?O_#51!4?@??A!4?@?@#33!5?_??O!6?C#57!236?@?@#10!69?@#56!103?C?B#28!85?___$#0!59?g#52!6?C?D?B?@A??@?A#88!4?_?O_?W?GWSMGsDoGsGB@#67!401?GW$#69!69?E?CG?AC???@#0!424?o$#72!69?O??_O?O$#59!74?_!6?G-#161~`|zAkPAKO#52B?OaOC?`??_???@ABCKQC?OaLoMxEiY@~PKaK?_#64???oo?@#3oGA!8?@@??@`?_W@O?G#65G#66?__?_o_o_go_O_#1?A??A#13?__?_#49???AS?I_COA_Cg?AgAChAHQCOcG_G_??_#57C?O#54???A?A#44??PAK_ACHSGC?A#33!4?COA_?G?O#44!4?C?DAhOCHbY@N@IBK?JS?TIdGB[@kPKShBcTgRcXmTkR~YjUydAGdPKADoD?O_??O?_?_?O?OGgA#46O?P#20?S#25A#34??G?OaGaOIo@S?h#38WD!6?B@HB?@@G_`__??CG#108C!6?_?O#0?HO#67?_OW??C!5?O_?_@??@?@CEC?CCC!6?O!4?O!8?_??___#3_?_?_?_!7?_?O_!6?_GOGA@@?C#36C?@_??B#22g?HA?hO?GB?O_?aO@OD?C#16?cHQ!7?KKi_LsGuGQkPIoIpIs@mPI@Q_@IOdGIOI_ACg_OWGGGCCEAA!4@#69_#3CCC#51@@#49@?@#80O#88_C?ACLW_#64CG?O#131@??C#51??O_O?O?OO?WOo_O??_?@#3G???A?A?@#116@#136O??_O?O_#51@AA???OG#161__sgqPwx[x[{k{gKKG[c]O{OkO_#64@@@Is_#28BO#94@XR$#162?KAC|A#69??Bgo_?Tfz~[kO#57@C!4?G#66BADJEDHQKpCo@cw?ioL@CA#56!5?CCLB#88__x\bEI?EC{{SKC!5?A@?A@@#69?O#72?C#57C#51O??O??O_?_!4?C?GRLthUsHqDgS@AOC@G@AHA@IE@CGSG?g?O_#54!29?C???G#47???A@?C!4?C#42!9?@A#47?@A@C@Q@Ca?COG#46!4?_O?Qg?_!4?G!8?O#34??@???@#86!10?C?C#26!17?_#58_G_!5?C??_!8?QGo#48@?DC@??A#145?C#4??A?@#116?_POOOqaWo_Oo!5?@CI@I?HA?AG#64!10?@???@??@??@?@?@?@A@?A?F??A!4?A#8O_GOc???@#33C#65_o_s??oO#68qCB#34_???_!5?_???C???_I_@#25A#42??_D!7?_@?O#47!18?_G!7?C!9?O#10O!4?CC???@???O__#99S?C#100GoKOA#136O_#60@??O#57?_#163BDA@E@B@ADBB@BB@B#131?@@#56G?G[O#33G?C?A?A#94_!4?P?gg#10@?C?_#34C?C#52O???C?A#162!8?COAS?G?k#70!8?CAC#33S#100@G#136E#77C$#163?Q#66???Pm|oFK^nGG#68??ARlS!8?G_???_#36!10?OAH?DiCBLRA#0?o\F!6?@AA?__AO@#64_?_??O???GC???C?C#32A?A!4?CA#57GE@#66!4?G?@?Q@ISHyPkpUsqSsskogWoO_O_#14A???GO?_!9?@??G#46!13?@!4?A?GIO???A??DAOUdK`bO???_#57!8?G??@#42!11?ShDY`vOMpsIDYdMTiUlwU|JulUhaT?lACi?pEgbUHDQ@K@CA?A#40??Y`IPi?@Cg?_???pSQGO_Go#32@?CA!4?@_Ac_??C@E#28_?__?O_#61!6?GD?C!7?OO?OoOc`?H_??A??_a#59!4?CG?CGAAA?AC???A#4!8?C_??cO#67?O_!4?_#61_#72GA#69?@?A#26O?YAch?CAC_OI?MWDKcG#20AGZQC#14gdfTyy~~Ba@#30!28?Gc[G#18_?OG!5?A#57??G?G!6?A#62??__#70??@!9?_#49??GO?G?G!6?_???@_AB?@#1?CS#0W!7?@!5?ACK#49@???CGCKC?A??@!9?_#51?_#68?@??@?@@?AE_#67@AG#116_#105??g$#64!19?AIr{`C?SWo_#69OwgO!12?oOLQDA#60??__A#5C#100?G??G?C??G#60?@@??_??O??O?GCCC?A?AA#4A!7?C#14O?OGO#162!14?C!4?G#47!4?@@ABBBEZCKI[_go?_!8?G??A?A#32!12?A#14!8?@O#42A@#27!42?S?I_AHOI_I`IShADGASHAhETA|AxiPjKxVSheWd[bsJ{j|UF!8?AEOEQACB!4?@??o#46B_pog_?S#56!7?@C@?@CD@??A??__w_#103???G???GGGOG#60!13?KG??I?cCAA?OOWWoOkOg_X@XAB?BO@@!4?_?OKYGDB@#64gKIK@#58_?_C?OCH_?O?CO#27!6?kPcH#44Q?G#13OG?D#15??O?OAO#13!33?_!7?C#147?A#68OOO#0!8?O!8?K#146_#66B?G?OO?O???G_g___C__???_!5?@#70A???@@#165??C?A#1???@#62A#13A#28_#42C??O#66__OW?OKKEC@C?@?AAA?AAA_?AmOg[O#52??O#57G#88@?Mu_$#36!21?G@?A@#3_#51?@???A??@?AGS@A?CB??A#70!6?G#8G#29???OCA??@#116@!6?OG??GC?C??A?A@?@??@?@#0!4@AB@#161!20?G#48!13?@?@B@AFFLE[~LqYaxa^szu`rxt|!4~zn|^zu|nv~|~psiwElwuW_{_gOoQ[ci|iSWuwakQiPbQkQiVkZE@i@C?D#16!10?_#47!8?A#41!33?UTmTyewOo???_?GKVN\C#55A#50C?O?G?CGC#70!10?A!4?ADAC?C???O_?_?_?__??@?@???@??@!7?G?C#61!7?C??@#56?OgOWc{c[{lKG?mBZfZEJB@CAGGA#57C?O#42_?KA#27@#40?OC?AS?Ci@IPCI#21???O_#47!4?OG_D#17!4?OCLaJvHvlRmtNtMtJ}Pmt]d^}tnYvtbPBtZVNFBFFFB!4@#8?C#64G?GO???A#29??GgSGA#94ko_?O#65?O_#36!15?O!4?GCOOO?_#31?CO#88KWO_GOA@??D@?WoO#3O#131BBBA?B#69?G!4?AAABAB!7@?B@?BEAm{w#65@#60?G_$#70!22?AA???_#49???@???A??@??C#68!5?_OgOxKA#65?O#28!4?A??@?A???AIOGEIBE@DAB@?A@?@#3!4?@!9?@#46!37?@??AX@?oLdKCP!5?C?I#39!119?DW#34???C@_O#39!5?A#71_#150@#43AA!5?O#60!11?G?G??CA@@C!5?GOO_@_?@@@???D#70!26?GG??CC?AA@???A#32O???GC#2??C#59???QO?@#52??OC#38?OCOHAQ_HRGD_@_@_Q@Q#46!6?A?A#51!43?_??_??_#12???A#4CCC#33!10?A?@#28@AC?O_#161AFMKAHM@MCEDACCE?CA?AA#10??C???_#43_#29GO_??C?G!4?AC#33??G#6G#17CGG#163@B?BB@@_??_?_?O?O?O??O#56!12?O$!23?WG[#161!7?C#136!27?_?W?O#67!5?A?@O??GG?CC!6?@#9!7?C#47O??_?O_Kog_#25!176?G#48G#44CKA#25!8?G#54G??COCWG#135!9?_#57A?@#135!16?G#36!47?@#9E#10?W#47!16?G?@#19!70?C#36__?OO?WOG???O?p@_?A?A?@!7?AC!5?_#47!13?GCCGC?Aa#32???_#99_#28C??A#60!11?_#14GO#47_#131!17?_?_$#9!23?C#60o_#94!36?O_oGwo#33!6?C??A!7?A?A?@!5?A!4?@#50!184?O#46G#20!11?_#44O?G#33A@?@`HUHqGI?IC@C#65G??GGGJZ]F?C?G?OOPQOacefMiZM}u^}n{~~^Ovv}PzQZWT[lGdCADB?@B#30!27?O#21?C#14!71?O??GGG!7?@#139!10?_?G#67?@?G#68?G??_#57!20?GC?AA#60?A#64?@#100kS?m_UlIgS#162!4?@#8O#57_$#139!62?C??C#146!9?C#56_!5?G!8?A?A!7?A@#16!196?G#56A#116A#62@??AGO#66!172?_??!5_??_#137!10?O#116???A#59A#162??D#34!23?@#69!5?@#139???GO??gOCA$#101!65?_#36!12?_??O?G?GG?GGC?CKKKGgO_O??IPC#3!197?A???A?cLQc???GA?G??C!4?OAIQa@??E#52!145?_O??O??_???_???@?@!8?@???O?_?_o_O_W?WGGG?KCW?O_A_?@?@?@$#70!78?O!7?C#10!10?CGGKC#35!385?A#56G?GIY???A??A?@$#52!79?___?Oo??O??OGG_O_O!7?ABCQ?Ag@CG_@A??B??@!9?C??COO_#32!349?AAA??K[[GCA$#68!82?O!4?G#88!9?@-#161JCB`e\qDw@O#52?CA?PGC??O_?O_!8?A@BEL_IFKhAG_?GE#70?_#59oOK#28_WLB#94O{NFGp_oJB#65_KA#66O?_O?Ge?F_i?B?`@?O?`OVC}ln}yswO#57C?C?O???_#66??Q?HqDidBtB@?j?QLI_SG`TulyuwwOGs__#14@AhG#32?C?Sg_GOgC???G?G!4?C?_C?C#46??oShAkOGCQG`?G?c?C?A`?A_G!6?OG?_??GC?A??A??A#27!7?O@#20!4?@!7?_?C!5?_O?@??O???A!4?G#38???G@?CAFOo?_!5?A@!5?A@@#56_!7?A?BAA!4?GoO?__!8?A#103ooO?_g??__#70???A??C#59???KGG?KGE?AGGwO_Oo!4?_??_!4?GA@#10!4?@#116??C!4?@#64_o}}[EA#20_o@???GOW_O?G_O!4?OG_[_^aS#14o_ATjhtumnP?_?_#47!17?C#15?_!5?C#56__#46G#69_#51_?O_I?S?CgOCI@e@UO???G?_#10@G#0@!7?oGK#66_OGC??AB?!4@?@!6?@@AFZM]woO#32AC_#88O_A!6?K@#68OCA#36@#131G??A#49!11?A?AZ??OI_X??`@QQC#52CCA#14G#43G#32C#0D#88HA#105U$#162_QgUXaKw#69?AD?HC~mvxvlM@k!9?@!5?GC_ASGADA@#67!4?_?[C#116A#137_G#139A#105owuK]F#84C#146W#28S@#36CGAA@A@#162??_???{gSEWelYEGo@#52??@CGA_OO!6?CAh?cO#162!4?S?cSVCYd_`Ti`Si?O#52??@CB?GOGCo#48ATjBNJF^VlVjZ~vv^aN@n\iJMr}z~~NACDACBIDfQZvuZ~y~|]~LRA@ID?B?B?JCjDrxn[lUG??_!13?O?O#27!4?_DOA_PC_TGCISJOJcZaLtZsjezu^#40UcI?HOkBkQgOcjQc_@?@#32_?_?_??_?`As@GB?P_?oG#0???G?CG?CG#65???C?CC?CFEFFFDDFNNCLjz~vVqovBBE?@@FFAfCMKwQsOOgOOO!6?OG!4?G?G_OGEVA??@#57_?@#22G?I?A?DA?A?ACJC@?ICAC@#16???GAC@!7?iaQLBKbKRcI`KqGqKqGqOp_L?ITYSEIAD@@#66OO?GC?BA@?B@?SHGhf|ua_g#64A#57Q_#33AO#88?A?Co?CF@#56G?@#49_G_K?C??A???A?D?DHQGdGc?_#47@#10@A#56CW#0E#100B]HIS@#132??_#135O?A#162o#161WKEQExDRCJUHe\BsBkrc?_o?_O#162_O??_#66`@BBA#36C?C#56B#33A#100c@#136_$#163ShSG#66??@AF{i~qx#68???AGQ`]Og!9?@ASW_O_Go?_OOgU@@@A#3?o?A#77??_#136???@?@#98GO#116?G#56O?A@@#161?_?O??WO!4?gA???_?A?O#36???@DIjKoi_IGkOw#49?C@C?y?Q_AOA?O`??S??OG?HA?GA?cO#47BCBIC?A!7?A#57??C?G?_Sos???oO#44!6?hQwPispgOK_?G!7?oKtysQ\slkvcZSi?AO`Q`Ti\@[DQgEOdjWeX~jMlelQ|ASQK_Z?hS_RgP_?g#34?@?D?A_JCPCH_@#41Wo?o_BGBLFNJSl#27HSqBIBB?AA?D@#33???AcDg_g?w?oi?KA?QAgB?B?C@#61_O!7?_AA_?O?_S??Gg@F?w_@HMKooCGW_#70@?C?CG?_??O#67!4?_?o[{s}uwqSIDG?C#36??_GD#27[?qOG?UGC[CWCICGSGcICY?E?@#44?C#46H#47_?SAG@???G???O#42!20?_#23_#13_!4?C?A?@#34?_?__#52!10?A?C@QOG#32@{#28A!7?OA#116A#162_OO_GOKgCO?E?QCJGD?_?A???_#36@?E?o#29@@K?_#92O_#77GA@#103?_#28A@#163?_ookwCykzshuXa{J{P?O_??_#30!4?G#42G?G??OG#20O#37o#53_#62G#117G#29Q#94sH$#64!22?BF]{oo?@ABMKW__O?O!4?_??_GsIDNB#65B#88_o[F@!7?cB#64_wC#51_?k?QGAW@C@?A!8?@??O?@A?C!5?@?@ADEHYaL?TG?G?g!7?@?A???D@CBGB?GO#57?@#10C?Sw#46_?O?_??O_#33!4?@?I?APC@G@#14!8?@#54!5?C?@??@#14!7?C???G!6?O#47!5?G_S#16!23?G!7?C??G#58!15?_AC@?G?CO?O_O#39??O#34I!5?@??@AC#54???GO#62_#116C?C?C@E?_?@CG?COCgs_WI_@?g?G?G?OOGO_GO#64!8?C#103O!4o#67??@?@??@#56@???AHAADBCADCA@@?A?@?@#59?_OoGG@@#68A@#42G?A??oW?__?_?_O_O__O?_?_O_[aHQK!6?O#17?CHq[b[rkZt]rLvLrLvLnMZq^tIDJ@@@#36O_OKEA@@@#162?GA@#69!9?HWU#14??_#4A#29?CLO???_G#67_?D@#161_?OS_OQimgwlKhoQoqS!5?O#6???@#57G_#99G#136?@?@GeXC?B#59o#64GA#66CA@#49@@#131!13?KH?[?GO??_?O#47???G_?GC#57A#122_#142o#92?G$#56!24?@AGG#65O_?G_?_#66?@?B@O@A?D#57?O?_?O#60!4?@#101!7?A#119??_#0??A@#52_[KAD?@@?A#69@#14!19?@?AI?A#48C?O#161!10?G?G?g?CGQ?I?E#36!9?C?BCoGO_#62??O#108_#56!14?O_#42!33?@gA?Q??O???C!6?@a[bylVxnYCeXe?S_QHQlA|J?bXCmQHA?A_AsBcYcWoGC?O#22!6?@?C#44!12?GKCKKC!6?O#3!5?K?LAG?CTBCW?@@!4?BAD?A@!7?G#60!23?A??EGGJBDAGd_wOOoWc?aA???A?B@_??@#48???O#17_???_!7?_???_?O?_!6?@#13??OG?C??@#14!30?W?CAAA@@#47C#131??_?Co?g?S#36!7?@?DLC#48O#100?@QksOHY#64??O?C#52CGB@@#131_?O?C?A??C!4?cO?O#52?@?EGC#1@#33A?_#94?Oc@Ocy^#61?G#65C@#70@#34!19?CADA@ECJECC#69!4?@@@#70@#141O$#60!25?@D@cC_#116_#57??CG#36?AC!4?T?ID?OICG#3!19?@#49?O?Wc?{?C?}?TIOdHQCH_G?A#46!7?@#10@?KOs#69!33?_#16!62?_#46!45?AG??C#26!19?A#42!13?C!7?CGAJ#60!9?@?@?A!9?@!6?A?@!8?@A#3!19?@A??A?C?CDGICjdG?GE?@#61G#103???CGCA#30!7?O#47A@#26C@@!8?@!7?@?@#10!6?_?OAG#15O?CPC#10!26?__OG?K#49?__OOSGOGQCi`iOu?G#56!7?C#99?G_#77@#136@??C@#68?_O#36GAB#51CA??@!9?AGAGcW_#64!5?G#116??O#139??C?A#138!5?C#69_G#51!21?@IDCHACpAYCkoG#17o#46_#64A#123O$#70!26?A?@QLCOo#69!69?_#56?O?_#47BAH?O_#25!140?@???C#39!16?w#46!14?O_O?G??G???GG???O#67!4?C?@???B??AgbSAW??GGK?WOOOWG?O_?o#64!23?_?_?O#4!8?A@!4?@?@#21!13?C!8?GCO???G#48!13?A#11!36?O#147G#57?O#52??GGE?A?@#1!19?_#139??IgC#59!5?A#163!9?O?O_O?_G#69!11?_$#3!27?CIG?O#13!73?D#15@#16!183?_#47O#57?oOo!4O!4?A!8?O??__#28@SO???G?_O!7?_#116!31?@#38!34?GCAD@A@B@A@??B?B?@#161!57?G???C?O#94!19?BA@A$#67!27?A??O#48!264?GK?_?oEt@GO?O?O#65?A#88!10?O#70!5?@?A@#135_#34!71?C#16_#44_#40!4?@!6?@$#50!295?C#25?A#20?GC#34!115?AC??P?AG-#161NOCE?CA@_H_A?GO_#52@OAg@IOISa#64iRn[W!5?@BEDMCNEB@@B@#94?_OoGKCFFDAA@B@@@#70??C@#52?LGoO?O#51???@!5?@#52!15?D?H_!7?@ALSw#162?G?BSB_D_HSDI`M@IB??@#36!4?O_SgZ}n#10|W!7?GOo_#51???S#14!5?_??C_#42O?g#27?Og#50I?C#54??_??O#47!5?OG#42OIoG?@#25??C?C#48??otItWLaHC#20???_#27???C#50?O#48?O?Og?_A#17???@#20?C?AOC#48!6?_#20???_?O?G??ODOc#38??S??_O_?LPP`!4?AEF@#26A#54@?GCOWSG#42?W#56AeCO?O?@?_!4?_@@C#0??KGCG#61@?@?@O@AfGU`Y{}~~^UcUfmFBBCH?@?@?@???A#67!5?O?O?O_O[kKVDBEDBB#64!5?a?JQJB#14AD?@G?A@?AC@!5?A?ISGe?WeOUP~uR~^bjn~vM?H!4?G#51!7?__OogOw[CQC!7?_S?Q@O???G?A?D?C?@YdOd?g?JCJ#57@I#3_!6?@#131_oOo?@Ch}wuwsO?Og?iCg@?QCi?Q??G#52??AK#56@#64w_!6?_E@#161G@???@sOXoCgAGAtapiu{~LvLRBA_?oHqK#52?rWA#116_O_#100CA@#94@#162_$olyXMBL?@#69???B?G?aFTV}?C#70!6?BC???@?A??A???GKEEC?@#0?A@#132?O#100A#61?__#64_?OOO?O?O?Mk#69A#66FNM|lo|sGYdDiOK?CAAcBpQlZnn~~~w}cO?O!8?AAVa{ggWEoLQg_SYpMtw^|m^VnDME@#64?_#57?OA!9?B?D_@VBUDkBcOc#46g?OBgTOmO_PaHSHETC?C??C??A!4?ACIO_P?sGA_??_#47PCA@#14!11?_!4?O#27!6?Q?@O?AO??A?A@GFWfHulTmYdY~t!7?AKEELECKG?GECA#86CA?CC#47!4?wW#116?GCGEGQGGCKYCW_W?cOFgFQPAX?E?O!5?@#67!9?_C?O_#59A?IAD?ACSKBC??@??@#103!11?_#59WcWeWD[[CC_#56_C#57O!7?GSG?oK_?CO?O_O#20@#13!5?g??G??GO#15O?G@aO??AO!6?`A?@ACBCA?@@A@#3_#10A@#64GEA@#34?__CBEBF?B#66!4?`!4?IARB^oG#36omC#32B#67_???CA#161_WKjBMQJU?FHFAHcJDyDzTkUcg!4?D?O#69?D#68_{!9?WA#163o}~^~}JnENjVlVlITMTHB?A?A#69??_C!4?oGCC!7?_O$#163?A@?@#68!16?tjtj\TkO_!8?@?@B#69?@#60?G??AA@!9?O?G?G??GI#161!5?@!4?GO`?_OkQmQdcQoAgOC#69!5?A?QK_?_#10@#60?CG#51?@AGDGOBOA_GAO?AG_??_??_???_O??@#32!6?@H@!4?EO_#36EGYs_g_w?oG#44!4?MkVAn@FTmXUjEOAH_GO?iGvLtJup{`_INi~JFGTIdQKqPiTiOdWuwjitkVdQj[huObSAY@sk_mPkyv\\`]cO#34!5?A@!4?I!8?A!6?CG#47_#25G?CA#85?AG#3!8?CGE?QKA@AB@Q?G?M?A?O??A??C#135_#60!25?OOG__?OGGUFAB@GAHOCaR?gQCHA??@!8?O#10O_W_w?C_OSgOoGqO?O?O??_OG_?G!8?C#16???oKa~cldaNyDy?I?@G#49???_#52?__!6?_?GA@#163!4?_??O#161_o__G!5?_?_O_#52?pCO#64o!5?_G?@#163?CKoko!5?HeZcQDO?AOg#49@?ThkIqc#36???B#57A#65A[!6?O#69_K#66@#131??_#66!18?o?og{[Y~MuKBvG#64_G!7?G$#66???_owo}]u^|{vf^[gg#65!11?BA_??@??G?OWOO??G#88?_?G?ICB?GG?C?A??A#56GO??O#49!5?A?D?@E?I?@!4?G@?C?@#36!8?@?BVg!4?BA[O??_#161!6?GA??Q!5?C?A#52O_G?y@GAPC@#8??_#48A!5?@C??O!4?@ABCBNBFj@!9?_?Ohga^rn~Pf#14?_#16?C@C?O?@#57!8?A#42???gSiTNYfHBSDIBGA@?ACHf[i\`KHAJPkBCG_?[_RGfGu@Qg?_I@#40??@}@YdIT_?_?A?BA@@#50???G?C?A?A#57!6?_?@?_!5?_#70@a?`#28???O@O!4?O#3!27?_o?O_?o_?goCgeKCMI@?a#61!5?_O_WcW??A#48???_G_W?S_SI_@_?D?CI?G?C???O?O#17!15?C?X?IToDyD}S\]EDJ?B@A#36OGgCAP?O?@#162!7?Oo?W?GO??GA!4?GC#47??A#10??@#56[#29@D??@#69_??@#162??@???@!12?A@GP??@??@E#3!4?@#28AA!5?B#59@#162oE!7?_?O?O_O?G!6?G?C?@@?@?@#36???A#70sW#103_?_OGC$#56!30?_#67D]yc?G??O___?OO?G?C?@???_?OO!8?A#162!8?AIAA_COYCB_PhOWHGKCA_OO#68!7?GC#72Og!6?_#69!26?_OgE#56!4?E#62_AA@BNW#47A?GADIG?G?OGO?WOO#27!27?I?IT_O#46!25?G?C@??G??_#16?_!6?@??A#58!16?I?w???I!5?@#46??_!6?P@??GAK#4!4?A!7?_#61?C#67?C?APQ@_A_GKWC?GKGOC??@#64!20?A??A@#116!5?_??_#9!23?G#36D#47A???G?CA?AGA?A?A?E?AEGCCGC_C?GC??O!5?O??A!7?@!7?G?@IC@A#14@#70_[#65C#49_?OGALG_O!4?G@AQ?vkdQDWC??C#116!5?O?O#61_G#68OKA#51!23?A?U?tGO#60!5?C#33@#88@gO#98O#132OE#61G#51!38?@#14@#10B#3G#28O???AA$#59!31?_#116@CGO?Go!4?_???OCGE!5?_!8?C#56!41?@FAnySw_#49?@??D?C@CP??c@?@C?O#1!18?S#99GSIS_?_#14K@#47!73?CO#26!35?_#41@EDIT_O_!5?@#48?_?!5_o?`?pQcC?p#65!7?C!5?Q!10?AA?AC??GC!5?_HXHWPWW{jU|}tkIC\JH?CB@!6?_?_??P?gwoWCGE@Bab@xoLC#42???@?A@??@?@??@@?!4@?@?B@@BHA?@???C!5?@???O!8?_?Og?S?C#72??_!4?_#66?OOKA@#0!28?KA#100B#65?OCA#66!30?Ah~wO#67???_#94FKGF#103oC#56!40?C#6A#88GGCE?@$#60!31?W#61?@AACC?G#119!15?G#77??A#65O?G!4?GC@#57!38?A?O?@?C#33!39?_@??O#46?@#20!121?GWo?G#33!10?_?_!6?@o?PC`T_P!7?H!4?C?C#56!34?_?OgOKGU?PA@@#46!23?CE@A!5?A???@#16!4?@???A#57!39?O#56W[KAB@#131_?_[?qKHkGfCDUGdY?O?G#99!11?A#28Wc[A@#66OCA#116!36?[#146O_?_#129@#8!42?C#117AA#29@#65OGC$#28!34?Og_O?_!6?o_GO?CCB?@???GG!4CAC@#64!40?GCO!4?_#0!37?S_O#29_#44!127?O__OOO?XOX?G?A?C#60!12?O?O#43!84?A#32?_OG_#36???C??GSGcG_G??_??_!4?_?_#60!31?_#68!4?G#69C#88!32?_GB#136!43?AE#154G#138G#43!42?@#0C#63@#61_#68_O$#103!35?C!8?_#136!14?@#68!4_!6?B#4!41?_#31!41?GCG?_#42!125?gOO???O#32!5?@?@?`B@@I?B_g???A?_???C#44!84?@#56!8?__?_#100!131?@#105@#53!45?@#120O#138G$#146!36?O!8?_??O_Og?o_OO?G?C??A??B#80!88?_#103!157?G??G???_?_???!4_g@o`Gr`YcA@???_A_???_#119!197?_$#88!37?_#101!22?@#3??G#36!4_o??Oo?_$#57!64?O!4?_O_-#69G[WWow_O@Pjyu~yv]NVJ@G@#65!7?okzC#28im?@@!5?`O!4?A?@#162_?o#52!6?@@?AAGEWgWO_?@BEG?W_#162@@?BJVK??a?HDD#69!7?AC||nS_!8?@?@C??G?G?K?K???C#28?_???_!9?_?_!7?O#67_#56_@?HMFKE?C#14@@?A@@C?AG??G??O#54?C#42!8?C??C@?@#50?@#33?_??o?O_?_#42??@A?BGA??A#32?_?O???_O?_#14?GO#47_?_???_?O???C?G_#27!4?@C?@A@???N_g??aA??_?ooG?_aCO#57G?SGC@A!4?@@A?A??A!4?@#70?A?@#103!5?Cid??@@A???@@#59!9?SAgAC?CQ?GG_SO_WOs?GO?O#56A#61W??_OKA__Hxk{lYm^y~rAC#56?OGS??_OOcos_xux}wdy_v}pmI{OkOg_#14@A@IcA|^vvVfB??OO?OO#51OwcIKAAH?@?P#64_?G??_OCC#68B#131gSi[jWNDQD??G?O@K@I_H?A#69!6?{U@!7?B#66@#161@?@!13?@??DQDhFjV}w{W_w#69OhKB#70o{B#88_A?W#29O#10G#16O#162B!7?O`LADQ@aTgBSf?CO?g#69?ACgpo@GDA!5?O???@$#66E?FFNF^n}mC@??CG#52_?_??@G@#70!6?A#59?C#67zD#146@C?A?AAA?SEA@??@@#66_?OGWGSeUd{A~\ksWcO__!4?@BFf^}[ys_?oB[@kQoiTj]j~~~|zA?O#36@JeG!5?@EACWCW?W?WOw?K?K?MKE@CEBB@B?@#29?GO?WCThA@BD_M#32?B#64__OO!4?G#46??A???A@D?F?A@IBEHA@G!7?GGC@A?aCA@CAO@G?G?M??GC?GS??I?@!4?@!5?C?GC?C??C?G!5?G?O!5?cTO???O!4?G#34?G??_??_???o?_#33C??_ACBC@!6?BEHFCJA?B?H#135!7?@#61?rp?A??@?Ea~nnh_ooG{i{V\BFBCQO??_#60?@a?A!4E?DE@BBB#59!8?O_O?D??GbC?A#10_LZUjKRLIU?HE?CACB??A?O?KQhU\o[ocAO?_G!9?_#36_#57_#66?O???CCAA_?]F@!4?oWK#51D@P#163_?__ogw}tuhm_A_#49?BCQCT@#64!5?_CF???_GA#163g{}{}{~~~z~}x{gw_osxaw_oOoOg#162@DBeC@#52??oK#64MB#61C@!4?@#36C#51KO_#49_#66!18?BA@BCwoAMLAB?@!4?__W?kQdY$#64o_#68_#52_#161!6?OCH?@?@#68oG_ueu[vZvGhA#116!4?O#88OzM?B@@@bA@DA?@#161!4?_?_ogWgW?{?_#49O?_#64?AD?H?_?O?_#161???ADGSgB{b[RcIOiS`S#52!6?A#68?iSXo#56C[?BC??G?__#70_#68O#60!5?__??Oo#56OG?G?GCA?AA#62?C#99?C?A!7?@#60??O#36O_?O?@QOGOHOGW??O??O??_#14!13?_??_#54!4?OCH?DA#14!5?OH#57!9?__#33O_O#42???@ACAHC@?@@@!7?@ATIBAA@?I??@#50?B???OKCG#41AjBF@?E#20??@G_#32C?Ge!5?A!5?O?G??B?@#65???O?AADB!4?KM{{wo_[w[?OOU^NNvB@@?_wwwhdfvNJf]DgHopgwnW?_SKcrTJDQ?@??@!5?pGB#60_#36_I_???A??@!6?O!6?C@A@C?AC`I@GcA#15???gO!5?A?G?C#49?_OC?A??H?@#56_?GAB??B#49??IC?CA?A#66!13?G!4?Sj~B#68G!6?oC#131???B?B???C?@EBVF^NIE\ALIEGC#49??A?@A?@#68??o@!8?B#20_#161@?O?_?iFIo_gGsGaRkJG^wL}SzDJT#52?A{CI?B#116???B#103@@$#162@B#64!17?SGO?aGcGvU|LR#94!5?_{{Kk[SG???@#69__oOWG?CCA??AA???@A#57!4?EOE?O?_#64!32?Ew_???A?CW?O??!4_?O??O!4?C#0_?OoOOoWo_cGE?A?@!5?QK#10?@???A?C@AC?C#52O_O_?O_?_#47!35?_C??O?G#27!18?@#50???@#32_#14?@!7?_?O#54!7?O???_O#39???C#48!8?@BBRb@A???@??AC?@#60!5?_O#28!19?@#64!21?G??O?G#67??@?@?@@?__O_OG?_SGC?O@#64!8?OwFD#70@#57A???_???HA??@A?@K??C??A@?A@!4?O#47@!4?G!7?@??G?G?A#30?@?@#69??_!6?_#161?_oO_??O?O?C??A?S?Up[sSo_x?I{hS#52??@G#116_GE@#59O#65F@#66!32?WEmUB#28???_!4?K#59A#37_#30O#163AFN^^~TgSA\QdITGCO_O_?_#68!9?o???@??OGCCB$#137!37?O#105??_O_#119G#61?_O???C?A#36!14?@?BDAHAxSkgO#72!32?@#59A@o#57?@!7?_!7?O#67_#116!4?_??_!9?_#100??C[ICGG#3_?OGG#57?@?@GGA?ICGE?GG!9?___@__?_??_!52?_#16!9?C?@#86!6?_!5?C#16!10?A#46C#44O#56G?OOkOCYsEKgKEHcP?G?C?C?@#3!45?A!7?@A#72!21?G#9O#48?OCHC@GA#8??C!7?G?G???_#13!8?O@#16!5?G{~|nHWM@A#162??O_w_OOWUM#57?W#36ED@??G#34!5?B?D#162?G@A@G@A@G?A@GAC?gsAA#56!4?_O@#138GA#135B#162?wSA#51!30?@#65!7?G#146W@#94~C#31_#60C#47G#131?KW?_#64!29?_sGC__???A$#136!40?O#138!4?G???A#56!25?G#69??C!4?_#70!28?@#60AB_A?G#52@A@GA??O??C??A???A@A?@#10C?C!4?@#80!5?A???A#31B#4!4?EE#69!4_?_?_#42?@#47?E?CGCWGOoO__?O?_??a@qPI@#25!71?WOK#108_#50!13?G#47?_#116??GO_O_?gO??O_W_?_O?s?SgAEgTI_VjCQ??A?D?Kb#70!27?C?G!6?C#32!25?_#9!11?@!7?@#46!11?G#17!8?A?EC@EDBB??@#131W?G#65!5?_?_#51!26?@?A?@#70!5?OGA#3@#116!44?O#103E#119{#100?_#138B#62o#34??_#70!32?OGCABP#161??_wOkYd$#65!46?_OGG?C?A@@#67!58?GOGO??_#51A???C?A?A!4?A#8???G!6?C!4?@#139!5?OG#94O#72!8?G#68OO??O?O#44!4?A?B?B@AC!5?AG!5?@E@AQKBCAD!6?@?A@?CA?@C@BH@@E?A@!5?G@@ABIEIEMABDA@A@E@ADK\GAMOGMMFG#26?FC#40@??_`?K?I@?JC#54!7?@?@#3_?GO_?_GC??sOG?GYgCC_D??O!9?GO#103!37?GG???G?Q_EAAAC@_??KC#32!19?G#4?O???O#3_#43!22?_#11_#42!5?D@#163??_#161cc#70!4?O#52A!6?A#36!30?A#61?C?WG#129!46?@#61!39?_wSCCA?@$#135!46?GC???A#3!62?CKOKoo#66??BCFBFDB@BBDB@@@??@#33O??G_G_GOGO?G!8?C?C#48!6?@?C!5?@!4?CCK@KXQL]SV^\]LiSOqwKo{WxGyqLikV}V|omOpwu`isss]WNl]n]n~]tqgSoOwowOwKq[}x}oSg_O__wo?__owc!5?O#70!19?GG!6?_#48!108?@?@!7?I?C#60!32?O?WGB#103!35?O__C#132!46?A#65!39?O?GIGCA$#64!47?_O?GGCCAA@@#0!58?_#28_#162!5?@#72!7?OG?G!4?A#32G?G???CAABA@#66!18?_?_?___?_#51O__?_#38!89?AAB@PSOOWCKO_Z[#10!12?@#67?OO!7?_?_?O_WOgoKgOOG!5?EA!125?_G?A#28!36?_OC$#103!47?GCC???@#3!78?_?_??oOO#88???_!8?OgO_co_S?G#58!111?@!9?A@S#36!16?A@#3!155?SCD$#68!48?_OO?G?CCAA@@@!7?@!4?C?A???W#1!64?G??C!4?@!8?@?A$#132!48?A#4!95?C#57@?@-#64AAI]w{_W?_??_!7?@??QbYr}^B@#28_gEA!9?I#64Iw#69g@@#162O!4?Ac@OG#69!4?KA!8?A?A?__!4?BK?_?_!6?_?_??ogy{~~^dI#9??OG#1_#3ZD!5?@@?@A_?@?@?@!5?A@A??@???@?@!5?C?AA#67@#64A@#69A?B?@#131g?O!4?_?O???g?O?O?_?O#47?@A@A@AAID?P#56?O_C!5?GC_Ws?PWgsO?ACOGQ@ICA?GA?CIS?[oKyHkWtGOKOKOK?CGOSg??o?W_GO_#54@#27A!8?]@?@@?@#47?CCEB#33_GonGVlGUGPC#70CA!7?O#135???A#3O#138A#70!4?COGO#59??O_#67!12?_O?O?OC_OO_OooowWGGHKHZ\nBRHb?r?o#127_???C#135!5?__#64??A?Gc?GcGO??C???DA#135???_?OO#67EKKkW_#0_#8AC?GO???G?G?G#52_!5?__#28C!5?GG???C?CI#61@#162?KQ@#131A?A@A!9?C?O!6?C#57_?_#36_#70__O[K#120GI@#61G#72C#162oB#131?KYswOg@_!8?O???_#161??A`ejv\ZVZNNE#0_?O#94_OgEB@#63O?@?_?g?_#62_OoO#7O#3__?O#60OO?O#64???OO?!4O??CG?aZG?B@#52!6?_$#68LWo`@AK?OWo???_GaDIUi^dlKdK@#65_KMFB#146oHA!7?E#67@#65T#68FV#66}AB?@!5?A_Za\QpOvT|L{nM\{{oc!5?@?BV^[^{ivY|UZ^\~~NVDB#36??_Womx#64JF#59C#0_A!4?BAA?Ao?IG!7?A#161?__?_???__O#0??@@@?B?E@@#47?_#162???_O?OoG{g]SYmjUTkQsgWc[O?_?__#57???@@@?DIOkS?OCC!4?CA@ECAEHIBGGAC!4?@A@@A@@@IAE?C?ABACB@BA@AIACAG?CG?C!4?G#42?@#38~?@???_?@#46GG??AAC!8?A#3_!6?o`O#67GO@??@??H_@@_M???A!7?BA#59!11?G?C?@A?@?!4@BD??_#28???C??C#61_?WlCJK^j`nZnZDZDNCGKWOO#32?@#70??C??I??E???A#120???_??O#4?@@?B@Sowo#9GG#48@#59_!7?G#57??O???@!8?A#94?O_#65?A#161??KydS`O??_!5?!4_`OGSXZUJ]LLE@A#68G!6?GA#163{~rdJFnV}^!8~n~~~^~~|]XSGACGC#70__?OGE@#139?Og#29okB@#122c?O?O#47@??C???G#56OO#116_?_?_!4?_!4?_?_#52A?A#65WCEB#49O#131_$#69o_???@@BNEN]^y^V[wtgO?O?O#61!4?_OG#138O#67@#120C!9?o#70_#161???{k~}^~|Z}lVc\alAl?gAO?O_#36???DADG@?G?C#161???B?ATGdAhC_A#68!9?ADPE#10_#4_O#67C#28GHA@A???cWCO?CIEDA@A@?@#33???@#56!5A?BAA?C??OO??A@#49!4?GC?C_?S?GC?C?GA?G@cGBG@?c?G?O?G#48???A?F@@J?@@A?@?@??@?@?A?C@A@?@A@#65?_#67???_#48!4?@@A@A@??@!5?@@?@@BBB@BB@E@E@A_#40?_?A`?O?_#25A!5?@#34@@#108!4?A#116?O??o?oGoJSjuJsP_I@YsNse\_La?@!6?@#60!16?WG?WEMUIAK??dF@CB??A#103?_?Q?OGs@??S!4?A_oOWso_#56B@BABB@BB@BB?@?@@@!8?A@?AHICE?!5O?G??G??G???QQ!9?A#70@#116A#163!5?CWj[m|~^!5~^^Z^MNVJECH#47_#49@A#162A@#69?K@!6?OC#66!30?_!5?@@#88_?OGDOCA#117CA#123OWC#20@#19A#31_?O_#66A?CCC?G?GGG?GK?KKCEB@!7?_!5?o??uT?A$#52?D??A?AC???_???_@A?@C_I#59!6?O#103_OC#135G#94ow]FB???@W#116C#163!8?_#52!12?GA?a@?Pa@BGWO?_A?EG?G?_#162?@#72!21?C#56OI#88?_utICKKCW_GC#64?__?OO??GG??CC#49?_?O??_#4???AACC#29B#8?CC#161!6?__!5?__?O?g_PCACA?_COG?O?O#10!9?A#32IoMQ_CDAa?@!4?@?D?C@CAGCC!7?CA@??@!6?C!6?@AC?SGUCGAG#44!4?[#22?P!5?@#20?O_#32O_?G!6?B?OC??@@?A#65??GC@??K[___?O?G?Oo[{weAkz~G??@aB!7?@[B}iee`F_`?_!5?oOooo__OWKcCA!9?_!5?@?@K?W[oWok{sw{W[W[WKKCCEC#116??O#28O_#10???@B@FFMMCKA?C!7?@??__#68A??A??@!5?K`#66!19?__?_??O?OOG]@!7?_G#52!31?_??OO?KD#100_?_??G#142Og_#53?FGCC?G#162?A??@?AC@C?AC@F?E@A?@#138!4?_#161!5?_?[{~N~FHAD$#70??D?C?O__#161@?@?C#88!21?D@#98GCMcKQ#119_#49!26?A#64!6?@?BIksg?O#65!28?@#29?O?G!6?C#67?APA?G?CCC???@A@?@#51???OG?g?O!9?W_AOAO!14?@A@A?Ai@AGSG?CO_#33!8?`gIpISHQCIOICO??O??GOCG_JS@E_La??C?G??COCGAgQc@m?SGOG???_GOcOUhU{#41??M}{]zN]#50?C#48CgK?CG!4?@C!4?AE#103!8?CIA??ACA!4?@A???@!8?CC!4?O#3!9?W?G?GCKAKAA?EA?E#59!10?A?SIO_OCwCI?BABC__?_!9?A??CA#3???@A?BA?E[[_???_#60__!6?G??G?G#67G??GCGC!4?C???@CO#52!23?_?__??_?O?OE@#103O?DOC#68!34?_???O?A#65G#146GCA@#141???WG_?W?O#51A??C#69C!5?G???G??G??GGKMDF??@?_OGCA@???w?Gy|$#66!13?@#105!22?_owpZrk#129@#57!34?ACO??O_?_#94!32?OGo_o#60@?@?_?P???G!7?A??@@@#57!6?G?_?_#14O#36!27?@!4?@?@A?COAwCg#3!5?OGo_?G_?G_!4?gOO_?_O?OGS?[?W?_#65!11?_#67???_#3_O_?_?_??_#46?@#26!8?@!5?_#42@??@??AA@#28!17?_!8?G!4?@#64!4?AC#60!64?C??C?A!7?A???BBB?@@#13!13?@?B@#32C!6?@B#3MGC??G?CC!4?A?@#65!35?_!6?B#64!34?_!5?@#61A@#166!5?CA??_#34@#15A?C#161@?BAB?@EADDAE?B@A@B#162!11?OG?A$#68!78?GOCPB?O_#100!33?C_?O#139G#116A??GC?C?A?A@A@A#88!15?@??@#52!31?A@A?ACGCGCw?_#4!10?O#70???_?_??_?_#116_?_?_O_?go?O?oGo_?o?o?_?o??Go?O?_?_??O!5?_#39!9?C#58?_#44??QEAC?A#36?G?G#61!18?_BSK@!8?B?Gt@??f]{{Xw!4~n~~}Bc@@@#8!44?A#61!10?___?_OOG?G#64!16?___!4?O?O??O??AA?AA?AA?@???Kr!34?AB???o?@#116!36?_?O?CA#37!8?@#50A#16?C#46CG#10G#163@???@A?@A?@#103???_#68?O!4?IG@B!4?C#163_$#136!119?_O#56!6?@#65?O??GG??CC#1!16?A#10G?GWG!66?@#57!57?OG??WOWCA?H?@!4?BC@!100?@#103??_?__OW?GGO#14!13?B???@#5C#1CA#47!6?_#33GC?GO?K!4?G#116!37?_?E?_#67B#69!38?GA#115!12?A#110??G#49@B#48?G#36G??GG#67?_#70?O?OOO!7?OOWCCD?@$#33!120?@#36!10?_??O???G!9?E??CKgOo?_?_{[A@#56!121?ooo___o[oC?_GO?gC_I#138!109?!5_#62!14?C!4?@B@!8?_#88?O?GWGGO_!37?_#28_o#135M#99!57?O#1???__#5O#65?_#28???_?___??_!7?_$#70!131?O???G??CC#32!16?G#36!275?O??O??O??O?`#8?O#51@#6_#29O_?_#139?_#56!42?@#57!64?G#61!14?___?_??Ow$#52!132?_!9?GC??G??COG?O_!7?_?@?@#68!266?_??O#0CCEEC!4?OO!5?C?C?O_#146!37?O$#69!133?_?_OO??GG?G?CCC#11!285?@?A#6A#4!11?C#117?_#69@@!8?_$#66!134?_?_?!4O??WG?G?w?O_!9?C[{KCIFJFBB@B@@?@A?@???@??CCWC?cgo_O_#16!234?A#66_??!4_!4?@?!4@!9?Q@$#68!135?O???G??C?C!5?C#70!282?O#72O#43@#53@#100!14?Oo?o_$#162!137?__??_OO__O!4?_#102!282?A#69?_-#68GoiEyc!6?@@JEKUCED?B@#103?_O?KA!8?OOO???A#68_D#66?vC#163???A_?O#66???@?@A@NQlUkZfRqeriNSKsO!6?@?@BBMJ\n^~NdA?@#36o?QcQF@A#33_??A#94O{n]j?~#146Q#67O@#68sA@#161__??aAe?DL@D?_@_D?QDDO?O_#52?@E?gO???BCG?o#161CO?@HEEA@c_EE?B?IBA@?OAHBCG?C#36oGo\YF@#32HEB?@???C?o#116@!7?A?D?LQKE\rCZcJSFOfKBlAlCHUIiDQEPL_SIODeHY_LOaKqJo#46[_!5?O??GHM???G#65???O#67OGO?OOgWGCOAELCAA@C???AG@IDGGSAO!8?OO_#64!7?A?A@I?AGA?A#103!8?G?QGoHsMUTfRiWuigowCCEA@???CBF}O[[mwOKGKMEEBB@@#98?_o_o_#94?_O#3??@?@@@?@#69?oKEA@@@#49?C?CGAC#52@@AAC?_#32EC#29qI#100}?I_C#28@_KG#36_GG#52K?K???C??AAACBD?H???O#33?AC?@#100ASAAD?Ck#70@?C#161@A?CGG_Woowowo[s{Y}y{yxzphXoG_#64BCCK_#88BBCCG?_!7?ACC#28_#70C!8?@@A?ACGC?G#94@?ACSrr_Mwo#64B??O!8?_Oo$#69vBDG#65??CJ{w_?O!8?_?_OGM@@@!7?_?_?_?OG@#69W~G#161y|~~|^~n|~~}~}|}olQgRcWcHOCO_#68???DiPo?d?ACCGO_O_!8?CgCH#57_G#59_O#4C#28_G!9?i#64GI#66gmUAG@@@!16?IDNiUGo!7?AKG___!10?G???OgO?_?_ogO[@@EH#57_?GA#1o!7?WO#60?O??_C?OM?qO_#28?C?GAKiCBsagC?oCOG?WCgcSQkOm?]GsaW?s_[ok[oKs#32B!8?_??_?Q?CO?G#0!4?C!4?A#103?O???O?WLkAwW?_?_??o?_G?o?_O!6?A#67!16?o_oOo_O___SsGuKuG?HIGG?E#59?@???@#132??_#120???C#28G#67???G#59@?@??@#28_#138??__oKKC?A?@???@@!4?O#103GK#64oOCA@@#161_SOcYOW__O_??_#36HBMC#56w#3o!9?_#68@A?C#51?G?G?_Gc??C?GCgKOgOO_#56?GO#29ACg#94?Cgo__O_o#36A#66A?C??_O_#49?G?G?C#66!7?CCCKOc?oOO_O#116AD?C#94BADKM|BQAUCSC_#0AA#3A#64o???@@`DGASECOcWwo_#103GO!7?AG?_#163?A#52!6?H?G$?KO_!9?A???G?GA?C?@#120?_#146O_gea@oO??!5GC#120C@#52?_#162??@A!6?A#52!10?@???GC!4?@?@_Sg!6?GCG?cAO_??G_O?G?g?H#56oUL#10@#0OF#29HCA#136O#105_SJ#116??C#65u@#69S?@#131GS_SgWcwA{OlQkXaTgO?G_#36!4?@FTmdtFCOOo#162BAFXeSpplsZUxPrkZdcCilNGUGADB?A#56???__GC@!7?INmpt?`?go_??@#67?Gbp_??_??@?B?A!5?A!9?@?C?GAC@A??@#50???B!5?O!4?C#33?@YqcMsG?GD?E?G???@??@#135?o?_O#65?FBF?E?C?A`@?@@eOB@!4?CO???_o`BpVt[DeLfdE?JaIYiQU^?J@!4?P!4?C!5?@#138__oO{}w#64!10?@#120!6?G??AC@??@@#129?[#28CJFUHM?A#68?_wKA#36?@#66AA?@@@B?DBCG[SG#10@A#62@#1I#117@C#139?AC@O_G#65@AG#66bqqU?eMU?BEL?L@??O??_#6A!4?O#28G!7?B?C??_#61o#69G?O#52!7?G?G?C?CA?A?AA?B#70?A?B@@GG#103G?O#136A@@???C?G#84?OG#129O#31@#62@#68K???AGGiqk?_gGO_#146@?AD?@_???@A?O#69ACG?_!4?qq\FN$#64???PDZIo@A@FIKSwo_woOMWMCB@A#28OCG#94?WFJDGCC?A#70?_??E#69!28?HGDOirIG!7?@A?C@!5?oQ\n}BV@A#72??G#3_WK#88osj??@???l@#57??@#162OGSbSGO@XAo?IOL?EOI?gIaD#57!6?A@?A?G?_#131??G?AGa?G?G???G!4?G#52!9?_??OA?D#10OCA#0oWDODIE__#70??CAGY@E@K#61G!4?O#120???O!4?O_G?O??O#146?_?O?g?g?_?_?G#103_#120O#0!6?A#53???_#26CW_G???A@A#54??C#57_??O#28!6?_!4?_??_O!5?G#3!8?C#59!9?A?G??@?c@GDQcGgG__X_OG@@C@D@DG@?@#135!11?@@!5?OWGKA@Fzr{w?___??_oOooWWC?A?B?@#154?C???_O_#67?A?E?B#59G?@#162ow[gkYcIcWSgWwu#69?_o#68o#43@#31?@#99K#88o@??A@IU#67AO#69@O#161@@`pP?@?G_??O???_?O#46A#10CDG#1C#0AK_@!7?A???O#131??E@FCNCE?A??@??@??@#69???C?KDLkW__#28A!4?O#101A!5?G#29@?@@@#67C#65w#36AA#66K[#52?OCOCOGGO_#120@@!5?o#105ACKGo#138@G?_#161@BDJ^^gQ$#70!6?pCA@EGCO_!5?gO??IC#88?_?O?DA#98GCI?BAB?BA#132A#36!37?A?C!9?O#64!13?OC#32???A#1A#100???@#84???S#36!8?@#49?G???A??A?A?A?G??A_D?IO?g!9?@A?@?CO?G?OA?H?_COc??OCA?c??OA?G#8!6?o#33?GCAI?DH?@#36??GGO?g#33!7?@#0!4?@#138?O?G??O???_?_!8?O#25!17?W_!4?G#47O#48_O?P_CG#3A`A`Ma_H?NADCAGD???@#60!45?O!5?@#28??_?_!8?C#132!32?_oO?KAA?ABA??O_o#135_o#61oK#65EB#131!8?_#51A?B??A?@#57???W#165???@#94?spS_O#0@O#64_OC#162???A?@?BO@Op?oO#57?A?@#48?@#32AF??_#88ACjPD?JR?KGOo#163@BB@EGB?B@FDBbAB`?@@#68!5?A?A???GOOo_#77?@!8?A#146???g#103O#5?@#69{qa{eQ???_O#116@@?A?C#136???A#84GG#98?U#137?C#59@???_$#61!9?CWo__!8?_O_O?KA!7?_!4?OO?C#57!37?@??c?A?A?O#8!21?@#98!7?_#51!10?A?C!5?_??O??_D?O?O_?T?oG!6?BC@COG!14?O?@?O?P?C@_?QC`CA#100!5?_O__#3??@???A?A?A@?@@_Aa#88!7?G#103???G??_?S#33???@!7?@?@!4?@A??@?@?K#38?B??CFA@#10?O?O#62?G#116?@@?@A`DQaX_dQ`TaITGaIdOA`?_OD`M@I@UGD?C#146!80?O?GG?C?A???ACGGC#136!32?H?GAC#116??C#60C#49!6?OGO?W?G_?GO?_??_#8??@#3@#116@#31O#117O#139?gOI#136CG?O?_#64C??o#65!28?A???Oo_#61_#138O#119O!8?O#116?G#56??@?@#3!6?@#65@??A???Oo#132!5?@#135??C#154_#68CG?o!8?_$#69!15?@B@B@?@#119!8?OWC!6?C?C?@#64!39?ANZYLCWO__#56!67?AGg?_#69!31?_G#88!9?G?G#4??AC#65!4?@!4?OCI[O_#3!12?@??@?B???@?@@?A?A??A@!4?B#40!7?AHA?@?@#21C#56_?_?@!4?S#120!11?_#61G_!5?O?CgWOOoo??AomIXNk}|~vnjK~Z}VIKWC??AO?O?OGK?K?C?CG?A?C@B?B___O_O_HSVNEIB@@!7?@FABOFMAFB@@@#119???_oOG[G[{??_#77!38?G#131!11?_?_??_???_#47??C??G#5???G#92!7?O#68???@??CG?o#67!30?C?G#146G?_#105?AKkW_oGg#9!6?@#61!10?A??C?G?_!8?C?O$#67!30?@???_#116_!7?G#70!43?og?_#64!69?G???G#28!42?_#29COoo#64!6?C?S#135!21?G!4?O#22!26?CO_G#27cAC?D#88!134?G#36!59?A?A@?EH?Goo_#60!11?A#146G#65GG#98!41?_?_??_#67!21?A#88A?ALG@#66!7?@?CGO?_VlLCA$#138!32?_??O#105B?@?@?@#56!45?O_#72!70?O#70?O#9O#99!45?A#65!33?A?A???@!9?@#41!14?@CO#20O?C???A#14!197?A?C#162!17?@#133!43?O#100@?@?@?A#28!20?@C!11?O#162A??C_$#135!36?OO#64_?_??_oWA#60!116?_#58!111?A@_#42G?GG#139!265?@??A#65!35?G$#84!36?C#154???C#28?G#43!236?_#34?A-#64wcWcxinz\!9?@!8?O?G?K?E?B@!5?@@@A!7?_#162!9?@?_#69???C!7?@A@nV~~[g?_#36O!8?A?CCAHAC?@#28o_?OCI?@O??!4GCE#61@#52_Oo#161FKqp?@???C?G@G??CGOG??_@?G?I@G#52A?TGc!5?@AC??G?O?O_GO?G?C??GA???A#4WG#32GCCA#1?A#28OC?Y?GO[?GAG#68_?O?GG?C?C?C??C?C#65@???AA??A!4?C?CGG#9__#3OOoO??O?___W_WOOO?OIGG!9?O???@E]CTaT?U#103???OOoG`oOhO_??o?O!4?wQl?b@IC~I|KbCHP@#60???C?EWAVCV??@?@#61?__?C#135??_??O!6?_!8?KE?A#120!4?A!6?A@?cGS_C?A#146GC#134?_?_OgCwUguHq??C#119HA#103OCA#68o[A#66uuJC#163???CiOIc?H#66!4?hzc#68BA!9?_?C@#131??_??pKoL_I?GA`?gO#52!5?AHR#56@X#0z_#139C?C!5?C#59_??@#163ow{}}[v}lyDI@A?DA@#66_Wsg]@kR~~m#52??gCO_#65?K#146YA!9?A#64OK!6?A!5?@CAN^|^q}{o#146@??C#105?y?_#119O#135EK_#61_!6?oO$#68FYdZEP#70OCaG_#116?AC!7?A?@??G???A#161___o_wogW_go_!7?@BBFFn^!5~}~^zvmZuN{aKPcO@?O#52???@AO!9?@?@A@?C?@A#0??GK?A@#120?_#146?O???C??@#68??N#66Fo_#163DIVm\q]g{QgoGo?o?_#66???G??`?CoShIo!8?BAL?kGGKG??KBD@FBB?B@@#33!4?O_?OS?I#117C#100CG?MA@A@#70OOG!6?A?A?@??A??A!6?C??C??G#59G#4??O#0??_?__o?_OGO?O?GGGC??CDG#46CO!4?s?C#33PgTG@?BI??A#135!7?_!6?O!7?_#3!24?C?I?AEAAA#116??G#103C__c?Oa`_Xyh~tAw~|YXMNFB@@@#135!10?A@AEBVJABB?@#119ooWGCEA#129!4?@??@shE@#61_G#64WKB@#162?GSqxqSgOiTQlQlrmzS#69CJS#64?T!8?OKA#163_oGSgS#49?`IQA@C@??GB?D?@#36???AgS#3C#33C#88FWp!7?A#68oG?A#66@#131???_#162!7?_??GwS?H???O#69???@~~OA#64BJT#67`#88cW#94B@!5?@#135_G#72_#68o^^_WulsGvCIMIZso_?_G#103??@!9?@AW#69@AEI@#65__?g$#52?@A??C#65???vK!6?@?@@!4?_O?G?CAA?@#162???O_O#52???ID?VGvkOwOo_#68!28?aTmW?_!5?@A@E?AC#3???O!4?F@!5?_?O??G#65GC#69??G#162GRGCgOaG`Q@cQCqCy@m@}gSayOCO_#36!4?FIWg??@?CG?o_O??_?o_?o?WGGCKKEMCAB?B@?@#29?G_O_Vo@?AC?B#33@#36O??C!8?C?C??C???G?G???!4O_o_#135@#146!7?@#103???A?A#4_???_?_?oO#50BG???GB#57o?gD#70A#116_o_G_TA\`ZeLaHFOEHEOfWDiDhItAdWCG!4?C#0!21?C#59??_#28!9?_O?Wc!8?_?O??_?A!11?@??@#103???D#132owWOGEAB@#133!7?ACG#154??oC#138GA#72_#69?_{H@#161_HELjRDD_HQCOKPCA#52?OG#56?Ao#0mG#100AGC#61_O?A#161_OKqHVjMA??WsoooKc?CgQcQ_#68??C#57A_#100?GbG?GC?A#132_#103_O#65GA#161oGCB@@BG@QDyt}\~ytEJfAV`}Bk??O#68??Fxk#70Si#116Q#28@#119d!7?G@#61_G#65B#69__^bH?@QGq@o__#70@??A?@#61???C{#138C!5?@?_#64AKw_o_WWMF$#61!10?R~`@@@B?AA?@@!6?C??@@#49!18?C#64!33?@FgW??@@@ACGGGOG?GA@?@#9A#88ogGovQDGC???CA#131!12?D?@A@CBDI@E@S?RG?C_#64!8?@CO???A??o#49A??A??C??@#60??___??O#57G#0!6?GWKge?b?_?__oOGCEB#4C@#52O!5?G???G?G?G?GO?G?OO???_#2!17?_!7?OO#32?C!7?H`CA_#67???o?G_!4?o?C?EGC???CaCAEC?CW!8?@!4?KWEK[M[]QkwewgogXX[\KNNVjBMR\MLMFA@U?IO!4?C#132!5?_Ww[{}|vj}#154!12?__?GCCA@@#135!11?o??@#131!16?_A#36!6?_|G#60K#33P#28O???WKB#66_?C@#51!6?D_D!7?_QCA?@#64???g#60A#29?O#94?AhaGtxY@@#28B#162_GCA#98!37?{M@?OoKA#138OC#52!5?C?QGd?Hs@S?G#65!4?C@BMw#88@?g#133?@CO#59??@CO#66@@@$#103!12?W?AA?A??A!6?GCA#69O?G?KCCA?A?ACECGIgvG#60!38?@FM!6?O?___?oO#67_#33??O#94??_?GKI?JCA?A@#51!20?@!6?@??EG`IDHE_?O!6?@?C!4?o@O@I?CA??C?@???@#64!5?@#8??@#88OG??F?@G@C#64_??GK?CEACAC?C?A?AC?!5C?GK?G#120@!6?@@#135!11?@#8?___!5?_#10_#25BwC{c#43G#54AG#56?OGUG@#0???G?G#61!17?@AH?DBdQ~[}py?s?Q?boa_P_@!30?LF?ADA@#98!31?oWgSzFhVGsHuJQ#132?GF@#57!27?_#67B#88?B{ay?A#3C#69OGA#66!17?H?@OL]|s#77!6?CA?A??C#138OK#61C#64CA@#105!39?om^F@B#154O?@#67!25?A#28A#94@B~CbE#68???@ACWC]FF@$#135!12?CA??CC!9?C#66?__OOW?W?KCCKSGWooo???BM?KGWO_!8?CGP_HoB\rmZm{}?g#57!6?C!8?C??G??C#100!7?OC!6?C#64_!4?]_#49!20?A@CAS@@QCOA_O#69???_!7?G?O!9?G!9?@?@#5!6?@#99?@#94!4?C#3_O?GC@#56A@B@@?@?B?A???A!6?C!5?G?O?O?O_#5!16?O_#9?_#62?@W#26C@XA#16O#53?C#48AA#28!9?_?cXALaG@OAhEGBGPGO?G_#65AA!9?@A`O???A_B_hR@@@?@?___?ooO?OWPGA@!4?C#138!8?__owOw[cFbB@?GS@~~{{{wW__WGCDEB@@#65!17?_CB#29!28?c#136@#94@?@@#116?@#162OGADA???O!4?JELQRSA?gG_#136!10?OSPIC?A#69??OC#78!41?O?GE#129o?A#103OF#116!26?O#132O!4?AGO$#28!13?O?C???C!5?O!5?@#70!56?A?`AAAEG???O??O#77!13?A???A#36_?__#56!38?BFOCMCG#162?@?EACEA?EDA??A#64O#67?_#3o__oO_ooGOc_#60!11?_#69??__???!4G???G#49_?_O??O?_?_??_#47_??_#33!23?G#99???A#37?_?_#40@#20@#138!32?_#120?O#59!107?O#139!32?S?E#65_G#59?@#92!34?@_??@#146G#135??@#134!43?__#133G?C#120!29?i#98???WG_$#120!13?_G???C?CC???@@?A#65!61?OWC???o?_???_#67!13?_OO#105@@@#57_#60!42?MA_G?_#161??!4@?B#56??_?o??O???O?gCCE_A?A@#1!14?A#66o_OOOoOOWw?OWOG?W_GO?O#28?@!4?@@?A??IDIJEHmDIBDBC@F?B@E@A#144!4?EA#55?A#92!176?@#132!95?_K#129!33?@Ko$#138!13?GSG!6?A?@G#70_#68_oOG?C?A?A@B@B@A???D???O_#116!39?C?w?O?_!6?_?o!8?_!8?A#70!40?`???OO#68!4?_#72!5?O#70!5?O#59!29?A#162__???_??O#51?_?_O_?_?o?__O?_$#146!14?_oWG?GG?CSYAE?@@@#56!60?@???CG?O??O??GKMMDB!9?_?OOO#67!42?_O__#161!50?_?_?_?_!4?_#116!4?@?@A??A?A?C@?A?A@CPE?Q@!4?A?@C?@?@A$#94!16?_O??_?W?C#67!66?_W?O#69!4?@?@A@#136!12?D?A#60?O???G@#3!40?W?O#72!52?@???A#3???@#67?!5@!5?AC?C?A?KGCGC?G??GCSGCGC?E?I?C$#119!17?_O_?W?G#67_#28!67?_?__#70!22?_!5?O#57!41?@#60!56?@??@@@???AA??AA??C?G$#132!18?G!4?A?C?B#3!65?CG#101!23?@#103!118?@??@@?B?A@CAC$#84!18?_#98OO_#88__#57!216?G$#61!240?A-#64z~~^~n^VHQ#103??AwO#94?@^~s|B#67_?@#52oA#161?OcR\s~\z^mvZlqkZtITiDO_?_!6?@FNV^l^|yVyf{B}@nOF_D#68!4?wExu|A#70a#60WO#28LpA@?A?AA??@OG!4?@#161oOwOwgg?_O??O!8?@???G?_!7?afJeBOSG@AC#52??aW?I#57_I#56H!6?B??A??`?O!5?C!8?@?@??A??C?Goo`_#52IOO#49Cg?I?G_?G_@S@?Q?_@h@C@@Q@IUHG#42CABA#14@OAC#1_!7?EM#4@??_#48??_#57?O#46_?G?Cm@AAF#57wAC#116_DAPkA{PaXaPj?CPcXCW?_OI@C?A?GADHB#61!7?`BJFNn\nNm\Z{{u[}S}|AFBB@@@?_!6?O#138???_Sgwk]l]f^f^dQKaDRm\}{WPJFJF@B@@#134_OOWgsw`wexeko~i\BD#132_oKF#103oI#59ssO#68A}}_#162?@?AFLFJCJ]DNDRD#69_ow@!9?OC@#49!9?I?A!4?S?G_??@#69!4?_vwC#70c[#28@O???W?@#69G?@#131??O#162!7?_?WQlShe\RCQ@A?A#69?_ws{^NB#64Ci^D#116O#119o#88@#98LpyCD#68?_WKE@?B??NQtIQsAtC\QBA?@#65?__o\E#146wM!8?@!5?C??A$#68C#70??_!4?_C#28???AG!7?K@#64sA@#162?GWkAJ?AC?@???@#69!4?AD?CAG?T???_?G#49???_#162A?ADGCG#69!5?B?E?d}~~FxE@#64A|\#59C#116GQ???@?@?@?A?@!4?A??@#131?_?O#49OkWa[gCw?_O#69???G#163?BAFDIN}P^iTiTG#131?O#49?GA?aC?OA??C#36Tt^T!8?@@?@??__O#69?O??GG??CCC?C?C?CGG__!8?@A@#161?CO?AQZEPA?GAhKH?CC@??G#47!4?@@?OPAL#46_#32@@A!8?GOo?__#14!4?__o_@OGO#48GB#56xJFAO#0A#28AKAkSCSMSDwIICOa!5?@DG@?C_C?AC#65!7?O_O!7?BB@_@`??@!5?_#28?CGDA?T@BGB?D?DA?@#135!15?@AC#119??___OOGCCABB@#133?CA@??@!4?G#119??C#127_?G_#64?HJl#69@@\w{[G_#161A?CBC@IOAG#52???CA#67?_G!5?C#162_WAO!9?O???A!5?_TIfTA#68???Co!9?oCA#66!20?_yl}w|s}[BA@#52_?{A@#61?GF!7?_#59W?B#72@#52!6?l?TGBGAGA@C@A?@#61???_XB#94Ok@???OSY]so_G?WCW_$#65!5?O_gEhn~_#135C_O#129S_#105?HA#88{B#56_?@#66K~eB?_??_?_OGcQKRcItgOWi[oG__??ABEO_G?O_??_@OB{@}OkwXyY@#52!5?G#65???b#67F!7?@?@#103@???GCC?A@#52!4?A@?C@@A@AE@NQKg???_#66!16?@??GOhtyT_A#60??_#3OK??@@A?A!4?AQG?@@A@!4?@#36?A?B!4AECCWoo_??IYt_#162?GCXDZTHcpI{hUxCrOwQgWoI??_#34??A?GC#17GC#10_OA#62S[#0AB?BDI?oO_#36?CCGCG?CK!7?_C#32C#3_?O`G!6?G#138??a!4?@???_OI!6?A#59!15?AO?PAC??GB?I@A#135!12?aA?___wWgOCA?A@#154!19?OOGGC?A@#129!14?_O[J@#135wE@#65?A#72cO#66??AFAbtWowowo_o_wcy^NB#64_w!6?_OEB#161CB?@?@?AHA??@\a\y@I?SBisWIs#36???@#64JY_!6?_GA@#68!33?o?XC#103?_G#146N#94}#134_?C@#135_WCA#66O_{wO#64!6?_?oGo__g{|{}N]B#103?_C#119_@??_O??@??C??C??C#138@$#61!8?O?O?\#116@#146@Bg#98??A#33??OC#36GK#69o?@#52!19?a?@ESISb^DKogO_#3!29?_!5?@#77O#105_#138???_!4?A#162__?_!5?O??_!8?@BEKLwqtO@m_TiTiTWcXskbDO_#4!9?c#67O!7?C?_cO???CECA@@#72!8?@#8??@@??ACC#6C#51??C?K_?O??_!11?C??A?C?DaDHUSYdGCO#8??_?@!8?C???@#13!6?O#16?GOeC#44D#51O#70??O?_#67GCOo@AHa?_#88?O?cO_#135?@@?@@?G??Oo!7?O#116!35?@#132!10?O_O_W_W_Ylr\ykPa@AdiswSGECAA@#78___O#138!16?O?@#61OJ#68!25?OD!7?_G#131_OCG]^MUHETtnwaT_?iOfGS#52!8?A#56?@#65A{#88BAC#119KA#61WC@#161GA@!5?o?{`QlaDAbSXaK@??DAH@BCHA#70???_O_#65Q#120_#105??OE@#154_??@#69_?GAFk~~o?I_DGD?B?C#28!11?@!9?BA#103!4A?@$#138!14?Ek#154A#0!6?A#3A#68!24?@?@A?GW_?O#88!34?_E|EECECCEo?GB@B#66_?O?G?KCCE?AG??_?oG_@bUcWoO#51!17?GOaLAGDGBg#59???A#0b!8?CCOGC?B#68??O???CC?A?A!7?G#5A!4?@#72?@#131!6?_C_C??C?A_C??AC?OAGC?C#52???__O#36__??@#13G#11A#29__OkCOOO#10??GG?OOXo_OPCNB#42?GO#33!5?G?C?@!8?G#146A#120?@Ag?O#61?A!7?@#146!77?G?C?A@#154!19?A#67??C#36!26?KAB#116o#28G!4?B#65@#163?ggv__ohsog!4?G?D#66?_?@G???_H~^G#3???@#116Ak#94@aB@@#162?o#163o{}~n~~~N~B]LQDgOGA#84!24?A#78G#129?O?B#138A@#162_O#70!15?GO??A?O@KA#98???OEzSHI@C!5?_?_?G$#32!23?G#57!28?@!4?O#146!36?G!9?YECE??@#60C#70A#36!6?@!5?@@A??O?O_#28!34?E@E??K??G!4?C#66___?oOO!5GCGCgKwOoO!9?ALqBa!14?A??_Ao_WO__#15!5?GAK#43O#102G#99?SG?G?C??_#9DABIBABNJAB#43?O#47??_g#65!4?O#60G?_#103?@!4?@???@???ACm^KcoqytmBwWa{lynn~~~M[CO!4?O??_!8?[g[sMk]FYVy\KguCU[FABA@@#98!24?__oWGKCCIFYDWEXQN?TaSiB#56!33?[F#146_oGGA#59G#51!14?C!5?DOA#146!15?gOO?A#66??C#133!45?IA#65??C#88!30?A!6?_???CC#84??G?C$#60!23?O#64!30?AC?G_#94!37?wWGgGwwK#129C#61_O#68_?O?GC#51!6?A@CATICGS?k#29!38?G!7?OG#60A!9?A@@?@!6?@#10??A!4?GEC#48!37?_#31!5?GC??A@G#56??ADW?C?C#20!8?@#67!29?C!4?C??O?O@O!4?O__G_O_?_?_!9?_O_GoQ?w@_?_O?GG@#88!89?OMBB#61W#129!39?C??C#67C#132!49?WC#105!33?wCHACG??GGWOW_O?O$#36!54?@#136!42?_O?O#64!5?_?OGG??EAB@@#33!50?PA??C?C??O#8??@#1@#161???_?_?_Oo__OO_#32!5?@#33@#0@@#62?@#3!45?@#88_??_?_#33!190?A@#94S#103_C#138!39?O?_#103_?A#78!85?A#129_???_#65?@@??@#59@#120A$#101!98?_#59!8?O#69_?O?G?C?A#88!51?_{GSW?Ww_#65A!4?G!4?A#162_O_?OOg_WOo?_#57??C#9?GO?GO#100!45?OoG_#5@@A#0!188?D#138?_#135O#132@!40?G#134!90?C#133@A!5?_???_$#65!108?G?C???@#94!55?_??_#64??@!6?O?GG?C?AA?@@!9?G#1ABA#33!47?B#137!197?C#136!41?@#132!91?__#64!5?@@#135?A$#119!109?@#139!60?O??O$#116!171?A!6?GCGACA?@$#100!171?G??_$#136!171?_#70@??A??@!4?GG?C??A$#77!172?_-#103_?!4_?_OW?GCB#119__??C???_#61K__#69EMKGWwooO?K??A?G?A!8?G@@BNVoG_!4?O?_O!6?G?W???R~~~C@#36G#59_OA#28gA@!9?A#65]@#69sB@#163O_MsLs@i#49!4?I?F?J?HA@#52??AAH?J?c?W?_#49CO?GO?G_C@??C#69??__IA#64O?A#0W?@!8?@#56A#66{A?@@@#49!11?GC@GO_#69???@?AG#56@D@@?CG?__!8?_#70_!5?O_?oGOGS#57?C?@@#0GKCqOHOHGK?C?@@#162_??_!7?_??_?_?_#64GGO_#3@#116?A_DK@ITI_K?b?A_D?CP?O??O#61??C???G?SG?g?wcwsoksLqcp{l{b[ns|g|]|NRMbZAOB#138??_w_Ws~}^nVfSnUdIPUSKNDJDB?@#133_??_G#78!7?AJShTPdP?BABE?_#133C#129CN@_#135CN\O_#65?O??__?_#162???@#66A?A?@??@@@#65???_G!6?A#163_wy~z^}^!6~}{Ow__O_OGOg#66??_s~L#68?_IG!7?oA#161c???ogswu\]~E|v{MDF@#69o_ww[\i]gf~vn\F#64oO?I@#132_!6?K#135C#59S#68[b!6?_oWPJOePA?@#65???__wO?B@!8?W???O?O#120G#105@@ABL$#61Oo?OOWWOGCMEB#94_?O_[RBFK#67A#65AO#68JOOoo_#52!6?_O_!4?_GOK_@Gc?G_G?gHaTBZEMGO?_??O#68!13?z]V#70OG@#3C#88s]F#105__?{wj#98R#146A@#61@#68IJ#161WwM^pJqHsTM|jntf_wocqgokO_!8?BD?ARb@lABP_C?@#51??C#52O?D#56?_C@#28K!8?u#67A@#36B@#163g__o_ggOcG?O#66!5?O?CBVz~mWO!9?BBEOKMKK?GLCAC?AFBBB@@?@#4???CKOA#28?OGA???C?E?@#66_o?oO!7?_!6?OO_#68O_#60N???O#67A@#135G?A???KCCC?_G?g?A?C_o?o_?_#67!6?GAHABAA@A@I?@A@CB#65?JADA!8?@#135__oO?^eJ?@#132_OgWjO`YtmhjroySYKNEFFB@#89!10?BAIEYM}{{ww_#98CjZO#154]#132^r#146_#127AC?O#64?OIOO_?!4_?__?_?_?O_?{oUF@???_#162?_[F#131C?C_@_!6?@AdATICI?A#69!7?o~^P!9?K#163?~}~NVJFHA@?H??@#66?ow{M]FFBAD@FG?G#70!4?_yDE#65@#103@!6?G@#64b#66[_UxYN@#52?GcA?@#70?a???@_??G?JC#146__?K!5?A!7?CCOo$#65ML]LNEFMFB@@#116G?A!7?D#28O#64FSg_#161?A?AG?BKBKbTIDMPFoLQHaCGc#68!5?ET?GC??_#161?FHfQLu@}?\_G#64!7?__NB#0_?@#94_gZTi?@Ok#84_#103C#70_#64s#66?c#162E`!5?A?_??O?GGFCWCSA??C!5?@BBCAILGSkQP[i]jZGC#36???_WnN@#88_oYFA@???A@#65{#64S#162?cOAG?DP?AP_O`W_GqGERhWgC#36??@KoK!4?@ACGO!6_?O??OGGK?K?CKC?E`R`b__!9?_?O??GCCC?A#42CA?@?B???D?@#36?@A_#65COsm@B?___#120???O@?_H??@??OG#59!20?GOGOGOG?O?W_O??O#127!8?O#146!17?G#119!8?_?__?O?G?CB@!22?__#138?GO__#59FENGO#68NNWQOO?OoOOo?w?{gW{AL@!6?oKB#161?@!12?@IDITjSipkUu~TJ?A#52??cA#65_!5?_G#162oZ!8?__?oAGApI?A@@#68??__O_O!4?_oLBC#61o?A!8?A#69??@_?_o{^FB?c#61!12?cWKE!8?_S{{k??_#116?_#98@?AO$#64@A@A?@?@#138??__OGKA#88C@#105?So#56??@#70G#66?@@BDFDFNkroRKGtqpkWFa`U[rRRu]so!6?@?DMWEWlaH}@vaFv~~k#65!7?_?A#29G#136?OCG?BAC#119?P#116G#131!10?AG?PAS??O#66O??@?@KQlW|so_@ACCGWo__???_???Ocqz~ZN@#8??O#3wCB#29C#94G|AI@~|#146G#68?g#161?OCO?A??@K?FMCFVdDBGcAC#52!4?C_?_!5?@???W??OOO???G??C???C???A?A#3!4?GOO_!7?G???A#4A#32A#51GGkGSGOCG?[?GSGCI#57@B#70J?IPG#146??AA#103O??VBoGWo[OoO?iDjrNNzJ^~^v~jv~VvDQDGD?A_C@EA?A!5?A?`Aokp[clm[^NNF?@#129!19?_!4?G#134O?Ogo[om^~\sgS_g?_@?@C@^ZO_#61!6?GWH?__#69??FLNN^MLNLN]F^AAEB@A#70G?A#67A??GA#66O#49!21?@D?A@#64!9?dR!7?@#131??@#52!26?O??OAGAK@#67?O!8?O_#161??G?ED?A#64???kOmXK|~}}^~^VF?B#116O??@#154`WGCA@#94!6?@A?EG[on$#120!6?_?__OO?C?@!6?G_#64!36?Is_w#49@A@#56!22?C#67W@#92!4?A#101T?C#154??K#120_#51!22?A???@A@#36?A?C??_?_#131?@?C?A?C#68!10?S_#4???A#137?_#136O?C@c#51!6?GAC???A!11?_!6?O_!9?B?K?IO?@?GA?@C@A!5?A?@#32!6?C??@#100C!4?@A@#69__??O#49?_O?OG_G?O??g?G?G#52?S#56C#61!4?sKwOO?__??A#138?@?A#146??A#65!24?@#154!51?OOWG?CEB#103!30?B?__#72FD#52!16?C#61!5?OK!4?C@#162!23?C@?H?I#70!6?OKF#146OGbD#103M@#116!40?gKA#84O#98yo{\#154A#138B#72G#162??UH#103!22?_?GB!4?_?OCIAAA[gO_#84?C$#146!12?_OOK@!4?BO#57!41?o?_#60!23?C#116O#135!11?O#69!31?L?y?G?O#77!26?_#101?GcA#52!7?@#131GUKYCU`IO`I_GQ?o#68!8?A?CO#70G??A#28_#33_#8O#47C#49?@C???A?@#68O!5?G#56!5?G?GK?WQA_b!9?GG?C#33@#10?AA@?@#131O?_?O?OO?O#28!12?C@GTG_?OA?@GEhED_SG??C#98!70?_?o_W_SMbNP_?_#103!50?_???o?@#61!38?GA???OA#88!41?O#146GB!5?_#132!29?OO??_OGC?@#88!5?A?G??_$#135!14?@#129?WA#84?G#68!115?AOCGO_#105!29?oOW#64!33?@B?O???A#162!5?@@?A?BC?A??@@#33!14?C?I?`#31__#8__O!7?@#47C???A!9?BC#88!11?C!9?A#116!145?O@#28C?A#116!39?oD#119_?A#59?C#94!42?_?D@?__#119!31?_o[E??@!8?@AG$#132!16?A#98_g_Go#64!116?O#60!69?AAsA#57@?CGO#161???A??@C?A?@A#10!16?@#1??@???O#5O!7?@#57?@#161O__?_O??_??_?_#120!168?_K#135_#132A#59OG#94!39?_O#28?_@#119!43?Ok#134?CB#129AP#135!31?C!5?WG!5?AC$#72!209?_#3IkGGO#131!6?@#64??__???OOO??GG?_c_?_A#88!7?GCCE?@?B?A#68?G#14!4?A?@#17AA?@?@?@A#138!170?oZKD#120!40?GA!46?C#105??I#120!34?GAA#98@BB#67_!6?_$!211?O#0cO?_#69!8?O???G!8?A#29!12?CQI?A???A#60C#48!6?@#21?C#34CGCKACAAC#146!172?@#132!42?C[G#138!86?_??C?@???@$#116!212?O#72!14?O#60!4_o_Oo?O?O??W#80!9?@#52???_?OO??G#16!7?@??@#138!219?@?O#129!86?C#28O??A?@@@???O$#64!256?O??G#30!12?A?A#59!310?_?_$#67!258?C-#120KMYgABB?@!9?_!4?OM#116G#64@BN]yWo{_O_!4?_#161?@?CA??C!4?A#64!7?A@e@uwq???o_!6?O?o_O[NB#28gGA!8?__#103_O#64_GV#66oA#163_GrKrlQhGO#49!6?_??_#66!5?`Gdneuo_???@A@B?DELLMFFB@#57O??@#88_ok]F@__?O#146O?G#60_#64o#68~#66m#162tjcHOC`OdO_Q@IOACoOmphsraO#36?C?RFM_!8?@??__O?G???C!4?@!8?@@?EGGW_??BE@?O_!8?_?_O?O?K?C?E?A#65??@@?BAEKMUoGW!9?_?E!8?GCG#67!9?A#65??KCGA!4E#127!4?GAO?_A#138_owk}LNEBF@B?@#119__o!4O?WCK?A@?@#78!20?NtI???_GDIA#129?ogSDAA#138??oxHMC!8?C?C_#94GwwGWkOsK{ko_#59@#68HO_#131QDNFFFBE??_A_?_#49_#52!4?_?__?_?O?O#65_#116_??O?@!4?@#56A!6?_#69OO??GKCWWKW[{]zm~L]D}WTj?@?@#103_??GYDG??AA?CO!4?C?O#52!4?A??OGC#61???g?_oGLFH!5?_?CA???GBiFrerr}}cO#88@A$#135AOd#146CGK?E?@@!5?_???O?GJ#65???{o_#52?@GAT?TGOCqCPC?Q??Ca?I_AG`OEgJ_NPM?_?@?BCK?S?o?O???g?O#70_??G#67CA#88ON!5?_!5?@#68oG@#161lVqKqKQkUtlYg~z^~^~}^zulzdIdGOG#68@?DA`???CG!6?O?o?K?B#56CA#67?@!7?_?O#61G#69??P#161G!8?ADGcPexWKbOMCI?G#69??_o?_#64`W#3V???@?AAA_C?A?B???@#161_OOWgOGG?sgcWO#51?_?o?_!7?A?C!4?c?AO?O!7?A#61!8?@?!5@Mve[GO???___?c_mAmck{w{oWo~n|~LRfbqd^eBpOP??XHMDj@dWA@#135?[WEFA@A?@#154?__o_oOO??GGKKCAAA#129@?@!4?A?_!6?_#89!9?d~~~^FA#98_oxMC?A#135!6?opIE#61omEMMIA!5?A?A??@!6?O#69E#161CG#66_#161!12?_??___gA|Q^FM@#70?_???O?CB#28A!4?E#141G_#66@??K??GGKGEA?CC?EBB@CP?o_o?_#64??O?_WCB@@__OwW{Wo!6?@ACGOo?gO_`ox|Z^VD?@#103oqwE@@???OKA@!8?@??@?Wgo_$p@#138?Q`_?@#98?O{[[Q]ZJLKM?F#61?_@b}#68??@DAA?GkGogoGO?_!18?gO?W@?CCG???_??_?__OKNMB#61oO#60@#146_K!5?_?_#101?@#138O#65_OB#69_M#162OGD?@??@??A?D!6?@?CHQCYSQQ#69?@?G???A!4?K?GO??_G?A?@#3_gCF!7?_??_#103C#163???ASZunz]nYlYdW_#66???@!5?KSnzYN#68_W#57O#56EG!5?@??@?P?@!5?@#131?_?O_?_O?O??_?O#9?@#64A??O???GGO_#161ICO?L@KCA???@#57_#70!17?A#59?G#103@??argddfDA]^ZGPHPRRAFBFbF?O??OkW[LW_P_?__oO?oowS{Qdl}^`FH?@#132?oow{W]K^M!4NFFBBB@@@#119!37?PJG??A#103!5?pxN!4?CG!5A?A?AA!6?E#162??JO#66!24?OOONFB@#103?_??G#119G?GB#29o#117o#62C#36B!4?O!8?_#52!11?G?CiSiM?E#61?ogc@??CC???w!8?O#59!13?_#70@#138!4?o?A??o??@#67?@#135!11?@A??O$#61?_#116?@#132S?_?A!8?_???O#70!9?c#69D@ABAFDHCGkW{gso_?_???CAgWVs^oC_!4?@?B?KGW?oB}^MFB?@#65_?CA@#94_O~vdbOS?GCC#120GA#61C#131!11?A?dQ?C_#52!14?OGEWO?@?@ACAGO__OGOK?A#70_#4O#59O#0GG#100O??O#105?CBAB#65_?ON#131!14?ACHCb?C#52!10?K#4??@!5?@???AA#60A??C@?A#52!8?@!8?A??O_!5?CG!8?_???O??G#120!21?@#135@CDYAWA\@??@!5?@!22?__#28!14?O#98!13?!4_O_gOKsMqCiOaPw\YtiToiXExA{OC#154!15?_OLDKA#65!7?Px@`@Po_!9?@!6?_#163?FLyowww{x~~^|!7^V\AL?G#162@M#68?OGKEB#146_?O?@??@#142G#63_#110O?_#162A?G???@E?@@AA?@#70!14?_W?GCA!7?_#67A!5?@#138G?_#132!21?o?KoGEB#65O?U`fPO?!4GK#116??@C#146AC$#154!5?OS_!5?@?_O?OO#105@#103__?oS#66!12?AA@BAAB@HNZX^t^|p[F`!6?O!6?!4BMLK??@#116!6?_?C@#119_!6?OO?G@#67C#36!35?@AcOG!8?O_?_?[`M#60??@#33?A#28@!4?_??O?A#51!20?A???A??@??@#65!5?_#67S!8?O?CE???@#60!19?@???G??OO#70_#162A@JL\AWBGDJE@A@@@???@#116!19?A?G?G#67!4?O?O#133!60?_??_OGoKqTdXc??_GOiDC?W?_#132!19?_owp|NEE#64!6?oOo_@?@?!5@!8?MO_#36!25?_#64?__??OWKNB#94_owPKA#65?@#53Go#161@@?FMFFA@@?A@@B#65!17?_OGCA?O!4?CK!6?ACG?_!9?__?Y]MF#120???GK#154oHE#135G#59oG??GOCkDoCOC#120!4?A?G$#119!6?GW{mAb___??O??CGFC#57!40?HAGC#161!6?C@A?@#135!9?O#120O#105???GAKFJBFA@#154C#116G#64!37?GKScKO?Oo_!6?_?OKA#116O#94??_oiLZ[LKN#120C@#49!21?G@?O@???C#116!7?@!7?O!5?A#68G?C?AA?@?@!9?@#56??ACKuFK#67?_#49!4?A?O!4?C@A??A??@#138!23?O?O#59!6?c?G!9?A?a!6?GOIFKHH??@A?A#134!29?O?_???G?ICGFADAD?IPE`E\Bnz~oIO!4?owTLE@A#116!18?C??_!7?A?@#138??LO#69!32?GCA@#88??_?CE?oK#99?O#43C#42C?O#163@?@#64!5_#135!25?OO??C#146@?@@?AC_?A#69??@@?BFLTEF?A#28!12?C#119o?E@@#64?_{gUGa?O#138!8?@C#119@$#94!12?BC@???@?G#36!44?C#70?AGwoo#98!22?WOG?C?GAA#57!38?@A_I?G#161?@!4?@#139!12?G#136CQ#154!5?B#33!38?G?A!7?K#65G??CB???@#2!17?@#4AA@G#131!8?A?A?@#68??_!6?G?c_?tIEKKSGg?O#28!141?GGO#146Sc?CC???A?AAA#135@_#61!32?_???OGC#84??C#51!5?ACG#46_#57_#52O#68??OOOow?_o__?_???A@A@B??DOF?B!5?_?_#98!4?@??o_#135?G?_#116!20?A#88?@#28!21?G$#78!13?G?C??A?A#84!77?G#132?O#70!43?O#56o_o_#162A@?A#0!60?_@@AA?CCGG#8?@#66_?oO?G?G?C??A??!5AiKWGo!7?ADL?o?__?oOGOGKKMCEBE@A!5?_?_#70!146?@?@!5?@#101?G#120A@@#67?G#98!42?A#14!5?G#49AC#47O#120!34?CAA#138A#119@???@AYG?GO_#68AFKGq?hWMDAAC#146!11?A$#133!16?C??@#49!131?A#28!61?As???C?_C#64@???G?CC???@@#72!15?C#0@#5@#3?oo_#64!15?_O_ooowWW[GDHAAACCW_o_#67!140?O#88GGOC??C?C#136!48?_#16!6?O#37_#131A#116!35?G!7?_#94DuCO_$#134!17?A#138?__?O#88!191?I{KWGwG#59?_#72?O#70G!4?AA#120!202?C#136!5?o??G?O?O#17!51?G#132!38?@!4?@!5?C?O$#94!216?__o#10@#69!6?O??G??CCAAA?A!6@?AC?C!6?@???O?_!5?_!6?G!4?@b?ooOoGoO_#105!150?_O_?_#28!94?A!4?G??@A$#139!216?O#29C??O#51!5?_#49?_?_!6?O?G#129!307?@#84G$#162!229?oOW_G_CKcSeGCWcKS?_-#65uMC??_!9?owWSKQC!4AUF^\`@???O_#52!4?@G@CAOC`[?i?iPgCHOAk@?I?G?C#70A@?@@?B?EC?CA@#94_!6?A#69_?OOGGG!4CKKKNF?GO?O#131!5?I?IC@?c?CGO_GAG#52!8?@DeQK#56@Nl??@@A??CCA@#67?C!9?@#3@@#36A??@A!4?G#163?@A@DBALB?A@??@#66???oOpw~r#68OwEC#60_C!9?A#36V@#162_?i?O@?c@?aSaLOClWiQO@t?A#36??@BAwo!5?@B?AB`B@#162_oO?WGCC?EQCA?wChDyagOO#64?@M]c!7?PrRBRDme}mM{MgWO?CO#135!6?AQS?D_?HFAEECCKEEB@@??@#134?!4_oOgWwwkGU?CA?@??@???C??G@??G!4?C???IO@QcH}sjO_owEF@#154?w{kJD#138??o}~NA#103?oV~|g!8?@G#94AF~ySAHAPIPLM#61_@#68_JE#161pBYYSYQB?`w_goOOG#57??A?@#70@#116@?AA???_#105ACI|ss?wowO_#88GOo!4?o__O!4?OO?O!4?_#116O#52C@A@!5?_??G?C??@#61??y}#138g!8?CG!8?_?O_O?CA?_?C@#65WQoYTQ\AI@@@#103_Sgs{{w|nF$#61@`Q{{!9?oGCCAB?B!5@??A]}C[W#69!10?H@BABBCUCEFBAD@??@?@A#65!4?Rma!7?CCA@@??_O??GCC??AA?@@@#52!5?_#163?A@?A@C@#162???I?G??O?_@___?o`O_OG#69?__?_#68_#57A#64o!5?@AB@A@!5?_OOG?C?AA?@?@#162_?O?_@BFELMY{|Q{jSiPiO|SwFmMF#69?KNF@#70_G@!9?c#52G#66}\_#131SiLQlB[bLaLaChQDODiOACO#52???G#56@A!8?CCC?CAA#70A#163_o_oowOWK_O#49!4?A?CO#68?@FMP_#61?H[A?AR{GKGGG!7?@@`B?B@rP~ndO@#138?iqqRRqoxwgWW_?OKEA@B?@#78!5?_O_#133!7?Wd[I`GSA@Q@AD?e@?DK@G?Q?D?Dg#78!4?JSnICA@#129_OwE@A#135!7?o|~Ng#61?AU~OK_Og?HKo#88@G#105?Dj|u|atAA#138OHA#65E#162__?O??_???_???O??_?O#64??AECCSgwo#120@#119A#94DLod?@ACCKCGWo_!8?___o!6?_#66??B@@!5?_#60?C#135!12?@#146BG!5?@???O_#68@#67???_#135??o!5?@??_#64??_KBdIDAD?A#116!9?A#135Ow$#64G?G#135?@?C[!4?_?C#64??_gokWc[{{gW_???@?AKE}}|{mtOgO_O?_!9?O?gO_G`Ih|mkO?@?B@AFB@A@!5?_oO?G?CC??AA?AA#162!4?A#49!11?@IFGRITNUTTRKHU?SA#36!5?LR{!7?@#100_#28O?O?gQA@G?CAA?@#57!4?@#51?G?CI?a??_#161!5?_?OhSkTeAjFG#52!5?_#64?OXB#28_WB!6?B#56@#57_#51?A@?CA#163_?O_?O?O?G??_#51???C?IcJgMsg!9?@#52@#57?A??@#66!4?C!4?!5@???@@A?go__#65@Zua!4?Ba??OCG@W@@pA_UC?kwGG#28!9?G?K?GC#146??_!5?_OG!6?A#129!11?_??_?S?gS?cW_IOGS_?oCQg?iO#89!9?TJD#119??C??A@#59!14?@?m`CgO{S#67A#146@Go#101!6?G?C#103?_O#64_XC#66OAGCCI?CG???G?GgKSk[#68?c_WogO#103@CGO#84A#88_#98JO?GGW???_#63@?@#31@#28_?O!7?C!5?O!4?_#61WooOKKEAA@#120!12?S#94A]!7?O_#88?_#120!9?_#132OG?@OKB$#59?O_#120??@!4?_?OG#70!7?_W_???_?_??ABCBW@??AO?G#66!7?@@@#61!18?[mKKG??G??CAA@!4?G???AA#116?@#67@#51!24?C?A?GAGCA#60!14?A??A!6?C?B@#65__O?G?C?A#131!22?C??A?G!4?@#36!6?GA#3O?@#94?O{FT}o#116O#65W#49???]@O_!10?A??@!4?_@cAO#47?O#10CG#0AxO?C??G_O#8C#68_O?G?C?A?@@@#69!10?@FEG#52O#103!4?@|FTk?C#59?CC?OO??O?@O?AkQ?eCm#132!10?_!4?O?_OwgoWKEK]EFBB@#119!28?G!4?C#132!21?Osy~~N@#64!10?@?Q@F#28???CO#136!7?C?G#154?@#119A#28C#69?OHKC#131??@??_?O#47?A?A?A#69_?_{WW_G#61B@?G_#132?G#129O#136??AA#100@AAA?C?CGG?O??GOW?GO?G??O__?_#103???_!6?@@#119!14?d_???ACGO_#61C?!4OWKMDBBB!6?_gA@??_G_ot{{y]jVJBBF$#103??@BA]w_!5?oGCAA??@#116!11?o??_#103!37?OO???G?GG?C???OGG?C?A??@#8!49?O#0_wCGC?G!6?@#138G#69?__O!7?A?A??@!5?__#57!22?_#59?CA#88_{NB??@N#120G#161!6?@?KQGA[?H?ObO??Cg?IGPG#57!4?C#4@#3BGC?AACK?Gg#69??_O?GGC?AAA#135!22?_g#68!4?!4_#103!10?@!5?OYn{l@L?K?C??@@BBA@@#119??_O??O?GK?EAA!4@#65!66?QHE?Bao#135A#116_#129!9?_o?C#59O#163!4?_``?`#52??AAA@?DA@@B?@#56@?A#65A?CC?O_#139!7?@!4?C#166@#1??@#62@#0`??A?A???C?GG???O#65!4?GKgQA@C??@!5?_?_yD#132??O!6?CG#59A!7?A@AC!9?OC_K??_?G??AC@$#28!6?A!8?A??@#103!13?G__#135!39?__?O#146_Ooo?Ww???D@AA?@#3!53?Z??A?CC?G?C#103??O#61??G?C?A#131_??_o?gO__#67!33?GA!7?C#66!20?A??D_???OT_#8???C#28@?ECG#9A?A#116O#161!10?G?K??AkEABQGCGCg#28!9?O#72!8?A?@#127!31?@#154!5?__??GC??@@@#138!78?C#67!13?G#49!8?ChSLC@S@?C#36?A?B#146!8?@ADO#78!6?_#29@@AAA?EC[mC}AKCKC?G?G!6?_#98!26?@[BBCW#103@ACGG?___?oOGG[KFB!4?WE@$#138!6?@Bwo?_GCA??@#68!16?@?@?@A@?AeRcMg[?{OgOgOwsikRUmTuStQ?O!7?@#119!5?_!6?@@@#161__OoOooOoO__?ADNTMN\}z}t~toooQg`_!5?_A?`@?@#33!9?CA#88wogoO_o_gO_CEBFA?@#52G?C?A??A??B?C??_O#0!27?OC#136?_?gi#146??_#33!35?C??G#64@!6?OGKDDBB@@#131?___W?wCwCo?O#116!11?G#94!49?O?GO??C?C?A#51!103?OGC?E?@#138!12?ACG#142!10?@!4?A#121??G?C#56@@!5?ACC#67G???OO#105!26?`Wg_?_#65@???K?GDB#146!6?_OC?@$#132!8?EMWSCA?@@#120!58?O_?O???G?CwWH#154A#66??_??O_?gGGg?_OOw|O_g_o_!7?C?@!8?PCGMIlu~]YX#70!6?@!5?A??A#120??G#94???@#66_OOGGK!6CGcZ[sWGOO_#77!29?O#1!39?O#88AGOOOo_#98!87?__?OOWGGCCEEQvh~zDYaT]ajTihYdQsHubQrMrh@~OdiUlZu@!6?wWiF@#117!86?@?A??A!8?C#60AA#68@@?BAE?C?C?A@?__?Oo?GeAC?A@#134!8?A??G#28?A???O!8?_??G!4?O$#119!8?@?!4@#28!62?O!9?@???C!4?@#67!52?C#116?C?G?G?G?C??O#100???@#68G?C!7?@#5!79?_#29?__?G?O#99!240?A#3@!5?A!6?G#128!31?C??_#64??@BFANFIC@#119!6?_o??@$#154!9?@EAA?@#88!62?_?_??O???CA#68???O?G??C???A?@B#29!47?O#52???@#146?O?G??C!4?@#67!90?@#60@!6?WGC??A#4!234?@#33?A?A???C!4?O#78!30?_?O#154!19?G}MA$#146!11?G#116!65?G!7?A#67_#132C#162!4?_#161!73?_?OO_W?W??G#100!83?_#139_#70!247?@!9?G?GGG?C?E@@!4?_O?G?KAAOD#133!7?C??O_$#138!85?CA??C??A?@#49!72?_?_O?G_O?O?O#64!331?!4@?AACC?G!8?OWGG]UHtpyp[{N#95!8?O#154@A$#136!510?O???_?_$#126!511?G#101?_#77?_#69@?@@FBCAA!6?_!5?A@$#139!513?O-#61X^n^IC!8?tf!7?G!8?Avw`AAEGOO_#68???@C?ATAIVHDZIcJQLjXcXj??A#65_}K#103zA#146YB!6?@#69_??A@#66!15?AcAG?fFKwp@EKKAO?_#131???@#163?G??_#66!5?is~BD#68_{s#64GC#3uC#29C#136_OO?KQ#146?c#61wA@#161o{KADA??@???@??@@AG?IC_G_!7?@??@ACA@?C#36?__{JA@#100_?OD#136OJ?@#67???G#36_P#49HuHPC#163??`?C@I@OC?P_GC#49??A??OA#52?gR#47C#10OB#0oN@#29A???@#67F#68C@#162}RD?A#49!8?A!7?A?@I_DgO#68@@!9?GOGYMWgYjHrPa#59?cIT#103PGy{_P#132?_oooWSINFB@@@#78!5?gS?a@@!4?A?C_#98FHFjE[M\lQHOHaYCyDydI\i\i\ixiUYlJTH!4?o[I@#154o[m^bMP#138???r~~sxA#103??F~~}o!9?beo!7?G#68oGA@#161KQfSgvxN]U@BBAA@#68??G?gULgX_QnBQ@#61?AI#146yC#98_??SH_?GO]D#132WA#61{!8?CCKGG?O_!7?G??_!11?ogwM@#146YB!8?O!5?@?A???@???O__#59A?CCO#135!8?_O_W_w{YLUnF$#103__O_Tz^R!5?~A#64?Om|~ivjCzeLrEXeW_!6?@FNN^^~}yZ|?gpgcy_cZsLaOCZeS~~|^@#61pC#28T!7?GE@#66wKCA@#69!18?B?GREMy_ooO_#162?G?O_?@@G_Oa_\b\?I#69?{y^A@#57S#56H#67GA#33@#137@??@#103!4?C#64{A@#131?oCy|}^snV|e\fWeSbS`G?O#69@J?HBE!4?GwO?_?__s?SA#56oWKA#3@!8?_O#56M#52m#51aGQC#162@G@GC@?@A?aDAPuIdTkTPnT@#36?_wM#56S!9?A#66I@#163kyz|v|z}|}xi{woGO_#51?G?Ag?LQTm[w?O#60CI!6?_#70_#52???O_?_#65??CG?@#67??@C@#138Ogl]CNNFB@#98__oOWwc[cIBB@@#133!14?CG???A?O???`??_#119???_!5?C#133O??O_#78??_]@@#119??_?C#132_O_[pm~~~K#135??JE|~~w#61??@N~|q_PhOh~WW!9?CA#64@#66C!8?__wWGC?EBBA@#52O?OO?G?OC#65???h#103_#28C#94R^!9?A#138_C#103A!8?A!4?G!8?C??_#52A?G@#103!11?OA#132C!7?_??O!9?@!4?C@#103IO__?_?go{g{y{\m^e^FBDA@$#65E#67???_#28?_#135kF#120_?_G#65?GWnPA?TGCPCGqGXeXf[G??@???g???_#52??@???D??Q?CP??_??A#59!8?A#138?_D#94_zSi~N@#56gC#61@#162o??OA@#52!20?@?CW??_!23?@#36IbA#59@#28O?A!6?GA#135@#69w?A@#163w#49??@?A??A?AWEWgSGCoC?G_#68A???G?gOo?_#52!7?G@C#60_?@#28CB!5?__OA#57O#66?S@#161_g_QsAoYyc[lWygK@oIgP_C#32!6?_#8GB#88o]C@ACK#103w#59@#69s#131???C?GAC@A@ET@FDVdIVtChAS?G#52?@?A_#10go#61@@?A#28_#69?@G@?P@B@O#61!6?Simu@A#135NEQ@J#154??__???CCA??@#89!4?Gs[}}!4~|gO#129!5?@A@A?LALQLCYDYDYtATaTaTADgDA!8?_O{JB@#59!21?AKO_QhS#28??@#138C_!4?_OCB#162_owB@??@??o?HCCC@@#64!8?ACQk?olmv#116?S#132@#88g#105?n^@a@T?@@#129OB#88@#28@!5?@@!4?C???O_!4?@#67B#64DDGO_om~}]jDI@E?@#116?_C#119_G!6?OO!5?_aA?A?AA@AD@#138U@_#65@ZXLB?@#138!12?_ogOw$!8?wNADv#70!9?Oa?P?C_!8?@@E??O_!4?c?iOC!8?OC_#135!10?G#88_K!4?oE#60O#64GCB#161wKEhATbTB}d^zn~~~|Z|v{Wo_???@BBLN~V~n^}}uv^N\^a[aT@#65!7?o#88?_y[a!5?R#138@#68?CA@#40!5?_GO_?O#51!4?@?_?@?_S#36?@QKgS#64!16?C#0oOA#94?gkS\WJF#70?_#60@#131???CAYdISJ_CO_A@?CA?@OA?I#51_?G?RKB#57@#3_#1C#100?_H#77O#136C#137@O#64?o#162!16?A?IPgA`C?`A_#36??A?CCG!6?O!4?A???A#146!19?GC#129???_#134_?W_Ws{SaI@!7?Vj^wuwOo`o_O_cacO?`!13?@_?Siu^`isNBDA#65!25?@NECAA?C#146?BC_!4?G@#65G#69OKA#163okWjUGE#131?@?A#69_ow{w{{t}FhaDaD@#70?G?OGS#135@#78???O?iCSGCE#119_g#154C#135w#65?_!8?o??Oo_!8?A?O!6?_Swt}xFEF@#120w#88@#94sF!8?BT?qQ!8?Gw#61??CLW?_A{VMBVBDBA@?@$#132!9?O\Y#103!20?@?FS#120O?O_#137!36?O#119CA@#67??_?A#65A#68?@#163ogS{i[i{@Y_CO#0!46?@#94??CLq@H~#66!4?C??@#34!6?G?G_?_#162???BO?XC!9?A@?@@GCB?@#68!4?@#70A#67C#88wkQB_a_OWN#116C#66!26?I?_}C#33!4?G#94??omxy_#72?G#161!16?G_?C??WOS#66?O?A?_#47CW#4??OO#3O???_#66@?@#119!26?oOWGICEBAB@#89!52?SI#64!31?G?C#135!4?G!6?_#72_?C#134!40?_?OA?aG#64!5?Osks_{_[oGo?_#132C#105??AACO#137@#154G#135?O#70G??O???@@?A???GO#98!5?w@??qPGLKGGGCGCOc!4CMtA_#64???E?A$#154!10?_#116!24?IkSgO?_#98!36?GC#0??O#36?O#116!62?G#100??H??A#162!6?G?A#66!18?AAB?S_?O?BFEDEEKyTy[ZI^B#116!4?G#105!6?EC#146??@#28!42?A#64!34?AA?C??LL\AEQc_cCCCSKmXvZ_#132!118?A!4?O?A#67?@#128!43?G?O_#70!5?CA??K?K_G#119@@???CC???_?OO_#68ABGG?O#105!16?ICB???A@?ACG?WCWoWwOO$#135!37?G#133!40?_#105O#101!71?_?oC#52!29?A?[c_@?GOG#65!104?@@B?MKOQ?C#72?C#94!131?@BC??A@#59O#95!46?A@#60!6?J#3@?@??A#146@@?A!4?G?_???C#116??O#69CDFF#78!17?sIWDA??AC!5?G?G_?_$#138!37?_#154!40?@#139!72?A#105C?_#57!34?O?_#8!109?_#1_#0__?_#120!134?W#119KooGGA#133!47?_#68!8?GOGO?O#120??A!4?G#84?C#98?GCO__#134!24?ocGKF#138__??_#133?C?_!5?_$#77!152?G#64!37?_O?_#59!109?B??A#154!135?O!4?C#56!57?BAA#116A?A#88???A!4?O??@A#133!26?@#154??_!5?O!6?A!5?I$#98!443?GKU@#0!60?@#94!6?@@BBBH\rIg#28!33?_!5?@@#120?@!6?G$#105!443?@B@#133C#59!67?o#138C!8?AG?_#135!30?_!6?@!8?O$#84!443?A#129?_!69?A!7?C#88!35?@@#137@#134!6?G$#136!559?A-#103BRND!9?^#65g~PG?O?A_I_HaLQdWPyt}k!7?@A?AHOOAKS_oOoOOO?o?OoWOSM@C#28OCHA#98__XAPC_#61C_#69@W_#163C??D?@D?HBS@Q#66!5?AG_AGPACaG?__???@@?K?G?wOO_Wogx}^#68?_syBA#61oK#28LH!9?A#64dw#161BYC#131?@!4BFNFnDB@bA?@??@#162?ASo#51@#52@@EG!4?@?AC#65?O??G??_?OGG?C#51_?W_KGOcWTiOcOC!4?_!5?_?O???_?_?CHC#56O?@#29_#88iZQSo?OG#67_#59A#69P_#163?^^~}!5~^~~^njTEHE#49?_?_?C#52?_cH#9G#8CA#29C@?@!5?G#48@#36@???@@@ELK#56g_#61?@UG#135@??O??_#129?A?FKBWAHO#78!4?Q|kODOCGA@A#129_?_?O_?W_OgO_GO_G_?O_!9?@#119!9?_?g@A#132WszA|u~~~NRB#135???Ovk~mRdG#61?Bnz~mw|yv!7?OC@#66??@OoOoS[WQSbDkWI#68??_O_gooWLg|CVA?@#61??_o#103]#138D!9?OGA#28@#68_?_#70!4?@#59!4?@@#61!4?BM{!8?@AK[__O[AAIB#120OaEA#98SmVDB@#61??_G?B#28?@S!4?OO#105?@?BG?gO_O_#138AC?G?OOW?Ww{sk]d]tivh~$#135{kOyMFB@!4?O#61_V#64?mv~n}|^s^qLqLQdICG#103??Jq???@?K?O??_#68!6?@?@?@A??@?@#103__??AC@#120A!8?o#56@#64o_#161DJHVQeyIRECB]L~n~\~|v^|vm|j\qkO!6?BBFE^BnM^dJSA#64!6?{\G#116_O#88QT!7?@#103`#65Y#68F#66[#163Dz~}!4{wowOwsKWsSkGO#66???@B[y{O_!8?@#0??_!9?A#49?O_WoOGO__O@@E??G?OO?oO?g?G?_?SI??Q#36_?YlA#0W^@!5?_#3_#103[#60_#36_#162^~__#131?@!5?_!6?_?_HC?C#51?@?@A#56?o#4W#0o@!7?`A#53_#46C!6?__#9?O#59@hsGo#67__#132?@FNGwwo__#134???A@SD\~dAA_?__oWMKBBJBDADB?@?B?@A@BE?JCjCm[qcCK?KI[GCBBB}lDA#154?o{fB?|AH#138???ok{~~~nGP#103?PkYv~{OC!5?GzF@???_?A#69OOI!6?_?G_WoQbt~~^n^VNDFABAA?@#65?OgOKN#116`#120W#119oG!8?@#64ow[}^|~~~}~~z~s}}~WqG#28@?C#135g_!6?K_!9?_#138O!6?_OK?@!5?@@?E??_O#84??O_#95S#133A?G_?_#103@BBBF@@A@!5?G$#120??_?OG!6?l#70!7?@??@?CO??G?_@A#135???GO??C!4?_#64A@A@@JENEIEMIKN^MMAMB@A@#67?A!9?@#65W#36A#66FWo#131A#69!23?O?DRMZ|`BCEK?wo_??_!6?_~^JD#70?_B#60B#67A#94_g~Liq`o\G#135[#69??_#162_#49!11?A?A!7?A@AGAC#36A@Ta@!9?A#116?_SH!6?A#56A???@@#161!6?@BFEDHIKFBIBOth\F??`@#52gOS@#32?WE#100?O?_#136AL#28_?O#135B#64[#68M#161!14?_OSGWUPCQR?Q#66w{[@#57?C#32_#33GA#139_??O?g#3??@#62M#14A!5?_G#10O_#65?AAI_#138??BNMwoO#119??B??K?o#133?C?_#89!4?PNyNZFD?@#119!6?_!6?_#78!11?GWwoGo??OGKsc@A#129?gSM@?GC#120!14?A#59!10?PFAD!9?G#162_gs}nMlMhAedJCI@C#64!8?G?_O?_gw~eUjO#146??a#88A#98ut?Ao[GBB#65O?CA@?A!6?C?I?@?fLso#132@??CO???A[_#64@AB#116!7?AG@#154O!6?@#67?O#135@??CAG}{oG#116_#146GG__!6?BAC#132GWoo___o_#28???O$#138!4?_OKE!4?A#61!15?_?AC??@Rs!5?@AKL[oMgs__G???!4_?__?_?GOKGB#129_#94O_SFC??@A#116I#70E#68K#162?A?sggXCokowg__?O?a#52!12?@C?QcIOO_#162?@?C?@?ACBC@#59!7?C#65O#132_#146C!8?A#161!17?GoCHjQvn}|{gC_#69??g!6?A@@A@@!7?_#66__?O!9?@aA_GWOO_?_?_!4?C??_?S]DJA#57_A#4_#33_?C#137C?_???C#65?@#162!18?a@_GqHkZlABA#36WuBB#1C#28GA!6?A#31O#108O#16GA!4?O#64@ABFKC#103?@F][o_#154??FFDKWOo_#133!27?C??C?G?O?g!5?@??A?A??@A@#67!43?C#135WE!4?K#68oKF@#161??@A@A@@#52!14?A_OC?H?C#94!8?L#84@#105AG???B#154CCA#61G#120!21?A??W_#105?C#65???@A@GLM!4@#132???G@???_OKAB#120!8?AGCG?_!9?@??C???K?C?C$#116!4?@#132_OG@??t#116!25?C_?@?E?O_#135!22?_#138_O???@#105?O?[AW#68!37?AGGoG?O#100!23?A#105?oCL]FA#138_#64!34?AWc@BACC!5GF@??_?O?G?C#131_???_#47CGAG?C#131!4?@AC@AGC@CA?A??@#68!5?_#10??D#139???_G#101?A#105@#10!38?@#88_SLQ?`@RG#99c#50?_O#51@??C#70!5?OO#98!14?B?FLsijYa?G!7?_oo[{S{i\Yc^iVkZUd]cXVcZSzPaDB@BDADADA!4?OYTB#120!36?_G#138B?_O?@#70!30?O???G@CB#78!5?GQP?A#132_WGC@#103!22?wO!5?@A?O_??__{{s{LD!7?_o{Mu~{z{a???o#132C#98@BCFKTiDgOGO#135@A?CKGAEHEBBJB`Q`ITGU$#119!6?_?ooy#138!26?@Dk??G?_#116!24?O?G#88??C@!5?@#64!37?CO?__#136!26?A@#119???_C#70!35?C!4?GG?O!4?A#103_#88?DBBA@@#36??CA?AAB#162!9?GACP?GsOLA?AwITI#94!11?DH?]NB#100!37?OGa?@?AgS#37??O#57@!7?O#89!67?o?o__ooGW#146!42?o#132K?GKB#134!45?cgN@C#135_#103_?CA@#119!20?@??CpBEG#59!4?SQ@A#146!6?oC#84?@#129_OK#119!11?@?@?AC?O!5?C?KOO_$#154!7?oKMD#120!27?IRA??O#70@A@C?DGQ?P?gDG@CP???@C@#146???__Wc#119WI!5?[#101!69?O??G#154?O#57!35?@OA#67?Oo??__?OOS#94!5?@#52?WGK?C?C??DACGWGo___#94!71?O_EIC#17!5?C?AGW#119!123?oWBA#64_?A#128!43?@#95C#129?_O#59??_#146!23?AA!4?@#119!16?gjO?G?A#94!11?@?B??QG_?@?FAC?G_$#146!8?A@?I!28?WYo_#133!32?Ga@#128__#56!112?ICKC?O#28_!4?_?GUIKCCA?@@#10A?A#34?_#136!83?G?O?C#20!4?gWOo_#154!124?fS#129@#138!78?C!7?O#133!16?G#134A#129!14?A???G!6?@$#88!40?c#132_#95!36?_#78K#84A#3!113?OoG!5?_???A#161!6?_#67??@?@#92!85?C?C#25!7?__#94!207?@A?G#133G#154!38?C#134!4?A#78?O$#60!194?g!7?O???@#3!10?@#77!87?g?O#30!6?CC#98!208?@BEowo$#33!196?_?_#4O#61!5?G??O???C#42!101?A#47@AAMO#154!205?G???C_$#68!197?@?ACEED!5?_?OO#34!102?GC-#132c_??@_!4?o@#103||#65HNpSOG_SGJu|y^]INE^NNF#138??@@{!4?@_K#61_O?@BZQfQbb!5@#154G#119GWwkSAA@?__OO???C#65__gS#69?@BECkKkKwwO!7?_??_?__?o?SIPC@vs{!4?@AAFJCMMEFBA!9?_?OO??!5GC#163?CHV!8~vnnPaHA@D?@#162?OEzQ#69??pME#64RC!9?g??@#163WgOoOO_O_#34??@#66!4?A?DAPAS?@?_!4?CAGE?C#60??_!9?G#162_O_OO_G!4?o@B?AA?A?A?A!4?Od??AO@#52?_O#36__#32_?@!8?A#102A@!6?A@#32A@#33E???G???G#132_??_?gqs~~Wa#129@DH!8?_OgCWeOmhaTQgDIcPIcJ_TI`QHqCPiO#134@GVl{GGG!6?@B[sG_#132?@IJNJ^N\!4Oo?!4_#120O#103MDN~NN??f_oKCA@!4?A#36G_#66@?_aO?EHGSBA@?@A?A#64!7?cPcix}jRNQ@A#138_!9?KA#68_o{]DULAD__??_#65??A??oCp]~EE#103`NA!9?@AD]VPB@!7?oKAh?S?v`v~y~^mPe?AA#146@@AGOW_!9?@??AK#138BKN^P~j{nl]$#146OCOa!6?G#138M#28A#61Au_!4?O?_!4?__pow_oOgN#135??m!7?o?GGC!4?GK?O!4?A?@@#98O_osiqRWCKKMAB#68???@@M[wWOoOo#52!17?GC!7?I@GQ?@A?DGSGO_?G#61?_?_K?A!4?GG?C!5?@#131!14?GO??K??O#66!7?Di~MP#36Ok#56O#65@!8?SA#162GKC!9?C!5?CI_Gc?_G!7?@??@?@#67???__!4?O???AA??@#36AA?@?B???G???_?_#47!15?CWCA!8?_O_!6?G#52_?O#64o_#67?A!5?@@A#129O#119_?_!7?Aye!9?_OG?W_OSGgcQgpIcpGsI_tGcQ?g!4?O#78?ABtO_?A@U?BEK_#119@?KWooo___?__#135??GGK?FN^NPYo!8?OG!5?C@#10_#68@!8?o???_!6?O?mzezJeXDA@C#65_OG]LB#132O?A!7?@@#65A#52?_!7?_#70!6?OCOG_#61?wx]#120oKFA#78oO#132???@Mg!6?O?CA?OOKB#61oSU|j~GU#59G#135???_PmXvOXcw?o#98BFEC[ysIk[kOg#135!5?@??I?SBOQ`$#154GOc!7?F#59!4?O??__???o#103!10?_Oo~}!8?@RFAICCKW?OOaa???@@#84???GC#78GOK??GBA@#103_??M#64?i}o_?_#161??@??@?BMHlZCfJTJViVWtITJTm?G#68?@?o??A?CG!5?W?CD@B!6?_OO?GGG???CEB#49!14?O_O_O_O_?O#52!7?_#68h#60?`#67OC!6?OB#68{A?@#131?cImITELU@m@R_AGCGO?O#57!4?GCG?O?_O!5?G?C#0GKO!6?@@#131_?O_?_O!7?A?E?B?F?HE?AH#14!8?O#10[@!6?_OG#16GCAA!4?A#46C#56CGWp@@#28COcO?ECICA#154!6?f\{#98?O~TI??_?oWMCRF@N@ATAHDQCPICR?TI?UHcLRmTnwfgO#89?AfV~|}h~{wo#98BIqO_#138!7?ANNNFDR^WO?___#61??O?^^W^NB@!6?C#57O#69A!5?oxuDj{|[~}|~|~n~PCXC#52OGAOC#70?OK_D???@#146K?@#78wC#154?oG#59_?C#69???O_?_#146!20?oG#138@!6?@UC_??__!6?_o?B@#127!5?G#120!8?G@?YCo?_#94?GG?@??@A?C?OOO_#120???_C$@AB@#98?GGS]@#64!6?MjNVNjVCHAD?@C?@#28!7?O#146BgP@AcG#116A??D?G???C?C??a#146OWOOcC?A@#105?C?KA@???@#61_OOV#66!4?@!5AFEk{o_O_oWSisGTGFA@i__?zuG!7?@???B@@@#116???_??k#88OE?A@???A#67??A#161!20?M@UlMi^}nnxCgT#70!5?I#3m#28iI?@??@E#161??Oo_UID?dGgO_wO{c[xooO_!4?_!5?@#32!9?O#33O??C@#68?_O#66_O?GG?C???cGBSguCCG!4?_???G???@??G`A@@#57A#56@#43G!9?C!9?A#4C#3EG[_?AA#29G#146_OG_?O#134!12?atnyPoKE?B#154__#133!26?E#129!17?D?CG?C???_#28!5?A#65!10?_o__!4?A@#132C#84O#94__#0O#8_#59@#51CESG#61!31?__OCA!9?G#135!28?@#119OGB???D?O!8?_O{LH#67!4?g?A!6?D#138!6?Gc?AK#119BC?Oo_??@?@BJOCg_$#138A#135@!9?o#132!29?S???AS#65??_oOO_@?`#138??C?S!5?A?@!9?_O#135O?@#162!6?@@@?@??B?@UACJ#64!18?ABdhkgg_O?o_O?_WGEC?@???_???G??!4CA@#116!37?P#88sFEBRm@#66??_?A@@#49?@?A@?HA??GAC@@ACGc?O!4?@?A?DA?@A@#28!4?O_?E@F?A#161?_!5?oO!9?C???@OR?P??OC`C#62!7?O[???_???C#42OGO?G??C#13C#69?_#62@#60??_?@#65@@@#103??@@BC#133!12?G#78?ODMN?@#154!49?BBED?OS?O??___?_#146!16?_OGAA?O#28_#33O#64E#47W_#162@??gN??A???A#103!23?_GC@!7?OCA@#98!27?_oK@Dby#129[#135??xY_gKCIA!6?K#116!17?c#154???@#132C?_#105@@B?JcO_?_#132ABFLB{Qo?_$#119?GG[}Fq``}#88!31?A??@#120?B?K!9?GG[GmecEA?B!9?_O#146?GL#70!40?CGUOSOO__???_O_OWGB#65A@??OO!4?C???A#146!40?@#94goKk#129O#103g?@#69CA#51!8?A???A?@!5?AGGaOc@A?B?A?@OEGAE@A#116?_?_OG?C#49???O?gGOCG?o?_O!5?GGKCgCQ_lGcQGJsAH#1!4?_A!4?o#31G??@#49__Oo?O_#15G#116!6?I!7?C#138GGYOLJ#89!14?B?@#119!81?_OGC\G#138B#60G#34??GGC#116!34?O?A#98ocEr[E@@#64oWKB@iHq\y^^^~^~~|~nJjE@?@#154!5?C!5?A_!9?G??_KA#129!24?G#134??A?C#133?OA?O??_$#94!5?ODI#129!33?@#119mC{W#64!5?__?_#132!10?_#94!4?G@?@!6?K#103!59?O???`O!4?C?A??A#100!42?O#139G#77_#60!5?@#52!20?@??BDIRUPAKG?G?GWOK@U@@!9?GCC???ACA?CG??goOO_Oo??_#8!15?A#29_?zUCE?A#51_?O?_?oG#36O?wG#0!4?Syc?EE#135!4?@DF#120!101?C?@??G#52???O#49a#161PTF#119!33?_oG???_GA#134!34?_y[#133?_#61!4?@#28?A#120GCLB#129?_CA@#154!36?CG??O?_$#94!43?Y#133!31?@??@#146!62?_?GGNKC??A??@#136!46?O#36!28?@??CG?g_?CGOC?_?OW?K?@#70@!6?CC#51??C?CGAGCGA??OGcGOC?W?Gc?AOAgS_A[SiEH#11?C#99?b!6?A?@#20CC??A#88!11?W_W_O?O#98!104?_og#154AF#131!6?AG#120!34?G?@#134O?GB@#129CA#94!33?A#116!11?O#146_O?@$#98!43?_#134!31?CA#154_O???A#120!58?O#138O_!4?C!4?A?@#64!80?O__O?__?__!8?_???G#57???A#68!4?@#69???C#163!7@#117!21?[C?OGC#108G#28@#34??G??G#66_?O#94!169?C?@#135???o#105!34?K#98!14?_?QA@$#129!76?_#67!66?C#28O?@G?C!4?@?@#47!79?C#56O?_!6?_??IAA!9?A?@@@#0!36?_???C#50!4?@!5?@#84!169?G$#59!145?_#119?A?A??@#4!97?C?@#65_?OGC??A#88!41?@??A#30!5?GCC#17C#88!172?A$#94!148?B@@@#88!99?OG?E?B@@#125!43?GG#137@#35O#37!6?@@?B@$#66!149?_??OO!6?L#3!91?A??G!5?@#100!41?B?@@#25!5?A#19A#24@$#161!150?_?__o___O__xUg#29!89?G#69??_O$#162!151?_???OOO_WQA_-#103{{w{wwo!5?BFN?SWWwG[GGEGSGCWKGGKCFB@!8?[@#70}@!4?_??`O_O??_#120@?@??AO?_#129?@#64?ow[wK{}~v~M^~fVBEICWwo_O#52__?o!4?O??O??O?G?C??Q@GAc#70@CA@?!5@???O#66_OG!4?@#162!12?A@G@CO#66WG?O?_#162!13?T[b#69?wNA#60_G#65@!9?@#64Bw#161@}lQK@cAHiTiVjTvlUz]vU{h{aCO#64?@AK_!9?OG??@#66?@#131?CpAXaDb?DA@A?IDI?G?O#36?DAD?a!4?CG?K?CA@!6?C?A@#49?A@A@ACA@#69???GM~?W#60?@@#59A?__#119?@@BN\pB?!5A@!9?@#135o_?_?_#119!5?@?@B?DBDDGERiwhCO???A#133?O#78?@MOa?__??I@#119_OG_OGGIWKLEHC@B@@A@???AAC?KKKUqAA?@#133S?GC#88?@G?OOO_#64CGGCGWWowooo__!8?ciMNBE@#116_O?C#146?K!6?o#64???F^Oo??_?OiDky~NvJBD??@#103?_OGKC#138A!6?__?_!4?@!7?KR_#61GEDJFlADH#135??S`}[z~FYE@G@DA@#146??_?A?GOo!9?@?A?C?C$#138@@@?B?ACWO?K?OO__!6?_!4?O!7?GC?B!6?@#65o!8?@?@BAECKSwoO_#119?!4@#59??G#68?_?o???G!7?@@AFCI[_SOo?oGw?g#161?@??@#68!6?o_ws|O#61??@e@???AA?@!5?@#131??GO?GD?OAP?C#52!10?_O__#131@??CAAD@?D#66!6?O~E#64_wCA#88_wNA!5?A#61A{#66@{#163@Qlr{R{qCgC?CAGQHC?G#162g@UAHG#52@RkO#56AKO@??@#94_G#61?O?C#162oGkAA?@!9?A???G???G#52@@ATWp@?AAC?CE?EA#88_o_?GE@#70G?A#162?C!6?ACA#36!4?@???@#70?A#116@#61AA[W_#120G#154C!4?!6@!6?_GC?E?@A@BDBDBFJM]}{]w{wwqo#134!8?A?BCn]o!5?_wSKFFB#154??!5_OooWkc[SK]NN^KSYsoqq_#134??K_o?a_P#77?C#0@@CGGOOO#65OO?__!17?_oo_??B#138OG?_O!7?N#65?G#68?NNxyW~lSgPC#65?oGO[w}JSG@?A#120_oCB#98w{y[GCIFFEMoSsCCE]Nb#129a#154`#103CYvxyswQ\yu~~j]@bC#138?O_x{T}y[K]][o!4_#133@??CAC`AO!5?_#138@@@B$#135AAEACCKw???@???W?__?o_oO?O_?_?_O???G?AB#84_#133_?O#154?O#28?A#61M@#64}~bjPFjUMmKkwwOg#135@A?CG__?OG?A?@#65CAB@???`_?WgGG__#66?@??!5@?@!5?MNCEJFEB@B#64!4?J}z_#103?o!6?A?@?A@#163oowok}uy~m|i}xiSy_i???@@@BBBE@EH?W_SGO_?O#52!6?O@#36A#3_M@#94o{L_SBjw#103C#69?E#162A#131!4?AG@CPAPgOg??_?`??A#66??SrmkO_#70@@??@#132???O#146?A@#69_?CA@#49!11?@??@???@QcGO_!9?@#162@#116?_???G#32@#0A?E@#161_OGWCKIOSkww{ysg#64???@@]A{{oO#146@@AC?O#98AMK}CKCCKUBJolMBB#138_ow?WoO?O_#129!5?@??@A?AACHgTDUPmPmPg#89!4?@n\~^^^F@A#129??_OgCQT?Q?GCA???A@#120!6?@?@!4?@#78??C???_#136?G#139C???_#56CG#68CEAFEEKFMKJ\WtwisGvy~ZT@#61??W]MCB@???___!5?C??_#69?EDF???A#59!5?_#61_??sivYNDB@#132W!7?OO???@@?@!6?Q_#67C!6?_#120!10?gD?A_??@a@?A#119@@BEC?G[?O_?@@B?GCQ?[_?W$#132???@???A?eQ???_#61ABFCCCAFFHFJFJFBEFBB#146oO?O?A@???f#68!5?SCiOC?O?O#61@?AAACG_O_!5?o?CAA@#70!5?O!4?c?CG#65!33?[WA?AA??@?_???A#69CAA@#49!23?CGUHaH?I?A#57!11?CG@#28OE?@#105oGg{#84C#120@W#49!23?@??@#36!4?AK_?_#67i!8?@#161?O?C?F?ADADiWFYkWT]tOo{_O#64!4?AK[eCK???G?G??A!4?O#69_#163?_o?_o_?_#68!10?ue_{#65??KK?_#103Oo_#94?_?_#134?__GWOGkcNA@#132?oWK?MELM{I[y{wso_??_#98!4?@?C?A?i@mPkT{jO_!8?_oWgSNFRD?F@A@A@A?A#138!9?@??@?@#128???Gg#105CUGoo#29ACG#36@A?A#69A@@?@@B?@BCAFIFTJvGD#70???O?K@?@#132_??S???G??O?A?_#52!8?A@??@#116!11?_O?I@#119oE@??OG??G?O!5A@@?S\#28?G#135@#28!21?A#132??_O_`@M[[OGGo_!8?@@DAA?MC$#28!5?A??_!4?G#59?@??A??@!9?@#28?_#119??_O_W?YE_m#52!6?GOCGOg#103!4?@@@?ACKGO???_oK?A?@#61!11?OoOO_#67!34?C!7?OGC#161_oGKCEA@@??@?C@ATjD\Sv}zmeUkKWOgoOsdOiti^~n~ibK?@#68??@#56C#77!4?A#101OA#98?O#119C#146_#57!32?@O#68O!9?_OKA#66!18?ACA@AccG__??!4@BA?B?@!8?OGC?A??D???@?DJVvo#52?G#67!5?@#135@?EK#132A#138W_#133??O#78?GOo___OO#133?O?C#132!60?_??_?O_go_oo_p@_?A#116@?@#89!6?O#94?ANAOo__#8C#3G#66@@#67?__#103!23?_OGC?@!5?_!4?JO_#70!8?OA!4?C?A@???C#135?_O#146?_K!4?_???O_!5?@#133???G#98!36?@@ABBBDISoKoKoO_#120@?A$#146!5?@?@EHGoG_#65?C??@BB#116???O??O!8?C!9?_#138!20?AACKOWGCA?@#69!16?@?ADBMIMMMNuF~F~}`oyhsoxw}KLEB#28!6?GOK!5?EA#68OGC??@#70!46?O#67O#136!5?F@#60!38?A???@#103!4?G#163???_Ow{wyKwcWOCw_Ocg_?_#51@?C@AIOG!6?A!4?@#28_???gC??A#131???_W?SgIO@#28!16?A#105!9?O#129!9?oWCBB?@#146!59?OG!8?g?I!6?@@#100!8?G!4?_#28__#70G#60O#120!26?gqiR@O??O_#133C#154!33?@??_?O???@@#84?_#94!44?AC??WgPICaKQCgGW_Wo_$#129!6?@#119?@?_?_#67???G#120!7?__?_?_O_oOw???C!6?W#132!21?@??AK?C#67??C#116!54?E?C?CA?HCB#135@#65!98?@O!4?@?_GAA@#57!28?A!5?G!5?C?@#146O#61_#65_#68_OGC#52?A@#51@???@#98!119?GK{o]ABH@???_#99A#31A#10A#47@#51@#28!35?_?O!6?O_#94!30?A!5?G?O_#129!48?C?_#105???G#134??_#154A$#120!10?DAC#94!24?_??WCH#154!25?@@?AAA?@#88!55?_OGGOwC#64?_GC?A@#116!95?C?A?A??A@C?@#56!31?OgOG?!4OGG?A!7?@#46!140?@#32C#52?A#119!35?CF???GG_o#78!33?D?@A!5?G???w??_$#88!12?O!24?G??C#105_#98_^@#146!27?C?A?@#120!54?G#94_o__#138CG#3!102?IC_A?B?@#70!35?G#60O??O!6?C#103??O#154!185?IG?GG??\#134!35?AE@D!6?GG??_O$#132!38?GC@#146!93?SG?O#28!104?WC_?CoCA#3!35?_O???_?OOGCA@??CA#98!183?FECFB#105!36?@!5?G?CG?o?w$#0!137?_#88!104?Cw[[aK#67!38?_?__!8?WG??@#78!183?@@#28!42?_$#33!287?_!7?OC?@??@#135!184?O#134A$#47!293?@-#138wg!4wWwJ@!6?@BBECpAA```!4_?_#98?CCAH]^CB#28?Og#61_~@#64^n}]}[U~M^kEjD?A?@#103?_OF???NA#59@#64zCqO{v^~v|ztIbDzsG!4?ABFN}sO???Ok_?_G?gU_??_omc}h^B#28?OSG!4?AO#64I_#161GgQ_#49!14?Q??g?G_HODO#52CBLZSkKWW[O_??_??O?wB#70gS#28gjA??__???GCA#66?_B#163O^y^}~~t}TsKoAhS?GA#162A?w@j#69???o[@#36C#67_O@!6?CG#161ABNsO!5?_Ic_LOdYdJtnB@Dwy{gC#52?AX#36}I#8c#33O?@#94?CCS#120?C#65[B@#163O{{!8~}xVoT_Og#66?@BMw_#64Pn\~}}o#61@Es#120@Q#129A?O#134B?GQ`IOJuw#129BK#138ABW!7?A@@FG?GKo?O#146!4?A#129??@CHISbIS@G!7?_OKADA@#146!4?G?OA_?DOA?ti?c?Q!5?AGOaG#119^|#134?]|jDh@QcqgS#119oO?G??OO?O?_#67A!6?O#120??_!7?@#132@?@#120!9?o_#119~BEGO#59?A!5?_?_S`@K@#135???{OG@!6?O??A???@}wGoO#105??@AD?Co_#154?CKGO?_#120!12?A???A#132!12?ciONSswwog!4?_?_$#103!4@?@?@#146?OH??@?AC?gww??G?GSWJR@_H__?_!5?_G#135OB#65?]_O???__?o_OoSou|^}Q?A!5?@u!8?GACI@CqCIv__@??CG_#68@HeEkiEPIMSuMP?\j|MDPI?A#61_?_G!8?_#66Gv#131U!9?A??O@C@?@C@?@CA#66A?fH{q?@??C!5?C?iQl\@#64oUJ#67E!8?O#64__O#69?W#161{f_D?@??I@iIRMxUi~v||~F}CXA#68??_[A#56OA#65A!6?B#68@#66@K_#131GcATGfGD?WA!4?Oo?OkAOC#51C!4?C#64?@P#32O#0g[#88AlBBB~@#67a#69O_A@#161A!8?@EGFIUnT]}Go#68?@EOa#65?@@N{_#135?A#138K_#94ig#78?nQl]dNsG#132??@D[!7?@??A?BFFBL]n{ytgo_O#119?BEPgS_?S#133_#134Gr~YsKFB@#132__wWcGoCwOwEwCgQkP}??}P~lz~vzlPc@#98???A~`A!7?QgGG#138__???_!5?C?G!7?__ooG?CA?B?@!8?@!4?@AGO#68@BANA#103!7?CwcyBF?A!6?_OC?BL~}#120@AC??_#78??@?CG??_#120?@?AC#146!30?@?F?GG???O_O_$#135EUEEFEBE#154??O#119~veUGWg#135?@@A?@#154???C#119C?CKUNZWSo__wKHD#103?[#59?_#70??@_@AG?@?AH?AH#116!5?_?@#138JL#68???CzLNBG_#70!5?sWG!7?@??O???g?O???O!8?O??O@S?CO!9?O#52B#162?@#163l^!7~|~~n}z}lY?SG#162!4?A#69!4?ca#64O#70o_#57__#49K?[GS?g#68?_?K#36@#65_!8?__?OKE#162??G#131??_!6?@?@C?@#66!7?Oe|~NB#64_xM@!8?AO#162?O#163BJ|ivWvYtB\qnYdICA#66??C!4?@A~wE#56?_J#3B#28@?A!4?W#135@#68kE@#131!12?_G_G#69!5?@F]g#59!6?AW#28??_#88CO#98F{!7?@F{o#135??f~nN~NFU|}{wcwo_?_#98!11?CBGTjiVvK?_?ow[MB#119DA@#78!36?SiEYgAC#94?@BBMAKMKCY?O[?WO?O__#129_#135!7?O?ICE?A?@#116!7?G#146U!6?_#72??O!4?G#138!9?_??CB???_?CA@!5?@qGgW_!8?@?ACGGGWOoOo_wswt}x~^^\!6~]^v~~~YTgo_#94?C?GAC?ACGODZ$#132!6?_?sMe???@@ACO??KGSKUIAOCWQ?O?_!8?B#68!6?@?@!4?@#61??G??_?l^L!5?{G#69???_#61!12?@?^EaBCWO#52??AG@QDgAT?I?oCG?SA?I?@#65???wNF#116I!6?g#56@#68O#161!19?czAvuZslwGq!4?BBBFABEBB@DDA#69AE#0???O#146Cg__?O???C#103G#68_wF#49!12?_#52!17?A#57?@#60C!9?_#49!19?G?OgA?@A?@#69?@_#57?C#4?C#80_#100kO?w#136G#138?A#66??W#162kA@!13?@?A`?s#103!11?@J{#132@#119@D#105??OC#89??O_#154!4?Aw_#127??O???_G!4?O??O#154A@?BDIVL^n}wo_#78!9?DI@#154??_OW?cYvNzFfFhDZVgBk@IT@I??C?GCQkRm\v_#89!5?OOc?G#133???C#129_#132OO?O??_#84G#61@!6?C?G?OO?G?@@`!9?__oWL!7?ACW_!6?_?oryFYD#120?GSGB#146C@#98CBD@#61_W[wq#146!5?AC?G!7?@A???O??__#119!30?BBBECCGkWoO_W_$#120!6?C??_!8?C?A?t?Q??@#94?GA!4?BHA@?@_A#138_C#52!9?@#28!14?O??A#103!22?WWcW?_#69???@O@?@??`@@@A`A??@#67!8?_@!6?D#57C#69C#68!33?G#163!4?@?@#131??A#3!8?@#88OT!4?O?G#119AB#28!36?_GMA???@G_#52Co#162!21?OgB?@Uw#116!7?A#29O#139?o?_#146?_!40?W#128!4?`#103!14?o?oW_#89!32?@A#105!47?!4@A?C!5?G#0@#101C#154_#65@@@?B??G?GGGKCE???_OOGsc[q~Z^NFA!7?@??O_!4?C@MK???@#132???_oG???_OKA@!7?@C?COo_!7?ACG?Oo???_#98!31?@B@ABCJCMAC$!12?GW?_#28!7?_#88!7?@#133!5?C#105??A#129OS#154A#120!25?gUCO#116!22?@CG?_#146!30?_F@???O#33A#47!41?_O_#94!13?^^WHIKD#3!38?GC#0@#94oOm{W#70?C#57G#165!39?G#128!150?COGC#139???@#88@???@??C?C!7?_#132!23?G??@ACO#64@EKLo|^~RIOA#28!7?AC!7?G!7?C#119?@AACGWo??@@A?OO__#133!40?@???A@$#94!14?gO_O#135!52?G??o#120!23?O_#88!32?oE??AL#51!43?G?oGO#105!12?CA@BA#88!39?ooDD@AAo#116O#136!199?C!4?C?G#98?o__o_#68@B@BB@@#154!20?@???CG_#116!19?@#154?OW?_OG?@#65__#67C#98!7?@@BECAjpEUKw__#103?!5@B@A#154!33?O#105?@?@$#129!15?C#132!54?_O_#94!59?g|N\_#101!60?@C#61_??O??@#100!37?G#29A#136O?_#146!202?O!4?_?A??G???O#98!27?sWo_#119!22?__[ZCA#94!17?GO#134A?GO#135??@@AEEEMKML^FJFI@C?___!6?`_G$#146!71?_#119!60?OA#84O#98_#136!61?A#98?C??@#92!41?_#129?@#146C#135!202?_?_!7?C#116?G#84!29?G#134_#129!26?_?G?A#133!19?G$#105!134?_#138!65?O???@#137!39?G#100!205?A@A#133!5?G#103A?C?G??O??OOGKe]PGGC?AA@!4?_oE!5?@CG_#94!21?A$#29!450?@#28_@A?BA!6?O???_$#64!460?@?BACECCEAB@!4?__o?W_K?C$#59!460?A?C!9?A!6?G-#132~fYv~}~ypl}A!5?A#88I#138y`!9?@??CB@BEGo???@?O_#65?BB?FKNEFEFFEFBBB#146??_OKE#98Oc#65???@{?o_gOg?S@I?@AOdMwVLho!4?@!4?_#61!9?_??O__OWCDF#94ooKUFA??@E#103ACO#66B?CGGo!10?_?_?_?__?l_mdpwW#52@SB!5?@B?A@B!8?G#163_O_O_OO!8?@@A@AB@A@A?@?@#51?G???O??o#36?go#70A@#67@!8?_#65_L#68Z#161_~_i??_?G?EO\aGBSrK{s[z~~p]_#69?Eq#36Ea#4G#0y@#100HlQHr?O#67?Ao#69Bw#163@^}!6~}~~~}ziS@O#162OJ#66x|?A#64O[}nfX_#61vp#138O_#129A!7?_?OG?D#135!6?@BA@!4B@B?ABA@#146!7?A#129???A?D?@!5?gK@#138__w[Kg_?G#146!6?A!4?A!5?A!4?HCGQCPC#98?N~GO?_owCEB#103_OGMF@C@@@?AKw#132C!9?Eo#61CO`_!4?a_oKMBDA?@#120tD#119NO???@?GO_#59@BBCAA@#146!5?_OG???G#65__[CyNFF@#28???Q???A#132!4?BKO#98ADKWrURhKWS_#138??@BDJVFN|OQ{{{IF^~M~^}~|zsx|u~ZqqOowowG[CA#5_$#138?WcG???C#146MQ@OG!4?Ot#28DC@#61{{}{{{wCc#28??@???_!6?E?O?_#64?B?B?@?@??@#138??_!4?I!5?{#64??B~N\UnV~j}tz}|nYpFgQ!8?B@G?^N{Y^YX]QRMMJFBA@#120O???A#98OGgHE@Eg_#64@?GO#163@?@B@B@B@B@D@A@??B?@#69???O?OOKAd}gw#64Vnt!4?!4C?B@!4?C??A@@#131??_?O_#49?g??O!4?G???A?@CScGCoCg?OG#52GwOC#32_#3[#28KA#94?DrsS\Y@#103O#64oC#66^#163?^T^f^VunPn?SB!4?AG`C#66!4?^~x#68Gw#56CO#33CG#125A#94O_O?`_C#65@#64Aw#162AS_#131@!6?@#161???@CTj}nnsE#69A~S#65???OOEK#103?M#28a#88G@#98[z_?__oWGKAAA#103!8?@#129??C#119!4?O?!13_c\zI!6?_OAA#127!4?O#135??O#120!14?A!6?_#119!6?_r~o#134?vf{WCCI@#138_O?A#28!7?@!4?_#134@!5?O#135??KP#65EY@_OYtSMF@#135??_o_{I!9?AAK??Oo?__OW[IB!8?A!8?_L~\vGdAG#119??BB[Wo_???A?CGG{o__#135!5?ABCB@BTg!5?@!5?A#146G!6?A@?C?@@@$#120??@??@?@#154???ls#98?@i#94AD#103??GkBB?AB@EqYy}ISWs!8?FKWWoOo_o_o_?o??O?GWM@!7?}#68!4?A@#70!6?C#61!7?_UKo??@A?WW#68B???@C?CA@C#65?_O_W[KCIA#119_!9?@W#65AKO_#68!31?BCgO!7?A?B???_OWGCC?A?@@?@#162!6?C#52?_O__#69_#131C??GCG?GO_G?O#69???CDB#64G@#4_#0o#88L~_??_#146__?B#61A#69_#131!4?_W?g@?g?AH_[bKp?A#52!9?D#64@#57X#8@#99@#28_!7?@C#68@C#66C_#68!23?hnb@#59?G_RG#120?C#146V#119w`!7?O??C#127!17?@#98!15?_?O~ES@Aw[F@#138!27?_#78!15?GBCJA@#132_WKE@#146!10?BGG?__!4?@@#138B_#64GC]^f?I@#138!7?O??_!7?@?KO_!7?_?cSN@??_OC#64?_WC#120!9?AG`Q!4?O_#105@A?AC?GC??_#146???C?_#28!32?C?O_IKA$#119!12?B~{T|_#116??Q#135Q??@@??@@?@@oge?W_!8?_??_!9?_??O!9?@#103!23?BKw@?C???_#52A??A@?@C?@C@#103!4?_#146_!4?DA@!6?C#135G_#61_#161@AFECeKMKMK]QMl]N^{^}^^~A^@IADA#70!5?IE??KGGG?CCA?O!4?A#162G!26?O_O?pE`#68!4?CB#56[#65A#116A#100o?O#77G#101A#105GA@#138OC#49!11?O??_?S_G?A@#162@A???M`#32!6?E#88?O_??C?CJq#138g#61C#161?@I#135!33?H#94?CA#105C?_#89A???A#132_??OWG^^[[{S{scgCcKCGCKKGGHHN^NNC#133!4?C_?G#78?_??@#154?OKB?@!9?AhRgVgF?HA@A!5?@CAQtlzMG!9?_W?A@#120C!8?@BCOO_#154O!6?G#103?G`!9?oo{YLFA#132?G!7?ACO_#120?_??_??_?_OG?C#138!18?_?SG|v~{_#154C!8?@B?C???O#103!35?_?O#2_O$#133!14?A#129??G#67!9?A?G?C#120!4?G?S!6?G#61BFCKKGO?WGWwGWWKKCF@!8?@}#116!22?@A?AGC?_#28!19?_??A#129G#105__OS@mw#116?@#132O#69@?CGOo#65!33?oKG!4?GG??aHGC?A?@#136!44?ICHB#98?C#119M#135G#67!35?_#29?ESA#139LaC?C#146?O#103G#134!41?BOD[JCDAD@!37?oj]|FA#132?_o[^EbbV^nv~~~|UkTgVw~s|{|~~\~~]zshA#89!10?B?@#129O?C#119@#61_?owMz]EM}wo#98?@?ECNO_qcg_#28?A#59!4?Gd?GPGA@#116???G#146??Q#94o#98l}A@E[o_#61@A?CGCCEF@A@!9?O?_@oWGEH#146!13?G__#78ACGG??O#132?BBBNYKYsgwo?kh?A?_O_?p?_??ACJE?@?cLLnNDIFB#88A#0_#3O#67K$#146!37?@??oAIIw_#116!13?_?_O???A@!5?A#28!24?A#138DG#132?_#70BCES\_o?!5_gGO`C??@A@#88_?K?@#95??_W#78O?O#154?_#68ACGo_#61!34?Go!8?cW?C#161?_oO_Wk[kM{i[yVxfJiELMD[Y|{tyqbAFBFA@CAC#77!59?G#136W?G#78!43?CHWBCB?@#138_?__?__abA_?A!4?A?E@?@EFEE#135!72?G?@#119!10?ACG?O???@OO#84!22?A#134@|Mw#154B?G#65!4?B@@#119!7?___CAF#134!26?@@?@#133CO__?O#129!40?@$#132!38?BK#88C?DS#116@#120!18?__o[CA@!30?U{OW_#138!21?OW#67@#128!5?_#162!7?A???GW!5o_goO?o#57!20?@AA#146_??_#88__#69!4?_!6?AA?@A@@#92!87?A#95!45?OA#154!4?_?OGGFF!5?G?GWOwWow_w!7O?OOX^^Z#116!65?O!8?C??_#94B?G?`@@IE#78!26?o?_#129C#103@??[W?GWWWMDA@!6?_WK?B??_owu~^_#94!16?_#128_#129?A$#119!39?@Jlo#94!24?_#132@???~#135!27?_#146??_#116!22?G#36!53?@??A#66@!8?_O?GCC??A??CAD?EW_SW_OO?_!5?G!4?G?IlrBA#133!104?G#128C#28!7?_??_??@#65!92?_??o?_wo#88!6?O#78A??KG#28!37?_#132!10?AB_O?AB@$#154!39?A#94?O?_#119!24?ownZ#103!109?_?O?O???G!4?A?@#146!151?O??O#95!110?@#105?KQC#154!48?O[ZL?@$#116!181?OO?O?O?W?CA@A#67?@#128!268?A$#60!181?C!8?@$#28!183?_!4?_O?C??@$#47!183?@#120_#138??O!4?A@-#132~~~v~~zVjDA!7?AGC#135A?o_Ow_W{CmreL]zmS_!9?B?@C@?A???@?@#28?@#98OgssuvEH#132s#138x#103^C#65@DMMOlc`YODW`Y?lY@dNVF#103o~K!6?Aa[O?GOGGSO?Q_`#132GO??_!6?_oOGG?B#64__on{_O?__?_#162@AC@?@???@A#52!4?O???_@G@?K#65???AY!7?_#66OE@#131kO??A#49!6?c?AGOGA#66?@@FBGw__@ACKGG?WOWISF!8?_O?GG??CCCAA?GCGOo_?___#131GDY?K@#162??_@?_@OJ#69?ci^C!9?O?G???C#131!6?A#69???!5_!8?h~LQG#65??_MN!8?__?GgO?K_#138!5?@@JFEUQs{woo#119??@@?EMKLO_#134IkSIAABC@AL{o#119J?_#138??BNjXNITWo_O_?_!9?CB??@#119??O?Ga\NA@??_G#138_?KEB#65ocE???OBENNDN#135!4?G_oYK!8?@?Co!4?_w\FL@BA#98?oCa?_OOWWHJJ]USkckGC?KcUa#132Oe@#61GGCMENBM@A#138!5?IsZolV^zNZcGOgO#98?@FACJTYujscGo?__#138?@BBCIANFLVMNM^^^!4NIrI??@A#6_?O?K#10A@$#154???G???gSyS|@!4?AKo#103?@FMKCEKEBR@KX_@?P#138J^[!7?C!5?GC?AD??A#105!5?GG#78G#154??A#135?_#61z}wo__?O???_???_??sQo_wJ!8?@@aFUQETp`b_`@#88G#146CAR`#94A@H!6?G#103__OGK#70??O???_#161@?@@?@@A!4?B?H@A?@#64!9?OkB~{_!7?G@#161O}Rco@?WAtGrKR\|vfv|~}}w{o!4?@BBFFNFNEDA#64?OGA??__??GCC?AA?@#162??G@#49!8?COB?@A@#66!9?_yXT#68_B!6?__OGG???EB#162!17?a@DjWo#68??qkf?K#61?OOJ!9?G?CCE?UKwo#132!6?@@HBAFMK}{wwo#146_#78!7?GsKsCQiLq#154???^O_#146???O??O???G!6?G??_O_?o?r?O_??Ha#98?_o{mbpWEBB#103OG??@!4?_O___??Ww||rMK#120Co_O#98BNzvMo_#61@AFCEN@#120???G?SO@C#134?GCqWdG@ACC!4?OW?oGoO?g#129C_#135?S?_!8?_o]|~tJcJOG?C#146?CG?A@??o#94GCW_G_@O@IcICWOoOo___#28!10?_?O_OsKs]M]GF?@#19_?_o$#119!6?C!5?}~vXFDp#120AHcW?O_?O_?_O??Q_??_?_O!5?@ACECICE?A?AAB?@?@!7?GE#64???A@@B?JUdNYf]dZAdI#138!6?oV???@??G!8?GC?GSA@_#98GC]OUxSYKIFA@@#61O?N#68??B\?OO?O?O__O_O_!8?_?__Ooos|aR{#61?@CO#28SG??B@#56U#52m_#163??JN}|f|IvKrGa??G#69!7?BETYqC???O!4?OGGFA!5?_!4?CCC??A@!8?O#64!22?wN!6?O???CCE@!27?@?~r^#59`#103_sL!5?_OGCCAA@B@BFM}}{wO#154!5?@?@B@BECKWoo_#89!6?oGwgSo#132!5?N^~{o?e_difNVn]~^y|vz~^n^~J[KNM^FJ#134!6?O[MF#129OC?@@#67?A#70!10?A#28!6?OA_#146@A#134???CGo#129C#154Go#59@??@#132!7?_g#119_oM@!4?_!4?O??aAA?C?EAA@XD#103?GVOwowo{p}|^N`A#120!4?CA_!4?BTkO?O#105??@B?ACG?GOO?_#146?@CG???OcOoo_??___?O_O_#135@?@?@?@#5?O???@#14K$#146!10?hA!7?Co#61??@BB@B@#28?G!5?C#146???B_G??@B?GGWGoWoo[CGCC?AAA@!6?@#59!6?OKQ?G?_!4?CO??G?G?C#135?B#132g!5?G#65?@_`d`_AA?@#120KUAo_#84??G#133?A??G!5?A#65__OON#69??AEKGWgO_OOcWgWs__oOowuLy^^MMFIA@#103!4?@k_#88@e@{#0A#60@#51?G#52!24?C@IDKgW?O?o___?_OG@@#116OCKG?C#161_oOOWgWWg[t]vyunK]lSXGswc{_UxR]S~^}nSDA#70???oI#103o_O!4?C?A#66G???E???G?OwooOO???_!5?bFE#70???O#135!6?Q!9?A!7?@@@AAcwwgcG#129!7?AA???O_#135!21?C#127?O#154!6?@??DA?C!6?_?o_?wcuT\A?@!4?_GKA#59o_GWgC#116!14?@#138@@AO!6?@?AW!7?_ooGCK@#78?_WLBACED??C?G???O?_G_O#116!5?_#59AA?@#132!16?_?o_Oa@Ckg?o#133??C?@?C!4?O#129?CG???O?O_#116!16?_#0_?_OGAC#16OG$#98!14?GeW_#138?@AW_#116??G#132!15?M_!4?C?_?_!5?_?_G#154O#146!40?j@BAK#135!4?G!6?O#154!4?C?O?_!9?CA#135CA#66!5?@BEFEMMKIGFEFJ[]CmLFGAD??@#116!10?@!5?O#69??@#68!29?O?_?_!8?C!5?OO?G!4?AA@#65!34?o??__O?G???A#59@#138!36?_L???_OGC#64_oOGoOG#98!21?@@@BAN^tRb@@@?@???BNs_#78!44?@#132O#135o???@#132!21?Kgo???@AEK_#103Gwwo}^FA?A#28A#146?OI@#94A@!9?@#146@???AAA??@???O#65??D@@#154!24?AAC??_!8?@?A#135!6?@?@#103!18?O???B#147_#8G#1A#11CA$#129!16?_W#119!23?@RmL}?g#103@?@A@A@@@#116@#119oO_?{ok??@??xe#154!30?O???o#70!7?A#119!8?GKEP?__?_!4?C#120?C?@#52!5?g#138!35?A@#94?GE#3?G#64!33?_#70O_!5?_?__OK#56@@#0A@#139??@#146@#163___O__O_?_?@@?B@AJAF?A?@QgEk@i#60!11?C!8?C#163___O_O_`BBB@FB!4FBDA?@#132!17?OC?o??C?A@@#28@!9?C#61!83?_?[CAPz~~NkXOOwofFAAC#119!4?@FKo???GO#65???B#84!13?O#128??C?B??A#105!4?G!4?O#133??G#119!34?@BM?W_O_???A@ACHCG?_?O#3!22?_OGCKA@#20O$#154!42?C???G#28A#88??_O?_?G_O???C#119!40?C{s{#116@?C?G??G?CGCI???@#105!5?C?E@@???@#146!46?AuPG#67?c#162!40?@#49?@#61??_OcG!5?A??@#67@#56!37?@#67C!5?C#161_o?OO_O_WIwsKk?KGGGW[Y\~[}ySCGO#154!11?_?_?GC?A@#72??_#95!148?G#154?_?OO?__!7?@???I@#132!44?@?BABEKKIDG?GAGpoP???_!6?@#67??C???@#37_$#94!43?Pq?SOoO!9?GOg?K?UJA@??O!31?AG#120?AsO#78!20?@?A??@#138_OOG#135!44?G#119??o#65!48?_G_?OGGC?AA??@#116!37?HC???G#162_??O?GGGHFSC?o#28!29?A#119W^KC?A@#132!155?!4_??@#120@?@??@!5?G#12!77?_#23?O$#98!46?_#129!10?G#134!71?G??C@C#57!101?A#103OO?C#0!45?A#28@??G??A??@#146!39?a??OG#59?OO???G_?o#138!155?@?@@!7?a$#129!132?_O#3!103?A#33@#28C?AA?@@#146!41?G?G!5?@#88!40?@#98?BBB@$#128!133?A#88!105?AB@!45?AN?C???@$#138!287?O???C??A$#94!289?BAAB@$#61!289?O#100@#136@$#129!289?C-#132NRNBFDALM`Cg?O!8?]w_GO_?GO_!4?_??o?W!7?_@!5?@???@@@AA???C??_F`O#61?B@F?NBEMDFMG[GKWHKEFB@!9?oB!9?!4@AAAKWw!4?_GA#68owW{WkwSiC!8?@?BFIFM?uKGIAD@A?e?A?D?A@#103??_OC#120D!7?_#66AC?W__!9?__???G?CAt}T^K#68A{c#64o_!9?WA@#163wS{}]n]qKrKzEhTaHC#162?@!5?__!5?AAA@B#65???_???CQL?@@#161??A@!8?CATmXvz]~}{ogo#68ADAOAA?A?AB@A#132?_?O???M?B@#69_#72!6?@#61!5?Maz}goO#135ADVM^}ws_#132?@IT~~}Lo#119@L}#134?Dv!8?BTa]goGg?_#138?@?NNF^ZFK}g#154???COgoYKJMD@@@!7?_A#135wA@#65??GS@B_#135!5?oCO!5?o?PBJ@#146oC#154@!6?F_#135?JMK?B@#146??oC@#129???OG?A@#28??G?C???_#119@AA?KWo!7?CGW#135BFEElEPNLzkJogCG#132??rm|~NVMDMFNFKKYKOGoO!6_#129_O_`O??_@?c?A??@#5??_!4?A?@#21O_W_ASHa$#119o!8?G?CNFFuPNRNU#120?@E@??@O@?@A@A_BGCB#146wFA@!4?WCIA@@@??@@@??A??AC?GwOGOM#59!8?@?A??C?@#120?_??__O?C!7?O@#65wF@??@???@???ACkS?_!6?o??A@!10?GOoc?WOo#69?@?@F@B@@!5?@?@#61???_??o[I@!8?O#161@AME\Tz{x}t~|~~^^n^nVnZdI@I#70!4?BFVB!5@#68???_CA@#69@#49!4?_??G!4?_O#66!5?@@EJNRRqAA??!4CEB@@@!7?G??@#162!16?@???G#52@A??W#70CcCCK?KGG#119???___O#138@O?CA@#103!13?@C@VNn|ygo_#154!10?@qN}o#98@~y#78G~BC?@O?BCiS?S?oO_#129C#146!4?O!9?OihSJcRC@A???@#134[SMSB@#132WCB#103s?@!7?Iu~~Nzn~|w[MNFIC#132_oGA!8?^wo???_o__EB!6?OGEEBB???BAOI[SWO_!6?@@AC???o#116??G??_#146!14?G!6?ABC?G??GO?O!4?_!7?@??o@???@@?@#16_OGGK?A@#24?C?aS$#146?C?SWQKo_OIO?_!6?@V_#138@]un]mum]{{w[[owKF#119_{sm?Yae#88OC@#61?O_wWowoo_#119?@??@@BEDA??_#65!8?@??@BAEBEEB@#132?_O!6?_?D#64??w}|NYFJMmuk}{wOgO!8?{MCEBFRFjTz~~~sj?BFENKwswooGoacso}w~Wz{|i}TKFFB!9?@CO_#131@#163@AIC@E@I?A#69!4?O_O_O_W???_ax#61??G#60G!8?OC#66oCA#131!5?@C@#69!14?@??KKKskCK?GGGWKMI?@!4?o#162oOCE#131_G!9?@#66!7?@BEUC_???!5@#146?_?_!5?Gk@A#59OGC!8?OhOM`[#138!8?@?@FJ^~}ti#129!6?A#89!5?{z~}n~{w?G_#119@@?CC?GGWo_#120O__O_?OO_#119!6?_oowmEAEB!4?oA#138wC@#59WE~tjYSB#116!11?_#138_??_oOKE@!9?FCooq[M^^H!6?__oGGKCFJ][{Gs___#78@@A?C???_#129C!4?_#28!27?_#129@#94?@?@A@ACA?B?PA?V?fPFJuMI[CGC#0__oOKMEB#37O_OI?CA#22G$#94?gO?_!7?OGo??OgoG#135!4?@??@?@?@ADB?FB#129???@!6?G#28OCEA?B?A??C???G?G!7?I?O#64!11?@??@#116?O?G?G!9?C#70???A_??_@??A#103??@@?ACC_O??OE@@#59@#69_?_#70!8?B?F#52!9?G??S?HA?D??C?A?@#65?ooG?D!7?@CG_#49@!5?A#52!18?_?PCBW#65??oAA???@@?B@#161_CiB@`O?HqCrCxUILuz~}~}}wsO?_@@@B@@@A?@#61???_OGgKAB#163?_ww[u!8~z{iPeGC_#69???@?H\y`b??A?A@#88?_?_???O#28A?_#67o#68?_Ooow{gO#98!47?@@AMC?WWoo_#28??G#135?CgO#98!14?Ow{w_b@j{E@#61??Gew?A?_g[~tH!6?AFB@#28??C#119!4?wMB#78oWe#129??w#103???@B@#119!5?wY@o?GCC#135?O?oOgO__#146@E??G?_!8?A???GO#119!31?AECMCGGGWWGWH?W?I_SG?O#132@??H#1!4?O??C?@#10A#19C??@?@$#154??_???@A@E@B!4?G???_G#94!21?IP?@?@B#120_???A#67C#138??!4A!4C?Co!5?Co_!16?_!5?A@!5?g#68!5?OcWSoOGO#28!4?@!6?_#61!17?CGWw__#70!9?O?G!7?O?GAG#28??_?AC#94iSAKO#70A#52B#69CGo_#116!30?CCC??A?A@#52?G#162W#68!29?GOWOGO?O???CEAB??_?GCA@#64!26?CGWo??C?CELNFD!6?_OGKKFBVn}~lQM_#154!45?BAAACC#78!27?Go#129??GC@#64!7?C#120!14?O?w?GCA@#98?oSJC@~~#28!6?G#154!6?C???_OGC?@!7?@#98?@@BAEKYxkVMKWOo#138ACGGwO?I?A?BCFFrb~~KPA?o_oypwoWoo_o_o#120?_#105@#133!11?O??O#88@a?EOIIC?A#23_#147O??C??@$#129???g?gO?O?O?_?G?__???_#98!23?~c\#103???_OcK?CK?GGWwwowo_!7?FK]w~o{wowWOo_oo?o?G?CEB!9?C#52!5?_#135!10?@AAW__WK#116!49?I!6?AG#68?GO#103!32?GO#88O{CK[O#64!35?_?O__?_oO?WC?B`OGEA@#65!30?G??G!6?AF!8?AB!6?AC`PO#132!45?@@@BAF??_???B@Fn^nPEBC@#134!48?gCbW#120!8?C#98!6?_}N~CAB@#103_o?_#120O_@#94!6?C???_!9?_#154!32?@!5?O!9?_???_#137??HO?C#7???_O#6G??A#25??O_?OI$#88!6?_???_#98!4?HA?C#135!32?OG???A?CC?C!5?O!6?@G?_!7?__!7?OOG??@!7?I#59!17?_#138@?CKGCA!49?_!7?O#67!36?_!6?E#52!35?_??O#116!6?_#103oO#59??C#61!34?W_!4?OOWGGB#94_#154O?@#134!155?B@!18?@CAA?_??_#28@!6?_!8?G#98!20?@?@ADFCFEDEGEGCG#28!6?_aOTIM@?@?@$#133!16?C#116!34?G_G#65O?_#94!7?@??A??aT_#88O#146!23?_WKE@???_A#132!20?@AAEA@@#146!48?OwA??@AK_#28!36?G!4?Ak#70!37?_??_??_O?GC#116!38?_??O!5?O#135!5?G#105!176?@?O_#154@A!4?O#20!65?__O__KA[`_SH$#154!65?@#129A???G?G#119!25?_oOgF@ME#154!22?!4@#88!52?X???@#146!39?_?G#103!90?OO?OO!5?C@???GCA@#133!174?@??O_G?_#102!66?G#35C??@$#120!67?G#133?@#94!30?GU?A#154OX#120!24?O#129!53?_#119TJC?_#138!38?G#56A!4?_#120!87?__!7?GCA?C#128!179?GO#120??@AC!6?C??CO_GO?S!8?@$#84!100?_#98?w{@!81?_wO#3!41?A???G#103!286?@??@@AP_oO??O$#105!188?_#94!41?Oo_$#129!230?_-#146_??_??P?@?OI_XAC_O???@AJFNCgO__#154!9?ogi!5?BCO#135AEKW_WwWooOo?WoKqg!9?@@AB@B@A@#132???G?C???_O_!4?GE@#65_?Bt?G?_?_?O_OwSRX\NB!6?n#64s!4?OCPAw~~}j]tjUO_!4?@BFFFNENFJAEDA@@#138?O?G??A#88@#105?O?SGAS#154?_o#65@Ao#69?@?K}M]MMMCVMLnQmQdDBFFJB@#64_Sb{~]!8?BC?_#163@A@D@DADGDGACHAG#66!6?_YD[Y^bw#64?BJgo#65_?@!5?@#51W#131Gs??_!8?G???A#66!8?o[pQM#52?`#68WvYCW#116@#120A??_#98BMZCGO#29?A#0M#64A?WWW[|ZmnvItAG`#103!4?@WSz~~~tIoG#138?jV~}#154???SnZw#98?{`!6?_OGGEAEAFCEKo_?@BACCG{wO!17?@B]~RkW_#135@CWo_!6?_og?N|Z|VSJeXC#132@Y@#154A!5?_???BCCC??C?AA@?O?CB#103O_[~vNFfum[K#120OA?G?_CY_?G#98@BBBY|rreKWwo_#135@@A?@!11?o???o?_OSGG#146!9?@DDAJD@ICA?@#5_#1_??CA#37_?OiAd$#94JUHUISG?A!6?@ADALFAGC#138??GRlATI?BCA@#146!4?O!7?B_#28@#103@@EKEECNKNN^FNBD#138Cq!7?@AAC??_?__A___?_`__!8?oA#135@#59Gw??__#68?CHA@#61!4?gC_oKB#120OBC#68???J~~zm`ym|F#65??@S`ISHhG?_?@C?G?O!4_oo???GAABA@#154_#119_[_??_!5?N#120B#103Ao#68@F]kO?o?_oOW_oo?gOgGwsWo_[yMg[A#65?`k!6?@??O#162C?COO#69!20?_?__WF~s#68s#70VK?@#88GCAQS#0K#36GA#161sH!13?_Owl~y|~|~~NbM@?@#64???Dzco#138@C?O#154O#105?CJug#5??@#72@!8?O#61!5?G@I~}}djC#135???ItNd~Sg#132?@~~~bO#119?F~!9?_oO?S?C?G_o!5?@@AC?Ck{WG?CAD!8?@BM[_??BAO_#103A?KOowwgVw^MVnOACA?GO#146??_o?C#134??WK?GLBCG?o___??_?O#129?_?M#138_OCF#61??GoWWGO#138???_|v^S??C#154@CCW??_!4?@?AC??O#120CAG?@#28!32?_?AOsa[NMMBB@#16_W?KE?@#24OI`GcBcHQdQgC$#119SGS#132?SgC?CAC?C?@I!8?GOBCA\Io~{z|}~~~N!9?I_#61?A@B@?B?B#28???_#132!4?@Ag!7?_#103@#88G#28_!4?_???_#88A#94???O?HA@?@A#28?CA#61vC#64I~V^^zQ{k^nFjCA#103??ok!4?^#69!4?COM@#59!10?_?O#103NI??G??_#68??@???@#116??_??O?G?C?@#84??G#109G#78?CGA#129?O#132K#135K#64@Mw_O_???O??_#70!18?@#61??R[!5?A?O_#52!32?G#61??BR_#146Ca?DB#1O#9_#52_#69@#163A~~^!8~v~^nDQ?DA?A#69!5?kp}]fG_#65?AKwo#28O!8?D?O#68CCEFBAC@#127!12?A#120!8?Q#146!8?G?c#134??B]zC??_o[KFC!4@?BHAKYyaCCO?o#146AA??A??A@??G?_Os?[Qk#134??@?kO_#138BEG_!11?O_?_!5?ARID#98Oo}fopOOOZv{GOOOooOog{]E@@#135_gWb!5?@@brn\A#132?_JzdZ{ow__!7?@@A?KKGWSgW@gQ_!7?@!9?P`EJRNJFMAAD??A@@#67_OO?G#6O?GCA@#20gOGGsIfQsBudAdVi$#129?_A@?B?IOGA?@AC?CGcQGCO#120???_!4?D#119!9?FTy_A@?[w#120C!4?O_?_??_?_??oGRK#146xC???@BACKWsWWW[[[USUYPQPR?_!4?_oG#103w[#70!8?C@A#59!5?_A!8?O#52!4?@#61!13?EFo@BEAGOW_O?O???wwSOKKKCA@#132C#98_?kcOjqth~#138??o#61K#66???BB@???@?B!4?@@@A#103!14?b!6?G#66BA?G?_#103!32?GO#138o#94O{G#29_#3a#48O#66C#162A#61!36?@AC#146@I?_!8?_#36B#69B@#59__?_???S?c_Cad?@#78!26?CzC_AG@A?B!7?@BDD[wwGo#154A???ACv]X[i^FVBBHRb?Oo_!4?CG?_#127A??C?A?_#120!9?gBcXcGC?A#78???@MFAK#132???@AB@FBB@!4?oWKB#67!5?_#146!14?A#119BAC[g!4?@AECGWo_#94_#28@?@#129!39?@#0__OCGEB@@#12?@#21?_???O?GO?G?G?P$#154?@?G!6?@#119!5?@A@#94!24?D#98Ux}~_#138?@[wo_#65??@#129!12?C#154O!5?CG!9?G!5?AG!8?@#135!22?ON?B~_#116!22?S_G?O_#135!11?_#103O???CA@#161!20?@@@?@??@#28!21?f???@#84_#60C#70G#161@BEHMi}y|yvyv|zu|v!6~^dYbD?C#135!6?C#28GA???G#56?C#68@#88!40?AC!7?FWO#60GW#61_#65_!4?O?G`IXVQ[O#89!30?z^\FA@#129?_WG!4?O!7?A!6?_!23?O#61@CALIBDUGF?@#28!7?_#119!5?_gL@!5?__??A!6GKCA@H?A#94!27?C#134D?CKO_#138??@BAA@CAUe}Vl^!5~N~}~N~^njvv~m]xskoswowwWswk?W#88???@#3OC#147_??G??@#25O??S?H?G??G$#88??_?`?aSGT?TACg?W?W_OwdOo#133!19?HC#119!25?A`U@E?O_O!9?G?CGKKCLJO_ow[D#138!24?_[w#120!25?KoO_!13?___???A#52!21?_??_?G?AOC?COAG_GS_CPB#135!6?W#146zU?EKO#120_#69C?Oo#116!34?A?@#67@??@#60A#103!41?G_#94EKP_?@FW__#56C#67_#154!54?__GwWo#89!8?_#132@@??A@@?`__O_og[kAk?k!7?@CGO#59@@A@C?@#133!22?A#129?_#146!6?@?E??C?@??_#129!29?O#78?A??G#146!4?C??O__#10!45?_!4?A$#137!7?`?_G_G??O!4?_??_?_O#94!44?@]`UGsGO?_#120?ECe@A@@B@@#98!4?ACE[MFA#132!26?_#135!26?O#146?_#70C??G?W?WCK@AHE?@?@#94_?O?[QBF?@#138!43?C#94`}O#68???GW_#120!35?C#132?G#129?_#119!45?@@?_#78?O??_#132!60?o?_#105!10?G#138?@@#120@#105!108?G??O_#11!54?OO?C$#28!8?_?_?O_O_?_#98!55?GgoG_#84!21?@#146!76?oOWG?A@#132!48?G#88@#119GO_#132!90?G#134???_#8!248?G#19?_Oo?CCA@!7?O$#98!233?_-#28EAFMADMJDEN@NBFJFFADICMKI[jYwywoso__?_#88?_#119@@@D\YcOVny_#138BBRr[yKwswo_CYL[]PC!8?GB@#116@#65?wsK{ssKCCK#135??@@?C!4?A#65?@ECC?@FEN]LANMFBA#28_#135?G?B???~#64?{m??_Aosx}Z}NzJSJUnZU#61?u!9?_pOG??!6G!5?K?C?GG_!5?DW#68??XZq|YFJ@?IEBBEBBEJDG@DB@?@#61?wD@!9?@??__#69A?CKwwOoWgGOwwwooa[BmTYMBVBA@A#61???@CO!6?O_#69GOo_!9?___oOoOoWoWWWMNFBF#70!4?_G#138?_O#146UC!9?@?C??__#72??@#103!10?A?L]N~zu`#138C?it~zS#154??_IxKBC???_??CEA@???@?@@BF]{w_!8?Ax#138?Es[q??_#120GC#154@aIdYV~I!6?@AG_#103@BAVyr[fGA#132!4?}{K?@???_oOGEAB@BBM}So_!8?ALGO_#103BBB@BB@@#120??@!7?@A!8?_#98BBFKLQLGsGOo_#138?@A@BFN^N[\]}^VNuRobn~~^fv^VfJVJDF@#103C#6?_OWCA@#18??A#19A???C_]saS??GC$#0w{wo{woswwo{o{wsww{wswooo_O_#138?@?GGCSGO?_#129?A??A#98ACJNG#120!4?G???DOAHACY`_Q`_?_#119?@@SgA~G#135?{!4?@#59???G_?W?G#28???AG???@DW_#59??@Ao?_O?_O#103__?_OWMDB!5?F#68?P~~^{NJE@C@#59o??_?_#103!5?@!6?@@O?HE@K???CC??!4C?C?CC?O!5?I#69!4?_C?C#70!5?G?C???_!5?_?pSD#120?o?@!8?C#64@BCGo_#161@@B@B@B@@@!5B\BC@A#64!7?_GEz{O!7?G?__#162??O?o_oo?W_G!6?A@#52??_??_?G?A?C#61??OoN#88_GA#105?GO_@???_#138?A?C???_#135!18?CHYZvTI#132?Cj~~^CE!7?_OWGKEFFBMN{{{w_#119BCW_!6?CS#132E~xB`L~~^vz{KsG#119???tw!5?AC#138@EC!7?CHV}|}@AA@!6?_OWKCM{{o?_#134?@?@B@\cO#146?@#138A@GOwc_??O_Oasy{|~~zQtag#146?A??RcI#133???C?AA@AOI?g#146@@@?C?GG_?o#135BA@@?GOHKDKO???WG!9?A#1?OGCA#16_WeYdP#20MPIPG??@GZdrZa|ZuX~$#88@@?@?A@?A@?A!6?@??B?BDA?D#154!13?GCG!6?DW#135!4?B?BDADJ@YD_A@A#146B?S_!4?Su#61?O{{E?!5AqbQu}WK!8?AXzy|MwW__OgOOw[LF@A!6?wB#69!4?@#65!4?_??CoJsHOch~H#116i!5?@?A@A?@I@?A#146??@?@?@?@??@??A?Cw_Y_#28_#52!6?A@??A@?@C?@C?@!5?CA#65?_wEA#146wC!7?@?_#68?BEK[wo??_?_Oog!9?__OggWs]TvXC!8?ACGO#131!7?A?@#68!6?_?_?_?_?_oOWs?\FJA@#65_K#116O#28K#120@@#78?O@@?EG{#116??@#103B?CGGOo#116!19?C#120_G#146!8?p?B#98?a~}[KMAB@#146!7?AA#129??@#98?BE[o???C~x_#135???G#146A!6?AP@Qdg#98??FnS?BE[w#129o#135@I{|_?G@Wrsg@A@?@!9?__Ogo#119!4?B?KGo_!4?FKO_#135BLCW[}{k]m\JCBA#132??ClI[T~|~~kWosO___!8?!4AHEC!5?__?___?_GO#120!7?_#116!4?O#0?__wGE@?@#37cWcWkpmtaT!4?C???G???C$#137!4?@#146!14?A@!4?@??CCDD?GGSgOO?SqG!8?C#28!11?C#132!5?kW~!6?a@#103?kAA@A!6@?`@@eq{o!5?FW_#64!5?@?@?D?@#120??_?oOK?A?c#72!16?C#28!8?OG#88C@#138@???SK!4?A@!8?A???AA??C!4?A#116O#69!21?A@A#103!7?GA#119O??@???@Eo#65AKWO#162?@?A?A?A#52!11?_OGC_S?cH_G?_!9?@#66BC?G?W__!4?__OOW[GKIKKCLEDB#64??_?w_wo|]FB#103?B#154_#119GA!7?@AK?__#64@?BAIDEHeW?S?_#119!22?o{X!5?C#135?___O#134!11?@BKya^z#129?AH#134!20?Oj^kx_#132@EWo#61??G@C#154!10?oWM??_??KE@@!4?@??KOo!9?O_!26?BCG!5?_#105C?C?O#88!7?O?O??_#28!8?A!4?_??_GWsgsiwMPBD#7_#147_oW??@#24!5?GA_JSB_QC_TAcHa$#137!22?@#132???C?B?AA!4BFNN^gCo_?_!5?B{skK_?_#129!13?A?@#133A#28!5?A?@#64?Go?G?OG#146!6?@@?Z[q_#67?C#70!9?A#88!8?_?_#119_#132FJ#67!25?C#146vZAAACc#135??KC?C?CCC??CC!9?CG!4?@#88!33?C?@#105?[a\gO#84G#28A?_#66?@@AA?CCKKCEEECCCKK?_W??@@#65!9?Ag!5?AC#161?BCCG?GWGWKG]CNFF@FADB@AA@AC@#98!14?_wdIC?@DAM[oo#61B???KGog???`?@EBL\~q`o#134!20?@BA@@#78!18?BC\_!26?_O#127!9?C?a#120!9?@#119_o[F?GG#120???_O#146!5?G#78??@A?CAG_#120???E?A#119!26?@BN[WWO??_?@_A?G?O?G?_?_#67!26?OGCA@#3A@#10C#25!10?@??_?G$#94!42?AO_?O__O#154!22?g[#98gSt#138!22?Aw??GW_#146!20?O?[G#94!28?_W#98O_#154?G#65???_OOO?OO?O?!4G??G?GOo!7?fO#94!31?g]fA@??A#132?G#103KO#67!36?Bc#116O???C#154_#60?O#163BB!6F@F?B???A?@#94!23?ODA!5?@#132???GGO#129!41?_OOG??@#89!16?@#146!52?E?B#129_WSC!9?@#28!14?C#94!33?_OgOb?ECKS{go__#12!35?G?A@#17!10?G$#133!45?@#94!27?AA@G?@#88!21?A?AC#129!22?_O#154?o#119!29?cK[wO#64!4?___O__o_OoOoOwoowo_!9?n~eCH?_ws{}to!4w{Wswu{yW{MIA@#116??A#98_W?KADko#138?O#28!38?Ag!5?_#134!45?_?q?q?O_#154??O#59ACF?C?QO_PA@A@Ka_#78!26?@#138?__OOWgw{oo#98!65?_WFBBB@!9?BAEK[y_RNwo_#134!35?@#129!5?@@???C?O?O$#132!100?Cc_#138!24?K@?O#84!29?_#120@AAgA?A???A@BBAA?AA?AAA??A??w??@K#134!37?_O#78_Q#103!42?G!5?G#128!46?CQK_#65!7?@A??@C?hUGC}gwOO$#119!100?@#129?@#68!68?_!4?_?_?_#119!7?BBF[_#146!84?@@s?B?O#89!47?G#95?O#135!7?GOO$#132!176?@A!7?@!5?ACO#138!84?CG?C#56@#133!50?G$#154!178?@?@?@@??@@???@#88!86?A@B#119GO$#129!279?A#94{o_$#132!282?G-#5d@@?A@AC?O_???G?[G?K?aECIC?GOGO?O?_#138??@?A#132??A@???O!4?@BNUQtiDIOpQ{{y{}dH_!7?A[_#61??@?BAEEB@A@#67?A#120?_??DA!6?G!4?_???GSGMB!9?H#61@k#65WS_GoCoISiTipa`W]LMF@#135C!8?I#61n#64MNoAhO__YoM_`theLt~NJ!8?MB@@!4?JDjV|v|AFG@@@?@?@#135OoGG??@!7?O?G?GH@#59?@#64BFCLEYSslQ_O_O?_O?_co_gOwTWt}UZF@#146_WA!8?G#64AACgwooo?_!5?w?okUk}~}EjJRDA@@#138__?C!8?__!6O!4?@B???GO#64@#120!11?O_gQs?H?C#98?_??O?E@A@#61oG?gWG#28???G#132!5?@@BF]{o_#98@nw???fe_#132?v~}~|}~~nM~{zsW#119o~D!8?C??o_!9?_OWNN#135??_G?EBOTr|rt__#132ADBVF]}{o_!7?@BAE[{{ggo_O_OcWtmXny^vNNVJL?@@B@?QAgKC?_oo_?O!4?@!5?A?BCA??A#138?!4@#2_#7_oWG?C?A#43O?CGC?CA#20!4?@???KQjUGDoKZlnZ^^$#1Qiszk]{z~j^{miUYbvnr^WWysW{skOGs__#146!4?@?@B??CP???C??a[_g#120!5?LEKABDA@#146Q?R!7?@_#103@@BAEKKGGKMLMNLN^B!9?@BEEKCCE?ABA@#119OGISOG_t~#135?A#67O#64@FA^uFzBTj@iDITQ`?A@#120?_W!8?@#65?p#68oN|E@^LdNPNUAQHQ#65???o?@!5?G@#69_GS]yT?O#65???AGA\WvKESIEAEEBBB@#132OC?_!4?G?C?AB#68!6?BA@DJJQkLiCgWCgSIG?V@mAiFG@#65?_?GB#94_O{D@??C#135@AC#68@@AECGG?oOoog{tA}NRhR@?@#65wOSKW[KI?B#146_?E@!7?G!7?_!4?C?GO#59A???CBCA#138!10?^EB?@!6?o??C?@@@???A@ABBVn}}{_#154@BN[O!8?FG#138?@?A@#120??O#146p?ACHA#98??y~A???@BfGoo#138AEMMKGGEBB@!6?oGCA@!5?KI^^|y{gw_#98?@BBLSrnIcWoO_#120@#135???@?A#120!17?A_#135_#146??EICKSOowO!4?_?_!4?B@B@H@G@`P@?D?A??A?@#102_?O???A?@#46!4?G#18!5?CG#24!4?@!4?_A?C$#6GOGCO_!6?OS_???O?_!4?_???_#88!8?A?C?HEGAcGOGokO?O#138@LITytaG`@??@#154?GuK~s!4?Q{#138@CWwo?_!5?O?O!5?y!5?@?CGG???o?o??_#94O?OCGaOA#138???s#103m#59Q__!4?G_?S?OCGCE@O#103__[@!8?CO#69!4?Om?A#70???O?G?O_A?oC#61~}!5?t#68oKui`DiVCySg#61???__?owgSWWGGCC#146?_?oBO?O#105@#134@#116?_!5?G#70!6?O#69!4?@QCHFFJB@D@E?C@#61!7?wE#138O#28C#119_?A!5?OO#103Gw#69@@BECKMKKNVBID@#52!7?@#70C??A@#103_?WKE@#98_ssyYGKCEBB@B@BBFN]UWsoO#65@B@??BB?A@@@#132!8?_O[JMB!4?_?GK?A#146!16?W_#119??BmO!5?H]#154!12?@?AdN#134???\dACi{Wo#132BFKWOoOosWK[EFB?__oKE@@#28!4?G#120A#119!9?@@AKC__??@BCK_#138@?BBBUVL^n^nZfIPeOD_Googso^]}{wsgo?_#154A#98@A@@EDAHA#154??_#88!4?A?ASAGCyKQG?I#147!5?__OwGCQ?p?_??_#19!5?BE@???`YA`??O?_$#31?C!7?C?A???_!6?_!7?_#129!13?BCG?c?GQC#119!20?J~Krul#120?AW_??_#28?O!8?O?G@!7?A???O_OG???GC#116??@#154O??_???I#72!4?G??G?C#61!7?G?__OWA#146_`E!5?G#52!9?O??_?G?C??G#103!5?O???W#67A#52?O!5?g#70_!9?A?A@?D?@#120?O?GKI!4?_??G!4?C#52!14?@Q?_OCi?Q@GQ?D??A#103!4?OC#67@#88DB#98WQBBQ{_#65@?CgO#66?@BB?BB#61!15?!4_QSF?@#119oWI@??o?G!5?A?KGO`@C?G?o#129_#154!19?_???GMR@_O?C?A@#134!23?FuhvWO#129@w#78!19?_Y|rS#154??BKG??_?_??_o_wOKF?OKA@#61O#154!20?OO!5?@AC[o_#119!28?@@??@?ACA?OGC?KPISP_O_?_#137C?O?GC#0???_ooW[MCAAA@#16oGCyApY`OCOjCW_O_#25!6?@O?O?_$#0??A???@!4?!5@!5?!4@B@BBCFGM[[wwwoo__#119!4?AB??@@#137G_#98!21?BK@#135???AECKWO_!4o_o_o_?s}!7?@AC?G?G?GKC#88!6?A?@#68!8?@?@#116!17?A#28A@!6?_#120!24?gO_gA#103!22?___O?G?CEB!6?__?OOG??ALWOo#70!16?O?G!5?_??hC#116?_G#120A#129G#134?_???_#61?@AC?O#162!5?@#116!22?_#135?O#120WI@#78?GC@???@#28?_#135__?__!6?A!4?OO!8?ooG[ShG#119??__oOoke^@I@#59??_O#78!23?HUG#89!26?G#135!6?!4@AFB@#146???G!7?A#94!20?G#134A@KOcW#129A?G#146?G#94!31?@@A@C@KKHQHUPEtIe[i[wSw_gS#28a?`gu[CMDB?B@#3@@#11?C?_$#2!4?@#7!10?C!5?C#2!4?A??A#94!16?@?JRE#129!24?o?G#59!7?@???@#132!11?@C??@???_!8?_???_@!6?p#138!25?G!7?O#28!24?A??O#135c#116!25?_#138_?_oO!4?__?OO?GCCCA#105!41?k[{#132@AC#138?o#94!37?A@#129_?O!4?A#103?_!8?@ACCKKgwwoO_oowKNfBBCB#146???O?C@#129?G?GC?A@#127O#103!64?@#98!7?_???@#103?O_{w{niC#78!16?A??O?_#129!36?A?G?G???_?_?_!4?C?C!7?O#1!9?CC?A@@$#28!29?@?B@BBFFEKLY[swogO_?_!6?_#65!30?@@#146!12?{AG@EEK?Oo_O??o?o?O_kE_`!5?C#88!25?S?@#98{?~o#129_#116!25?D#138i?C@#154!31?_?w?O?OGCC?A@#78!44?_#128?G#154?G_#105!40?C@#132__OOGGKGG?O!6?G#135!32?OOGCAA???Htcw{{gO#129!60?_O?A@#105!24?G#133!47?A!5?G??G#106!21?O#12G#37_!5?CIDIdZnSzeZcXqlSgU_KAC!4?_$#154!47?C#88!54?O_#98??_#61???@@BAB@B@?@#98!5?KE\I#119!28?oG@??N#132V#146!26?@NA#119!34?CNMCKC?AA#65!6_?CGWOg__#134!79?EBB#120!8?_!9?_#103!29?__??kSev~uGQD#35!174?GC??@$#129!102?g#94CWgOO_#59!4?@#129!11?@#94!31?GuAT#132!29?CO#88@#98!36?@BAAB@@@#154!94?OGCCC?CC!5?A#94G#61?@A??AFC?Kh[MMFB#53!208?_G!4?O$#154!102?@#119ReOG_O#78!51?I#94!69?G#61!4?_?OOOSQeg?__#105!96?G???_$#134!160?_#84!182?_#134_#28??C???_-#1|Yxl~Sztjf~vzl~|jnSnj|vXm|~iu}jzsV~^yveUYk{g_OOo?_#146@?C@???BC@?G??C?WGgSHqg?o!5?D[wo#135C@ItBDJFDJADAL@I#132?oK!6?o@#103{[CEAAE?E?EAAAECCo_??@EGOo__?_?_owOGGIDEA#119__?sF???_?B@#103w@#64Y~}x[q}|!7~nVn?I!9?l}g{sset[}NReNRcG#103?Ap`?@!4?_GGC?AA@@#52_#61!10?@B??wP?EC?W!7?O!4?_OOGKMCF@#98?ow[A_pYGCF@#65_?K?@!5?AAGCKCC?C??OWWZMNFB!9?__?OO?WcKuM}kww#28?@!9?E@#61p_@Bj~]cS??G#138@BMO!9?AHO_#59A@#135!6?i\~~vMxA#132??gU]zJ#119?u!6?_IA#146?E?C!6?G?C#134!4?MADAB@A@??@@AEII?K?{Gs#129?A`#138G]qC!8?_?gOitz~^vNvHA?_#154@CMOo!6?@??CIO#138??JCBCXqw~oty~z^nNwr~NJN!5~}}kya{coCoSogWCW?G#0_oOG???A@#53_???O?_C?@?I?G`S??QGC@?G#20!5?A?GbW?A@??_?_$#5A?C???C!9?O??O???AO??CG@S?A???@??G_O?O#28?@BBBFEHAKM{Y{ySoogogO_?O_#154?D@?@?O?@@A#120?B?AGO?GqC?I_C?HOA#146??Na!6?KC#61?_w?KCWKwKw{{{gww!5?@EGGWWoOoWG@KCCCA@@#94OG?A?B#133c@#129?GC#135_@#65od#68?@E`L@A#65!7?O_O]D^E!6?y!8?_?o_WokIus#135?KO!6?O#65_!5?A?A@@@?@?D?@AGCkW?_@?AE?woogO_g?owowWGKC??A#154_!9?_O?C?@#68??oW{}WO_O!4?!4@#138!8?_OGCC?@?_?GGCCAA@#120!10?BCO!6?W_#65M]ysS??G#120??@?AC@_#119FZo!5?Oo#135E#103HWo_??OiV~Ta#120??G!6?G_#154C_}H!8?[@@A!6?_oK?E@!4?_O?G??CKGWO!5?@!5?Q@#146_#135GzZ~woWo{z^~VmTIC#132?_Go?Q|~^}wo_!9?@B@N]LSz{zeHF?NID?C!7?_!6?@?RCWAYKwEH?EA#154@?@#67_O?G#5_OGGC?A#1@@#43O?iCOgS??C!7?_#19!9?_D?CfWA|VN\\$#31?C?A?@?G?O?G!4?COa?S??c?A?O@??C?g?_C?G_C!5?_?_#137??A?A#132?@@??A?@??@IFVFJuGQCI!5?ObC?_#28??G!14?@!7?O?A?@!5?A!5?A?A?cA??O#65CFE@KKFFEABB@#138_?OG??@!8?WE#61M#52!4?A#70!11?G?@_#116?_#135GD??@~#68?Q?VAJ?GA@???@#61??@@J|!9?O??C!4?@#59!10?C#103?BFDGm?Ww_#59C#68@A?@!6?@#103?__O!4?A@#94GCB@#133A??A#135?_??C@#52!5?_#70_#59??COQG??G?GG?C#120!5?_?GA@_#98??B@@#64___o_WoGo?O#138!5?G_!6?@#64???CG#67???A#28!4?C#132??L_!7?FLO_#138!14?pE|~~V`#146@?S@#98?~N@??I\T#132_}WlZn^~^~YDBB@!6?_OOGWwoo__#78?CCwow?o#132???KU@@#120?_!10?@#146!7?Gc#119!4?B?K?o!4?AAFMWo__#120!6?C#135!7?_OoFK?oSo#146!6?@?@D@?BAGaI@CGE?A@#3?_?GC#12_O?G???A@#37???_O!6?@G@QgU`QnsJ~g^qKbW`W?_$#6?`AO?g?ASG??CQ?A??H??AG?@??@!4?H!4?GO!5?O_#88!6?@?@AC#138?@GNEVNQd?_#119!4?CJ?mm~umG#138??NXudAsGowoSxysa{t~!9?IB@@??!10@?A?C?HCO_#64@?@A?@#120!4?__oOOG?EB#132C!7?oE#61!20?_O_X@!5?D#69?@???@#59!9?O#116???A?@#132o??aSEFB@@#64GKCECEUM^]^y~}|qwO_???@@@FBMLTmUVnDEAEE@B@B@#119??_o!6?_???A@#64_o{MfB@FN\lrh@@AEAEFFFBC@#132!4?_??G?_O!4?A?@!12?AG!5?E#154!17?AG!7?G?_#61EFL]~~nTg#134!19?osGttA#129_@#138?_O_O_???DA#119oWGEB@?O?G???A??C??_??@@?B@@DT?_#103?C?C?FNfNBC#98!18?@BNMz]XNwKWO_#146C?@Q_#88!32?@??@???@?@A??A@#2O??C?A#10?C#16O?G?OIP?IdGbSAKqDc@AhUHOJs?T#18??O#24??C_AGOa$#8!5?A#0!32?@@@BBFNMKK[Wwsw_o?_#129!16?O??@#119!25?O|YSgOHB#135_??A?@??A!9?KW??AG?_!10?O!5?@#154?G!6?oG#138!25?_?A?S#52!5?@?A@??@#120!10?MUABA#68!4?_?ooOgwWwgo___#116!8?A?O#28_#70!6?A?H??I?L?@E?A??@#146?OKE@#105_[D??D#61??_OO?B!9?A_oGGOWo___?O?GCB@!8?OO?GGCAA??@AEF{w#154?@?O!4?O#146!18?O_#134?KQmw_#78!32?IvI#120!11?_#98!4?_ow{}pLICCA@?BBAELXppEAE?EIyg#78!34?@???_#129@!7?_#137!35?@!5?A#103_#6!6?_oOGKCA#35AE#11_#14!10?@!4?G#17!15?C!4?A$#67!53?O!5?_#88!13?_#94CP??GO_!22?AdADiU#59!5?O?G?o?_!4?O!8?@A??CBAG?@C??A@#146__OGOA@#105???A#146!28?O_Gw#70!8?GOGA??K#138!9?gC?@@`G?CAA@@#135!18?A?W_#52!11?@#116!5?_O?G#135?C#88??A#134?_WK#78C?B#132?GE#103GA#72!12?A#103oo!6?__OOGCAB!5?_???GCCA??@@?@@?BEw!9?[?@!4?`Pjy}sW__#98??CNrdPF\_#129!56?_O?GCCC#134!51?C@eO#133C??_#28!43?CO_o?{SMKEFEBB@?@#7??@@#13!10?_#23???O$#154!105?_??_#65!5?goo_??O#146!5?@@?BI?__#28!15?G?C#98??oGw{Zs^F#103!26?E#120I@?A#146!24?GCCWA@#59?OGGC#69_O#120!17?C#138!21?_O?G??@!7?_OOG#88A#116!16?_!9?O?C#119_??MMNCEA@@#146!15?C_!4?G_#78!21?G#129??A#78!59?@#135__#138o_#94!52?_!4?_#129!44?@!5?@?@??@#147??__OwKgCSJ`CIdOrSIGrK_A?S??c$#98!105?HAD#132!20?@?@_O#88!18?_?C#134!5?G#132!29?OCFg#119!25?wg#72!5?o#120!44?_oOGA#128!5?A@#129OGG#135!20?_!6?__?G??@A???OO?G!4?@@!8?Co!8?A!9?D?B_WO#105!129?o#102!60?O??_!5?A$#133!106?O#120!23?OC#119!58?_#154!28?OCG#146!94?OOgS?P#119!21?BKo??A_$#88!131?OG#154!57?O!127?O_O??G?CA?@#98!15?@M{l{$#84!344?A#128@#134@O#94@@$#78!346?A#105A-#1~~^{vlt}ji^~Z^~^G^VVz]v^|^]z~~]VjHj^|uUy~[}}~}y~Zz~T]{}wWoowOW_o_#138?@#146A?@BACGaHQc???@AJmG!8?A?C!4?AsG#119N}wf^#135??@EKwo_o___Oooowww[IQ!6?@???@@?K?G??C??C!5?o!4?C#64?@FJFuJVj@BGBEHAD??@#138_OG@!4?Z#64??vLzFzF\jUdJQl?`IT#103??g~W!5?Ew#68?N\{^|y~nRDGrIsG@C#65?S?@[sC??O!6?_P?J!7GW?O?OOo_!5?_#68_{CJBnRj~~c@_H#65???_J}gG#120BB@A@?A?@@?@#61G?CA#69__!4?_#59!8?_OG#103?A~M#120TA#132w!4?zG#103?U???CAFEMN~dyw#120ITiO#98?BF}O?F{_#135?BOw???_own^nJTI?S#132?_{]B#98_wNb?_?_fnO__!8?_G[FN@@#138?OKGVg@iZr}__#146C#154GACGO__!5?@ACGo#135BEAFHBTaP?G#132???aS`[_Yd\j!4~z}[oo__#134@#105@AC?CO#129?G??IO_O#137?_#135?CAECIDBA@?KGCGG!6?OOG?C???A#6O?G?CCA@@#53?O??O?G?G@G??@GaDOH?Ag?QD??h??O#20!5?A?@G?Cz?jC_$#5??_A?O???O!6?D_?G#62!13?Q#7???@#5G???@@?@@!4?I_A?C_G??_#67??G?C??G#132???A@CA?G???_??@Oso???_?T?G_WsgY|B!6?B~o#103@BFMYK[[[kKKMEFFBC!9?@`OO@?W?!9GWO!5?BG_#59GOG?_??_!9?A#28O#146_?IB??S#59!11?G?C?AG?_#135!5?e!5?G#64A^o_?_???OkgvKsJv}z~j^a`G#103?BEKB@AAC[G!6?BFE?CCC??G?GO_??w#59[CB#69ws{OkS#70??@?@#61!6?@EO_#132C??O!5?FA@@#28A#52!5?_#61!11?GD~|#135?pi!8?~#61hhEjXlwXPo?G#138?@?AS_!9?@Cg!7?O_Osgtzb~Y@#119__YF!8?KWO___???__[uB???A#103???_gO#28??C#119!5?@CICCGO_!4?ACGO#120@?@!4?_??O??O?O?G!4?_!5?C#154@AM?GO?_!6?@!4?G#28C!8?A??O!4?@!4?_CwAgOcx?FA@A@#12_O??C??A!4?O#37!5?C?G!9?@?aHCQlFiVmTyliUtIyCzC_G$#6???@GA?@C@#0??_??_O?_!6?_!4?_?ScO#8!4?@#6?@!6?C??_#0@@@BFDNEMe]EGWG_o?_??_#129@?C?OKOAGS#138?FJN~~^Swi\VZfBD@#154?Gvo!4?{#138?Mwo#61?@C!7B@@#138??__kg!4?@?AqKCCE?CA@A?C??!5CG???EW#68!5?@#103!11?_OWKEB!6?^#120!22?@V#154T??_#59?D_#69?AB?A@#70!6?@#59!6?_[AAoG#135@B{A#132W_#64???_Oo?O!5o_o_o__!8?W?A!7?Q}]u~~~^s?O#103CWo_??@@???OG?C??@#138!22?{!6?v#65??Ux?AO?__#135??A?Etg#146@?c#134??@mRw#129?G#103??FE~|}^NF#120!4?A?CG?DA@#154WC!9?A??OOO_ooW[A@?_???CB#129!12?A@??GO#78?A?COG_#138?@@ACW{wu{I\mNv~n~LjUb]dYAS!6?_#98@@BDMM\yfWeG_#146@@DACGSOoo___?_?_!4?_!4?_#120?_?g??G#0??o?GCCA@@#35WwC?@@@??@#12!10?_??@#23?A??A#19!12?S?At@?COHV$#31!6?I?OC_?C_??a?G_C`G_A?`C??@g??C_AG`C?a!4?C?_C#3!7?A!8?OO?_?_#154@#88A?CG?gOkOs?O_#120!4?_JF?__??GQc#94!4?@#98FW_#59!7?@#120!12?O@!7?@?AA??A@C?C#94?@!6?A???G#116!20?_#135_?G!6?_#138!24?g???BO#52!7?C???Q#61!12?@Jsw_??@@BbVMNCEFFC??C?!4G?GO_!5?A#72?@#52!7?G#135!9?@A#116C??@#94GCG_#154?K#67_#65O!4?A@!5?@?@?IOfYFbq#146!5?@C#154o???C#59!5?S_#116!6?OD#132!4?KO!7?@Mw#67?@#61?A@#146!15?C@#134?oKtOkJWO_#132FNG?G[LNFB@???_oWGCB@!6?@^^zswoo_!8?@?EKw_#103@#28!6?_#146!8?@#119!10?@?MOGoO_??_@aAE_#138?@BABNNJSXQdW{\i~rvZuv^~^]ZFDDFBAF?@#5_?G?C?A@#102_Q!6?O#13!11?O#102C#24!25?Q$#28!59?!5@FBELF][\wSWo_O?_#137???_#28!45?@#146?Vq?B??AWC?G??@?@A@BAAA!5?CCgUo_#132!22?O!5?C#146!25?iD@#120!32?GcSg#68???_?o#69_#120???@@A@A#146@#154@?AA??AA#138!23?@?GO__!5?GCAA@#119!25?BM_?r#154!20?A?_!6?Eo#78!25?OINRS#129??@#138??CFF@A#129!5?_WOMCB#98!15?@B@AKWojcBKwO_#94!33?C!5?OBGTSW[o_??_#103???@?@!4?C#132!6?_??@???A#67_??oGWCGA@A#11_!7?@`?@GOI@G???G$#2!64?O__#119!9?@A?A#132!52?CG??Ac_#28?O?IG?A?C???C!5?G!6?O#119!21?_O_N{!28?y{#138!32?s?GO#70!5?@#135!5?A?A?CC?C??O??CB@#59!20?_#146??MWEA?BAq#64?_oWWK[ENN^n^}~}~tnWdWC#98!8?@^l#119!21?@J[w!4?BO#28!36?@#146G#120?A#135!10?_ogoU?F}T_K#134!8?ADBDNOIcO?_#133!35?A???@!5?_#132A?C?@G#120???G?GO#103!19?O?G?C?@#10_??G??A#16_???C?G_???_?O?_DO?CAgCQK_L_QDOgTgPiDO@g$#94!77?@F@BK@#119!49?GASCOG#61???__oO?!5Oo!4O__!6?EWo!5?OG???___w[KCA@!7?_!9?_???O???gO_V!7?@#138!46?@??A?C??GGo{A#119!26?C?gcC{#135???C??@#129!27?OK#78!25?@k#135!39?A#133!38?@#105O#146??A#78!40?G#88!7?GO_#2!31?_O!4?A@#43??G_AG?CAAfD?CaDOA??H??O?_??O$#129!132?@_?G#65!8?!7_?!4_!8?@E?coGSgCU{v{XU\ABB?@!9?~GqCwCwaS@Yol?v]TAn^#132!6?A[_!48?@?AA?CCCK?@#98!28?OOW#68!5?__o?Woo?O#134!21?A#7!195?_OOG#106!8?G!8?_!9?C$#88!133?@?@???G#154!11?@??@?@!4?@#120!24?CC#98?O#154Bj#119!82?@@?AA#129?@#3!265?C#147_O_?WC[kuqIcCOasQPIeTOIeawDPH_W?U?gA$#94!133?CgQkOC#119!13?@@?@?@AAF#129_#94!25?_[_#98!86?!4@$#154!133?O?_#132!17?!4A??GO$#98!158?@@$#120!159?o??H?_-#1tZvq~nNhlpSNMCN?F~CndnJVgExjuQnBxGcNnZ^]|tx}|~~z^tmm}^\|~~zv~|}yxZmym[kgKww?___#146?@!6?DB@CGAKCGCSCgOSpUgO!5?C?_#135?C?@BB@VGFHV@AP#132?on}^!5?_#61EZ!4_!5o{YowEJ@!9?@DACGMKKEAB?@#119OSKOYR@ooSR#28?_#61~?o!4?a!5?_`o}~}^L#138_OC!6?D#61Bs#65sO_??_!7?o?gCpWEtO?MC!7?E#68ALU_]CRBI?@#65?OaWqvK!5?BO#68N}oP_A|iYe?I#65??a?BgcsFMC!9?DG_#52A?OdGT_I?HC#65!5?Tl~d#103OxvNcC!8?|?_?o_swoyry\K#138@gQ@#119o]A!6?C__#135B@ABbB|[KA@A#146_OG#98??SCFFEBB@@B?ABFLIpb`ARe^w?@#138C{ooGAC?C?CPeXvJfv||]}gO#119??APAW_!5?FFGWo__#135??@#146!26?A?EWOO??G_!9?@A@@@?BJEIA@AC#1__O?GCCEA#5A@@#13G#35!12?A#16OC??`?AO@_S??COA?C?@SAG@LIKaTGSheXsKS@S#24???C$#5ICGD??O?OGB??@?A#62!16?C@O#5!5?A?A@A??C_GP?@_?A??CG?A@DEcOCO_?O_??_#67!4?G!7?_#138AADPBASRaH#129??G!7?A#94_O#138@J^y~m{{y_qgug{{m~NO!7?P#103H_#64CID@B@@C?A#103_??ws}[_!6?@EAKGSOO?GKCB?B#94GOk?C?HA#103???]#65?~?ipuoKz~t|}]]N??@#103_QND!8?{#64@Jm\}u]x}~{|}yN~VjE`GAD??G!7?@tqgO_wggtv}^n\fL?@!6?Fo!7?dW~t~~\nSRHG_#103??H~{!7?_#69??Fi?PA?@#61!9?A?QnA#135GoIH!7?|#61AmZ}N^JFNDKD#135_a]AC#146oE@#134?OekVi#133O#129A#138?K[o_??_ABb\}\NNBA!7?__#120_#78!8?CG[K#154???wsB#135?MNbpxzy|ykXEGsWG#132AA`@Vn~~{_#98DE[}x`nVwwo_#138?@FM[YfLvLzChOF_H_AGoogsxv|}{{gog!4_?_O#88???_?@_@?DAC@AC?kS__SG#0?_oOOGKCA@?@@#16O#102!15?_?C?A?C???_#37!5?_OA_???ao?qPiDbEXEJqj[julyP$#6?_!5?CA#0?_O_Q_Sg?o?O?_?TgEOHCOkAuHo?___?GC!11?A#2!11?@@?A!5?O?O??_#88?A@A?A#120!8?@!4?A#88?G#154@AMCG?GF#120?AO?@!4?CGDO??A#28@#146!4?@!4?GN#135G_#65CZTW]GKMIJ@DFB@#135??_^_!4?@A???O!7?KA#129?_#98??CG]EGJ#64!5?NTMHEPC?I?@#120!7?_?_GB#154B???{#59??I#68?@A@H@E@?B?@C#59???O?EP?GI#67??_#103~#120A[?lo#70GG!6?S?G?_#59!4?GA!6?g#69?@Nm]|AT?@#59!5?OgCOBW?G#138??@k???@G_#64AWS_??_GTO}_zwz~^~IO#59?G#67?C#120??PAD!5?J#28A#65?PC#59@#116!8?A@#28?@#132_I@!9?@R!9?_?_O?CDB@??__oWWS{wow_!11?GwB@#103?OKAC@A@A?_#154!12?@Cw_!8?C?GO#119!30?@A?BBBCBCCAC?CO?Go?O?O#154A?O#138???@@?@A@A#6_OO!5?A?@#13!15?A#53C?C?COD?S?_@?@?CG_T???@?C?OG??_#19?@?_?G?CG$#31???G?O_Q?EG?@g?`O?JOIOSgAP?C?h?O??A?OC?@?A!7?A?P??_#0!13?@@RFREDM[MW{Oowo__?_#119!16?C@zV~TG!22?_]{DpO#28E#120O#59???A?CA?@C??GC#28???A#138?W!4?ACGOO__??_?o!4?A@!8?C#59!7?H!4?A?@??@#116!4?O#135IB#132K!4?By#52!12?A?@#61!4?G?_Gatpr^!5?Bo#69??@J@@#61!11?oD!4?C#66!5?@#61!13?A??pru!7?BIO#138!26?OI!5?o#120!14?O_SHC#154G!8?@]#146?_#103M\W[K#120?_O#119!4?__ooIjW!5?A?@??GoO???@?@?DF#28!5?C#129!22?A#133?@#134B?EY?G#132??BAE?Go_cWqGqCjUnoZuV\vFNVJEGA@BBVNS\WCKKW__Oo?_??_??`?_?_AA_!4?C#3???G#35?__!4?GC??A#106!16?O?`#23!12?C??C#20!14?A?@Q@a$#62!11?_O?OG#28!57?@?@B@FBENCML]|Y[{xqkog_g?o?_#154!33?_@??_#116!19?@#132?C[!6?_#59@!6?@#132??O_???_!4?_GB#119!24?_o[EB^#135!31?\??AG#52!4?C?AC#103!11?yF??Bw#28!26?AO#154AWAK_#135O#70C!8?_?O?C??_?_#146!8?__E???T#98!21?_|nP?gDnw#28!5?_?O#154!8?OGK??_?OG??G?CK?O#146!39?G#105???@?CO#78_#120???@@MO@B!6?O??GC?G_?G#94!13?@???B?BALBJQN]qG[iWkGS#28@??P_sy[HMDFBA@A#147__O?gS?pARhArAsHtk\Ljxp?QOhiYBjzRih[axOIgPK?C@G?_?O$#3!74?A#137!7?@#29?O#132@!8?PA?HAVLbEgP_?_?_Oj{C_??O#94!16?@?I#88!23?@???@#98_#67??G#65?B@@BB@#154!9?__???C#146!25?O#98?_o{_#146!31?_?[O#116C#135!20?W@@C#132!28?A{C??O#68?@FhWDYE_IC@E?BC#132!13?O!4?gC#78!23?GR?O#65!7?C#129!13?_O?WGCCE??A?C??_!9?A#146!39?C#28!37?_??O#98???C#137???C???C!5?CG#116!9?@#43__?O?_?_!8?BAAB??O?IGhGAC?G??GCSAG?A@?aOAo???A$#129!135?A?E#120!22?A_#119MYcWo#64!4?A#28_#120_?o?W?C?@A@!8?@#94!26?H#132!34?`A#138!22?_MEW#146!28?@#119@`|O#154!31?@??oA#134!53?@!6?@??ADMWU__W_A#129!81?G?H?OOg??@A!4?Og?GO#12!11?O?OG???A?A@$#98!136?o#146!24?B`?ACGO?_!7?_ogK?_A@!5?_w#138!62?A@#132!23?oW_#98!30?A?_#119!31?wVKN#154!155?C???G#7!26?GG?C$#154!162?O#94dW??_#116!6?O#105!13?D#119!66?_#154!24?_#120!33?AC#98!31?gr#11!188?O??_GS?{K{kU|K|GsGO_A???C!7?C!4?@$#10!538?G?C?A?@$#67!538?@-#1~n]m~l~ZT\QMoQGu_CP?OID`HDQaDOBLGBCFWIgLSFUmvzzRv||n~~n~^jv~z~vnK|}}oRzrJBGAUcIXBJVcMCKG_GgW_Oo#3?_?O#132?@!5?DATQ?o@T???G???_AcI?aCOy]~V!7?C_o_#28???G!7?_??C?O!9?_K???@!10?A?AG???A??O!7?OG???C#116A?@#84??G#98CINNN#28??O#61O^_!9?O??__o__?WNNBB@!7?N#64Yv~|}NnNZdM]dFA#103?WB!6?}#64B}wo?Owy}~\~~NzEhUHO#103??_^B@!8?kG#68A?`dAC#61!5?G??_OWMP#138_??A!8?C_??_OoowOwWCKAA@!6?___O!7?!4@B@!4?_O?GKCEAD@?DdPwvrc_#154?@?CK??o???@BK?O#135@B@BDBDG?u?A#132??GAS`S?~M~{}vK{woO_O!7?@@LDJYLA@Q@?@@#28???O!12?A?A!4?O?O?_gqSWkICICBAB@@@#43O?_CO??p?GE?P_PO@O?O?oA??P?G_!4?L_Y?dKGcCO??s?_?@@GB!5?G#14!5?_??O$#31?O?P?Q?CGAg?C?A??oC`CPICaGDGQkOq@Og?_@COA?G@??CG?A!7?O#0!5?O!4?@???CG!4?_?c??@@@BBFBFACECKWw_o_?_?_#88@???GSGSg#120!11?cGOGC#146??__!7?O#103B@BBFEHEFENFNBDFJE#146?gE!7?F#103oCCEEA?E!6AegUko!4?@CKKGSKK?KCACCCA?@#94!4?ID?O#129O#103???n_#65^yTySq[zsxcxY\IHY]^F#120?_!4?@!4?@#65OdG???o?ocYo`QW\B@!8?G!9?_??oCxU@sm}]#120?_O#138G@!6?q#61B_#69??A#52?@#70_SocZS`I?@#103_?oMJ@@#132CA!6?n`!8?_?__OWGK@???_oOWO_o!7?AAE?C@@!4?GCAA@@!7?GKZ^~}wwoo_!6?BMKw_o_#103???A#120!11?G!6?O#98??@BBNEN]\mw?o#146?AACC__o#135??O???_O_wooogK[kRSeGcGg?g?G??_#88?@?@???@??@#5O?GG#35OO???A?@A!6?C!9?A#53!7?O??C?G?O@G??AG?C?@OA?_Q@CBCA?CH?_$#5??_!8?_#62@?@?@??A#33!12?A#62!8?_#5!5?CG?AO??O?_CG?C?G?rA@@MkCK_Sf\hZTeWsgGOg#28!5?@A@B@FBMNKY[yOkoo_?_!4?_?_?AG#154!11?GL!5?FG#138MGW{o_?_?o_?_!4?_?_T!8?WBA!5?@@@?@@?@@@?AagAC[O__!5?___OO??KAA@!9?m#64???DiDjDbCJEJEDATAD#103?__oOC??@!5?O#68!4?A@#70?O???@?G#61__{E!7?@_#68@FN}nFD@?A#59!6?G??@@A#135?KA!7?@O#65EO!8?__So}[NF@#120_?SI@!8?I?_??_!4?CAW@CDA#129!6?CC!8?A!6?K?_OG#135_oOOGGWa}~yQeC#119!6?BA?GSGo_?AG?o_#146!23?B@!4?G_#134??@#133?@_O?G#138!5?@A\Ml}n}}~^nNFNNNVrbRkjXvXvT~V~vm}K{WSKGC?C?C#6__OOOG?C?A?@#13???G??O!5?G???@!8?CA!8?@#102?CO???A!6?G#23?C#13?C$#6??@#0!4?_a_DPIdsHUHiSjcoYSqgTgBk?skRwFsRahW`OGC?_#4!24?O_O!8?O_?o_#67!4?@?GA?C??O_#146B@KQIKG_???AJWoOo_#119!13?Q~sRcJwo#135@?E?KWoCwGWoWO[iwOxA#119?@P_??@J}#120??@?@?@!4?@!8?G!4?G?O_?___O?O???G?C#59!19?G!6?_??C?@#135!4?G!8?_#116!17?_G#135@!4?B#59?S#69???@#61!12?_A@?_\#146?_OA#94?A#134w#129?O_#64??@l~[Y{ZjNZCJAD@A#116?_??O#146_O!8?O#28O!6?A!5?A#154_??EA__OGG?KGGO_??@?ACKGG??AA`OKCA?@#28!6?G#120GB#146!6?C#98@BB?AD^j[_o#138@BE[M[y{yt~H~|~~v|j]jv?p???G_#94!9?A?Co?o?o?_#132!31?@!4?@?A@??@?A?@#11___?GK!4?_?A_?`?O??OAHaCI?C@??@A_X?I??C???Q?G??O??CG?C$#99!13?G??GA?G#3!55?_#2!7?A?Q?SWsOcWg?o#138??@?AD?CA!9?_FNFN\v^|ZtZTjf@`#98!4?JkZs#120?B?C!5?O!5?_?O???LA#88OG#98?I\o#132?@#61??wGG_KCGCkKs{wGSgO!6?@BAEABAFB@DBBB@@#154_?_??o_?_?wB#138!25?OWK??@??oM#146!18?_]A@#98[#132@o#119!26?_OK@?@d#67???O#135!19?CIC?@!8?^Kw[MKKFKBDB?@#119oOgkVX?C???ECGO_B??CG!4?OcKMF@B@@#129!19?CG!5?C#119!29?@@BAK[?Wo_?@@FIGo?O#146!33?@BBEA?@AA?A@#10??_???G#16?_?O?_???C??CA???_?C???@??G??O?G??Q?DO???OAG???HOEG_CGSJ?I@CZCoTS`i?dW`Ug$#129!104?@!4?CIEI#61!34?@A@?@#129!10?g#94e^tAIs#135!4?B???B!6?@!4?CO!4?A??OgOOW?G?GGG#119??OGW[U@Oo?_F#132!26?_!6?_A#28!19?S#88_C#119UBg#138K#28!26?C#132CB!4?FK#119!23?_o?FF??H#103!4?RFA@B@?B#146???_!4?@!9?_!4?@!4?CAA?@#103!4?__oocW#134!16?BDI?S_O#154!29?AACC??__!7?E?G#103!40?_?O?G#147!7?__OWGk[[MtQxMiHkfmDU@zC|w}mdu\NadtqG`oIbcAbhdfJhOegoUGcaOgP_Y_QH??O$#119!107?@?@@#137?_?S#133!51?_#84C#65!5?O??_?!4OG?CO#146??@@DTwW__#65?@@@?@??A#132?_!4?G??__!6?{@#119!25?__?GKhI#129!22?_??O#154!28?G!4?AW!25?GK!5?~#61!5?@#98!11?_OOGENBA@A?BEN^w{kW?oOoO_GO??A#129!71?A?C???_?O#67!38?_?O?OGCG#1???CAA@@#23!11?G#62??_#106!15?@?A!9?O$#94!108?A??@#59!61?__W?W_g?_#88!9?AC#116@A#146!12?_ooo?sEB@#116!33?C#67A!7?_#94!19?Wg#133_#154E#98!28?_o[F}G!27?_wwNjQ#134!25?@??@?@??C?O_o???_#133?O#129!123?@#3_!6?C#12!4?C?A?@#23!33?O#37!5?A!4?@O@?_O_O`?_EIJMTnYf]hV$#64!174?O?O_#67!14?@#132_#146!56?OMAAA@K#120!21?@#133!33?_#94!30?O#134??oS#129C#133!26?A@#94!6?A!6?G#0!127?o_OGCCAEA@@#102?_?A???@!4?A!4?G!6?Q$#94!251?O??C#154S@#133!91?_#78!39?_$#98!251?_ooO$#88!252?C-#1}j}v^D`mrl\SepiP??@??A_?QG?OCA_C?G???C?BWjaVdY\?}\~z~~z~}^vZb^}V~vmn~~M~p||{~[oOTLLX?M?EO@WIgY?d?_owg{{y?WGOO#146???@?@?A@ADGACG?CG?UaGoCPaO_O!5?K#138@E^~~~}~~}wy{|y|e!9?@EO__#59AC?B#28!7?O#138???@}_!9?OGC!6?!7@A?C_?@A??!5_!5?o_OGGC?c___??_!4?WP#61?EO?_?K_coOoOG?@B#135A!8?A#59CQ_!8?HC_@#28??_#138_??K!5?__!4?FGO_!8?__??GC!5?__oOOOo_!5?@?@B@A!5?_O?GKCQB@ZpX~~wogO_#98@BF[Wa?kZg_#138FW_!8?O_Sh!4~}TGu|C#154??@`E_O!4?@AAC_w#138@BCBLBTBCBS@iT_?@!5?O?G?KWwo__O_#133?@?A#138!7?@@BBAAFEFMMI]PKW^J}NELIBC?B#6__OO?C??A?@@@#102!5?C!4?A!4?O#13!5?_#35???W#102?G!6?_!4?_???_???C!9?@??G#13_!5?G#14C!4?O#19??O$#31@S@G?AG@C?a?H?C?PEGQdGTgCqHaX?I@S?I?Q?h?C?OG?`?S!8?@#3!20?A?B??_?_@oG??c??CO???@?C#67??@?C?C#3!5?_O???_#120?A???@#132@??@GVNZkGC!6?JB}w_#135???@??@@C@A?A#146@NCO!7?O_#65??@?A#120!12?O!8?@!5?C@??B#146!8?@!4?eO__#59?@?A?B??@@#132??_?oOwW?OOPOO?O?GF#103E^w_!7?_??OGC#138GCA!7?X#103w_#72HA#68LIX#61!6?A?Q@MEA@@#119gW[I??G?Ww[o#135FG?_!5?__OOG???A@!6?___#134?@#94!4?_!7?_??A#135?_OO???WY_AC#154!4?@?CGG_#134BD@ZB_#146??G_#135K__oowsz}nZjU#132!4?@ivHAZ~~}[w!7?@@BFFM{y{q{i{z{j}Ti^|}!4~]n^v~RFAM[WkGOo?_!5?@A!5?C???_#103??@#67!7?_?_OG?K?CCAAB#147O?WGc]?SAh[YHWcFPCaBhFeSj[btWeYdAsBYNrxTYFltWYHdWIDpOIPkeplAAH\QamtAH{diTO`YdOA@_AO$#0!4?_gUOGQ?jOMPmmxulYtIVhDuLa|TwjUt~lzU{bSL_YCai@!8?_GcW_!5?O!6?A???CG_?A?A???hO?_?_HOG?G?@@B?EBFa?cO_WW?oo_?__#127_#154!11?A@A#94C?W_GO#120???@!8?E@A?D#132?Woy!7?EG#103AKGOWOowo{is~]NBNJ#146?@XC??@@?AXdFF??AB#28!11?A!5?A#67@!6?G#146!11?CA@???_?_?__#59?@?_?G??O#120???__o#28G??@!7?C#61B#65H?_!5?AOap[]LM@@@#146_???@_#105AC#78@#61!6?FGGOO?o?GCEAAB@#146O?_SA@!8?O!6?C?CC!4?@#103??__owkc?C#119!7?@BACW_!5?V]o#120@O!9?C!11?_#119???A?Tc_??ACKkGW#146?o#135?@#120!12?A!5?`?_??_#146_C!4?O_!6?@??@EA?KGGOGGOO!4_?_#0!6?_o_OG!5?@@#53?_??_?G?A??A!22?_!8?c?G?COCG_???GAOd?AgCO?_SA?O?iC???GOG??G$#62!5?O#33!25?A#29?_#62!13?@#5?a?C??C!5?C?@g?GP???p?MA?B?`JcIqOeLoNP?mADU@cIsYCC!4?o_o?_#88!4?A?@#138??C??DBBUIRn?D#119!4?CHJmD]vnso#88!18?@#154__!4?A?_#61@BFKALKFNBTJ?@#88!6?A@?@!5?G#103?O??G?CCCWeGUAEu{gO!4?@CK??!4O?WMKCEFAB@@#154GGG!6?E#28?G_#65?M\[poYHMLNMFFA#119_O_?K_o?z#132J_#64?CU\qte~~|nSI@#103__oOGKE#154_!4?!4O??@?_#103?OO___?oOWGCCCAB@?@#129??G??C#133!8?O#120?@A?A@A!6?GC!4?_?K!4?CA#146E#129?C#94???_!5?@#28?A#103B\OHNFJC@#129!18?@I!8?O#119!30?@@BDACFC?@?IG_G?C#137???O?O#3!19?O??G?C#9G?C#14O#43??O?SI?E?DA?@???@GCO?H???GA?@?@J_dOK?id??I?@CQcP?I_PIQP???G#37O?@??I@??GD!4?I?@ACPeDq{$#99!33?@#4!50?O??_C??O!5?C#28!6?@??@BBFFEDMML]XYTWwsgocOgO_?_#129?O?O?_#119!24?I[w_?H[g#135@KOO___!7?__{osm#129?C!6?_?A#61?_oWOWwwwcWsg{wG?O!6?ABMMKIKEME?BB@?@#28???@#119CCKJGWCS`#64!4?@?BEBDA@A?@#146??o?GC@!5?C#135!19?O?GA#116@#94?A?@#134@AE#65!6?A???GCEDB?@@#154?__OOGS???G?CCC!4?@!9?CcSBGC??@#28??C??a#78!11?AC?O#116!4?C#61?ANE#98!25?J\zlXqOO_#129!33?A!5?W?o@?A??OGC?_??_#1!18?_?O#11__O??G!5?`_O?__cOOkhWsQg@aSb[ACXcQc?S!6?GO!9?C?_#23!5?O_??G#43!13?_C???_$#2!88?A?@?@?A?B@ABQA?CG??GKWgW_?_#137!20?_#98?@A@#94!23?DB?DA?@#129O#64!5?@#132!16?O???A@?A?G?BB??@?@!8?@BFNW?O#65???@@C?@#88!13?A#94A?@???O#72!5?A#88!12?O??A#133_#98]N|C#154o#132!20?_?OUf_?_??_??AGO_#64DFNFFJ@A#70?@#28G#132_oO?G?__oOO?!4GO_???BAA?C??DBB@_O???AA@!7?FJSgWoo#105???Wc?C#134!34?ACQcG_#94!36?@AGA\?^Du]s{wg___#28D?G?O??O?GO_?o?oGoOG?K?C@B?@#16!4_OG_I?T?`??@?gAOC???G!5?@??G!6?A??O??d?Q?@?I?@C?@GEOl?A_CO@?SA@Q?iDQ`OnSgBkHALB$#98!165?FYlU_#94!23?GcWESgOC#65!4?___#116!12?_#120Wo?KGWO!5?_?__?OGo?gCAQ@??_#98CFBB#116!19?C@#129?O@#105??A#120!22?OO#98!4?@KICHED_#59!6?GO?G#120!4?OGCGCEA@#98??A@@BBBABE[}?ooOo_ooOgO??A!103?@??AC#135!12?@?@?@@D?EBF?C@?PADCBB?B#103A?@#23_O#12G?CCA!9?A#106!23?C!5?A???A???A#12!8?C$#133!167?O_#154!24?a@#98?wgO#28?_O_?G?C#135CA?AA?A!5?CG???@C??OO??_?o?O?G??C?A!4?_#133CA?G#129G#94!20?_WkQ@#129!30?O!4?_A#119!19?_G?GMFLEA???KKxb?KKKgGWGGKOIGKDBB@#154!102?G#88_?_???@A??SW?O__O_?_#4!16?O#2?G???A?@#13!9?O?G!27?A#37!6?_$#119!194?Ye??EK!24?_#64!6?@??@#137!17?_#133!56?C!4?@#5!196?GC??A?@$#10!513?G?C?A-#1~y~l}YenAoCbOgSO@??@?_?AC#99!4?C?G#29A#1!6?GAGsItj^MVvf{PnAvCFekvfcFl}j]}zun]{~{ZQi@UUV~~ivdBABbBBAB!8~Sj}itmYloBp?u~mk{{gg_?O?Oo#146?@@AAB?DKI?O???@BCg?G?_!7?U#154???jHO!4?@Go#135??@@ADBJFBBDA@FE@#146??A!6?E?_#103To_?_owt]~NVNMF@NG#28c#120O_!8?@!5?@#59?_OGK?KWC#28???@C!5?@!8?@!4?G??C???A?C???A#120?G!8?GGE!4?A#135!7?B??C_#154AG_!8?CG?A@#61__OOggWWg?_#28?C#120??A#132@@?KWo_!6?EB_#61?KKI#116_#67Q#28O?G#120??@G?A#132!4?p?C{w_!8?@?G_!10?@vhuh}|!4~|u|wO_#78?@?D#133G?G?_#132?@BBFV~~^n~\lZFNNfMRFHB?@!9?!4@?AG?G!7?G??C#3_??GG?C??@??@#5@#53?O??O?gO@G@O?A_#13!8?CO?_O??g?O??@#102_?C??_???G#53?G?CO!6?C?A???AS?i@S?h?SAH?C?QG@?G?@G@COCOCg_$#31?D?Q?DP?GA?SHAHaSHaSI@SGPiCQH_A`?S@CPA??@C???C?`?G?A#2!8?G#4O#2!23?SGAc?SCcGC#4!17?@#2?A@@?@@AACEGK?O??o?_#132?@??A??K?SG?QCZOC_?S?goGom[`~~~S_!6?EMk_O!5?_?_O?o?_?o~|O!5?@#28DG#65??FH#132!15?@JO!8?A??@@#135A?A?@@!8?WO!5?@!5?AA_?OO???G!7?GO??CCG?OO??G?C?__#154??B#146!12?AG_???@??CCC?UG?A@@#65_G#67???C#127?_#119!8?A@EC?W_??Hx_#146_#135Q@__???AbppmSfmDeCI#146???B??O!7?@C?_?_!20?AH?C_#98BBMNYvNu{WgO_#146??G#120??_???Q#28!13?O#135?wgW#146!4?AAAECOC_?CWC?OC??G#2_#0O_OO?KGCEAB@#14???_!5?G#35!24?A?_O!7?O#43G?C_O_?AH@CaG_?O?cCO@A?G??A!9?C_AOC???A?@?A?O$#62!4?@#0_GOtLzGeTaLiu\it]jtaTzDuJ|Uljyzm|~v{rJtIO_Og?W?kOsGaGOA??IO!9?@?@cHTi?h???@?O?@!4?`!24?A@@B?E?GCGG?w?o?_#88A!5?_?qCW??O?O#120!7?_#119!5?EbWC`Y?_#138@B^m}|y{sW{[i|KwW}N!9?G#135O_??_!4?_?ogopw}ovZ_#146?g???@@B@YDFB#65oOoGGcOOG#138!7?Bk_!4?AACC?C?_AQ@GG?K??CAA?A?CGo!4?O??OOOG?COQPGGCC?AB@@@?@@J[o!5?@!4A?@_OW??AA?@@@???@?Hrhq}wo_!9?W@_!4?_!4?OgOPwXzt~M~w#154BEGo!6?E?GO#28C?O#154!21?AAKW__!6?@BACKW#138!4?O?a?cwooWpkwu{~}~n~FVf~~~}{{wwo_o?o_?oOG#67??_???O#6___?O?G?CC?A?@#37?GC?C#13_#12!27?g???_#37!8?@#12??O#106!4?A@#11!7?O?a@??_!7?A?@???AG#37Q@BIBDVN$#33!24?G??g?O??O?C#5!16?@A?H?XoHP?GPgQ@S`@CHO`A?A?c?Sh?g!4?GOc???_Oc!8?jS@TIPdQM{K}G?OO??OOO!4_#138@@?A??CG#94??@ADIDG#137?_#138?FJVLH\VNvNPBG#94!4?OG_#133BW?_#146@?O#120!12?A#28?@#119!4?liO?J}w_#61@INWUNNFI@#138!10?MS#119FkJa?ASEC#116_O?G#64_?oOG_?O#146!8?BI??BACK?G?GoG??AD@E?C?A!5?ADBO_O??___!7?A@#119!14?@B?_BACC?G?w#88?@#59!5?_OO_?O#94!9?@A#98@AJCS|~u#120?GC#103K]RPp]Ll[EM@A#119!11?@EKo_??@BGWo!26?@BC[Oo_?_@BESkWo__#28!31?C?G?GO?O_?__ooWoWGHKDEABB@#147O_OGSgCi?hSA@Ec?iDSGHsUQZhV|QmlPLkyFXK_O??JPHUHUlvqzFaRncu}w\oYnl^ZxliDSOiPSJhOCyB|D_YScIoCoH_G_O$#4!88?GWgWWSGW#67!27?@?A?AC?C?O?_??_#119??@#154A#135!9?AAA#129!12?C???@EO#94!21?AT@C#88?@#120?Q#67A!4?O#94!17?P_TisgG#28_OGCC#132!17?S{@!9?SSKKAC??AA??@@?A?AKkW?o!4?__oooGKKEC@BB@!8?@CO!4?A!5?_ULCEA@?@#146!13?C#105??@??A#65!9?AC@#98!19?@BMXQnu{o_#135ABBMMV]D?O#105!25?O#94!41?@?@?@@@AB@AB?B?A#1!4?OO#23?_???G!4?O!6?O!4?A#102!54?C#13???CA!4?O#23??@???A$#28!124?@@B@BEIEKL[WsWoo__?_???_?_?_?_#98!13?FwEcW!24?kzs#116???C#154!22?A#98SGSG#88?O?G#67_O!8?A??@#129!6?@???C#61???@@?@!6?___OoOWw___!5?CC??G?AA@???_???G?G??GWGg#94!4?CX@?C!4?G#120B!7?@#129!17?GO#154_!4?EP#133!28?@C@O#138???ACCOO`_`y~n~~~}GUHU@A#119!58?@?@A??@#137G!7?C??A#11?__OOGG?C?AA@@!4?O@I@O??_??_CAgAh?QKAODOc`NNE^sgUhUhA?@?OGG!4?@#14!19?O@??_!6?O$#4!125?O#3CG#129!8?@?BCOG@QDg#133!50?A#129!34?_#103???G?C?A?AAA@BBA@Pj}_!7?@BAAB?@?__?!4OGGKCC[WO_?@BA?GGG?GCCAB@??O?G?KCCEEEcU}o!9?@@!5?_OWGCCCEaF]]cX_G#133!7?C?@#134!34?Ak?G#120???@?G@?G#88!73?CACGI??K@GF?D@BCB?@@#8?G!4?A?@#37!73?_?O$#61!235?_?GKCC?C_cocw}mS#94!4?A]GGoO_??o!7?@#59??_??_!9?A?CCAC@@!5?_OO??O?gO_O#98!6?E[sgGWWoo#134!28?AGA#129!119?A??E?C?AC???A??A#16!6?_?O_oGSGCqAhOAgAs?i@S?@_C!7?A?@??A!6?A!6?C??C??H??A?D?O?_?A??g@C?i?SAh?CgAGD_IOdI`C_KoCgOG_$#138!236?AA?@@@#88!13?@!5?G#120?C?C??@#65?__!5?_!9?@?A??CA!9?_???WO_#84!9?A#78G?O#28@!6?_?G#128!22?@#154!132?C#43!11?_???_???C?A@C???C?g@UIHH_S???@??_A!4?O$#154!256?_!6?G???G!5?!5@??@@@!4?_?_#64BB@@!8?_?oo__#105!13?O___#135?@!6?CC?AA???@@YaUCSK#32!151?C?A$#119!257?s?G?Ooo?G!5?A?A#72!14?@@#28!9?@$#98!258?o?_-#1|~sVyH|MjUhAmoLA?xGO!6?C?_!8?HOaK`MpYtAsv^tJ~|~{~|T|x|~{UrlnMN|~ZvjMJgd]K{au}~GoqQvk]iu|FJVfR^VF\MrA@EJHumTz!4~vz~}j{|gtIzVrg{]ECKOKO__Oo?o#146?@ABA?A?A?EGA???_GO_!7?A?CGo!4?CGCC?C?C??A!7?@#138@IE{kgogooowqlz|}{ivv!9?G#61BEKKG[ADBBD@???@#132!5?@@{o!4?G_#103Sa!9?__??ow}~N#138B]w!7?kA#67G#61[ioO_?_!5?r]S@#135?|Lgo#119BMK_???IG#120@AO#61BAFEMUMRF@@#120??gOcGOG!4?O#119@X|_??BCG__#120@??C!7?@#132!4?_AdBq~|]sw!9?@?KNZQlosxqzv}j}!6~}|~|mv{gkGgGkOoo__???@@@IFM@EScHQ#103!7?C!5?_?O?O?G??C?AAA#147_?_?_GOGcYaC?A@c?O?cOL_DICiC@GhSSl?YHluOLrjlYdu\}TiQTg?g?AA??oAloWPbbVTnZmMw^{kfs}qzvQIKtQQGdOI@AOI`oLaQKda[`{ghOC_HO$#0A?J_Dq?pShQlONqLtEtinyT}|nyv]|n~v~}n~un\rMpM`I|J?_Is?A?@??A?A??@g???O_??_GO_OBO`A?\G@?QCHCGA?@??w?gOg?gO!4?O_?O#6!8?GC!7?SCGC???oo__O_??_#88@?B@#132!4?@??@A?BDN^^V?c!8?@MPS?A_APWcXy\zzyz~x!8?CSw?O#103???@!6?A#132!5?{T!6?[o_#65@B@A@#138!5?_O_O_Qsw~zU{!7?]#28J@#59O_???@?@OQO@#135!5?o{_!9?_@#59?SE_?_?_o__FK@#138!4?AEKo_!8?C?_#59@?@#65@@@#138!10?bbvz~}#154?Gc!5?BCWO#146_#135@NB^BZBG_@A?@A#146???GOC??@GC_!9?BAO#120?gO#146!15?@A??P?A?O?OOO_!8?A?@??CGGO??o?_#135!5?oXSiQIOCIK?CC?A??@#11_?O!9?@@#37?A!4?A???A!8?A#102?O#13@!6?O#10!10?@#35A??G?CS???A@#53?G!6?@A?@O?G??C??dOAGd?AG@?GH?OC_GD_???A?@SA`G?e$#31???G?CA???CO@??oI?ADODi@AO@G@AO?G?@O!5?O#5??C???G!6?A?AgACA?A@GAO`OA?C?CPcSI?pB?@??_BCH??@?@??s?GC_?gapK`mPseHOiC!8?S?AA?@??GEA?GGOG_?OO??_#67G?G?O?O_??_#120@#135??_#119??G?HXQO_PDYd[#138@ABN|^|mbRa@a?C@C#154?C{C!4?C]w_#146!19?BI!5?A#135BCO___?_!4?_??GkMgIF?Cg!9?_#61Wd@_???___CE}~NF@#132???@A!6?L@@#65???HCW!5?OG#146!7?@??O_#105BAT_#154?FW_#103JK[Wwogokw}}^hOG#132!7?@nQ!7?BEKWg#154!21?_BA?_!6?@A???_!20?A?G@FBECC?CG?GO_@BA?A_O#138@A@BJELN^^N~~nZNeJDLDLBDBF@B?@A#10_??O??G???A?@#35@#43OGO?OGAA@!5?O?AG_OAh?G?cA@@A!4?OG!5?_!4?@???GC?K?MCCg?OcPO?_?A???H?G`?AG??_?CC?C?CCGAO??A?@#23C?A??A#14@$#3!65?CO#2!19?@??_?P_SGA#3!11?K?G???@#8!9?@?B?TI_?_?O?_#67@#28??@@!4BEBCMELK\I[SwOo__O?_#129?U??CG???@??_G_#119!16?BXC_CPj_#120A??A!4?G?C!8?GG#154?_o#98?zt_#88@#103?AKWOOo?{w{{Y]lUBOD@#146!4?A!7?@#65?CISH_t?PELHH#120!9?C#146o!6?A#103[ub@!9?_?_j}~Ao#28O#132BK!8?EW_#146!24?c#94A#98A\~j[wo#138@BESow_{_{vZ}{~}|~^|QkH?A#119??@][o_??@BEG[o_#138!25?O?o?_#98A#129A?C??K_O_!4?_W_??_#67!15?___?o??GGKCGCAB@@#14?_?OG??A!6?OG!5?A@#11??C???A?c?O?KoGCQdA@a@iTlIU|U{O{RjA@A@_?O??A!4?D!4?A@!5?@???@?A#37?C???A@!5?C?O!4?GEOH$#3!86?CG#4!19?O#2!25?@???AAC?CK!5?__#138???C@JCLGSW?_#94??@AAgDQgYcW_#88?_#98!18?AZRjEO#135??@@@BVNVENJFLQC?@BT#119!4?E{?A^g_#28@#64???A@A#67!6?A@#119!9?@DoGs~c#64!4?IU^IyMWA#154!11?@#119Lz`@@Fa#28O#64!5?JFV]\N^NO#94!10?@?WO??@#135??@?O__!8?_UFfZvKSGC#133!6?A#134?S_#28!5?A!4?C??C#98!16?@BN\v^y{w__#138@?CDANJELCG@S@#119!15?@BB@HEHUG?QK?KO#88???_O_#28?O_?_?o??O_??_O_O?GO?GQGF?@@?@@#23_?OOOGC!9?@!5?O?_?_O?A???A#12!20?@?C@G??o??A??G!8?A@O!7?_??G#35C#102???O#13!7?G!5?A??C$#0!134?@@?@@AAG?KKG[?OOo___?_#88!9?_#154?C?_!4?A?O#129!19?__#133KOg#94!25?HBC??S#59!6?C?@A#154!15?AG#94IP??O#72!8?C#94!15?A?Q_M_#72!9?G#68@A#98!15?BCgi\so_#28?C#78!64?A#134G?C#129??C#133!35?@A@A#120!29?A#0??_!5?OGC?C?AA@@#6A?@#53???_???@?G???O?_@?O?A!4?@#102!21?B_?_?DGO??_?O?g#16!8?G@?C??CO??d?QG`OiPePGAOD_QGPaG@SAhOOe$#133!168?@?DA!57?G#129!28?AC_??@#98!25?CK]oG#129!27?O#78?D#133?A!71?_???O#94!38?DRK_[okSgO#2!25?_!8?C??A?@#46!9?_???A#106!44?C!8?_!10?G!6?O?_?_?_!6?O#102G$#98!169?AGC_#133!86?@?I#137?A#129!29?OO#4!181?__!4?G#13??O?G!8?_C#11!89?@$#98!260?E#134@#65!214?O#3OO#9_?OO?GGCC$#8!478?_?O??G#5?C??A?@$#12!481?_!6?CC?A$#16!486?_??__Oc[gULwJUkdOlqNgTGSHePCh?Oc?Q?Ga?C???G-#1~vruvszwZgz_mPrh?i@C?A_?Wc?SGQ`O?G_iOdIsJSxUaTa@~\QZsnI}N~N~^^~vM^m^vVJUdAGjoVSJnbELjY?J}f\ivL}jTSxjtohi_DMGKK@OC^dyn^~v`oOpPx\SuZm|qn_~IuI{EAoXSgW@Y`GOQ?Q?C?GGG??o__#146B?AA?CCG???[@CDyKW?GgG?GGG!4?O#119r]l?saNIkO#138B@]`^xvxtriPnnnF_#154?IUo!5?@Go#120C#135@A?FBJFBDAG?@??A@#120C#146!4?C!6?G#135AACk?o?oOwGg[KSK?NA@#132?[uo!5?EK?o_#61@@ABF@@A@#120!6?_?P?gG!9?@?M?C!6?G?A!7?A@?G#146@c!9?@!4?OO!14?@?DA?AO#98??@BANN^MuWSo?O#138!9?_O!4o{siugsg}|}}|}{uwkw_O__?Oo?O!7?A@?A#7_???O?G#16_?_O_O_?_W_qGzCzCpEDATIdLYdNpMyFXKiDmDQdGAGd?I_C??d?G?A?G#43!6?A??QLgeCHAGC???AGPCgChCHePG_A@!4?C??A??C?OG_?g?G?CG?OAG@?_?A#23??C#37CAD$#0?GC@GBCBcRCTPiKS~T}y^|Z}fJ|julUn\v^PnYtJsjChTiT}?aLcJOt?o?o?_??Gp_@??gOgQlQCJ_Js?OgOO_Os?O?O?O?Oa@AOADODO?ODQ_?@O_O#8!7?_?_?__?cPAH?@?o?_#2!11?@?@??AA?EC?K??O#88??@!6?C?G?gIO#154!14?BNK_?_???@??_#146__#120!12?G@#146??G!7?@Io#103??@!7?@C#119!10?@J]Pe]W#103@@@AAANEFMFEFBAB@A#28?@#146??BG!5?@??G#28A!7?O!4?G#146!7?@??K!7?A#135?@BBEIEKVMBN@A@#154!10?JWg_!5?BAKGo!18?M?Kth@gOsoo!6?@AK#146!32?AEOIS?SGA??CG???A???@#135?@?@#12o_?OG??C??A#13C#43!6?ICIKG?C?SI_?Y?COC_?P???gCOS?QGd?I@C???@#13??@#102!7?GDG?OTGiCg?_?Ca\Ek?TIUhUGA?C_?O#11?C!6?P???_!8?A!7?_$#31??G??G?C?C?I?C?A???@_?C@?OA?@?G?a??C!6?A#5?G?G???_!4?@!5?_!4?O_G?_?GOdOCG_?O?O_?Dk!10?i?C?ICOBO_Q`QqCj?IDO_?GUJFMMEAAH!4?OO??@???wKE`A@A`?AA?m?Kg[_o??_#132!5?@@A???G!4?P?CRefUAUtuut~~~{_!8?BN[M@]_EGEIKTmOOOo]~t_!7?ACIw_!9?o?o?_???oNNyO!4?A?w_#61@@@?@?@?@??@#119!8?@NCpOBCOo_#59!4?@#67G#132!16?Avoo!7?E?o_o!9?_?_Gsws}zv}O_O!8?BEM}kcg!4o_GqcY|~~~}pypISkSg#129A#133?@?O?OG?_#146@!4?G#129!29?@A?A@AAC@C?BCBG#0_???GGCGGECAA??@#14???GOKACGC!6?_!9?@???A???O#11!9?o?QGDOiSoTyKJcIT?oOD_AO@q?dAKIC_#53??A!6?C?O?S?dG?`C?cPG?Q_@CO?AOG?AG?O?CO?_O@cQHOg$#5???G#2!66?C@!9?K@AC?B?@GaDGa@CG!7?G_!4?G#6!16?H!4?C?M?@?TBxDB_IPe{C]skkOkoO_O#9??_#138!7?C?G??O?O!4?@?@W`T`I@@A#133!6?@A?AH#105_#119!22?@Ns!4?aS#138@@E\}wksw{y|uJ}N~\}z~Noo!6?@EC[wOo?oG_?_O_ogq|o{}~_!7?@ACKWooo_!6?_?o?{SykmUtG!9?@?KWGswogoso{|]~^vJFH?C#119???FEW?_??@CCOO!24?ABFGKKo__`@fIC_coo_#28!30?C_GO__?O_oOwWS[\KIEDADA?@??@#11OGg?c?A#37!9?A#13?O#11G!4?@!9?_#102!11?G???_#35???A!10?_!5?CAOOoW#106???G!7?@???_!10?_#23?A!4?A#12O#102???d#14???C!6?A$#3!98?C?G?A??A?_?@?_#7!24?CG!7?C#4!5?@!8?oOO#119!7?@BAB??A#129!22?OO???C???O#98!19?J~xj~W_#28!6?O#154!18?c_??@Co#28!4?K#116?G#120???O#154!13?o!4?G?O!24?AG!5?BCGo#103??!4@?@#98!17?@F^YVrUoW__#94!37?G#132BJNNV!6~^n!4NBJTHVJV@A@@A@BHFP?HC?KG??GA#137G!4?@#2__OOO!7?A#4A???@#53!13?G!12?A?O?O???`#12!32?O?`?@!4?A!5?G!4?CG?G!4?_#37!6?@??@?_$#4!104?CG@???CI!8?GCG#0!30?@@?@A?A??G?GWo___?_#94@?@B?A!22?G?COOO_#133!23?AS#129?C#133!27?@CG#98!27?@Ilkg_#135@A?CKG?Oo__gowWsM~BJDAP#94???@#119E{_??@CGo#28!6?A#129!21?_#134Dg?g#138???@@@BJV!4N^vLZdA#129!23?O#94!35?@??@?@A?B#67_??_??O???GA??A?@#23??O?G!5?A!9?O#37!13?@#13O!5?_#35!47?C#16A!8?a?G?a?HCO?AO?CB?dAGDO_HQH_Qh?g$#3!155?@!7?G???O??_#129??CA?C@E?A#98!19?FHO?_#134!25?C#98!31?iO__#94!25?A??OA#103??@@BAEKCG]MTMFFB@#98!10?@BMZv}wo#133!32?K?G_#154!77?@#88A?DGCH?CBCB?A@C#9???_!8?A??@@$#67!157?@?@E?C?C?G?OO???_#137?G!4?G#129!82?@!27?G#133CA#105?O#78!30?@#133_G#103!121?_#3!4?_!9?C!5?@?@$#28!158?@?@BBFBFCMKG[OwO_o__O__?_#129!143?O#105C#5!132?___O!5?C??A?@$#154!175?C@#6!287?OO?GG?C???A$#10!465?_??O??CC?CA??@@$#1!466?G?C$#103!466?@?@$#147!468?__Og??OGSgQcXDrCp?p?pA`i`OQc?oI_@Wc_CI@AhIA\dQYtTAzlQYeThKaDrsZtitIfGA?@O?P?HA@??pAp?p?Q_P_qH\iJQrvYz\XEeXLEOJmTlcbWtOGdqiJULC]H?oDQ-#1ASARI?iD~gVwV_vKZC^CZMTGtID?@AKpN@mTYPi\A@E@I@A?eLqnSrulY|ZsDRlq@oJ_RHa_UHRiCZALSciRiSaCyLqlYvC\pB]@IDaXA__OBa?IB`KHN^kLBRFN~TrXn^~zMlq~sj[rC@?DO_hO?__?yEX???T??_Gs@l\|CDEeAKCQgWW??o?_?__#132!4?@@@B@@BEIGKOG??_?PFJ}m{{{Otgtxuxu|~\}{oo_!6?B\oso???vi{p}w{ty{~~|~~{!6?BEWwOgO?O??Go??sy{z{~~N{!8?@BNMw___!4?WCWcYszk[j}~~|!9?@?IGPypmwSy}|u|Nn~~~n^cZdW_O_!9?@@BNVn~^}z~}|~~~Z]\~}|}~{z}[{w[wOooO?_!5?JA@CHQ??oG#103???A???O?OG?G??G?CAAA?@#12_!4?G??GCcABB?A@#23Q?@#43_i_P!8?G?_C?`OgSQGSAHcBG`C?Q?gA??G?PCI@C_A?@gC@C!6?GGO?g?s??@?O?IoC???A!4?GA?`?O??T?cySG_G!4?_T?cG@G@G?A_?DO?d??C!4?@$#0|itkTyTy?VgFgVGrcZ_zcPitIty~}|rMomPidiTa|]pmTyl~GqLOjCHQ`AcHqKALuNsZkuL^_UgTycXaHQDgTiTyDODOdGPaM{@}@yC_XAL?S?T?_S@A??A#6!6?_C?O???O?C!4?GzCz_LQCj\B]zCwe^ti?T??sI]Q_?WGOGOO??O#4??_O#138?@?@@?B@B@EAECKC?O???_!8?@@BBBnIVIEHEHA?_#154@BLGWo_???IC#94_#138FJN~}~GTBM@F@IDB??A#154??@wa??@@KW_#135@A?A@A?C#146??cG!8?Aw#98?FN}~{wg_#138?@FNN]}|}}fyfZdJC@@C@#119??AFFwo???@CAoo#138@EDMPFjD@AHAO#154!4?O_ZcYb]kWoo!6?BAkWogO#138??@C?@A#154???C@A?@A??AC@BAEAEKGG#146__!5?@?pEQCCGG?O?GO#67!5?_?__O_OOOGGK?CAEB@@#147__O!8?@??SiODI_XBsH_eWv_^OjSIdAHCAHsAWcQI_L_U@{Qs_kgpsiHTsMaPiAPcBcJ?\ADjURqHqGQcH?D?@IEL?Ao_OdWCO?GOg??Q@hu\D~uyzUIuGRGtierO]qIlUOdjWtCWsA_\c$#5?@G?_D#31!7?G!9?A!9?O???C#5???_GO_CO?P!4?G??C??AG_O?G??C??O?H_C?@?cOaHOC?@G@#2?aGA??i#5???_?S?HE?\ALgTItSIQcO_Pq{kwo?GGc!4?_O!7?yCYaLQCa[@C@??_ITii{^B@??AAaqgOk_wg?__?_?_?_??_#3!4?_#146??AAK?CS???o?O?_?_!15?A#119??ABCIKP\vcW#120A#135!4?@#120!7?A#119!8?AF[yQmEo#138@BElVl}l~zvNZvJDBCB#120??o#154@!7?@AK_o#146?OO#135@@A@@?@#120!5?QA#154O!4?gw!6?B?K?o#146!11?_O!8?C#119@BEC?oo?O@FCKO_#138!24?_??_?_#94??I?TAKiKWoGo#138@A@FFFBJBLh^KVJDJDBD@@@A@@#8_#9_OOWGGK?CC?AA?@??@#14???G#16?JOTAcGBSJOFGV_NOjSISaHTaHsBWdSZqLhU@lGUPCI@SaG@OC!8?A!5?C#12!5?O!5?O??`?D!4?@A#53??_?aG@???A??H?CH_HQcOAS?G?@G_A?GAO?AG@AGC?A$#5!17?_???_#3!78?_?O?C?OA?G#2??G?_O_#8!8?A?A?_?C@AH?JSbC!13?@#4!6?@#7???_#2???@?@??A?CCC?[K?WWOOO?__?_#103G#119!5?@??@MFEKIC?GC?O#129!17?C??O_??O_#98!24?@@kOg#120??C#119!21?F~wo??AECOO#146!17?_!5?O!9?BDEg#98!23?@@FMD~mmWo#146?AC!4?_!8?___???@?@#119???@?@@BFBCLI\RT@#137_!4?_?_#135??CCCAC?B?CACACAAME?A???@#11??_ooOOgwoWG{SkS_uhB?C??GAsGASH#14!9?@#53?_#11_#14!14?A??A#11???OAG_GEPo_ZsJSHAdACh?DAGtCICjODI!8?A??C!5?aG!4?A!7?@?c??O?G?@!4?G#13???A#37_?_R_X$#4!104?_??_??_#9!49?A#3!10?@#0@@B?AAA??KC?GG?GO?O??_#154!5?@BA!4?@?M#98!22?@@BEAG#146@??G#133!23?C#129@?O?_#133!26?@???O#98!27?BN|z^}ww#129!28?GG?G?@???O!28?@!4?C?O?_??QC?CG!7?_#0!11?_!9?C??AA?@#6?A??@#35!12?C#53!33?@?A#35!7?@O??GA???_??OO!5?@???_???__oo[GINN?@??BFFF#16!10?C!6?A???C`?CO?hAOCbG`EGS?A$#28!175?@?@@BBBAFAEJCEKEGSGWoWogo_o?_#94GBCZ??O#133!20?AC?G_#129!62?@#133!29?C?A#134C#105_#129??C#133!28?A??A??O_G_!34?@A#88??_?_AC??gOg?o#2!16?___OO!5?C#13!7?_O?gOi[GC_S!7?_??_#23!27?@#12!12?CG!4?_#106!27?_?__?O?D?CA@?o#12??@#37!9?@#106?C#14!10?O#23?@?G$#67!176?@?C!5?C?COGOgO?O_?_#98!5?@?@#88_#137?_#134!158?@#28!55?O?G_Oo_O_og_W_OW?[??H?C@DA?@#102!73?GO?S_?_???G?CAg@AS_IODHA?t?O_OcYG[?G?GO$#129!203?O?O???o#127!232?C#4_??O?G??C??A@@@$#5!444?_O!7?C?A??@$#3!446?GG!5?B$#10!447?__?O??G??CC?A-#1_z_UXelZwo?OK?NWMuHTgCYcZcZcYdYcXAK?@Ci@G?k?[rXQoTcPKTkBlWtJxTjECBfiojUzERc\I{B{TiPjSbgS_HAcJ?P_DZATBLOTgS_]`IR`^NS?Ow~~v^n~^f^^{~}]nvz|~~n?i???H|eDQD_@cQGbODaX??_?`A`@~?}@}AdR?A@B?A@@?C?P?@?_?_?_?KA!4?G#146??@@A@?A@#132@?@@?@A@A?BABDFFNN^N]WO?O_???`BjF]}~z!4~zv~~z~|y|yoO_!5?@FFK{wwtylzu~~~N~^~^~O}gO_!7?@BCK}z~]{!4~{}~~~^n~TysgO_!9?@BFLJVz]~|z~~v|nitMtisy|u{y{gwoo___???@BJBNFDfBSBBB@@?@@CA??A!7?@?@@@D@#4_??_???O!7?C#3AA#13!6?_O?g_SCK_YCxADaPK@AODQLAN]@R@!4?B@B_?_??_OG!5?_!5?AE@#14??A!4?C#62!4?G#102_?P??C!4?aCHQ_Q?Q?PA@?Q@_Ga?\?iPaHqCp?^_OK_QKsOkytd_`_G!7?A!7?@#106!4?C!4?O#23A$#0]?]heXQcFN~nR~odOHuiTzcZcZcZdYdZeLrn]jTmRNPnbKegNgRkRgRkQdIsAgShYsGTGShCxcXaSB{BiTiSj[Vj@AxIc|ADyc\i[oCGSg[`UcG?_?_O_#8!14?_OGCA??OC?`I?_?@A@A#4!27?O?O?O?O#2!8?B??A??C?C?KC!4GoOOOo_???_?__?_#154!10?@FNG_??@BAC?G!8?CG??C?ADADLe???@??G?WO#146!5?OCH???O!5?G#119??FLK?o?@Ba[g__#146!4?@!4?B@#154???_O?iDJVkW??_!6?EC?qCGC!7?AOTIpITJDAHBDBUCGGWOO???O#88???O?o#28?O_?_??_??_!4?_??g?g?PqCQCWI[KCKMCEEEFABBB@?B?@#1!7?@#35??A#147!16?_!4?O_?PGg_OGgQc@I_OKs_@SIdPIdOuPS?W@]_MOjcOGaKcQGmEphXkRl!4?C?C_CGCCICCAHtKqAT?I[SHyCw_WcRK_QG?O?A?@???I?AJ?~V\jgUVlRImRGmqxdcqGCoaKPeip$#5@C@!9?_???_!16?_?O_O?OCOAO???C?AGA_A?O?A??CA?O`GO?D!4?G!8?C!5?A!5?CA!4?_!9?C??_?gCA??G_O?_W__B?@@!7?zTItjSA?W_GOkQHdOLaWCaSJSISKY?i@S?hWG}lkc]lSmmr~mn}^N[][[[oWwGWO!5o?___#138??CAEBECEDNCLKIWGoO_o__#146_!4?_???CO_??C#119!14?AG[qCunVuw__#154!11?_?_?_?f@VgOo_!4?@ACWO#119!21?BDkO?_?`@FFWwW?__!7?G#129!14?@BCCBGA?_!4?O?OG?_#135???O?OOW[wc{_GgoOO?O!5?C???G#137@?A#0?G??CC?CCA???@?@#16!26?_!4?iCPAG_O?GQCPLA?@Ug@OICPIdGehOCy@[_NOJ_THQHcP?GA?A#35???gG_??@?G???__O?_!6?@!5?IC!8?AB@#53!5?_?@?C`?G?CO_GO_COg??H?QG@C_@CO?G$#31!15?A@???A?@#2!10?O!6?__!5?@#3!20?A#2!5?A?`!11?[s?O??gW!5?AjaBBB??@?U?OHC#32!25?S?SA#6?W_KoNQHcQKaWDa\jSjShQc?T?i@SAc@?A?@?A?PG??O!7?_?_?o__#88!5?@?@#137@?A@#103??G!5?O???_#129!8?ACG`!5?_#138@@#129!17?@AGo#138!6?BBFFIDA#98!15?AAJLu[{#94G?O#138@B@C?`A#133!17?A!4?CGA#146!6?O??`?AC#119!19?B?CCKxVnicSC#146?g?OG@_#127!10?OC#146?@?@A@A?BCA??AABA@@#10!4?_?_?OO?G?GG??cS??A?@#23!18?O???G#43??_C!8?ALBA@UId?IcOI@G?G?C_?O_COH_S`Q@E@?GC_AGAODO_??H?HFAHOJ@KSAOH?_?C?_?@?A?BG_RK`Ag?CGQKQLQDs[_W?_?O?@?AG@??EP???A??AG??a?PC$#3!90?C@OA!4?_!9?GO!6?GC#8!60?OG_?g!7?_O_?_#67??A??ADA!6?G???GOO_OOO?o?_?_#119!10?TJDYUK[GO#98!21?@@H??_#133!26?@CA@A?O_#120?A#98!24?BN^MzUkww_?_!28?B?A?A#138!8?AGCI[[k}MneABXBNPVMFMTMKKGGG#88A!7?@?@?@#12_?___!5OW_O?C_OAG`POC_ACw?C_O!7?_!8?G#35_!13?O#53!7?_???@!4?A#12!14?AO@O_??@!16?@AC!4?C!5?@!9?_???C!4?@#16G??CO?CP?CAG@CqHCWbKPCI$#4!110?_G??ABB@#3!75?@#0B@B@?B?A??CC?EG?G??O__O?_#135@G!4?O#94!12?QCGo?o#133!23?CAGOG#129!27?O?G_?C!27?O_?O#105??O#94!35?@@?GOCW_g_#103!21?_?__?O#3???__!4?O#6!5?O!6?K?CC???AE@@#14!29?C?C?@#23!19?_???@#106!50?@??@!6?GQKQDOG_O???_?A#13!12?O$#28!198?@???@BBBE?CEEKMCIKGCGGOGOGo?o?o?_!5?_#134!98?@#133!40?C#67!33?__o_O??OO?GG?G???C?A@?@??@#53!116?A$#4!199?C#127@#129!215?@!5?@#8!7?G!7?A$#65!416?O#5__??_??OO?GGG??C?C?AA?B@$#2!419?O?OO?GGG???C???AA?@@$#9!420?_?___??OO??G!5?C$#11!431?__?___WGgWGkSSIibZDxADyXMb}|nyLqLO`mCSiEPV?EODGYcO?_?G?_!9?@A!8?A!5?O_CQ?PcOD?IEH]_U_ogOC_gOA?@C_IS_!5?@!8?D!9?A@?A!4?C?_??_??_!5?G@??@$#147!433?_-#1mLvL{lSi^b^_GcGUgrcq_Oq_VdQ`Z?B@S?s?s?WDaZtn_qszLzD|B@DBiTITITGuh?G?HA?_@?C@aCa@A?_GpMPFScGC_?aKSIG`?mOQ?@kXmZD@KFLWfeAQikGMKqlZd~~^ti]v~~~{u|{|Qc\qze~O]TKID?I@Z[MhV\YU~{zw~{~}WWM{mcUMOc{Okq\A@?GC_??COCG!5?A#7??A!5?GC?O#67!4?@?@?@AA?E?C??C?G?GG!6?O?O_??_?___?_#154???@BBBCKCGGG?QC!8?A?A@A?A@C??@EBLSKC_?O?iEESG!9?A@A?@???@?@@AECKG#98?@A?@#137!6?_#138??aCGKO@[?JI@FHLKDKDECCEKCK?CGCC!9?@#10??_?__?_?__?__oOOwGS?CGS_ICPDg@@@?_C?G???AA@AA_#147!15?@@a@@_`QcPiApdSIaBbOAaOAkPRCiPCiPCa@@IAX[qhKgcP[ii`SY`SJe\HulYT?I@g???O_!9?b?AO@_AOC!5?@???A???@???@??OGcO?lGSJE?@gSlSlBt|}evRI_DOyPdFW$#0PaGqBOjS_[_^tZuhVGRLRfHVgYdYcRcQhy@y@|aw?_ION?JCqCyA{}i{TititivHQlalQcyT}~z}\y\}{~[vKpKoBPtjU|\risv[|?cGOgOcPcGoA_AC?O`G??O_#8!16?HA@ADZaLCW?c?G?C!5?A?EGACG!19?kOLA@A?C_O??OG!4?aCA#2!11?@@@B@@?AA??CCC?KG?GGGOOO?OO_O!5_?_#103O?O!6?_?_#146???O??O_???_S?O??CO@C!4?G?G#119!6?ABAOItbV@WGGOO#138??C!6?_?__oo_Wo???_#28?_!4?_???_??_?_GOo_o_o_?o?O_?O_G_GOGOGOIOKWCWG[GKKCLELFEBF@B@A@?@?@#6!6?C!6?A#23!5?G#147!6?A#16!29?A???CP?I?@!4?@?@?@?cGO?IPCgPCQCPca?CQ`DIc@S?I`?I_O?_#12!5?_?S???@GA??A!4?@C!4?G?_#62?A!8?_???_???_#12???@??G!7?O!6?G#35!6?O#12!4?G??c$#5?O???A?@!4?A?@??CG?CGC???GC?cOgADGDGAD!6?G!8?O#2!9?COS?cHC!6?@??@?B?A?aGgI?OG???@??AAPIdfUB???qK@W?_G?C?@?@#6!28?H_aPoItOe?`O?_?_??@?A?@??_?O??G??kO!4?_[?@?HCcG@eXUhCO__OeQgVisgu_oggOog{WSW??O??O?O??_O#132@!4?@??@@!4B@B@B@B@BBFFNMKKKGOW?O?_HBjNN^~zN}pN|U|Vluz~~}x{ogogO!4?@`BBF^zj^]~~~|]|^]NNVeN}[{WWO!5?_O?@TJBBFRLJTBDI@B?@A?A?B?BA@BB@@@#0_!6?!5OW?WOGGKGK?CCC?A?A!4?@#13_??O?_?S_G??SoKoSyWP{?lO[?k_C@RHCB[@O?H_Q@_I??C???CkG!5?_OGC?cKCiSQ??BD_??A#102??C#53_#23???@#13!5?A!9?GA???_#53??S#35?BBACC?BLD|F|byGKKK??@C??@?CKKK_a?___?___Oo_#53???G?c?C?C@QgDgAhAg???P?_O?g?@gA?c$#31!20?G??G#2!5?GGC??A?A??ALC#5!23?A@Q?O@I#3!18?A?@A???@!9?A!14?c?OC#9!80?GOK?GSGSGSGSGS?gCO?_??Oo_?o_O__??__#129@?@??@#138?CACC?KECMCMKMKKGW?!4O___#88???O!5?_#138!4?Go?G?_O#98!9?@A@A#94G_S_O_??_#146!14?G!6?_?_o_???__O_??SWC#135??A?ICAKCSKWSA?A??G?G#146?A?A?B?@??C?@A?@A#6!4_??OO#12_?_??_???__?OG_?OAG?QAiB??OL??aC@QA_@O?H???A??AA@?A@?A@#43!10?G?C??AC@#14?O??O#43!10?CgWgYCXC_HCAOOGa@DOAd?B?@?A??@???gS?@?A!9?@?A`??O@G_QdA@Q?AG?C?AOC@GA?HACOAB@Q@a?g??A?O?OC?A??G?C?Q??COgAW$#31!40?O???OD#5!56?@!8?A???BO@W`CPAPaHQcY??_IT`G???B??A?g!4?@?A@?a@oIdWc?`O?_@`?ACD?A?@Ff`BPRhpBJBBB??_{}rQJZvi@a`UzLRL`Xd@_@BB@BBAAE?EAEGCGGGW?G??OOO??!4_??_#135!6?G#129!15?@@???OG?O?O#135!9?_#133!13?@??CG#146!4?Cg?CO_`#119!14?A@@B@EKGDTQLJIA?C#103!10?o??_???!4O?O???O?O#2?_?_?___!7?O???GG??C!5?AA??@@#56@#37!80?A#102!9?C??C!9?A???S?O`K?_!6?aO?ObcIObW?Wpo`q[T]X\\LYYVHEUxifP_W?_GO?c!6?O!7?@?GC$#4!104?G!7?o?o??G?COA??@#12!95?C?G!5?_??_!5?_#88!4?@?BA#119!24?AAADBBF?G#129!26?CG!6?_#135!20?_#94???A??A?GCASCGCG#67!12?_??__?_?!4_o__?__O_O!4?G!4?GC??C?CAAAB?B@@B@@?@#11_WgcGS_GcOSaMaAJ@D_AlOL_mPSZ}ksz{`{m~s]l{]t}}X}}\Y@RaCxCYG`SwKIpGDg@IcgOCiP?A@??_!5?OA@???O?G?OGCO?sHQCi\`A?g{i?P{QwAwA[CO?p?KQCGC?K?G???@?@?A!19?@??@?A!8?_??_$#0!226?@??A!6?C!4?G?O??O?OO!5?_?_#94!14?@ACC?C#129!61?O?SW?G!4?O?G!26?@@?@??@A#5??!4_!4?OO!5?G???G!8?@#35!94?_#53!33?@#106_A?A!8?A?C?CG?C???CA?Q@_OGCOA!9?G?C?A???O?B$#10!226?__#28@?@@B@BBADADEEEC!4KGKGWKOOGOGOOOoOo?o?___???_?_!4?_!4?_#133!53?@B??A#88!35?A?A?BA@B?@A#3?O#103!4?A?A??@#3??C??A#59???A#35!165?A#16!6?A?@G?C@I?D?A??@$#3!228?A!7?G#138A#137!5?A#1?_#137!31?_???_#88!60?_!6?_O??_#137!28?A!4?A#7!9?_#135?@#9!4OWWWSKKK?EAEAA!4@$#4!228?CC??G#7?_#146?@?@A!6?ABA#1!135?_!9?O#4!6?GG??CC-#1sPsgQGa_G?KHucRM_jCiTiDIDgDiDiDi@]_NwFWtdTaTiSJTM@MHCq@q?hQ@WbOGrCPC?BCHO@W@OGOH_A?_n?D?Q`AaS`Eh?I`C_?Ha??adUDIO?[c?_ORbK?@OOW@ELElJ}^^~n~n~n~~~ZtJdoo`_q?wcWa[Cnw~s~qw|!7~ysPsPqOpp?CY~zpcO?X?Cq?A!8?C#7!11?@!6?C@C?GC??@??`A@!9?A?A#1@?@!8?A!4?C!6?CG?G??G#63??_#138!4?@A!5@A?!4A???A#6??_?_??_?_?_?_?___?___??_?___?_?!4_?___!4oOo!5O??W?G?GOCC???K?C?C?G???GC??A?@?@??A!6?CG#13!7?G?_??A??TI?LJCJCdOxa[@iKLtRKiDBGS?GO?_?O??C@GCOAGo?_?O?@GCQ_?OI!4?A??O??_?_#43_O??oGqUjYTHagS`IO?E?aW_cQoPS?HGC_TOGFAG@?I??G?@!8?_C???A?A#62OG_KOCZ?iDAKA@A?B#43??@C?A?@?HC?UkRgT_D???OCgOkC?C@K@m?v$#0JcJUhvLZvlruHRkPFSjTaDIDyTyTyTyT}`^oFwFIYiTiTjSip}puzLuLzUlYf[JvKzeyv{zunMfmNvNe^|n^O~_Yl]{ThYoO~p]rSxoOxiTY`Y`aK_HaLa?O_Q_M`C_O#6!16?cASA@!4?F?Jc@aO?A?@?CA!9?Ac@a?dGA?@C??I?@?_?GCgS_@?@CHENiN~Ul~]]~nn]m^z~~sjOO_Oa?GGSIHCG_COGc@GCA@?@??ACAC?KC_CCGCGC!5?G#146!4?@???@??@@?B!4?A?A?@!5?B?B?@A?@???A?@??B?A#138?A?B@B@@B@?A??A@@@?@?@??@?@#4G???G#11_!7?__?___?_?o_oOoo_owoowOowww{w[gW{g[y}uiuS[gSiYOaPOOg_SDi?DA?_C@I_?O`aS`_AGaDQgSBnvnn^^ml~z}vzn|vN}^~m~ytxcIiDoHeI`Sp?`QQDHICDIAD!6?_O!4?_?O_G@?AG@K?H_E`Y?Ic?gDMA?@OE@C!5?Po?O?P?PG#7???_!5?_O_O?_#147!8?A@?A?ABI???DADY@^EAHVDAP|b[`_@$#5?I?@C?OC?Q???G??O?O!4?_?A#31!12?_!7?_#5!7?G?C??C??C!5?G!13?O!22?SG?G_O?_?OHQ@_?O`]`M`UhqxQs@?_?O?O?O!4?G_WMN]^LGF?BC@bOD?A?HDA!7?DHIIKLIEK~y`?CCZM~Er@HDhTy~yxExOP_?gQ?`??O#10!9?@gAHCAtqiSqYsGiEQGs_Oo[sk{koOgWO_WWOwOG?G#7G?GGG?GG#137!4?@???@#99!5?O?O_O?O??_#67?G!5?G?G??G#35_#7?_!5?_???_???__?_#9???_!6?_#146@!7?@#35???___o_???O??Oo#4A#147_?O?_??O#3@#59@#12G??GGCCC?CAQc???@??@?@`?b@?`??B@SHiW?_A??I_O?ICG?K?OAGcPOgSbg!5?_#147!11?@??@??AAGT?IDa?cYHEqGd_QcPI_TGATH_S_ACDAISdIhGVCaXHcMaHsQpYDICBoO_?G@IE@G?@!5?GC?G?K!6?@#6??_#53!19?A?_@???A?I?i?g@Q?I@I?OA?O?G$#31!15?_#2G???GOoO!18?G!16?_??_???G@!5?o?O_?_O!6?Yd??@GACHE?C?GJEELE@!4?CL@A?C??K?@K!4?G#8!26?o?O?W!5?G!27?_??Kq?Q?IC?CA!4?O#12!20?A@??D?@C??CO_cQASiC_GO?OK_O_GO_?G?_oOo#62???O!6?Oo?o_?O?_!4?_#1??_!5?_???_?___?_?_OO?O_?_???O??OOO?O??OOO?O?O!4?O#8?O#10!4?_?_?_!6?OO!6?G?!5G?G??C?cC?!4A?C@AS`C?HO?i?UGSdMTmKmBS@a?TwosPOiODADAO@AO!8?O??O??@Q#16!14?C!4?D??CG@C_GDAGDGQCPI_TGaCH?DgAGT`IODQ`GPCAO@??A?C#12!10?OC!4?A!8?@#106!4?@?@!25?@??O?AgOg?_!6?_?_???OM?e$#3!112?O??O!9?A!7?_#32!25?G#9!44?_?_???@???`??O`O_C??JSiEXcOH!6?@A!4@?A@?A?A?A?IC?C??C#11_#4A#67@?@?@@?@A#110_!4?_?o??_#132!14?!5@?@?@?@!8?A@??!4@!8?@?@#3!7?GG??GC?C!8?A?A??A#9??G?K??C?C?CECCC!4A@??@?@@#7?@@#23!71?@!5?@#35!4?GC#102?G!6?_#53!7?O#35!6?C!4?C!4?_!6?_???O__oo_oow}vz~zcAWC?_o___wkS[okZc^DYLb\A@B?B?G?GGHKC#12??_@!9?_!8?A?O@G$#4!112?A@A??K?KA!83?O#8!26?o#35A??_???O?G??G??G!9?_!7?_?owo#102_OO!4o#29???C?C?C?G??GO?O#116!5?G#62??_#88!6?A?A@A@?A@#67!6?CGG?GCG?GG?K?KCKCCC?C?E#12!11?O??o??_GO#43??O#8C#4!11?@?@#35!5?_???AA?@??@?A@#43!64?O_?O?O#102!39?A!4?_@OGDAOD?C@?E!6?IDbIrMBMS^CBBABB!5?_O?!5{~uztsuowsWCgPC??O?S??G_??O??G_???O$#5!245?@?@?@?@?@??!6A?C?C?C?CC?C#88!7?@!4?A???A#129!18?@?@?@?@#103!11?C???C#5O!5?o?!4O?O!4?G?G?!6G?!4C???C?!7A?AAB@?@??@#12!85?O#106!55?G#62!4?GC?C#11!48?_O!6?A$#2!255?@@!7?A?A#31!4?C???G?G??G?W_?OO_O_?_???___O?_???_#2!4?O!18?O?O!4?O!8?GG?G??G!4?CCC?C!4?A?AA!6?@?@#37!208?@#16C$#3!257?@???@!7?A#43?_#117!9?O??O?_O_!5?_#135!36?@?B?@#1!22?C?G???C#102!4?O$#0!258?@@@?@?@A?AA?A??EC?CCCK???GGGCGG?GGG?W?WO?OOOo!6O?WO?GW?!6W?WO???G?G??G?G?G?GGGC?C?C?C?CECECAAA?A?BA@?@?@$#28!266?@??@?@B@BBBABAAEBEACBCFCDECMCMCMCKCKKMKKEKCMCEKCEKCECFCEKACEGEACAECCACAAABABAAB?BABBB?B@B!5@#67@?@?@A@?!6@?@$#7!362?G??G?G-#1AdUKvkEkDs[a]`FHa\_b?RhqDW@]_YdWeOdITJQDyDyDyDiRM?]_?`W@gAPaDY`ITKACICAI@C@CA?@?i?a[bG??C?TAgQdG@?B_d@o?oQ?C@AH?DPeWrTgCk|rrzZ[{dAODYlZ}rn^~^rxv?@B@A^|r{wTwIOG_~}!6~VJVJVjVJTBEdBe@U_?r~HF@???@ETIO???G#9!9?GAOa?@CA?XcgFwD?BGBC_#35!7?R!5?A!10?C?CAK@M?@APowWg#53C?A??C?A!6?A#31??O??_G@?_?_@#102CA?aOEEaEaC_@@O@!5?A#6?@!4?@?@!5?@?@?@??@??@#10!11?_#12?_#13G#43?KEMJV?I#12!5?Y!8?G?@G?QCG!8?TiO`ITAc?@_I@CA`?O_`??GdwWkWoiWsI@sHAS`?QG?Ah_CA_#147!15?@???_?A?tITpEpKQhEXVOGcICtgItSJgUQ`K_@?BZGLJaTMA@JP_?_?_?@#62!10?O???S!6?iSgAd!4?IDG!4?@G?_?_?O?_!7?_#53??@??I?C??@C@?S?_@S?T?T$#0|QhrGRhRiHb[`]wu\a^[VkUKyf]`^dYfXnQdIslIDyDyDyTkp}`^~]f}V|e\yd]tip|ztz|t}z{z{z{~?v\b[fyDyvI|VlYv}^{NY}L~Nl~yu\aTyCXeGiTiPAG?C?A#3???G_?A?@#6!5?CA!5?@?AC@??DOHO@!8?_CGO_CGOA?@?C??@!4?AgC!7?aCNTALFLN^iTzSvlf[~}zl~eJFwFwD?BkB?aDO?GC?_COG#9?_?O!9?_#11@!5?_GSmHA???C#106OGA@?_???AG#117!5?@???_O#106!6?C?CG?CO?OA!5?C#12??@?@?@?@??@?@??@??@?@?@#16???C??C?C#102!14?OC?T#10!6?d!6?S`!8?`CGO`AS?i?DIS?GPASHSaOC?z@SI`O??A?A?C???oG?_G??G??_???O#16!20?@?G!6?G!4?_?_DO@?i?T_I_S@gCG@IC?C?A?CG?O#35O???CKM{Mg{!7~}{{k{}}jk???C~TBE|I@hSiDYF?C!4?c?O?_?_?O_???@?W??A@???GC???C?g#11??_?O?T$#5?G!7?A?@#3!8?_#2??@#5??_#2!7?GO_??o#5!9?@!8?G!6?A#2!8?A?@CA?TG!4?Cy@G_#5!6?_?O??A!4?@???A?i?@C?APA??K?C`BIXdYdOc?KO_?_GC?FMk}{_?GAFIAdef]?@!6?Go_cGO_cg{wYwX}gM~K?sOy}~~{W_cDzoITQgQO?TiCj??G@#10!6?O???AwCO??N[if|RxVGhdq}N|j^yV}T~tnwYsp_A?@Y_#102??A?D@rj@two_#141??C!5?OD??A!4?A#53!6?G??G??C?C?C?G#13!12?GC!7?O!9?_#35!20?A!15?DJA?DG`Q!11?C???G#13?G??O?hYCCI?SJsA@ePdJSfI@CAAS`IUGdAl?UI`CAGDO_@_IS!5?C?@C!4?A?ciC`APCQHE#43GiEYC@IAD?B?E@@?O?O{o?cooO??g[Sc!5?A!12?A!4?Q?I#12???g#6O!6?o!5?A#43!17?A@cGQc@OaXa[_OaAHOGa?i?I$#31!6?O?O#32!11?G#3!64?O#4@#3!21?G_SG#2!10?C??_??Oc#4A#8!12?G#4woO#8!7?_#2!29?@#8!6?@??A@IPG??___O___!5?O!5?O#7???O???AwCOgO??GA_AG??A#6!14?@?G?O_??C#147??C#43DAEGOg#110???A?R`GPcA?C??a!8?OG_GOA#43??OGWiWoo__OqGoSWOwOW_S??C?O?G?C??GC?C??G!5?C#6!32?AC?A!17?_#147!13?C#43!47?wE@#102!15?`?@#13!10?A!6?O!5?C#53_#12_!5?O?A#7!25?O}A@??_o~z|S}?O??O??O#106!12?kB__OaW_O_??aGO?@?i$#4!111?_!72?O#32!10?_#12!33?_?O?@O??C?_CQGD@O?C_Dg@i?IOF?IA\`]oDQh#62???_O??CADI\\kYtcOhohQdGdItYmXv\ip\pDgooH_#147???MIEEi@UI_ccEccWICO_c_cowIKGOGOGMSAYMOIQInYdMBEJE?_S_W_og?OId!4?dG#53!109?O#102!26?GAP?@?T!9?@A@?@??Ol~ty???@???CAD!6?_?RJ!8N^~~~}~D}Zs?WSD??DAIGT??ACGe??_$#122!282?A!6?A#7?O#99???@#7!8?@@@#35?!4@!7?@?@??ABA@#10!5?@!8?_#110!206?OgO!5?AH?c?O_?O_?O_#147!13?A@!4?AG@SDA?G$#63!283?GASCAgGSGTAC?CGACA?A#11!11?@O@?@?A?A!5?qiQYFIAFoascogQOjocPjthpOdYp{xSx^VjR`@??gdtY~~~?Yv!4~j]oo{tydYd]zvn]|j~?TiO`ItA|aU`KjOC?i@OIcBWC@?A?@?@CqHS`IS`dQ\OUH`[Z|vyn^}^tj!5~z}}zEXu||IP?IOKahCO_!4?_O??O?G_O?gSaTiBGc@A?Di`@A?AXaPA@!9?@A@A@?@?B???@#12!41?G!4?@!6?@$#166!284?@?H#16!27?G?CG_CG?G?G?C_??G?G?C#35???AAAE@_?@$#102!333?O-#0cHuGvHuZeXaLOjUiPkPmWa\`]fVVfZf^pDzczSjTiTiTiTiTyn|vznWbjvjnZvjnzmZn!10~Tj|jv!6~U|y~~vynnNnnN~F~{v{^kz{gPq?COC?dYdGO_??ACG_?C?C@C#6??D#8??@G@?A#6!6?A_C?A?OA@cAC?P#2@#4??@A!8?yC?OO#6B?@?A!7?i?O?_gOgT_ZChUIlqJ]b{n}fbfRh{AfxHfOjUXdUWVgIS_GPiDO?GA?G`CO!8?G?C?O??o?_O_O_O_?_O#110C?@#99A??D??Q?G?A#32@#7?_O?o[#35Oj[wwog!8?C!7?A#12!17?_#16C#12!12?G!5?Ow!4?G?O_?_?O?C???S!9?@a@HF?AO@gC_O]qkRs?bBLCF?_a_oYdZScYjc??g???CA?_OC_?D@@BA@??C#147!6?OcEHFHaG_O???COiCqXfQJIjXkAVIdPIsTIIHkeuXDvOYXLfICRmBPALO?_?O?OG?GOAAA@???O#62API_JSBS_ODYD@E@E?CG@???NPDgeP??_iw?O#53!4?A#43??H?C@?hAhAk?oD??COcGC$#1ZuHvGuHcXe\onShTmRmPf\a]`WGgWCW?My?JCbSiTiTiTiTiDOAGCOd?SGSOCGCOCPcO!10?iSASG!7?A!4?CO??O?_?G?@GA_PCAFmLvZdB@Y_GufLZu\hqT^qZxizFxAC?wCGRhFiJE@rhTjRhPnxeYtPZkwi{wx{y{BShQC???@B?@AA@BO!4?i?T?_#9!5?GC_QGdQHc@?B?@WKW_O?cO?C#12??CH?Q?CGAS_GPiDOAGa?S`COA?_!4?CHE`Y#102!5?@???@??@#31?S?PcGTg@iSAHcAGOcOG#110?g#43!4?@A@?@_!8?G_?O??C?QWMG[IgCkAcOl?ifxO`G???_!4?@C?@H!4?@#10!15?A!4?g@SAOjShm[wswythuPzVM_HQcItGs?Y_MPCJC@OC`I`CQgO?iO@g?OG_OGOoCOGOCg?W_#16!6?GO!10?@??@C??C?S?A@k?PIc@I_T?Q@G@aG?d?AO!5?C#12??_!4?C!6?C#7!16?_?_?oWGGSGty~~~oaYOHexZSC?G#147!10?RK!6?SA?G???A$#31!11?A!14?_??_?_??CO?G#2!16?A[#31!4?_?O#2!26?H?D??G@?Oo?OO?o?A?@?A?@???G_Iwy#32!24?A?C#2!36?h#3???O?O?O#8!8?DGAC@CA#1!9?_#8[?O#7??O?GCAPGCaOG!5?@???@#11!4?G!8?@DBbV`FbSoG_c_O_?_#62?IDE^FIFJNiYugV}Oj\cJTvlYnVnVNbNS_#147??AC^lb@D`DjOSAgcSyG`cd?U`CQhPlQlQkO?C?A_JsHAcIOAg@S?SitjSiCaDW_#6!13?i?@S??h?_?O?A!9?_#13@?@?@ICGQ`GPCGO@cI_IPCOHA@AO@kQOlACI?C??g?dOQH_ODG_OG!4?G???B?DA??CO??_?_???_O??C#35OG???_!6?_??@!7?gC???xJB@BEVBBAw{{}~zfb|mt^sj{jOGyDQK?EPa@AC!4?G?D???C?@?c!5?CA???_O#106@AtALAg?O?h?CWCG$#3!89?_#5!22?O!8?DQ@GQcH_QDI_HcAS?wEwz~EpugSwTsx}KSIOkSm?CW@GicAETBECBDB{jUlzDzUmk{m{l{kn!4~TTiiV\RmRgV_ZC`O?CO#10!8?CA@G?AOGfO_AG`a?T`ITaCOalvT|jUYjky[[g]wWa?O#7!13?Sw?_#63?@#1!6?_?O#6_!9?_#102?ADEDQ?A!8?G!5?C??O??O!7?@??A#35?O!12?o!6?@G???O_G!4?_Gowsj@T?AhC!5?@???@#9!6?@#147!33?@@?@??A#46?O#43G!27?G??G?@?C?@?_??c?A?cCQPGCQ?G_c??TAK?O?DO_!4?G!4?G#6!20?gA@?_Hq#102!10?GE?@?BBn!4~x|~u~Ga}SGS_P@DIDCQb?aa$#4!119?C#35!115?c?G?_???AC!20?@E@ZNn^N]^dYg?g#12!30?G#13??O?GOG??@@@??@??G??`#102!28?A?@!5?O_!116?A!10?_!9?_??O_???g?AC?[gSg?{Oc@@@??CWK!8?NF!4?_O#53!36?O?AG_Q?h?@O@$#11!314?[siSiO?a?PBiD`?HA?`A_DOAOHA?QDW?NGVsJu\Ztn|UyjCaSIOjCADYf^f^f^~^~NvJDJS?iUgAxESJU??CA?CGEGE?G??C?G??O?_?O_IPCI!8?TmTDmQ@lASI`UjIwEc?iDuBNyv^nvnRhuou\v[ny|~yjDy@EW@s_S??Og??AO@G?P!6?GAC?AW_g?P?iOALAS_?_?_?c@#110!23?_!9?C_AO??_IOCO#12!21?_#35A?G?@O$#16!315?A?AOCi?SAG??Q!6?@?A#13!27?C#106!145?@#62!10?A?@#11!70?O$#62!323?C!4?O-#1BMpMpApG?]`[_\CCxExChQdGTdGRcIPg?s?eGc?_@a@aHATAG@OIo@w?`KPgCW_TA?A?gCO?_!4?A?@APc?G_GOG!4?K?C@?@A??@?Hc@?PCh?@GQ`AS@SIDcQUEH\nX{j{X|s^FE{JSKL[\w_ecwSBB?@?ABBq|{!5|WiKkOCGETECdCO_?wwsRWHyLGG[CSiCsH_Q#8???O??O?O_G_?gOcOC@A#10?_@OO@_?OLChS_KaSBYGpmW]kZeYf\`^pCgaChS?hQ@S@OaOG?O@#62???GC!5?HQC@h@A?`I@SCpcaSgv?OGc#11??_Og?HQ?A?@?qD?AQAgSKsYKtMtGAoKRC@AkR_YTgUhF@@aDJOjPgOmT]TI?XK{dSbg[Gl{wk??TWADA?B[ApcIq?o?[CLQZQJW!4?o`GQ@A`[?gOCW?gG?o?GGAGSAHOGmtqIkfrW|{{~|~|~yz^~^~]y~V^VZlU|jVrniSgXnuN{eJxD_?AdOI`CiK?D??A???_?@?A@??bCI?B!4?A?@??C#7!10?oioratiTgVjTkATj~mnFFZE]ZHKBR?OG_O_#43!4?O?@G??TiAC?A?S?dAGO$#0{pMpMtIv\`]`^_zzExEzUlYviYvkZtmV~J~XvZnZ}\}\u|a\f]ntN}D}]rmVzf^i|~|~Vzn~^!4~|~}|mZ~f^vnTV[j^PnZ{|{|{|}|sXuxExEX}vlU|gSgDIBCHOA??AA?BA?A?O_#3!4?A#6!6?C???@?@???@#4!9?A#3??a?O#4!5?B???A??A#9!18?d???A?@A?A?G!5?WCI_KOJ?A?A?BA?@???C#11!13?AOHOAHADiJ{FLE@jE?G#7???A{sheH_??Q??cOS?S_GIQHHF?KE@B@#12??@#147ALULFSNuTGIdwhcTgQJcRIpI@DKRkocOQCWDiTGU??g@QShS???@A@A?@A@!4?S??A@#10C?S?gCOiGTk@wKOc?jG~bZqLclsdU{L}L]U`mt[AlSCa@cDCuK]cTGTALOIDO?G@?O?D!9?C??_?_#43???_!6?_!5?A#35!9?O#43G!6?A_OlO?gHOKQ?H_S??G?O?C@A@#6!19?G?G???A!4?HAC?@!8?_?_#106!16?iTK?C??gSACSg$#31!5?GC?a#5??A?A!24?OC!9?_#2!34?O???_g_O__O_AAA?BA?AAAGEgAOe!5?BA@?O?__?__!55?A#6???_?O?o???WgSiGu\_VGRdA\a\_^a[jTmPmz}|~FwCN_MSn_rSB[`TIoCpAOD_???@??A??AO@???_C!7?_C@iQdZaxBJUWsQgo?SqGGAoiAP?@SaOG?@A#43!5?Oo__ogo?G?_G?CO?A@?@!4?CGB_?JO@?GF_?A??wUOK__C?A@B??_??A!4?GO?@Q#6???O?g?Ag?T_?A??A?@?C??_?_#9!4?G#13!5?_??G??Q@IOcH?AH?@?aTAHOCcA@IDOBGCa?@??A???D#35!11?_#13!5?G?O@!6?@OCI?G!4?CO??O!6?C#35???A?ABJ???`oW{k|k~]z~?[b^bJYBnMJDTAKPI@a@GO_?O_???O_O!6?O!5?CG!8?B!4?_@??@$#2!46?G_O!5?A@#3!32?A?B??A#5!21?G??g?_?G@?HCAOc@S?cAH_gXBsjrobaF^XZBj{{}}}|{{KAB!5AfTpRnXvhixzYzk^|FDJKdeDAvvbbB@PB?ALgvkIWaLaL?S@S?@A@#7!5?A`?Q@??OG?_#11!4?G_CG??@#1!36?@?CCHk?COdGD?GaCG#35!4?rgsW}~^mC#13???G@?GA??A@?@A@_??_#35???oo!5?C!10?A!5?KOc?G?__[?aBQ_?B_c?A!4BA_DO_IO_@A@??C#147!56?A!7?@D?G?GCQHASGCODiPCOHo@WcAoNdPYdTYhTpKiQghVujRlYcTGgC?Q`SGC!9?@#102!20?AB!7?__o???@K?zbRBBB~~~^n~}v{~??pIituAjWxbF~~~$#5!91?C#4!27?AO_???_O#12!103?_!6?O!4?A??a?PcXcWa[_MpCGqChU?gS_AgOGS?g#31!10?A?@A?AG@A???@#102!15?@A#16!6?_DO?C?G!9?A#102!5?AG@_!4?_??gCOG!4?A!4?G@!5?A???@#9!14?C#16!85?A!4?A!4?OAc?I_?A??@??A#53S#12!10?O!6?@A?@#110!34?G???_?Q??C?C#53!17?P?G@$#3!122?OG#35!111?G??G_C@??@??A!8?G@C?A??O!6?`AO?SdYcT!44?O#12!18?G#13_!6?@#12!7?A?_CGO_??S__O?G@K?A?O???gS?@!8?GPKOB!8?A`Bq@A?@KO?A`?A`HAQQp?B_R?!4_a@o???cO?G?AAB!7?_!6?_?_??_!7?C_#102!18?A??@!6?cOG_S_ShCI?a??A!5?BA#12!58?O?G$#62!369?C#106!131?C#62!19?O?O?_C?w_[_[sd{OpsI?D?C?SGS_CGOcGO?O?OGCH@ccQ_GCGc[gS???_-#0!4~^yNo^a\qlYf\q|q|v|z}LvdVjufTzZm\rUZ|r|jT|T{pXz]tjUlRnzjV~jvnrnU|j}~~vl^~N~^n~\nuz~|v{|y{|}s|!9~NnN~nZe^lYViTiTiTcIsAcA!8?KOgO?_??C#6?@?H!6?_#8G#3!7?_!8?A@#0!9?A#6???GOcG!4?_@I@ApKO`?O?noNonOnXaShv^zmpNqJtQrThgUbHhGEc@_ShAP_!5?_OOo@G_??_OC!6?@?A@?A@GA?BgQJ|N]|MRNiHMVMpMTQLQCR#11o_Ogw{N|M}lUkKTeGas@cPEgTw~s^Yi]PItwPYvs_P?_O?@C???SAgPK?E`M_MoFwaqXVP`RB~PMb}OwClP??BGzC@?@?`?p!8?FDJBKRlQCH?EHCJ@SI_SP???A??CSgOsIzcuRWmen~n^~n~nv[zvfQzu?sAsBsrs^{uryzsqpQtIcQ?AaH@?DGEh?@_?_?_Qwu|K?@??A@?@#6!5?oC??c?G?@?_?_!8?C!9?DA!6?OOG#43!27?P!4?@$#1!4?_DON_XaLQdWaLALA?A?@q?Q_S@OaCcP_K`C?K?SiAAAE_C_GSgOkO?S_?S?OK?hAS@??GQ_?O??O?_O!5?@AC@A?HA!9?O?O??CO_QcGTgTaSiXsG|W{lZVm^lZv_DD?JOPoRjuiaPYYsyuWqKAlq~n^N~n~uSzkgKOYTMZKZU\U{y||SfYaOqo_PIDAxKQ_C#9!16?CPGoHsAdGaO@_SOC_O?_P!4?O#35!12?O#9???G#13???O?g?OG#35??@?B@LOU?cAO_?oGo?oO_OMoidalxkNInVFB_A!5?_#13?GO??G#12!4?A!4?_??_!4?_!5?@!6?_!5?C`I?S@I?BC@A_KOg!6?C@!4?O_??AG!4?AGOCq?YChAO_CO`?A???HoQHS?I`?G?PG??b_@?@`?_CQ?GaOP!9?A!5?Gq?oAS!7?C?HCG#13!4?@G??C#35?CA#53!10?@#10@#35??a}}}s_skw{l~n|JzunXnQ~i[VgYdGGM_MOCGEH?O?A??DG_!5?G!8?D?A_!6?_!6?o#11!7?O#106@$#2!6?_#31??C!10?G?C#2??GGG?GGG#31???A!5?A???_??C!16?O#2!10?_?_??A?HC?AGA?@A?@A!10?_O_?O_!4?_!6?A@@?@#3??_??_!8?C#4!30?@!19?KG#8?GC!8?QC_OA_C?I?ACHAG#10???A???G?CGE?G?AOBGRE?JSga@onQlYTidIeELIXEBaPc@a??aDOeH_C?O_C?C??_#62?A?c?SA`G`?@?G??A??T#43!6?@??@?A?@@O?AG??A!4?_??_GO??c??@GaS?`aO_NIH`SOapG!7?CGC_?A!5?O#6K@ICgQK@EGAC?Ak?c?oC??C_?O?_#13!7?@O_O??_?i@SI@EGEKpGTG`CG@S?H?c@?G???_#35!5?@??GGC?KJLHgJKI?AGG???GC#43!6?G??S?_P??oWGQHUDC???@!6?A#7!17?ADIVooO`jrrpe\ju|yty?D~V~J`D?_etytaO_H!4?IG$#3!37?G_#5A!5?G!4?@A?@A!56?@??@??A?G@???A?A@QCgP?QCGRiQnsHeKgSGTSmddJDHFDr|QL?O_O?O?HjCRUpmdipcrchah@DAAbG@TbDNVIso{CA`NYlj^?LOJOdOcXaS?_!4?C#12!6?O??_?O_?GC_AC??@OlQdIDIDGoQDaWC@aHa@Q@GaDGE?Gc_Og?gO!16?O!9?O#147?o?QgQ@iOELJSOmxSgF?H?DS@EDIFIDGGRCiSIPmQo?SAGBKAO!4?_?O#10!14?@A??aW`CO?HCBMJMH?njH^djU\gYokQkOkjUeHcQ_S_?TageprKSIqIQEI@??H?C@?O?O??O?OG_CGOd?@!8?_#102!4?C???G!7?_!9?C??G!4?O??@ATIPE@A!18?C#110??O!17?OA!7?G?O_@C_C$#2!46?@GA!8?C?G??G#3!48?G#4!22?_!6?AGB#7!79?C!4?G???E@GCAC?GOG#11??@?KM!8?O@?_C@gOKQ[KlUSG_Oo[qOKA#7?@@???@?@??@C#102!20?@??@!7?A#13!9?@#35??_#102!5?ACG@I?K?H?DAG`C!5?_?O!5?C?_!18?O#43!71?@!8?@?C#16!11?_@O?AO!5?C#62!12?G!5?O?OAC?HOAOd?SbGT?O_B@N?CG?GOaCH?DI?qW?g?cSAmLPIDIPnIUo@OiDFosXo!4?o?o_!9?_Og$#35!238?@??A??OKA#16!70?O??_??_@??@???A#35!16?C!4?o!6?bWSPJ?Dg?PG?G?MC{?m`O?_?R??eGOd?_oOoOOCA?O#147!69?@??@?@?A@AdItZkvT[AixGexUNeRhUhQgEHA?@#12??@G???A#102!41?Go@A!4?C???KYNPooNJEN!4~NNN^~~m~~n}}~^nV-#1OiCc@I@Q?P_@Q?@GbGVG@IPChQdGQHQcG?A?O?O???TgPAO?mPkQGaICA@??A?@A@??@?C@GA@Gc?PA_@!8?C!4?G?C@AC!4?CADKA?@MODiD[BmRsIPwDi`|a\e]eXe]]}}mm}~~lznzm\~^~n~vUgF?GFwEgFgFhRElYly}gYtg]gug!4~TiTav[juj???|ag?G_#9!14?C@c?~TiPm?Y`Qg?_P_?_!9?G_#35!13?@#13?G?SG?S?G_GSGC?_#6@?@A?@A@c@C@QD#147?O!9?OgOIsHA?I?I?AC?AXcJEPF_S!4?AC?sG_Ok_A???Q@CQLA?A@I?C#6??C??C??C!8?|?DOChSHA@ADgTgP???P?aO??O@g?PGC#13!10?@?@?@?A_AC!4?_Ca?i?i?TCPC_G??O#35??_O!15?i???|{}wO???sCC#16!7?@???A#12G!7?O#62!6?PC?OgP?SH??@?S`I?QDOcI@gA??DAG@?C??O_O?_??RGtgYkCIQgIoKQiSgRGCRGqGfplwWkTi|tJ@???_?|?gAD$#0nTzJ}TiC~m^}l^}VWvgvytmzUlYvlulZv~|~n~n^~~iVi|n~PmRlu\tz|}~~|~}|}~~}~z}v|}vY~m|^}!8~z!4~u~z}|z~^~^y|wr{z{ody@wAgOGJPm@y@W?TAX?XAX_@??@#6!13?O??_#4??U_!6?_#6!20?aCGP?`SG!5?\VjUHf?^_^a\eXvGvG~j]H~?iCmP~d]HElRKBDGfCz?TiS@KBCP_?O??`CQ??I_C#35!15?@E@E?DaoKBIBIDAANFFFN~~~???C#13_O?_??O!4?O?_A#102???_O??A!4?@gA?S_AO?PACH?Ql?pG`S#12?O@_I??G@?I!5?G?Di???_?O!4?dIVG??_O_?aOK?rCg?TGCa?iDaO???@iCiOe?ILEL_JMFIVhX??T??i??GDoG__OwOGoo!9?~?~?T???A?@?cG?_AGa#13!8?O#102!4?S?G_?O?_#12!10?O#7??_?_!5?_?g??O_WOkQwwsoypxw~nNn~^~~kFAF@BwSLFtNRlSiTkRgcPCOGA??_OgSAGsg#110!9?A?O$#31???O?_#2Sh!5?_?_#31C???C#5!18?_#31!4?C!7?@#2!22?@!18?@!9?@?A?@CA@I?SA`S@c?_???CEA!11?O#32!23?@#8!34?_GA!5?OI_C?G?P?G??C??_#7!4?P!5?_!5?O?O!8?_#102!48?O#43G!5?oK?A#16???G#12@#43!5?@!5?wGG??@?G?W?O?_?G?C`GCP?SH?q?aOG?@A@#10!17?sOCWCgS?cGO??A?NmNmTK_yCi?VierX~TqXnYdYuPyTiXut?HOJsPwtGQC~??T???CP!5?@??_?@A?S`AOC@Q#43!13?O???A?A_??@??A_G?GSjjaD#35?GC???!4~mz~nFm~Ju^dYcJUPlDYdJPCBOD?A@AC??B#110!9?oG?C?A?_O??_?@?A?cQG_G_?GO#35@!5?A?A!5?A$#3!101?_?_!17?C?C#10!108?CPAKA[IVGpCziTj}POJkViDi]YglYtSY_P_A?O?@}?T??@!4?A???C?O??O???O#62!8?@#35!26?OP_!5?B@?GK_g!4_??CC?_?_GQtpysqwtobobpvqyT??_?_??_?WO_???C??O?G???G@C#102!74?_!5?G#6!36?YcZ_GCQ?_I?CaC@gF?GD?MAC??O#35!8?O_O!9?@#102???@?E@FOCAEFBA@???S~~~^~?~V|w~n$#5!123?A?O??G_?`?c?@_@@?P@??QCOCPa?_???GHVwhVwFwVwVWUkxQdQD@VdIV`VHV!4?GPaKGA?@S~^v???S`UGt?Z_TaGe?vGr?O?Q#12!11?O?_?_??A!6?_?OAGTID`CQ?dI`DQaPg`Dg_?O?_H?@@EGCGTiOLaOS_sG_l??G_O!30?@?G?O#147!142?OG???PVlYmRDSIFR_@?CPIDa@I#102!54?@@$#35!241?G?C!4?A#11C!4?_O??@!5?GKETIaVI@fIVauy}WowogOG??G_G?_W?_w_O_???}NbRLdJUt}d~t~|i~\cZs@E_^j{meVdiFHVGEPBOEPICI???GPKAtMoK?IDBGF?J[N[M?L??AJIJBABA|A???aTiO@???@BD??Q!7?GC?dYdGC??C?G?oO_O?_!6?Ti?~?ziaYFv^NmFNFNM|~j]|nz}l?~?~?~~~?B?FJFnVHpXcGQdOkygDokTa?OCOyDyD~~#1!53?_-#0~lZnszU{Z}t}u}u|zv^vn~n~~tz|u|j\~uN}dmTj]hmvI|f^!6~iT}Zuh}ju|}n^f^~n^zyvz~zdz~|v!5~NvnVnvJv^uN|V~L~n^vzlyt{?waKxCYD~J}@}`KpG?GO?G??A#6!17?T#0!21?_???@?@#4!8?S#9!26?COjCQcAP?iCpGqHOE#35!4?_SA?CO?D??A?@??@???_??C@CA@A#13???OC??SGPJPT_JOlOG??C?_#35@C???_???@#13!6?_?O??OH?A?O#12??_H?G?O?G?G?g@GQ!4?B?A??O_KwSW#62???G??G??O#6CG!4?GO?B?@?B?@SgVgTi@@??O?SjOfHSjCPAGCP_G?a??G#13!8?_!6?@GQ???AHSa?O?I?_?L_?O#35!9?_!4?_?_CO`?PCGWw{Wz~~~R??_ooo__!8?Wwdrp{{^~}^]{m}^r}^u\WXi^U|ATA@AG`DQ?PGA#1!8?O@#31C#110!7?O!7?C??@#6??C?G??@G?P_#110!5?@G?C@CG??G!4?O?cGP_$#1?QcOJChBc@I@H@HACG_GO?O??AC?GAO_?H_@IPaS@EP?dAG_!6?Ti@_HU@S@A@??G_?O_CDGC?CAC??G!5?_GO???cG_@oAg?q???GCO@I?@ACbA`@q?c@Y@KrCv~fn|rsy[AsIQcYaSIqKRcJS~Ti|h}~}TzUlZtmTgA!6~Rrzz{vmV{woVgQC?\_[ehZXcOcOhQg?PC@#8!12?_@WCg#12??C@C?@???CB??@G@AG_OB?D?bDaDYtIVLBEI@?@?OGG??@A???A?D?BQDPNH?CBAAIA!5?A?@#16!9?A#35A!7?OoO_G_O{s[S{Uk{_?bOoGX?CC?C??GKYdRk?uHeBBD@?`?dB?@A@?@AiVgT??C_OGCA@?EG!6?c!6?C?@!6?_#6!40?O!8?C?yc???c#102!5?A@C?@???_!5?O_?A?G?A!5?@@?@#6_??_?___?__?sITAgpCy??gQkQJV?Qc?L?K_O_g??@??O@_#102!4?@A#1!4?O?_OC_#35?C#102!11?A?BBFBff??CBBAEN$#2!25?G?A???A??O?O?G?_O?GO?O#31!10?C!4?G#2??O_O!10?W??A!6?O??gOGO??G!6?O_??AC?B}DX?CA#5C??O?c?Q?I??O?ACJD`|JtlZd\jtLrkZsj?i?AU@?@iChQcIPiV|!6?KKCCA??_BFNgFlzja^APSCcAGYhEGTPcJOIs?gOA#7!14?o!4?GDAOG#11?__!17?o?WpGS[TmFdR{y_tmskiIOk?IeouqxW{wSdI]v~~{^}^fjVJfjfVcOd{hfYl^cEfYf\eBB@A?@?A^~K_LoA?g?A@ABfRdQkRuHuHwsy}~]vI{{u{m{u{@??AiToMlRJsIO_?og??C??@AGdAOHCqGA@!4?IX?@???CGQ_lUPhSb?[Aj?tIQ_ASIjU|Zug|YTNfN?@F?B?B?JC@BFFBBC???cWuGEKADIDcHsgTiSaDYCA!4?@??A@#7!15?_gTCA?ChE@?hs_~lZ~qnqZjNV|ny~n?}]~n^}qG~QTa?RCBASA@?FJTiTdYfHrWioso_OOOS?_??_$#5!28?@?C#3!84?O?O_G#6!61?GOG!4?O!5?`GA_AXf@EOdAmIomtJ~Vn|~~znSzLYdiVTzIEHumpawgCB_[PiDQGFO?aC?AGa!5?G#147!26?@A???_WS@G???_?_WSgSWCWgHcY@UGdQ?A@?D?A@??A!4?@???G!5?AOAh??_#62!18?G?O?G#10!7?IOAc_H_CHOE@SjgtQyCROlCqXCtc}~~~S`CiSjShQ?cHQ@iCAGT`HS`IOhASg@ShAcHQ?`IOG?vGOdG_?O_G#147!11?CGAGADIDIJuHTiDAD#12???G@A!4?_?O??G!4?AC#35!36?C?@#31!5?_??gC@gAGP?SAg?c?OAO??_$#4!117?G#10!116?GTAPgSJ?EOgdQgk|Wy[wSWdITgAo_?si_i@oAgBCHA!4?Oc??_???G!4?@?@#43!26?G!8?@A?@???O??@CQ@P#12!47?A?OAG@?gCAOH?a?AX!4?JSATiSjUhu@C?gCO_?GAc?S?DCOHBc!5?DAC_?O_GuGYSZkMOa?O_!7?G`?O@??O?O#13??A#62!11?_??_!5?C@?HADAT?HAH_GS?AW?hU?cPC?G!8?CO?AOC??j???O_@GT?kAXiCPcgI`wEWOiDgIcOUGcPCGGSGGnjj[WtGO$#6!290?C?@#16??_#6!34?_??_#102!5?CAC_lAG`??cO!5?@#43!122?@??G?O?O??A??H!4?C?@-#1@Qc@IS?SIPcHAD_D!5?O_GS?KOG?@A?C?DA?Sj?gOGcOcGOCG?S_HQGO_OG!7?A_S?A@A???A!8?_!9?@???@?@S_??@?CgC??C@q@aDADI`gTEiJyUColLOfW|q|ys~{~[js\iFtu^MnVnD}nT~]t~{{!6~AUHHLV[[~~nv]{w[wJwcQXwo??_?g??@?O?A#9??_W?C@?EL[I?C@A?|cY?O__?o??O?_#147!29?@!8?C!6?APi?OCOC?iS?I?D?COGkcTG?aHcQH?O_#62?_#102!7?KO!8?AD?@AC?C@CShQ@!9?A?A#6??@ShATgDQhCgTa[QhSJoBgA?gS_GOJc[cY@CG_?O_AO?G??G_Q#13A!7?B?@??A??A?A???O@#6!11?_?_@?O@G?QC?I?SgQH?gAs@Oa#43!7?GC?G?CGPAO#62!4?_?g?Qg_A??_I_GCHUGlIc?@i@ACB?BA??@A!4?__O???WsGQgDw?W_eKO_TKOcRcZCRCHaSLbGUhViXaTXU_ZagV_JSaPGmYShIa?c$#0}lZ}tj~jtmZulyVy!5~n^vj^rnv~}|~z~y|}jS~VnvZnZvnzV~j^uhvn^nv!4~{|{|[h~|}|~~~{|!5~|}^~~n~^v~~~}~}~}~}?\qlYvjVz~}Z}L}LydYdITiPCoDgPIO_KOC?C?C!9?o#6?@?O?g#7O#0!17?@?A#8!28?O?_!11?A?@Q!7?_#35!5?C!4?H_@?C?OC??C?I!5?S!9?GC#13?A?GFg?pMpMoKoKASi@ICGC?gcOGAH!7?G??@#43??C??O??O!5?OC!10?A#147A!6?@C#12!11?C!9?C?_!4?@?COG!10?_???B`GI_C?`CW@Gc?G_QH@lUHAB???a!5?@_??@?@c_ylYsGOcW!5?@AA?K_S@?_@BiQGc?O??A??gA!4?_!6?CGP_!9?O#6??c@??A`?q@aA@Y|}TA|Rg^oX~~}{VG[?\MClQDCIvGR!5?@?A?_Oc??A?@?A!6?@#7???AGaCgVCXFg^oj\evP`ISd[gO$#31!12?O?G!10?_!11?@#5!12?_#2!4?C!9?BAB?BA!7?@A!5?A@#3???O?_G#2!5?@!4?jALQcGO#5???@_???O?G_?SA?gPC?@iDAObGbAHA@J?B?bSJaTGIG_`O??y@Oi?`I?BB!6?|geoogbb??O?@BFBFcCQLcEKXs[lVSLSCkJslYCA!8?_??g#7?A??G!4?GCGG_W#14!37?C#16!12?_#10?A#23_!4?_?_#16?CO?G#35!9?C@AQAb}Q}~k`_lsGtG}|wow{{!4wyqA??GSzk~[}|]{wWw|~}iASgAQGCrBAW?gCa??GOOkA!4?_O???_O?OI?G??@#9?@#35!44?@?A??@B??@?@?{Do@Gi@Tw|}zbRBG?_wwo_?EAEBv^^V~lF^|R[]t\SyCgTOS@A??S#1!10?_?_??O#31!4?A!7?_!6?a??_H_IcP?@aOC`GQgC_TGa@G_?O??C!6?_?O$#3!121?O?O#2!11?A#4!42?O#6CA!6?G_??_?OBH_A@BeJBQ?jqijBSHQdZd~z}~xq`t]h}T~?Y@vNZ^uBrPADAeO[kSHCAHcIPC_PCAg??OA#14!31?O#43!9?_#102?O#12!9?A??@???@!5?O!4?@A!5?@?C?C?g#10!32?A@?AS@gB?HCACOJBGCU`TItKQdkuR}vVLckQHeXcHukXeXsHuG@iOaSi?YDOcHAcHCcOIOLQGTkaS@g_O?{?DcOBA?G#102!12?O??@?Eg?H_G#7!20?_!4?g?gS_Kc???@GvB~A@jQly`@?dCyF|E]Wrl^I@JR??C#8?G#32_???_#110!13?C!6?G??C???@UH$#12!230?@???C?G?G?_!4?A?J?aH_QP_iPCAgO??gCwCg!4?CAKcA_GO#62!65?A!6?CgCO?`??_??_#147!121?@A?A!4?O#110!55?A@@!8?K#102!33?@A??@A$#10!231?c!4?@C?aCWCXe@Q?aXcQ@cIPCAgPBUgFxEiTiTIT_GO?O?_@AA@A#1!275?G?O?o?Gu[HAWU_C?PC$#11!240?@A@!4?_O???G!4?gPCChB??@PATitiQpbXlVOExGoKpNrNpk@TmPjpJTBZdVwUzjFRBivv\uYluzh]lK|K@k@?RAJAJvIv??FLA@?CB@B?H?QlqB?B?A@A@B@FDA??@ShAPgDQGSgCb@??sL_EDOTaZtJC?_O???A@?A@?A!7?O?_OcWuHOCWeJsHu[Tl[jSJC?A@AtJQbZntNqKsgAO@iCYKs!4?A??LAsD[ADA@C[KsrmTF?B?CHcXsG?_!6?Ga@??A@-#0~tn!5~y~t~y~~y~v~^Tz~kz{zu|n~n!5~}tz|v~|zvnynzV\^vl~Ytv~|!4~v|}!8~v~zv~~~zv!6~n^!10~N~~~}~~m~~j~n{~k~KjFI\eWfISJCJCJcISgS_GO!4?O#6!6?C#0!4?C!6?O#3!10?G#6!8?GA?C??C?AOCA??ICXATaTaL?CXqSJUFHe{~rttZMZuzoqoFof?`oq_pspI@N_iS?HCI?MPC?I??AGCO@#13!8?A@?D??J@SH@I@CAPG?AA_SJ?B?C!4?SG???G#12???__??!4_!4?O_cgCG?G?C#62?P??C?O!9?KQ?S?G!8?C@#10@???_O#9!10?_#10pocA?@G?GS?sGc?@SApwqPCmzduYj\lqs`laytSlzexsQpsIUgfgQti@GAGACg`s`T_I`qtGIeTmiMLJAH?E!4?A#62!6?C?G??G?G!9?O@OHAT?_g?O?O!4?A!4?O!8?I?DG@CHA?G!4?DN?YOAPMAQkrk`tQHGEP`CBOAG?OAO?APCIPIBDKPSJ_uxaWbkObWdiul^$#1?IO!5?D?I?D??D?G?_i??@CBCHAO?O!5?@ICAG?ACGODOCga_GQ?`I??A!4?GA@#2!8?G?CG#3???CG#2!6?O_#1!10?O!6?O??S?O@?Q?rSwt_PfG`hSiszoJphFINbNf]|yf!4~veyepxqrSNv~FnNFooSooso~cosAK???^^Vl~z}~avPFqtrrsrChAC?G??A??_o_?E@A!4?___#7???H?_GOwSI?L?AG?C!4?_?O#23!33?_O??CACGCG@?O??@?A#102O#35!12?__??OGso~onNZVzvzpG?NMn~z~n!8~vak~j~v~NFBf^~}z}ypcI@CZP?@E@?@!4?@GDA?@?@?@??@A?@CA@?A?@!4?O?O???O#9???_!5?_#13A???@#6!8?@?O!5?I_OD?_?@i?O`___CAH?JKQH?Q!4?O_?_#12!4?G#102???@??@#12!4?OGA#6!4?IDMVM~nwoopq`}lq~|ujntNIDG@?@at`??_oo?__!6?C@SI@Ca@#110!6?C#6!8?@C?c_!4?O!7?O$#31!21?C?A!32?C?G#2!44?_???@??@#5!5?A?@!5?AG??CA?@??COCAO`OS_W`ADG!4?GX@XMELGjoG?wO_wNNjNNJN?ZEJ|r~~~___O??@?XGkgHGKK@GaSgXiTqlweLJ???O!4?A??O??C#10??CG?GCACHAG@ECI?T?JSA`S`oagt_Qhc?@iUwvwtaT_O`AP?@@#147!8?@G?O@O?AC?cOQG_`sJdAJLA?SI?CHAH#43??C#10!10?C?C#102O#12!15?G#11@@!5?oGSG!6?CIOCI?C?O?OMWE\AKA?OGSOUOAgI@IK!4?CGKI??O!5?KHE??@??A?@A@C?@o`O??lIToooqdgRCJMIT@MG?EtW??@!5?C!4?L?KCI?o?gOQMZCI??O?PAP@Q@@?@!5?@??G?G#7_?OCNMMLM@QL?AHCO?o_oq_qsLA]~~NI?iDIPk`|hBGA??__#32!20?O#7GBCJCDGEXfKRmCbGSH?_$#2!23?O#4!103?O#3O#2?_O#4!45?@#8!31?Q@!5?G?G??KGGC@C#12!5?O???G???AG?P???O?@oA`U@CQGT_QHC?DgEG!4?W`OoGGC?sIHuWDIHC!6?_???@#43??O??_?G#6!25?A@G!16?O!7?o_O_?@??CIPcY`citipmpexa\AKRgtiehehU@U?PmiXKAD??O?AG`C_A?AGAOC?A#35!28?S??IO???O???OPqoo~sr?CQ`p~N~BNdPcrtvvn~m{m}k}m}metg~^U~dq`_P??@?@#1!9?O???OACICGO!7?T?DgA!8?GOCWGUiWD[bJMHlytgQog_?Oo___#110!8?G$#9!214?CH!7?A???H?NCJ?F?B?@?OC!7?_#11??G?G?G@??GC?qw??@?FI\iFMKKeqy}?sa?eOcqwatjshVGonOmgvLEJ?Qpsqt~jt^ZU\U^NRJN?N?O!5?Cqbo_O#147!22?G#12!9?@!4?ICG#9!18?_O_O?_#7???_#12p?CG@C?A?@?O?L?IHOCWCIHKIDGEWV???MFLCWBCY?O_!5?@???@COQCK?H!4?_o`GC#110!56?O#31!7?O!7?O?CO?OI?CAP_eGPci`Os`sADGEhCATGA?I?OI@?C?O?@?CQ@?Q$#7!216?o_O@#35!18?C!4?_ooID?IC!6?A?D?O@A#16!34?G!4?G???C#110!239?C\ \ No newline at end of file diff --git a/examples/truecolor-test1-small.jpg b/examples/truecolor-test1-small.jpg new file mode 100644 index 0000000000..b818d8d62d Binary files /dev/null and b/examples/truecolor-test1-small.jpg differ diff --git a/examples/truecolor-test2-fullwidth.jpg b/examples/truecolor-test2-fullwidth.jpg new file mode 100644 index 0000000000..097bf9e9d7 Binary files /dev/null and b/examples/truecolor-test2-fullwidth.jpg differ diff --git a/examples/windows-terminal-solarized-custom-palette.jpg b/examples/windows-terminal-solarized-custom-palette.jpg new file mode 100644 index 0000000000..dd6c0119c0 Binary files /dev/null and b/examples/windows-terminal-solarized-custom-palette.jpg differ diff --git a/examples/windows-terminal-solarized-custom.jpg b/examples/windows-terminal-solarized-custom.jpg new file mode 100644 index 0000000000..04a04681e0 Binary files /dev/null and b/examples/windows-terminal-solarized-custom.jpg differ diff --git a/examples/windows-terminal-wrong-palette.jpg b/examples/windows-terminal-wrong-palette.jpg new file mode 100644 index 0000000000..e4a2d4b3fa Binary files /dev/null and b/examples/windows-terminal-wrong-palette.jpg differ diff --git a/format-draw.c b/format-draw.c new file mode 100644 index 0000000000..6cced9fd83 --- /dev/null +++ b/format-draw.c @@ -0,0 +1,911 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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" + +/* Format range. */ +struct format_range { + u_int index; + struct screen *s; + + u_int start; + u_int end; + + enum style_range_type type; + u_int argument; + + TAILQ_ENTRY(format_range) entry; +}; +TAILQ_HEAD(format_ranges, format_range); + +/* Does this range match this style? */ +static int +format_is_type(struct format_range *fr, struct style *sy) +{ + if (fr->type != sy->range_type) + return (0); + if (fr->type == STYLE_RANGE_WINDOW && + fr->argument != sy->range_argument) + return (0); + return (1); +} + +/* Free a range. */ +static void +format_free_range(struct format_ranges *frs, struct format_range *fr) +{ + TAILQ_REMOVE(frs, fr, entry); + free(fr); +} + +/* Fix range positions. */ +static void +format_update_ranges(struct format_ranges *frs, struct screen *s, u_int offset, + u_int start, u_int width) +{ + struct format_range *fr, *fr1; + + if (frs == NULL) + return; + + TAILQ_FOREACH_SAFE(fr, frs, entry, fr1) { + if (fr->s != s) + continue; + + if (fr->end <= start || fr->start >= start + width) { + format_free_range(frs, fr); + continue; + } + + if (fr->start < start) + fr->start = start; + if (fr->end > start + width) + fr->end = start + width; + if (fr->start == fr->end) { + format_free_range(frs, fr); + continue; + } + + fr->start -= start; + fr->end -= start; + + fr->start += offset; + fr->end += offset; + } +} + +/* Draw a part of the format. */ +static void +format_draw_put(struct screen_write_ctx *octx, u_int ocx, u_int ocy, + struct screen *s, struct format_ranges *frs, u_int offset, u_int start, + u_int width) +{ + /* + * The offset is how far from the cursor on the target screen; start + * and width how much to copy from the source screen. + */ + screen_write_cursormove(octx, ocx + offset, ocy, 0); + screen_write_fast_copy(octx, s, start, 0, width, 1); + format_update_ranges(frs, s, offset, start, width); +} + +/* Draw list part of format. */ +static void +format_draw_put_list(struct screen_write_ctx *octx, + u_int ocx, u_int ocy, u_int offset, u_int width, struct screen *list, + struct screen *list_left, struct screen *list_right, int focus_start, + int focus_end, struct format_ranges *frs) +{ + u_int start, focus_centre; + + /* If there is enough space for the list, draw it entirely. */ + if (width >= list->cx) { + format_draw_put(octx, ocx, ocy, list, frs, offset, 0, width); + return; + } + + /* The list needs to be trimmed. Try to keep the focus visible. */ + focus_centre = focus_start + (focus_end - focus_start) / 2; + if (focus_centre < width / 2) + start = 0; + else + start = focus_centre - width / 2; + if (start + width > list->cx) + start = list->cx - width; + + /* Draw <> markers at either side if needed. */ + if (start != 0 && width > list_left->cx) { + screen_write_cursormove(octx, ocx + offset, ocy, 0); + screen_write_fast_copy(octx, list_left, 0, 0, list_left->cx, 1); + offset += list_left->cx; + start += list_left->cx; + 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_fast_copy(octx, list_right, 0, 0, list_right->cx, + 1); + width -= list_right->cx; + } + + /* Draw the list screen itself. */ + format_draw_put(octx, ocx, ocy, list, frs, offset, start, width); +} + +/* Draw format with no list. */ +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) +{ + u_int width_left, width_centre, width_right; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + + /* + * Try to keep as much of the left and right as possible at the expense + * of the centre. + */ + while (width_left + width_centre + width_right > available) { + if (width_centre > 0) + width_centre--; + else if (width_right > 0) + width_right--; + else + width_left--; + } + + /* Write left. */ + 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); + + /* + * Write centre halfway between + * width_left + * and + * available - width_right. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + width_left + + ((available - width_right) - width_left) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_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) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the centre, then the list, then the right, then after the + * list, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_centre > 0) + width_centre--; + else if (width_list > 0) + width_list--; + else if (width_right > 0) + width_right--; + else if (width_after > 0) + width_after--; + else + width_left--; + } + + /* 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_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* 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); + + /* Write after at width_left + width_list. */ + format_draw_put(octx, ocx, ocy, after, frs, + width_left + width_list, + 0, + width_after); + + /* + * Write centre halfway between + * width_left + width_list + width_after + * and + * available - width_right. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + (width_left + width_list + width_after) + + ((available - width_right) + - (width_left + width_list + width_after)) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_centre); + + /* + * The list now goes from + * width_left + * to + * width_left + width_list. + * If there is no focus given, keep the left in focus. + */ + if (focus_start == -1 || focus_end == -1) + 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); +} + +/* 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) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after, middle; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the list, then after the list, then the centre, then the + * right, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_list > 0) + width_list--; + else if (width_after > 0) + width_after--; + else if (width_centre > 0) + width_centre--; + else if (width_right > 0) + width_right--; + else + width_left--; + } + + /* 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_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* 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); + + /* + * All three centre sections are offset from the middle of the + * available space. + */ + middle = (width_left + ((available - width_right) - width_left) / 2); + + /* + * Write centre at + * middle - width_list / 2 - width_centre. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + middle - width_list / 2 - width_centre, + 0, + width_centre); + + /* + * Write after at + * middle - width_list / 2 + width_list + */ + format_draw_put(octx, ocx, ocy, after, frs, + middle - width_list / 2 + width_list, + 0, + width_after); + + /* + * The list now goes from + * middle - width_list / 2 + * to + * middle + width_list / 2 + * 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; + format_draw_put_list(octx, ocx, ocy, middle - width_list / 2, + width_list, list, list_left, list_right, focus_start, focus_end, + frs); +} + +/* 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) +{ + u_int width_left, width_centre, width_right; + u_int width_list, width_after; + struct screen_write_ctx ctx; + + width_left = left->cx; + width_centre = centre->cx; + width_right = right->cx; + width_list = list->cx; + width_after = after->cx; + + /* + * Trim first the centre, then the list, then the right, then + * after the list, then the left. + */ + while (width_left + + width_centre + + width_right + + width_list + + width_after > available) { + if (width_centre > 0) + width_centre--; + else if (width_list > 0) + width_list--; + else if (width_right > 0) + width_right--; + else if (width_after > 0) + width_after--; + else + width_left--; + } + + /* 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_fast_copy(&ctx, after, 0, 0, width_after, 1); + screen_write_stop(&ctx); + + format_draw_none(octx, available, ocx, ocy, left, centre, + right, frs); + return; + } + + /* Write left at 0. */ + format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left); + + /* Write after at available - width_after. */ + format_draw_put(octx, ocx, ocy, after, frs, + available - width_after, + after->cx - width_after, + width_after); + + /* + * Write right at + * available - width_right - width_list - width_after. + */ + format_draw_put(octx, ocx, ocy, right, frs, + available - width_right - width_list - width_after, + 0, + width_right); + + /* + * Write centre halfway between + * width_left + * and + * available - width_right - width_list - width_after. + */ + format_draw_put(octx, ocx, ocy, centre, frs, + width_left + + ((available - width_right - width_list - width_after) + - width_left) / 2 + - width_centre / 2, + centre->cx / 2 - width_centre / 2, + width_centre); + + /* + * The list now goes from + * available - width_list - width_after + * to + * available - width_after + * If there is no focus given, keep the right in focus. + */ + if (focus_start == -1 || focus_end == -1) + focus_start = focus_end = 0; + format_draw_put_list(octx, ocx, ocy, available - width_list - + width_after, width_list, list, list_left, list_right, focus_start, + focus_end, frs); +} + +/* Draw a format to a screen. */ +void +format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, + u_int available, const char *expanded, struct style_ranges *srs) +{ + enum { LEFT, + CENTRE, + RIGHT, + LIST, + LIST_LEFT, + LIST_RIGHT, + AFTER, + TOTAL } current = LEFT, last = LEFT; + const char *names[] = { "LEFT", + "CENTRE", + "RIGHT", + "LIST", + "LIST_LEFT", + "LIST_RIGHT", + "AFTER" }; + 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 }; + int focus_start = -1, focus_end = -1; + int list_state = -1, fill = -1; + enum style_align list_align = STYLE_ALIGN_DEFAULT; + struct grid_cell gc, current_default; + struct style sy, saved_sy; + struct utf8_data *ud = &sy.gc.data; + const char *cp, *end; + enum utf8_state more; + char *tmp; + struct format_range *fr = NULL, *fr1; + struct format_ranges frs; + struct style_range *sr; + + memcpy(¤t_default, base, sizeof current_default); + style_set(&sy, ¤t_default); + TAILQ_INIT(&frs); + log_debug("%s: %s", __func__, expanded); + + /* + * We build three screens for left, right, centre alignment, one for + * the list, one for anything after the list and two for the list left + * and right markers. + */ + for (i = 0; i < TOTAL; i++) { + screen_init(&s[i], size, 1, 0); + screen_write_start(&ctx[i], NULL, &s[i]); + screen_write_clearendofline(&ctx[i], current_default.bg); + width[i] = 0; + } + + /* + * Walk the string and add to the corresponding screens, + * parsing styles as we go. + */ + cp = expanded; + while (*cp != '\0') { + if (cp[0] != '#' || cp[1] != '[') { + /* See if this is a UTF-8 character. */ + if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(ud, *cp); + if (more != UTF8_DONE) + cp -= ud->have; + } + + /* Not a UTF-8 character - ASCII or not valid. */ + if (more != UTF8_DONE) { + if (*cp < 0x20 || *cp > 0x7e) { + /* Ignore nonprintable characters. */ + cp++; + continue; + } + utf8_set(ud, *cp); + cp++; + } + + /* Draw the cell to the current screen. */ + screen_write_cell(&ctx[current], &sy.gc); + width[current] += ud->width; + continue; + } + + /* This is a style. Work out where the end is and parse it. */ + end = format_skip(cp + 2, "]"); + if (end == NULL) { + log_debug("%s: no terminating ] at '%s'", __func__, + cp + 2); + TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) + format_free_range(&frs, fr); + goto out; + } + tmp = xstrndup(cp + 2, end - (cp + 2)); + style_copy(&saved_sy, &sy); + if (style_parse(&sy, ¤t_default, tmp) != 0) { + log_debug("%s: invalid style '%s'", __func__, tmp); + free(tmp); + cp = end + 1; + continue; + } + log_debug("%s: style '%s' -> '%s'", __func__, tmp, + style_tostring(&sy)); + free(tmp); + + /* If this style has a fill colour, store it for later. */ + if (sy.fill != 8) + fill = sy.fill; + + /* 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); + sy.default_type = STYLE_DEFAULT_BASE; + } else if (sy.default_type == STYLE_DEFAULT_POP) { + memcpy(¤t_default, base, sizeof current_default); + sy.default_type = STYLE_DEFAULT_BASE; + } + + /* Check the list state. */ + switch (sy.list) { + case STYLE_LIST_ON: + /* + * Entering the list, exiting a marker, or exiting the + * focus. + */ + if (list_state != 0) { + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + list_state = 0; + list_align = sy.align; + } + + /* End the focus if started. */ + if (focus_start != -1 && focus_end == -1) + focus_end = s[LIST].cx; + + current = LIST; + break; + case STYLE_LIST_FOCUS: + /* Entering the focus. */ + if (list_state != 0) /* not inside the list */ + break; + if (focus_start == -1) /* focus already started */ + focus_start = s[LIST].cx; + break; + case STYLE_LIST_OFF: + /* Exiting or outside the list. */ + if (list_state == 0) { + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_end = s[LIST].cx; + + map[list_align] = AFTER; + if (list_align == STYLE_ALIGN_LEFT) + map[STYLE_ALIGN_DEFAULT] = AFTER; + list_state = 1; + } + current = map[sy.align]; + break; + case STYLE_LIST_LEFT_MARKER: + /* Entering left marker. */ + if (list_state != 0) /* not inside the list */ + break; + if (s[LIST_LEFT].cx != 0) /* already have marker */ + break; + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_start = focus_end = -1; + current = LIST_LEFT; + break; + case STYLE_LIST_RIGHT_MARKER: + /* Entering right marker. */ + if (list_state != 0) /* not inside the list */ + break; + if (s[LIST_RIGHT].cx != 0) /* already have marker */ + break; + if (fr != NULL) { /* abort any region */ + free(fr); + fr = NULL; + } + if (focus_start != -1 && focus_end == -1) + focus_start = focus_end = -1; + current = LIST_RIGHT; + break; + } + if (current != last) { + log_debug("%s: change %s -> %s", __func__, + names[last], names[current]); + last = current; + } + + /* + * Check if the range style has changed and if so end the + * current range and start a new one if needed. + */ + if (srs != NULL) { + if (fr != NULL && !format_is_type(fr, &sy)) { + if (s[current].cx != fr->start) { + fr->end = s[current].cx + 1; + TAILQ_INSERT_TAIL(&frs, fr, entry); + } else + free(fr); + fr = NULL; + } + if (fr == NULL && sy.range_type != STYLE_RANGE_NONE) { + fr = xcalloc(1, sizeof *fr); + fr->index = current; + + fr->s = &s[current]; + fr->start = s[current].cx; + + fr->type = sy.range_type; + fr->argument = sy.range_argument; + } + } + + cp = end + 1; + } + free(fr); + + for (i = 0; i < TOTAL; i++) { + screen_write_stop(&ctx[i]); + log_debug("%s: width %s is %u", __func__, names[i], width[i]); + } + if (focus_start != -1 && focus_end != -1) + log_debug("%s: focus %d-%d", __func__, focus_start, focus_end); + TAILQ_FOREACH(fr, &frs, entry) { + log_debug("%s: range %d|%u is %s %u-%u", __func__, fr->type, + fr->argument, names[fr->index], fr->start, fr->end); + } + + /* Clear the available area. */ + if (fill != -1) { + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.bg = fill; + for (i = 0; i < available; i++) + screen_write_putc(octx, &gc, ' '); + } + + /* + * Draw the screens. How they are arranged depends on where the list + * appearsq. + */ + switch (list_align) { + case STYLE_ALIGN_DEFAULT: + /* No list. */ + format_draw_none(octx, available, ocx, ocy, &s[LEFT], + &s[CENTRE], &s[RIGHT], &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); + 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); + 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); + break; + } + + /* Create ranges to return. */ + TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) { + sr = xcalloc(1, sizeof *sr); + sr->type = fr->type; + sr->argument = fr->argument; + sr->start = fr->start; + sr->end = fr->end; + TAILQ_INSERT_TAIL(srs, sr, entry); + + log_debug("%s: range %d|%u at %u-%u", __func__, sr->type, + sr->argument, sr->start, sr->end); + + format_free_range(&frs, fr); + } + +out: + /* Free the screens. */ + for (i = 0; i < TOTAL; i++) + screen_free(&s[i]); + + /* Restore the original cursor position. */ + screen_write_cursormove(octx, ocx, ocy, 0); +} + +/* Get width, taking #[] into account. */ +u_int +format_width(const char *expanded) +{ + const char *cp, *end; + u_int 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; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) + width += ud.width; + else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + width++; + cp++; + } else + cp++; + } + return (width); +} + +/* Trim on the left, taking #[] into account. */ +char * +format_trim_left(const char *expanded, u_int limit) +{ + char *copy, *out; + const char *cp = expanded, *end; + u_int width = 0; + struct utf8_data ud; + enum utf8_state more; + + out = copy = xmalloc(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; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) { + if (width + ud.width <= limit) { + memcpy(out, ud.data, ud.size); + out += ud.size; + } + width += ud.width; + } else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + if (width + 1 <= limit) + *out++ = *cp; + width++; + cp++; + } else + cp++; + } + *out = '\0'; + return (copy); +} + +/* Trim on the right, taking #[] into account. */ +char * +format_trim_right(const char *expanded, u_int limit) +{ + char *copy, *out; + const char *cp = expanded, *end; + u_int width = 0, total_width, skip; + struct utf8_data ud; + enum utf8_state more; + + total_width = format_width(expanded); + if (total_width <= limit) + return (xstrdup(expanded)); + skip = total_width - limit; + + out = copy = xmalloc(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; + } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) { + while (*++cp != '\0' && more == UTF8_MORE) + more = utf8_append(&ud, *cp); + if (more == UTF8_DONE) { + if (width >= skip) { + memcpy(out, ud.data, ud.size); + out += ud.size; + } + width += ud.width; + } else + cp -= ud.have; + } else if (*cp > 0x1f && *cp < 0x7f) { + if (width >= skip) + *out++ = *cp; + width++; + cp++; + } else + cp++; + } + *out = '\0'; + return (copy); +} diff --git a/format.c b/format.c index 972054810c..ee5073409a 100644 --- a/format.c +++ b/format.c @@ -19,9 +19,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -51,8 +53,7 @@ static int format_replace(struct format_tree *, const char *, 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 { @@ -92,7 +93,16 @@ 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_SUBSTITUTE 0x8 +#define FORMAT_QUOTE 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 + +/* Limit on recursion. */ +#define FORMAT_LOOP_LIMIT 10 /* Entry in format tree. */ struct format_entry { @@ -105,20 +115,35 @@ struct format_entry { /* Format entry tree. */ struct format_tree { - struct window *w; - struct winlink *wl; + struct client *c; struct session *s; + struct winlink *wl; + struct window *w; struct window_pane *wp; + struct cmdq_item *item; struct client *client; u_int tag; int flags; + time_t time; + u_int loop; + + struct mouse_event m; RB_HEAD(format_entry_tree, format_entry) 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 modifier. */ +struct format_modifier { + char modifier[3]; + u_int size; + + char **argv; + int argc; +}; + /* Format entry tree comparison function. */ static int format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2) @@ -186,15 +211,50 @@ static const char *format_lower[] = { NULL /* z */ }; +/* Is logging enabled? */ +static inline int +format_logging(struct format_tree *ft) +{ + return (log_get_level() != 0 || (ft->flags & FORMAT_VERBOSE)); +} + +/* Log a message if verbose. */ +static void printflike(3, 4) +format_log1(struct format_tree *ft, const char *from, const char *fmt, ...) +{ + va_list ap; + char *s; + static const char spaces[] = " "; + + if (!format_logging(ft)) + return; + + va_start(ap, fmt); + vasprintf(&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); + + free(s); +} +#define format_log(ft, fmt, ...) format_log1(ft, __func__, fmt, ##__VA_ARGS__) + /* Format job update callback. */ static void format_job_update(struct job *job) { - struct format_job *fj = job->data; - char *line; + struct format_job *fj = job_get_data(job); + struct evbuffer *evb = job_get_event(job)->input; + char *line = NULL, *next; time_t t; - if ((line = evbuffer_readline(job->event->input)) == NULL) + while ((next = evbuffer_readline(evb)) != NULL) { + free(line); + line = next; + } + if (line == NULL) return; fj->updated = 1; @@ -215,18 +275,19 @@ format_job_update(struct job *job) static void format_job_complete(struct job *job) { - struct format_job *fj = job->data; + struct format_job *fj = job_get_data(job); + struct evbuffer *evb = job_get_event(job)->input; char *line, *buf; size_t len; fj->job = NULL; buf = NULL; - if ((line = evbuffer_readline(job->event->input)) == NULL) { - len = EVBUFFER_LENGTH(job->event->input); + if ((line = evbuffer_readline(evb)) == NULL) { + len = EVBUFFER_LENGTH(evb); buf = xmalloc(len + 1); if (len != 0) - memcpy(buf, EVBUFFER_DATA(job->event->input), len); + memcpy(buf, EVBUFFER_DATA(evb), len); buf[len] = '\0'; } else buf = line; @@ -288,9 +349,12 @@ format_job_get(struct format_tree *ft, const char *cmd) force = (ft->flags & FORMAT_FORCE); t = time(NULL); - if (fj->job == NULL && (force || fj->last != t)) { - fj->job = job_run(expanded, NULL, NULL, format_job_update, - format_job_complete, NULL, fj); + if (force && fj->job != NULL) + job_free(fj->job); + if (force || (fj->job == NULL && fj->last != t)) { + fj->job = job_run(expanded, NULL, + server_client_get_cwd(ft->client, NULL), format_job_update, + format_job_complete, NULL, fj, JOB_NOWAIT); if (fj->job == NULL) { free(fj->out); xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); @@ -510,7 +574,7 @@ format_cb_current_command(struct format_tree *ft, struct format_entry *fe) struct window_pane *wp = ft->wp; char *cmd; - if (wp == NULL) + if (wp == NULL || wp->shell == NULL) return; cmd = osdep_get_name(wp->fd, wp->tty); @@ -557,11 +621,11 @@ format_cb_history_bytes(struct format_tree *ft, struct format_entry *fe) size = 0; for (i = 0; i < gd->hsize; i++) { - gl = &gd->linedata[i]; + gl = grid_get_line(gd, i); size += gl->cellsize * sizeof *gl->celldata; size += gl->extdsize * sizeof *gl->extddata; } - size += gd->hsize * sizeof *gd->linedata; + size += gd->hsize * sizeof *gl; xasprintf(&fe->value, "%llu", size); } @@ -579,6 +643,8 @@ format_cb_pane_tabs(struct format_tree *ft, struct format_entry *fe) return; buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); for (i = 0; i < wp->base.grid->sx; i++) { if (!bit_test(wp->base.tabs, i)) continue; @@ -587,11 +653,215 @@ format_cb_pane_tabs(struct format_tree *ft, struct format_entry *fe) evbuffer_add(buffer, ",", 1); evbuffer_add_printf(buffer, "%u", i); } - size = EVBUFFER_LENGTH(buffer); - xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); + evbuffer_free(buffer); +} + +/* Callback for session_group_list. */ +static void +format_cb_session_group_list(struct format_tree *ft, struct format_entry *fe) +{ + struct session *s = ft->s; + struct session_group *sg; + struct session *loop; + struct evbuffer *buffer; + int size; + + if (s == NULL) + return; + sg = session_group_contains(s); + if (sg == NULL) + return; + + buffer = evbuffer_new(); + if (buffer == NULL) + fatalx("out of memory"); + TAILQ_FOREACH(loop, &sg->sessions, gentry) { + if (EVBUFFER_LENGTH(buffer) > 0) + evbuffer_add(buffer, ",", 1); + evbuffer_add_printf(buffer, "%s", loop->name); + } + if ((size = EVBUFFER_LENGTH(buffer)) != 0) + xasprintf(&fe->value, "%.*s", size, EVBUFFER_DATA(buffer)); evbuffer_free(buffer); } +/* Callback for pane_in_mode. */ +static void +format_cb_pane_in_mode(struct format_tree *ft, struct format_entry *fe) +{ + struct window_pane *wp = ft->wp; + u_int n = 0; + struct window_mode_entry *wme; + + if (wp == NULL) + return; + + TAILQ_FOREACH(wme, &wp->modes, entry) + n++; + xasprintf(&fe->value, "%u", n); +} + +/* Callback for cursor_character. */ +static void +format_cb_cursor_character(struct format_tree *ft, struct format_entry *fe) +{ + struct window_pane *wp = ft->wp; + struct grid_cell gc; + + if (wp == NULL) + return; + + 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); +} + +/* Callback for mouse_word. */ +static void +format_cb_mouse_word(struct format_tree *ft, struct format_entry *fe) +{ + struct window_pane *wp; + u_int x, y; + char *s; + + if (!ft->m.valid) + return; + 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_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 (size != 0) { + ud[size].size = 0; + s = utf8_tocstr(ud); + free(ud); + } + return (s); +} + +/* Callback for mouse_line. */ +static void +format_cb_mouse_line(struct format_tree *ft, struct format_entry *fe) +{ + struct window_pane *wp; + u_int x, y; + char *s; + + if (!ft->m.valid) + return; + 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; +} + /* Merge a format tree. */ static void format_merge(struct format_tree *ft, struct format_tree *from) @@ -604,11 +874,42 @@ format_merge(struct format_tree *ft, struct format_tree *from) } } +/* 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; + + 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); + } + } + memcpy(&ft->m, m, sizeof ft->m); +} + /* Create a new tree. */ struct format_tree * format_create(struct client *c, struct cmdq_item *item, int tag, int flags) { - struct format_tree *ft; + 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); @@ -622,9 +923,11 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) ft->client = c; ft->client->references++; } + ft->item = item; ft->tag = tag; ft->flags = flags; + ft->time = time(NULL); format_add(ft, "version", "%s", VERSION); format_add_cb(ft, "host", format_cb_host); @@ -633,13 +936,17 @@ format_create(struct client *c, struct cmdq_item *item, int tag, int flags) format_add(ft, "socket_path", "%s", socket_path); format_add_tv(ft, "start_time", &start_time); - if (item != NULL) { - if (item->cmd != NULL) - format_add(ft, "command", "%s", item->cmd->entry->name); - if (item->shared != NULL && item->shared->formats != NULL) - format_merge(ft, item->shared->formats); + 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); + return (ft); } @@ -661,6 +968,29 @@ format_free(struct format_tree *ft) free(ft); } +/* Walk each format. */ +void +format_each(struct format_tree *ft, void (*cb)(const char *, const char *, + void *), void *arg) +{ + struct format_entry *fe; + static char s[64]; + + RB_FOREACH(fe, format_entry_tree, &ft->tree) { + if (fe->t != 0) { + xsnprintf(s, sizeof s, "%lld", (long long)fe->t); + cb(fe->key, fe->value, s); + } else { + if (fe->value == NULL && fe->cb != NULL) { + fe->cb(ft, fe); + if (fe->value == NULL) + fe->value = xstrdup(""); + } + cb(fe->key, fe->value, arg); + } + } +} + /* Add a key-value pair. */ void format_add(struct format_tree *ft, const char *key, const char *fmt, ...) @@ -736,6 +1066,23 @@ format_add_cb(struct format_tree *ft, const char *key, format_cb cb) fe->value = NULL; } +/* Quote special characters in string. */ +static char * +format_quote(const char *s) +{ + const char *cp; + char *out, *at; + + at = out = xmalloc(strlen(s) * 2 + 1); + for (cp = s; *cp != '\0'; cp++) { + if (strchr("|&;<>()$`\\\"'*?[# =%", *cp) != NULL) + *at++ = '\\'; + *at++ = *cp; + } + *at = '\0'; + return (out); +} + /* Find a format entry. */ static char * format_find(struct format_tree *ft, const char *key, int modifiers) @@ -744,12 +1091,13 @@ format_find(struct format_tree *ft, const char *key, int modifiers) struct environ_entry *envent; static char s[64]; struct options_entry *o; - const char *found; int idx; - char *copy, *saved; + char *found, *saved; 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) @@ -773,17 +1121,18 @@ format_find(struct format_tree *ft, const char *key, int modifiers) return (NULL); ctime_r(&fe->t, s); s[strcspn(s, "\n")] = '\0'; - found = s; + found = xstrdup(s); goto found; } if (fe->t != 0) { - xsnprintf(s, sizeof s, "%lld", (long long)fe->t); - found = s; + xasprintf(&found, "%lld", (long long)fe->t); goto found; } if (fe->value == NULL && fe->cb != NULL) fe->cb(ft, fe); - found = fe->value; + if (fe->value == NULL) + fe->value = xstrdup(""); + found = xstrdup(fe->value); goto found; } @@ -793,8 +1142,8 @@ format_find(struct format_tree *ft, const char *key, int modifiers) envent = environ_find(ft->s->environ, key); if (envent == NULL) envent = environ_find(global_environ, key); - if (envent != NULL) { - found = envent->value; + if (envent != NULL && envent->value != NULL) { + found = xstrdup(envent->value); goto found; } } @@ -804,32 +1153,40 @@ format_find(struct format_tree *ft, const char *key, int modifiers) found: if (found == NULL) return (NULL); - copy = xstrdup(found); if (modifiers & FORMAT_BASENAME) { - saved = copy; - copy = xstrdup(basename(saved)); + saved = found; + found = xstrdup(basename(saved)); free(saved); } if (modifiers & FORMAT_DIRNAME) { - saved = copy; - copy = xstrdup(dirname(saved)); + saved = found; + found = xstrdup(dirname(saved)); free(saved); } - return (copy); + if (modifiers & FORMAT_QUOTE) { + saved = found; + found = xstrdup(format_quote(saved)); + free(saved); + } + return (found); } -/* Skip until comma. */ -static char * -format_skip(char *s) +/* Skip until end. */ +const char * +format_skip(const char *s, const char *end) { int brackets = 0; for (; *s != '\0'; s++) { - if (*s == '{') + if (*s == '#' && s[1] == '{') brackets++; + if (*s == '#' && strchr(",#{}", s[1]) != NULL) { + s++; + continue; + } if (*s == '}') brackets--; - if (*s == ',' && brackets == 0) + if (strchr(end, *s) != NULL && brackets == 0) break; } if (*s == '\0') @@ -839,17 +1196,27 @@ format_skip(char *s) /* Return left and right alternatives separated by commas. */ static int -format_choose(char *s, char **left, char **right) +format_choose(struct format_tree *ft, const char *s, char **left, char **right, + int expand) { - char *cp; + const char *cp; + char *left0, *right0; - cp = format_skip(s); + cp = format_skip(s, ","); if (cp == NULL) return (-1); - *cp = '\0'; - - *left = s; - *right = cp + 1; + left0 = xstrndup(s, cp - s); + right0 = xstrdup(cp + 1); + + if (expand) { + *left = format_expand(ft, left0); + free(left0); + *right = format_expand(ft, right0); + free(right0); + } else { + *left = left0; + *right = right0; + } return (0); } @@ -862,204 +1229,656 @@ format_true(const char *s) return (0); } +/* Check if modifier end. */ +static int +format_is_end(char c) +{ + return (c == ';' || c == ':'); +} + +/* Add to modifier list. */ +static void +format_add_modifier(struct format_modifier **list, u_int *count, + const char *c, size_t n, char **argv, int argc) +{ + struct format_modifier *fm; + + *list = xreallocarray(*list, (*count) + 1, sizeof **list); + fm = &(*list)[(*count)++]; + + memcpy(fm->modifier, c, n); + fm->modifier[n] = '\0'; + fm->size = n; + + fm->argv = argv; + fm->argc = argc; +} + +/* Free modifier list. */ +static void +format_free_modifiers(struct format_modifier *list, u_int count) +{ + u_int i; + + for (i = 0; i < count; i++) + cmd_free_argv(list[i].argc, list[i].argv); + free(list); +} + +/* Build modifier list. */ +static struct format_modifier * +format_build_modifiers(struct format_tree *ft, const char **s, u_int *count) +{ + const char *cp = *s, *end; + struct format_modifier *list = NULL; + char c, last[] = "X;:", **argv, *value; + int argc; + + /* + * Modifiers are a ; separated list of the forms: + * l,m,C,b,d,t,q,E,T,S,W,P,<,> + * =a + * =/a + * =/a/ + * s/a/b/ + * s/a/b + * ||,&&,!=,==,<=,>= + */ + + *count = 0; + + while (*cp != '\0' && *cp != ':') { + /* Skip and separator character. */ + if (*cp == ';') + cp++; + + /* Check single character modifiers with no arguments. */ + if (strchr("lbdtqETSWP<>", cp[0]) != NULL && + format_is_end(cp[1])) { + format_add_modifier(&list, count, cp, 1, NULL, 0); + cp++; + continue; + } + + /* Then try double character with no arguments. */ + if ((memcmp("||", cp, 2) == 0 || + memcmp("&&", cp, 2) == 0 || + memcmp("!=", cp, 2) == 0 || + memcmp("==", cp, 2) == 0 || + memcmp("<=", cp, 2) == 0 || + memcmp(">=", cp, 2) == 0) && + format_is_end(cp[2])) { + format_add_modifier(&list, count, cp, 2, NULL, 0); + cp += 2; + continue; + } + + /* Now try single character with arguments. */ + if (strchr("mCs=p", cp[0]) == NULL) + break; + c = cp[0]; + + /* No arguments provided. */ + if (format_is_end(cp[1])) { + format_add_modifier(&list, count, cp, 1, NULL, 0); + cp++; + continue; + } + argv = NULL; + argc = 0; + + /* Single argument with no wrapper character. */ + if (!ispunct(cp[1]) || cp[1] == '-') { + end = format_skip(cp + 1, ":;"); + if (end == NULL) + break; + + argv = xcalloc(1, sizeof *argv); + value = xstrndup(cp + 1, end - (cp + 1)); + argv[0] = format_expand(ft, value); + free(value); + argc = 1; + + format_add_modifier(&list, count, &c, 1, argv, argc); + cp = end; + continue; + } + + /* Multiple arguments with a wrapper character. */ + last[0] = cp[1]; + cp++; + do { + if (cp[0] == last[0] && format_is_end(cp[1])) { + cp++; + break; + } + end = format_skip(cp + 1, last); + if (end == NULL) + break; + cp++; + + argv = xreallocarray (argv, argc + 1, sizeof *argv); + value = xstrndup(cp, end - cp); + argv[argc++] = format_expand(ft, value); + free(value); + + cp = end; + } while (!format_is_end(cp[0])); + format_add_modifier(&list, count, &c, 1, argv, argc); + } + if (*cp != ':') { + format_free_modifiers(list, *count); + *count = 0; + return (NULL); + } + *s = cp + 1; + return list; +} + +/* Match against an fnmatch(3) pattern or regular expression. */ +static char * +format_match(struct format_modifier *fm, const char *pattern, const char *text) +{ + const char *s = ""; + regex_t r; + int flags = 0; + + if (fm->argc >= 1) + s = fm->argv[0]; + if (strchr(s, 'r') == NULL) { + if (strchr(s, 'i') != NULL) + flags |= FNM_CASEFOLD; + if (fnmatch(pattern, text, flags) != 0) + return (xstrdup("0")); + } else { + flags = REG_EXTENDED|REG_NOSUB; + if (strchr(s, 'i') != NULL) + flags |= REG_ICASE; + if (regcomp(&r, pattern, flags) != 0) + return (xstrdup("0")); + if (regexec(&r, text, 0, NULL, 0) != 0) { + regfree(&r); + return (xstrdup("0")); + } + regfree(&r); + } + return (xstrdup("1")); +} + +/* Perform substitution in string. */ +static char * +format_sub(struct format_modifier *fm, const char *text, const char *pattern, + const char *with) +{ + char *value; + int flags = REG_EXTENDED; + + if (fm->argc >= 3 && strchr(fm->argv[2], 'i') != NULL) + flags |= REG_ICASE; + value = regsub(pattern, with, text, flags); + if (value == NULL) + return (xstrdup(text)); + return (value); +} + +/* Search inside pane. */ +static char * +format_search(struct format_modifier *fm, struct window_pane *wp, const char *s) +{ + int ignore = 0, regex = 0; + char *value; + + if (fm->argc >= 1) { + if (strchr(fm->argv[0], 'i') != NULL) + ignore = 1; + if (strchr(fm->argv[0], 'r') != NULL) + regex = 1; + } + xasprintf(&value, "%u", window_pane_search(wp, s, regex, ignore)); + return (value); +} + +/* Loop over sessions. */ +static char * +format_loop_sessions(struct format_tree *ft, 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; + + value = xcalloc(1, 1); + valuelen = 1; + + RB_FOREACH(s, sessions, &sessions) { + format_log(ft, "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); + + valuelen += strlen(expanded); + value = xrealloc(value, valuelen); + + strlcat(value, expanded, valuelen); + free(expanded); + } + + return (value); +} + +/* Loop over windows. */ +static char * +format_loop_windows(struct format_tree *ft, 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 winlink *wl; + struct window *w; + + if (ft->s == NULL) { + format_log(ft, "window loop but no session"); + return (NULL); + } + + if (format_choose(ft, fmt, &all, &active, 0) != 0) { + all = xstrdup(fmt); + active = NULL; + } + + value = xcalloc(1, 1); + valuelen = 1; + + RB_FOREACH(wl, winlinks, &ft->s->windows) { + w = wl->window; + format_log(ft, "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_free(nft); + + valuelen += strlen(expanded); + value = xrealloc(value, valuelen); + + strlcat(value, expanded, valuelen); + free(expanded); + } + + free(active); + free(all); + + return (value); +} + +/* Loop over panes. */ +static char * +format_loop_panes(struct format_tree *ft, 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; + + if (ft->w == NULL) { + format_log(ft, "pane loop but no window"); + return (NULL); + } + + if (format_choose(ft, fmt, &all, &active, 0) != 0) { + all = xstrdup(fmt); + active = NULL; + } + + value = xcalloc(1, 1); + valuelen = 1; + + TAILQ_FOREACH(wp, &ft->w->panes, entry) { + format_log(ft, "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_free(nft); + + valuelen += strlen(expanded); + value = xrealloc(value, valuelen); + + strlcat(value, expanded, valuelen); + free(expanded); + } + + free(active); + free(all); + + return (value); +} + /* Replace a key. */ static int format_replace(struct format_tree *ft, const char *key, size_t keylen, char **buf, size_t *len, size_t *off) { - struct window_pane *wp = ft->wp; - char *copy, *copy0, *endptr, *ptr, *found, *new; - char *value, *from = NULL, *to = NULL, *left, *right; - size_t valuelen, newlen, fromlen, tolen, used; - long limit = 0; - int modifiers = 0, compare = 0, search = 0; + 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; /* Make a copy of the key. */ - copy0 = copy = xmalloc(keylen + 1); - memcpy(copy, key, keylen); - copy[keylen] = '\0'; - - /* Is there a length limit or whatnot? */ - switch (copy[0]) { - case 'm': - if (copy[1] != ':') - break; - compare = -2; - copy += 2; - break; - case 'C': - if (copy[1] != ':') - break; - search = 1; - copy += 2; - break; - case '|': - if (copy[1] != '|' || copy[2] != ':') - break; - compare = -3; - copy += 3; - break; - case '&': - if (copy[1] != '&' || copy[2] != ':') - break; - compare = -4; - copy += 3; - break; - case '!': - if (copy[1] == '=' && copy[2] == ':') { - compare = -1; - copy += 3; - break; - } - break; - case '=': - if (copy[1] == '=' && copy[2] == ':') { - compare = 1; - copy += 3; - break; - } - errno = 0; - limit = strtol(copy + 1, &endptr, 10); - if (errno == ERANGE && (limit == LONG_MIN || limit == LONG_MAX)) - break; - if (*endptr != ':') - break; - copy = endptr + 1; - break; - case 'b': - if (copy[1] != ':') - break; - modifiers |= FORMAT_BASENAME; - copy += 2; - break; - case 'd': - if (copy[1] != ':') - break; - modifiers |= FORMAT_DIRNAME; - copy += 2; - break; - case 't': - if (copy[1] != ':') - break; - modifiers |= FORMAT_TIMESTRING; - copy += 2; - break; - case 's': - if (copy[1] != '/') - break; - from = copy + 2; - for (copy = from; *copy != '\0' && *copy != '/'; copy++) - /* nothing */; - if (copy[0] != '/' || copy == from) { - copy = copy0; - break; + copy = copy0 = xstrndup(key, keylen); + + /* Process modifier list. */ + list = format_build_modifiers(ft, ©, &count); + for (i = 0; i < count; i++) { + fm = &list[i]; + if (format_logging(ft)) { + format_log(ft, "modifier %u is %s", i, fm->modifier); + for (j = 0; j < fm->argc; j++) { + format_log(ft, "modifier %u argument %d: %s", i, + j, fm->argv[j]); + } } - copy[0] = '\0'; - to = copy + 1; - for (copy = to; *copy != '\0' && *copy != '/'; copy++) - /* nothing */; - if (copy[0] != '/' || copy[1] != ':') { - copy = copy0; - break; + if (fm->size == 1) { + switch (fm->modifier[0]) { + case 'm': + case '<': + case '>': + cmp = fm; + break; + case 'C': + search = fm; + break; + case 's': + if (fm->argc < 2) + break; + 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) + limit = 0; + if (fm->argc >= 2 && fm->argv[1] != NULL) + marker = fm->argv[1]; + break; + case 'p': + if (fm->argc < 1) + break; + width = strtonum(fm->argv[0], INT_MIN, INT_MAX, + &errptr); + if (errptr != NULL) + width = 0; + break; + case 'l': + modifiers |= FORMAT_LITERAL; + break; + case 'b': + modifiers |= FORMAT_BASENAME; + break; + case 'd': + modifiers |= FORMAT_DIRNAME; + break; + case 't': + modifiers |= FORMAT_TIMESTRING; + break; + case 'q': + modifiers |= FORMAT_QUOTE; + break; + case 'E': + modifiers |= FORMAT_EXPAND; + break; + case 'T': + modifiers |= FORMAT_EXPANDTIME; + break; + case 'S': + modifiers |= FORMAT_SESSIONS; + break; + case 'W': + modifiers |= FORMAT_WINDOWS; + break; + case 'P': + modifiers |= FORMAT_PANES; + break; + } + } else if (fm->size == 2) { + if (strcmp(fm->modifier, "||") == 0 || + strcmp(fm->modifier, "&&") == 0 || + strcmp(fm->modifier, "==") == 0 || + strcmp(fm->modifier, "!=") == 0 || + strcmp(fm->modifier, ">=") == 0 || + strcmp(fm->modifier, "<=") == 0) + cmp = fm; } - copy[0] = '\0'; + } - modifiers |= FORMAT_SUBSTITUTE; - copy += 2; - break; + /* Is this a literal string? */ + if (modifiers & FORMAT_LITERAL) { + value = xstrdup(copy); + goto done; } - /* Is this a comparison or a conditional? */ - if (search) { + /* Is this a loop, comparison or condition? */ + if (modifiers & FORMAT_SESSIONS) { + value = format_loop_sessions(ft, copy); + if (value == NULL) + goto fail; + } else if (modifiers & FORMAT_WINDOWS) { + value = format_loop_windows(ft, copy); + if (value == NULL) + goto fail; + } else if (modifiers & FORMAT_PANES) { + value = format_loop_panes(ft, copy); + if (value == NULL) + goto fail; + } else if (search != NULL) { /* Search in pane. */ - if (wp == NULL) + new = format_expand(ft, copy); + if (wp == NULL) { + format_log(ft, "search '%s' but no pane", new); value = xstrdup("0"); - else - xasprintf(&value, "%u", window_pane_search(wp, copy)); - } else if (compare != 0) { - /* Comparison: compare comma-separated left and right. */ - if (format_choose(copy, &left, &right) != 0) + } else { + format_log(ft, "search '%s' pane %%%u", new, wp->id); + value = format_search(fm, 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", + cmp->modifier, copy); goto fail; - left = format_expand(ft, left); - right = format_expand(ft, right); - if (compare == -3 && - (format_true(left) || format_true(right))) - value = xstrdup("1"); - else if (compare == -4 && - (format_true(left) && format_true(right))) - value = xstrdup("1"); - else if (compare == 1 && strcmp(left, right) == 0) - value = xstrdup("1"); - else if (compare == -1 && strcmp(left, right) != 0) - value = xstrdup("1"); - else if (compare == -2 && fnmatch(left, right, 0) == 0) - value = xstrdup("1"); - else - value = xstrdup("0"); + } + format_log(ft, "compare %s left is: %s", cmp->modifier, left); + format_log(ft, "compare %s right is: %s", cmp->modifier, right); + + if (strcmp(cmp->modifier, "||") == 0) { + if (format_true(left) || format_true(right)) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "&&") == 0) { + if (format_true(left) && format_true(right)) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "==") == 0) { + if (strcmp(left, right) == 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "!=") == 0) { + if (strcmp(left, right) != 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "<") == 0) { + if (strcmp(left, right) < 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, ">") == 0) { + if (strcmp(left, right) > 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "<=") == 0) { + if (strcmp(left, right) <= 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, ">=") == 0) { + if (strcmp(left, right) >= 0) + value = xstrdup("1"); + else + value = xstrdup("0"); + } else if (strcmp(cmp->modifier, "m") == 0) + value = format_match(cmp, left, right); + free(right); free(left); } else if (*copy == '?') { /* Conditional: check first and choose second or third. */ - ptr = format_skip(copy); - if (ptr == NULL) + cp = format_skip(copy + 1, ","); + if (cp == NULL) { + format_log(ft, "condition syntax error: %s", copy + 1); goto fail; - *ptr = '\0'; + } + condition = xstrndup(copy + 1, cp - (copy + 1)); + format_log(ft, "condition is: %s", condition); + + found = format_find(ft, condition, modifiers); + 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); + if (strcmp(found, condition) == 0) { + free(found); + found = xstrdup(""); + format_log(ft, "condition '%s' found: %s", + condition, found); + } else { + format_log(ft, + "condition '%s' not found; assuming false", + condition); + } + } else + format_log(ft, "condition '%s' found", condition); - found = format_find(ft, copy + 1, modifiers); - if (found == NULL) - found = format_expand(ft, copy + 1); - if (format_choose(ptr + 1, &left, &right) != 0) + if (format_choose(ft, cp + 1, &left, &right, 0) != 0) { + format_log(ft, "condition '%s' syntax error: %s", + condition, cp + 1); + free(found); goto fail; - - if (format_true(found)) + } + if (format_true(found)) { + format_log(ft, "condition '%s' is true", condition); value = format_expand(ft, left); - else + } else { + format_log(ft, "condition '%s' is false", condition); value = format_expand(ft, right); + } + free(right); + free(left); + + free(condition); free(found); } else { /* Neither: look up directly. */ value = format_find(ft, copy, modifiers); - if (value == NULL) + if (value == NULL) { + format_log(ft, "format '%s' not found", copy); value = xstrdup(""); + } else + format_log(ft, "format '%s' found: %s", copy, value); } - /* Perform substitution if any. */ - if (modifiers & FORMAT_SUBSTITUTE) { - fromlen = strlen(from); - tolen = strlen(to); - - newlen = strlen(value) + 1; - copy = new = xmalloc(newlen); - for (ptr = value; *ptr != '\0'; /* nothing */) { - if (strncmp(ptr, from, fromlen) != 0) { - *new++ = *ptr++; - continue; - } - used = new - copy; - - newlen += tolen; - copy = xrealloc(copy, newlen); - - new = copy + used; - memcpy(new, to, tolen); +done: + /* Expand again if required. */ + if (modifiers & FORMAT_EXPAND) { + new = format_expand(ft, value); + free(value); + value = new; + } + else if (modifiers & FORMAT_EXPANDTIME) { + new = format_expand_time(ft, value); + free(value); + value = new; + } - new += tolen; - ptr += fromlen; - } - *new = '\0'; + /* 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]); + new = format_sub(sub[i], value, left, right); + format_log(ft, "substitute '%s' to '%s': %s", left, right, new); free(value); - value = copy; + value = new; + free(right); + free(left); } /* Truncate the value if needed. */ if (limit > 0) { - new = utf8_trimcstr(value, limit); + new = format_trim_left(value, limit); + if (marker != NULL && strcmp(new, value) != 0) { + free(value); + xasprintf(&value, "%s%s", new, marker); + } else { + free(value); + value = new; + } + format_log(ft, "applied length limit %d: %s", limit, value); + } else if (limit < 0) { + new = format_trim_right(value, -limit); + if (marker != NULL && strcmp(new, value) != 0) { + free(value); + xasprintf(&value, "%s%s", marker, new); + } else { + free(value); + value = new; + } + format_log(ft, "applied length limit %d: %s", limit, value); + } + + /* Pad the value if needed. */ + if (width > 0) { + new = utf8_padcstr(value, width); free(value); value = new; - } else if (limit < 0) { - new = utf8_rtrimcstr(value, -limit); + format_log(ft, "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); } /* Expand the buffer and copy in the value. */ @@ -1071,44 +1890,53 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen, memcpy(*buf + *off, value, valuelen); *off += valuelen; + format_log(ft, "replaced '%s' with '%s'", copy0, value); free(value); + + free(sub); + format_free_modifiers(list, count); free(copy0); return (0); fail: + format_log(ft, "failed %s", copy0); + + free(sub); + format_free_modifiers(list, count); free(copy0); return (-1); } -/* Expand keys in a template, passing through strftime first. */ -char * -format_expand_time(struct format_tree *ft, const char *fmt, time_t t) +/* Expand keys in a template. */ +static char * +format_expand1(struct format_tree *ft, const char *fmt, int time) { + char *buf, *out, *name; + const char *ptr, *s; + size_t off, len, n, outlen; + int ch, brackets; struct tm *tm; - char s[2048]; + char expanded[8192]; if (fmt == NULL || *fmt == '\0') return (xstrdup("")); - tm = localtime(&t); - - if (strftime(s, sizeof s, fmt, tm) == 0) + if (ft->loop == FORMAT_LOOP_LIMIT) return (xstrdup("")); + ft->loop++; - return (format_expand(ft, s)); -} + format_log(ft, "expanding format: %s", fmt); -/* Expand keys in a template. */ -char * -format_expand(struct format_tree *ft, const char *fmt) -{ - char *buf, *out; - const char *ptr, *s, *saved = fmt; - size_t off, len, n, outlen; - int ch, brackets; - - if (fmt == NULL) - return (xstrdup("")); + if (time) { + tm = localtime(&ft->time); + if (strftime(expanded, sizeof expanded, fmt, tm) == 0) { + format_log(ft, "format is too long"); + return (xstrdup("")); + } + if (format_logging(ft) && strcmp(expanded, fmt) != 0) + format_log(ft, "after time expanded: %s", expanded); + fmt = expanded; + } len = 64; buf = xmalloc(len); @@ -1125,7 +1953,7 @@ format_expand(struct format_tree *ft, const char *fmt) } fmt++; - ch = (u_char) *fmt++; + ch = (u_char)*fmt++; switch (ch) { case '(': brackets = 1; @@ -1139,12 +1967,19 @@ format_expand(struct format_tree *ft, const char *fmt) break; n = ptr - fmt; - if (ft->flags & FORMAT_NOJOBS) + name = xstrndup(fmt, n); + format_log(ft, "found #(): %s", name); + + if (ft->flags & FORMAT_NOJOBS) { out = xstrdup(""); - else - out = format_job_get(ft, xstrndup(fmt, n)); - outlen = strlen(out); + format_log(ft, "#() is disabled"); + } else { + out = format_job_get(ft, name); + format_log(ft, "#() result: %s", out); + } + free(name); + outlen = strlen(out); while (len - off < outlen + 1) { buf = xreallocarray(buf, 2, len); len *= 2; @@ -1157,27 +1992,25 @@ format_expand(struct format_tree *ft, const char *fmt) fmt += n + 1; continue; case '{': - brackets = 1; - for (ptr = fmt; *ptr != '\0'; ptr++) { - if (*ptr == '{') - brackets++; - if (*ptr == '}' && --brackets == 0) - break; - } - if (*ptr != '}' || brackets != 0) + ptr = format_skip((char *)fmt - 2, "}"); + if (ptr == NULL) break; n = ptr - fmt; + format_log(ft, "found #{}: %.*s", (int)n, fmt); if (format_replace(ft, fmt, n, &buf, &len, &off) != 0) break; fmt += n + 1; continue; + case '}': case '#': + case ',': + format_log(ft, "found #%c", ch); while (len - off < 2) { buf = xreallocarray(buf, 2, len); len *= 2; } - buf[off++] = '#'; + buf[off++] = ch; continue; default: s = NULL; @@ -1195,6 +2028,7 @@ format_expand(struct format_tree *ft, const char *fmt) continue; } n = strlen(s); + format_log(ft, "found #%c: %s", ch, s); if (format_replace(ft, s, n, &buf, &len, &off) != 0) break; continue; @@ -1204,10 +2038,26 @@ format_expand(struct format_tree *ft, const char *fmt) } buf[off] = '\0'; - log_debug("format '%s' -> '%s'", saved, buf); + format_log(ft, "result is: %s", buf); + ft->loop--; + return (buf); } +/* Expand keys in a template, passing through strftime first. */ +char * +format_expand_time(struct format_tree *ft, const char *fmt) +{ + return (format_expand1(ft, fmt, 1)); +} + +/* Expand keys in a template. */ +char * +format_expand(struct format_tree *ft, const char *fmt) +{ + return (format_expand1(ft, fmt, 0)); +} + /* Expand a single string. */ char * format_single(struct cmdq_item *item, const char *fmt, struct client *c, @@ -1232,6 +2082,26 @@ void format_defaults(struct format_tree *ft, struct client *c, struct session *s, struct winlink *wl, struct window_pane *wp) { + if (c != NULL && c->name != NULL) + log_debug("%s: c=%s", __func__, c->name); + else + log_debug("%s: c=none", __func__); + if (s != NULL) + log_debug("%s: s=$%u", __func__, s->id); + else + log_debug("%s: s=none", __func__); + if (wl != NULL) + log_debug("%s: wl=%u w=@%u", __func__, wl->idx, wl->window->id); + else + log_debug("%s: wl=none", __func__); + if (wp != NULL) + log_debug("%s: wp=%%%u", __func__, wp->id); + else + log_debug("%s: wp=none", __func__); + + 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); @@ -1263,14 +2133,17 @@ format_defaults_session(struct format_tree *ft, struct session *s) format_add(ft, "session_name", "%s", s->name); format_add(ft, "session_windows", "%u", winlink_count(&s->windows)); - format_add(ft, "session_width", "%u", s->sx); - format_add(ft, "session_height", "%u", s->sy); format_add(ft, "session_id", "$%u", s->id); sg = session_group_contains(s); format_add(ft, "session_grouped", "%d", sg != NULL); - if (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_cb(ft, "session_group_list", + format_cb_session_group_list); + } format_add_tv(ft, "session_created", &s->creation_time); format_add_tv(ft, "session_last_attached", &s->last_attached_time); @@ -1294,11 +2167,14 @@ format_defaults_client(struct format_tree *ft, struct client *c) 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)); @@ -1350,6 +2226,8 @@ format_defaults_window(struct format_tree *ft, struct window *w) 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); @@ -1362,8 +2240,11 @@ format_defaults_window(struct format_tree *ft, struct window *w) 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; @@ -1371,11 +2252,30 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *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(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", @@ -1391,12 +2291,14 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl) void format_defaults_pane(struct format_tree *ft, struct window_pane *wp) { - struct grid *gd = wp->base.grid; - u_int idx; - int status; + 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 = wp->window; + ft->w = w; ft->wp = wp; format_add(ft, "history_size", "%u", gd->hsize); @@ -1410,33 +2312,45 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) 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 == wp->window->active); + 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); - status = wp->status; - if (wp->fd == -1 && WIFEXITED(status)) + if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status)) format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status)); - format_add(ft, "pane_dead", "%d", wp->fd == -1); + if (~wp->flags & PANE_EMPTY) + format_add(ft, "pane_dead", "%d", wp->fd == -1); + else + format_add(ft, "pane_dead", "0"); - if (window_pane_visible(wp)) { - 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(ft, "pane_at_top", "%d", wp->yoff == 0); - format_add(ft, "pane_at_right", "%d", wp->xoff + wp->sx == wp->window->sx); - format_add(ft, "pane_at_bottom", "%d", wp->yoff + wp->sy == wp->window->sy); + 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(ft, "pane_at_top", "%d", wp->yoff == 0); + format_add(ft, "pane_at_right", "%d", wp->xoff + wp->sx == w->sx); + format_add(ft, "pane_at_bottom", "%d", wp->yoff + wp->sy == w->sy); + + 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(ft, "pane_in_mode", "%d", wp->screen != &wp->base); - if (wp->mode != NULL) - format_add(ft, "pane_mode", "%s", wp->mode->name); + format_add_cb(ft, "pane_in_mode", format_cb_pane_in_mode); format_add(ft, "pane_synchronized", "%d", - !!options_get_number(wp->window->options, "synchronize-panes")); + !!options_get_number(w->options, "synchronize-panes")); if (wp->searchstr != NULL) format_add(ft, "pane_search_string", "%s", wp->searchstr); @@ -1448,11 +2362,11 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) 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); - window_copy_add_formats(wp, ft); - 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); @@ -1467,6 +2381,8 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) !!(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)); @@ -1476,6 +2392,10 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) !!(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); } diff --git a/grid-view.c b/grid-view.c index 1bb773178f..a4bd5ba2fc 100644 --- a/grid-view.c +++ b/grid-view.c @@ -64,7 +64,7 @@ grid_view_clear_history(struct grid *gd, u_int bg) /* Find the last used line. */ last = 0; for (yy = 0; yy < gd->sy; yy++) { - gl = &gd->linedata[grid_view_y(gd, yy)]; + gl = grid_get_line(gd, grid_view_y(gd, yy)); if (gl->cellused != 0) last = yy + 1; } diff --git a/grid.c b/grid.c index aa9aea4c66..a016dc122d 100644 --- a/grid.c +++ b/grid.c @@ -37,34 +37,25 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { - 0, 0, 8, 8, { { ' ' }, 0, 1, 1 } + { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }; -static const struct grid_cell_entry grid_default_entry = { - 0, { .data = { 0, 8, 8, ' ' } } + +/* Cleared grid cell data. */ +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_expand_line(struct grid *, u_int, u_int, u_int); static void grid_empty_line(struct grid *, u_int, u_int); -static void grid_reflow_copy(struct grid_line *, u_int, struct grid_line *, - u_int, u_int); -static void grid_reflow_join(struct grid *, u_int *, struct grid_line *, - u_int); -static void grid_reflow_split(struct grid *, u_int *, struct grid_line *, - u_int, u_int); -static void grid_reflow_move(struct grid *, u_int *, struct grid_line *); - -static size_t grid_string_cells_fg(const struct grid_cell *, int *); -static size_t grid_string_cells_bg(const struct grid_cell *, int *); -static void grid_string_cells_code(const struct grid_cell *, - const struct grid_cell *, char *, size_t, int); - /* Store cell in entry. */ static void grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, u_char c) { - gce->flags = gc->flags; + gce->flags = (gc->flags & ~GRID_FLAG_CLEARED); gce->data.fg = gc->fg & 0xff; if (gc->fg & COLOUR_FLAG_256) @@ -78,7 +69,7 @@ grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc, gce->data.data = c; } -/* Check if a cell should be extended. */ +/* Check if a cell should be an extended cell. */ static int grid_need_extended_cell(const struct grid_cell_entry *gce, const struct grid_cell *gc) @@ -91,9 +82,45 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB)) return (1); + if (gc->us != 0) /* only supports 256 or RGB */ + return (1); return (0); } +/* Get an extended cell. */ +static void +grid_get_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, + int flags) +{ + u_int at = gl->extdsize + 1; + + gl->extddata = xreallocarray(gl->extddata, at, sizeof *gl->extddata); + gl->extdsize = at; + + gce->offset = at - 1; + gce->flags = (flags | GRID_FLAG_EXTENDED); +} + +/* Set cell as extended. */ +static struct grid_cell * +grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, + const struct grid_cell *gc) +{ + struct grid_cell *gcp; + int flags = (gc->flags & ~GRID_FLAG_CLEARED); + + if (~gce->flags & GRID_FLAG_EXTENDED) + grid_get_extended_cell(gl, gce, flags); + else if (gce->offset >= gl->extdsize) + fatalx("offset too big"); + gl->flags |= GRID_LINE_EXTENDED; + + gcp = &gl->extddata[gce->offset]; + memcpy(gcp, gc, sizeof *gcp); + gcp->flags = flags; + return (gcp); +} + /* Free up unused extended cells. */ static void grid_compact_line(struct grid_line *gl) @@ -136,27 +163,18 @@ grid_compact_line(struct grid_line *gl) gl->extdsize = new_extdsize; } -/* Set cell as extended. */ -static struct grid_cell * -grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, - const struct grid_cell *gc) +/* Get line data. */ +struct grid_line * +grid_get_line(struct grid *gd, u_int line) { - struct grid_cell *gcp; - - gl->flags |= GRID_LINE_EXTENDED; - - if (~gce->flags & GRID_FLAG_EXTENDED) { - gl->extddata = xreallocarray(gl->extddata, gl->extdsize + 1, - sizeof *gl->extddata); - gce->offset = gl->extdsize++; - gce->flags = gc->flags | GRID_FLAG_EXTENDED; - } - if (gce->offset >= gl->extdsize) - fatalx("offset too big"); + return (&gd->linedata[line]); +} - gcp = &gl->extddata[gce->offset]; - memcpy(gcp, gc, sizeof *gcp); - return (gcp); +/* Adjust number of lines. */ +void +grid_adjust_lines(struct grid *gd, u_int lines) +{ + gd->linedata = xreallocarray(gd->linedata, lines, sizeof *gd->linedata); } /* Copy default into a cell. */ @@ -167,23 +185,29 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg) struct grid_cell_entry *gce = &gl->celldata[px]; struct grid_cell *gc; - memcpy(gce, &grid_default_entry, sizeof *gce); - if (bg & COLOUR_FLAG_RGB) { - gc = grid_extended_cell(gl, gce, &grid_default_cell); - gc->bg = bg; - } else { - if (bg & COLOUR_FLAG_256) - gce->flags |= GRID_FLAG_BG256; - gce->data.bg = bg; + 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; + } else { + if (bg & COLOUR_FLAG_256) + gce->flags |= GRID_FLAG_BG256; + gce->data.bg = bg; + } } } /* Check grid y position. */ static int -grid_check_y(struct grid *gd, u_int py) +grid_check_y(struct grid *gd, const char* from, u_int py) { - if ((py) >= (gd)->hsize + (gd)->sy) { - log_debug("y out of range: %u", py); + if (py >= gd->hsize + gd->sy) { + log_debug("%s: y out of range: %u", from, py); return (-1); } return (0); @@ -240,7 +264,10 @@ grid_create(u_int sx, u_int sy, u_int hlimit) gd->hsize = 0; gd->hlimit = hlimit; - gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata); + if (gd->sy != 0) + gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata); + else + gd->linedata = NULL; return (gd); } @@ -283,6 +310,15 @@ grid_compare(struct grid *ga, struct grid *gb) return (0); } +/* Trim lines from the history. */ +static void +grid_trim_history(struct grid *gd, u_int ny) +{ + grid_free_lines(gd, 0, ny); + memmove(&gd->linedata[0], &gd->linedata[ny], + (gd->hsize + gd->sy - ny) * (sizeof *gd->linedata)); +} + /* * Collect lines from the history if at the limit. Free the top (oldest) 10% * and shift up. @@ -305,9 +341,7 @@ grid_collect_history(struct grid *gd) * Free the lines from 0 to ny then move the remaining lines over * them. */ - grid_free_lines(gd, 0, ny); - memmove(&gd->linedata[0], &gd->linedata[ny], - (gd->hsize + gd->sy - ny) * (sizeof *gd->linedata)); + grid_trim_history(gd, ny); gd->hsize -= ny; if (gd->hscrolled > gd->hsize) @@ -337,9 +371,7 @@ grid_scroll_history(struct grid *gd, u_int bg) void grid_clear_history(struct grid *gd) { - grid_free_lines(gd, 0, gd->hsize); - memmove(&gd->linedata[0], &gd->linedata[gd->hsize], - gd->sy * (sizeof *gd->linedata)); + grid_trim_history(gd, gd->hsize); gd->hscrolled = 0; gd->hsize = 0; @@ -410,7 +442,7 @@ static void grid_empty_line(struct grid *gd, u_int py, u_int bg) { memset(&gd->linedata[py], 0, sizeof gd->linedata[py]); - if (bg != 8) + if (!COLOUR_DEFAULT(bg)) grid_expand_line(gd, py, gd->sx, bg); } @@ -418,25 +450,16 @@ grid_empty_line(struct grid *gd, u_int py, u_int bg) const struct grid_line * grid_peek_line(struct grid *gd, u_int py) { - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return (NULL); return (&gd->linedata[py]); } -/* Get cell for reading. */ -void -grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) +/* Get cell from line. */ +static void +grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) { - struct grid_line *gl; - struct grid_cell_entry *gce; - - if (grid_check_y(gd, py) != 0 || px >= gd->linedata[py].cellsize) { - memcpy(gc, &grid_default_cell, sizeof *gc); - return; - } - - gl = &gd->linedata[py]; - gce = &gl->celldata[px]; + struct grid_cell_entry *gce = &gl->celldata[px]; if (gce->flags & GRID_FLAG_EXTENDED) { if (gce->offset >= gl->extdsize) @@ -454,9 +477,21 @@ grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) gc->bg = gce->data.bg; if (gce->flags & GRID_FLAG_BG256) gc->bg |= COLOUR_FLAG_256; + gc->us = 0; utf8_set(&gc->data, gce->data.data); } +/* Get cell for reading. */ +void +grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc) +{ + if (grid_check_y(gd, __func__, py) != 0 || + px >= gd->linedata[py].cellsize) + memcpy(gc, &grid_default_cell, sizeof *gc); + else + grid_get_cell1(&gd->linedata[py], px, gc); +} + /* Set cell at relative position. */ void grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) @@ -464,7 +499,7 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) struct grid_line *gl; struct grid_cell_entry *gce; - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; grid_expand_line(gd, py, px + 1, 8); @@ -490,7 +525,7 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, struct grid_cell *gcp; u_int i; - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; grid_expand_line(gd, py, px + slen, 8); @@ -513,7 +548,8 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, void grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg) { - u_int xx, yy; + struct grid_line *gl; + u_int xx, yy, ox, sx; if (nx == 0 || ny == 0) return; @@ -523,22 +559,27 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg) return; } - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; - if (grid_check_y(gd, py + ny - 1) != 0) + if (grid_check_y(gd, __func__, py + ny - 1) != 0) return; for (yy = py; yy < py + ny; yy++) { - if (px + nx >= gd->sx && px < gd->linedata[yy].cellused) - gd->linedata[yy].cellused = px; - if (px > gd->linedata[yy].cellsize && bg == 8) - continue; - if (px + nx >= gd->linedata[yy].cellsize && bg == 8) { - gd->linedata[yy].cellsize = px; - continue; + gl = &gd->linedata[yy]; + + sx = gd->sx; + if (sx > gl->cellsize) + sx = gl->cellsize; + ox = nx; + if (COLOUR_DEFAULT(bg)) { + if (px > sx) + continue; + if (px + nx > sx) + ox = sx - px; } - grid_expand_line(gd, yy, px + nx, 8); /* default bg first */ - for (xx = px; xx < px + nx; xx++) + + grid_expand_line(gd, yy, px + ox, 8); /* default bg first */ + for (xx = px; xx < px + ox; xx++) grid_clear_cell(gd, xx, yy, bg); } } @@ -552,9 +593,9 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg) if (ny == 0) return; - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; - if (grid_check_y(gd, py + ny - 1) != 0) + if (grid_check_y(gd, __func__, py + ny - 1) != 0) return; for (yy = py; yy < py + ny; yy++) { @@ -572,13 +613,13 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg) if (ny == 0 || py == dy) return; - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; - if (grid_check_y(gd, py + ny - 1) != 0) + if (grid_check_y(gd, __func__, py + ny - 1) != 0) return; - if (grid_check_y(gd, dy) != 0) + if (grid_check_y(gd, __func__, dy) != 0) return; - if (grid_check_y(gd, dy + ny - 1) != 0) + if (grid_check_y(gd, __func__, dy + ny - 1) != 0) return; /* Free any lines which are being replaced. */ @@ -612,7 +653,7 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, if (nx == 0 || px == dx) return; - if (grid_check_y(gd, py) != 0) + if (grid_check_y(gd, __func__, py) != 0) return; gl = &gd->linedata[py]; @@ -753,7 +794,12 @@ grid_string_cells_code(const struct grid_cell *lastgc, { GRID_ATTR_BLINK, 5 }, { GRID_ATTR_REVERSE, 7 }, { GRID_ATTR_HIDDEN, 8 }, - { GRID_ATTR_STRIKETHROUGH, 9 } + { GRID_ATTR_STRIKETHROUGH, 9 }, + { GRID_ATTR_UNDERSCORE_2, 42 }, + { GRID_ATTR_UNDERSCORE_3, 43 }, + { GRID_ATTR_UNDERSCORE_4, 44 }, + { GRID_ATTR_UNDERSCORE_5, 45 }, + { GRID_ATTR_OVERLINE, 53 }, }; n = 0; @@ -779,11 +825,15 @@ grid_string_cells_code(const struct grid_cell *lastgc, else strlcat(buf, "\033[", len); for (i = 0; i < n; i++) { - if (i + 1 < n) - xsnprintf(tmp, sizeof tmp, "%d;", s[i]); - else + if (s[i] < 10) xsnprintf(tmp, sizeof tmp, "%d", s[i]); + else { + xsnprintf(tmp, sizeof tmp, "%d:%d", s[i] / 10, + s[i] % 10); + } strlcat(buf, tmp, len); + if (i + 1 < n) + strlcat(buf, ";", len); } strlcat(buf, "m", len); } @@ -953,164 +1003,378 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, } } -/* Copy a section of a line. */ +/* Mark line as dead. */ static void -grid_reflow_copy(struct grid_line *dst_gl, u_int to, struct grid_line *src_gl, - u_int from, u_int to_copy) +grid_reflow_dead(struct grid_line *gl) { - struct grid_cell_entry *gce; - u_int i, was; + memset(gl, 0, sizeof *gl); + gl->flags = GRID_LINE_DEAD; +} - memcpy(&dst_gl->celldata[to], &src_gl->celldata[from], - to_copy * sizeof *dst_gl->celldata); +/* Add lines, return the first new one. */ +static struct grid_line * +grid_reflow_add(struct grid *gd, u_int n) +{ + struct grid_line *gl; + u_int sy = gd->sy + n; - for (i = to; i < to + to_copy; i++) { - gce = &dst_gl->celldata[i]; - if (~gce->flags & GRID_FLAG_EXTENDED) - continue; - was = gce->offset; + gd->linedata = xreallocarray(gd->linedata, sy, sizeof *gd->linedata); + gl = &gd->linedata[gd->sy]; + memset(gl, 0, n * (sizeof *gl)); + gd->sy = sy; + return (gl); +} - dst_gl->extddata = xreallocarray(dst_gl->extddata, - dst_gl->extdsize + 1, sizeof *dst_gl->extddata); - gce->offset = dst_gl->extdsize++; - memcpy(&dst_gl->extddata[gce->offset], &src_gl->extddata[was], - sizeof *dst_gl->extddata); - } +/* Move a line across. */ +static struct grid_line * +grid_reflow_move(struct grid *gd, struct grid_line *from) +{ + struct grid_line *to; + + to = grid_reflow_add(gd, 1); + memcpy(to, from, sizeof *to); + grid_reflow_dead(from); + return (to); } -/* Join line data. */ +/* Join line below onto this one. */ static void -grid_reflow_join(struct grid *dst, u_int *py, struct grid_line *src_gl, - u_int new_x) +grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy, + u_int width, int already) { - struct grid_line *dst_gl = &dst->linedata[(*py) - 1]; - u_int left, to_copy, ox, nx; - - /* How much is left on the old line? */ - left = new_x - dst_gl->cellused; + struct grid_line *gl, *from = NULL; + struct grid_cell gc; + u_int lines, left, i, to, line, want = 0; + u_int at; + int wrapped = 1; - /* Work out how much to append. */ - to_copy = src_gl->cellused; - if (to_copy > left) - to_copy = left; - ox = dst_gl->cellused; - nx = ox + to_copy; + /* + * Add a new target line. + */ + if (!already) { + to = target->sy; + gl = grid_reflow_move(target, &gd->linedata[yy]); + } else { + to = target->sy - 1; + gl = &target->linedata[to]; + } + at = gl->cellused; - /* Resize the destination line. */ - dst_gl->celldata = xreallocarray(dst_gl->celldata, nx, - sizeof *dst_gl->celldata); - dst_gl->cellsize = dst_gl->cellused = nx; + /* + * Loop until no more to consume or the target line is full. + */ + lines = 0; + for (;;) { + /* + * If this is now the last line, there is nothing more to be + * done. + */ + if (yy + 1 + lines == gd->hsize + gd->sy) + break; + line = yy + 1 + lines; + + /* If the next line is empty, skip it. */ + if (~gd->linedata[line].flags & GRID_LINE_WRAPPED) + wrapped = 0; + if (gd->linedata[line].cellused == 0) { + if (!wrapped) + break; + lines++; + continue; + } - /* Append as much as possible. */ - grid_reflow_copy(dst_gl, ox, src_gl, 0, to_copy); + /* + * Is the destination line now full? Copy the first character + * separately because we need to leave "from" set to the last + * line if this line is full. + */ + grid_get_cell1(&gd->linedata[line], 0, &gc); + if (width + gc.data.width > sx) + break; + width += gc.data.width; + grid_set_cell(target, at, to, &gc); + at++; + + /* Join as much more as possible onto the current line. */ + from = &gd->linedata[line]; + for (want = 1; want < from->cellused; want++) { + grid_get_cell1(from, want, &gc); + if (width + gc.data.width > sx) + break; + width += gc.data.width; + + grid_set_cell(target, at, to, &gc); + at++; + } + lines++; - /* If there is any left in the source, split it. */ - if (src_gl->cellused > to_copy) { - dst_gl->flags |= GRID_LINE_WRAPPED; + /* + * If this line wasn't wrapped or we didn't consume the entire + * line, don't try to join any further lines. + */ + if (!wrapped || want != from->cellused || width == sx) + break; + } + if (lines == 0) + return; - src_gl->cellused -= to_copy; - grid_reflow_split(dst, py, src_gl, new_x, to_copy); + /* + * If we didn't consume the entire final line, then remove what we did + * consume. If we consumed the entire line and it wasn't wrapped, + * remove the wrap flag from this line. + */ + left = from->cellused - want; + if (left != 0) { + grid_move_cells(gd, 0, want, yy + lines, left, 8); + from->cellsize = from->cellused = left; + lines--; + } else if (!wrapped) + gl->flags &= ~GRID_LINE_WRAPPED; + + /* Remove the lines that were completely consumed. */ + for (i = yy + 1; i < yy + 1 + lines; i++) { + free(gd->linedata[i].celldata); + free(gd->linedata[i].extddata); + grid_reflow_dead(&gd->linedata[i]); } + + /* Adjust scroll position. */ + if (gd->hscrolled > to + lines) + gd->hscrolled -= lines; + else if (gd->hscrolled > to) + gd->hscrolled = to; } -/* Split line data. */ +/* Split this line into several new ones */ static void -grid_reflow_split(struct grid *dst, u_int *py, struct grid_line *src_gl, - u_int new_x, u_int offset) +grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy, + u_int at) { - struct grid_line *dst_gl = NULL; - u_int to_copy; - - /* Loop and copy sections of the source line. */ - while (src_gl->cellused > 0) { - /* Create new line. */ - if (*py >= dst->hsize + dst->sy) - grid_scroll_history(dst, 8); - dst_gl = &dst->linedata[*py]; - (*py)++; - - /* How much should we copy? */ - to_copy = new_x; - if (to_copy > src_gl->cellused) - to_copy = src_gl->cellused; - - /* Expand destination line. */ - dst_gl->celldata = xreallocarray(NULL, to_copy, - sizeof *dst_gl->celldata); - dst_gl->cellsize = dst_gl->cellused = to_copy; - dst_gl->flags |= GRID_LINE_WRAPPED; - - /* Copy the data. */ - grid_reflow_copy(dst_gl, 0, src_gl, offset, to_copy); - - /* Move offset and reduce old line size. */ - offset += to_copy; - src_gl->cellused -= to_copy; + struct grid_line *gl = &gd->linedata[yy], *first; + struct grid_cell gc; + u_int line, lines, width, i, xx; + u_int used = gl->cellused; + int flags = gl->flags; + + /* How many lines do we need to insert? We know we need at least two. */ + if (~gl->flags & GRID_LINE_EXTENDED) + lines = 1 + (gl->cellused - 1) / sx; + else { + lines = 2; + width = 0; + for (i = at; i < used; i++) { + grid_get_cell1(gl, i, &gc); + if (width + gc.data.width > sx) { + lines++; + width = 0; + } + width += gc.data.width; + } } - /* Last line is not wrapped. */ - if (dst_gl != NULL) - dst_gl->flags &= ~GRID_LINE_WRAPPED; + /* Insert new lines. */ + line = target->sy + 1; + first = grid_reflow_add(target, lines); + + /* Copy sections from the original line. */ + width = 0; + xx = 0; + for (i = at; i < used; i++) { + grid_get_cell1(gl, i, &gc); + if (width + gc.data.width > sx) { + target->linedata[line].flags |= GRID_LINE_WRAPPED; + + line++; + width = 0; + xx = 0; + } + width += gc.data.width; + grid_set_cell(target, xx, line, &gc); + xx++; + } + if (flags & GRID_LINE_WRAPPED) + target->linedata[line].flags |= GRID_LINE_WRAPPED; + + /* Move the remainder of the original line. */ + gl->cellsize = gl->cellused = at; + gl->flags |= GRID_LINE_WRAPPED; + memcpy(first, gl, sizeof *first); + grid_reflow_dead(gl); + + /* Adjust the scroll position. */ + if (yy <= gd->hscrolled) + gd->hscrolled += lines - 1; + + /* + * If the original line had the wrapped flag and there is still space + * in the last new line, try to join with the next lines. + */ + if (width < sx && (flags & GRID_LINE_WRAPPED)) + grid_reflow_join(target, gd, sx, yy, width, 1); } -/* Move line data. */ -static void -grid_reflow_move(struct grid *dst, u_int *py, struct grid_line *src_gl) +/* Reflow lines on grid to new width. */ +void +grid_reflow(struct grid *gd, u_int sx) { - struct grid_line *dst_gl; + struct grid *target; + struct grid_line *gl; + struct grid_cell gc; + u_int yy, width, i, at, first; - /* Create new line. */ - if (*py >= dst->hsize + dst->sy) - grid_scroll_history(dst, 8); - dst_gl = &dst->linedata[*py]; - (*py)++; + /* + * Create a destination grid. This is just used as a container for the + * line data and may not be fully valid. + */ + target = grid_create(gd->sx, 0, 0); - /* Copy the old line. */ - memcpy(dst_gl, src_gl, sizeof *dst_gl); - dst_gl->flags &= ~GRID_LINE_WRAPPED; + /* + * Loop over each source line. + */ + for (yy = 0; yy < gd->hsize + gd->sy; yy++) { + gl = &gd->linedata[yy]; + if (gl->flags & GRID_LINE_DEAD) + 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. + */ + first = at = width = 0; + if (~gl->flags & GRID_LINE_EXTENDED) { + first = 1; + width = gl->cellused; + if (width > sx) + at = sx; + else + at = width; + } 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; + } + } + + /* + * If the line is exactly right or the first character is wider + * than the targe width, just move it across unchanged. + */ + if (width == sx || first > sx) { + grid_reflow_move(target, gl); + continue; + } + + /* + * If the line is too big, it needs to be split, whether or not + * it was previously wrapped. + */ + if (width > sx) { + grid_reflow_split(target, gd, sx, yy, at); + continue; + } - /* Clear old line. */ - src_gl->celldata = NULL; - src_gl->extddata = NULL; + /* + * If the line was previously wrapped, join as much as possible + * of the next line. + */ + if (gl->flags & GRID_LINE_WRAPPED) + grid_reflow_join(target, gd, sx, yy, width, 0); + else + grid_reflow_move(target, gl); + } + + /* + * Replace the old grid with the new. + */ + if (target->sy < gd->sy) + grid_reflow_add(target, gd->sy - target->sy); + gd->hsize = target->sy - gd->sy; + if (gd->hscrolled > gd->hsize) + gd->hscrolled = gd->hsize; + free(gd->linedata); + gd->linedata = target->linedata; + free(target); } -/* - * Reflow lines from src grid into dst grid of width new_x. Returns number of - * lines fewer in the visible area. The source grid is destroyed. - */ -u_int -grid_reflow(struct grid *dst, struct grid *src, u_int new_x) +/* Convert to position based on wrapped lines. */ +void +grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy) { - u_int py, sy, line; - int previous_wrapped; - struct grid_line *src_gl; - - py = 0; - sy = src->sy; - - previous_wrapped = 0; - for (line = 0; line < sy + src->hsize; line++) { - src_gl = src->linedata + line; - if (!previous_wrapped) { - /* Wasn't wrapped. If smaller, move to destination. */ - if (src_gl->cellused <= new_x) - grid_reflow_move(dst, &py, src_gl); - else - grid_reflow_split(dst, &py, src_gl, new_x, 0); - } else { - /* Previous was wrapped. Try to join. */ - grid_reflow_join(dst, &py, src_gl, new_x); + u_int ax = 0, ay = 0, yy; + + for (yy = 0; yy < py; yy++) { + if (gd->linedata[yy].flags & GRID_LINE_WRAPPED) + ax += gd->linedata[yy].cellused; + else { + ax = 0; + ay++; } - previous_wrapped = (src_gl->flags & GRID_LINE_WRAPPED); + } + if (px >= gd->linedata[yy].cellused) + ax = UINT_MAX; + else + ax += px; + *wx = ax; + *wy = ay; +} - /* This is where we started scrolling. */ - if (line == sy + src->hsize - src->hscrolled - 1) - dst->hscrolled = 0; +/* Convert position based on wrapped lines back. */ +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; + + 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; + ay++; + } } - grid_destroy(src); + /* + * yy is now 0 on the unwrapped line which contains wx. Walk forwards + * until we find the end or the line now containing wx. + */ + if (wx == UINT_MAX) { + while (gd->linedata[yy].flags & GRID_LINE_WRAPPED) + yy++; + wx = gd->linedata[yy].cellused; + } else { + while (gd->linedata[yy].flags & GRID_LINE_WRAPPED) { + if (wx < gd->linedata[yy].cellused) + break; + wx -= gd->linedata[yy].cellused; + yy++; + } + } + *px = wx; + *py = yy; +} - if (py > sy) - return (0); - return (sy - py); +/* Get length of line. */ +u_int +grid_line_length(struct grid *gd, u_int py) +{ + struct grid_cell gc; + u_int px; + + px = grid_get_line(gd, py)->cellsize; + if (px > gd->sx) + px = gd->sx; + while (px > 0) { + grid_get_cell(gd, px - 1, py, &gc); + if (gc.data.size != 1 || *gc.data.data != ' ') + break; + px--; + } + return (px); } diff --git a/hooks.c b/hooks.c deleted file mode 100644 index 832e6f8422..0000000000 --- a/hooks.c +++ /dev/null @@ -1,200 +0,0 @@ -/* $OpenBSD$ */ - -/* - * Copyright (c) 2012 Thomas Adam - * - * 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" - -struct hooks { - RB_HEAD(hooks_tree, hook) tree; - struct hooks *parent; -}; - -static int hooks_cmp(struct hook *, struct hook *); -RB_GENERATE_STATIC(hooks_tree, hook, entry, hooks_cmp); - -static struct hook *hooks_find1(struct hooks *, const char *); -static void hooks_free1(struct hooks *, struct hook *); - -static int -hooks_cmp(struct hook *hook1, struct hook *hook2) -{ - return (strcmp(hook1->name, hook2->name)); -} - -struct hooks * -hooks_get(struct session *s) -{ - if (s != NULL) - return (s->hooks); - return (global_hooks); -} - -struct hooks * -hooks_create(struct hooks *parent) -{ - struct hooks *hooks; - - hooks = xcalloc(1, sizeof *hooks); - RB_INIT(&hooks->tree); - hooks->parent = parent; - return (hooks); -} - -static void -hooks_free1(struct hooks *hooks, struct hook *hook) -{ - RB_REMOVE(hooks_tree, &hooks->tree, hook); - cmd_list_free(hook->cmdlist); - free((char *)hook->name); - free(hook); -} - -void -hooks_free(struct hooks *hooks) -{ - struct hook *hook, *hook1; - - RB_FOREACH_SAFE(hook, hooks_tree, &hooks->tree, hook1) - hooks_free1(hooks, hook); - free(hooks); -} - -struct hook * -hooks_first(struct hooks *hooks) -{ - return (RB_MIN(hooks_tree, &hooks->tree)); -} - -struct hook * -hooks_next(struct hook *hook) -{ - return (RB_NEXT(hooks_tree, &hooks->tree, hook)); -} - -void -hooks_add(struct hooks *hooks, const char *name, struct cmd_list *cmdlist) -{ - struct hook *hook; - - if ((hook = hooks_find1(hooks, name)) != NULL) - hooks_free1(hooks, hook); - - hook = xcalloc(1, sizeof *hook); - hook->name = xstrdup(name); - hook->cmdlist = cmdlist; - hook->cmdlist->references++; - RB_INSERT(hooks_tree, &hooks->tree, hook); -} - -void -hooks_remove(struct hooks *hooks, const char *name) -{ - struct hook *hook; - - if ((hook = hooks_find1(hooks, name)) != NULL) - hooks_free1(hooks, hook); -} - -static struct hook * -hooks_find1(struct hooks *hooks, const char *name) -{ - struct hook hook; - - hook.name = name; - return (RB_FIND(hooks_tree, &hooks->tree, &hook)); -} - -struct hook * -hooks_find(struct hooks *hooks, const char *name) -{ - struct hook hook0, *hook; - - hook0.name = name; - hook = RB_FIND(hooks_tree, &hooks->tree, &hook0); - while (hook == NULL) { - hooks = hooks->parent; - if (hooks == NULL) - break; - hook = RB_FIND(hooks_tree, &hooks->tree, &hook0); - } - return (hook); -} - -void -hooks_run(struct hooks *hooks, struct client *c, struct cmd_find_state *fs, - const char *fmt, ...) -{ - struct hook *hook; - va_list ap; - char *name; - struct cmdq_item *new_item; - - va_start(ap, fmt); - xvasprintf(&name, fmt, ap); - va_end(ap); - - hook = hooks_find(hooks, name); - if (hook == NULL) { - free(name); - return; - } - log_debug("running hook %s", name); - - new_item = cmdq_get_command(hook->cmdlist, fs, NULL, CMDQ_NOHOOKS); - cmdq_format(new_item, "hook", "%s", name); - cmdq_append(c, new_item); - - free(name); -} - -void -hooks_insert(struct hooks *hooks, struct cmdq_item *item, - struct cmd_find_state *fs, const char *fmt, ...) -{ - struct hook *hook; - va_list ap; - char *name; - struct cmdq_item *new_item; - - if (item->flags & CMDQ_NOHOOKS) - return; - - va_start(ap, fmt); - xvasprintf(&name, fmt, ap); - va_end(ap); - - hook = hooks_find(hooks, name); - if (hook == NULL) { - free(name); - return; - } - log_debug("running hook %s (parent %p)", name, item); - - new_item = cmdq_get_command(hook->cmdlist, fs, NULL, CMDQ_NOHOOKS); - cmdq_format(new_item, "hook", "%s", name); - if (item != NULL) - cmdq_insert_after(item, new_item); - else - cmdq_append(NULL, new_item); - - free(name); -} diff --git a/input-keys.c b/input-keys.c index 85dc25912d..b0ea510417 100644 --- a/input-keys.c +++ b/input-keys.c @@ -42,9 +42,6 @@ struct input_key_ent { }; static const struct input_key_ent input_keys[] = { - /* Backspace key. */ - { KEYC_BSPACE, "\177", 0 }, - /* Paste keys. */ { KEYC_PASTE_START, "\033[200~", 0 }, { KEYC_PASTE_END, "\033[201~", 0 }, @@ -159,7 +156,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) u_int i; size_t dlen; char *out; - key_code justkey; + key_code justkey, newkey; struct utf8_data ud; log_debug("writing key 0x%llx (%s) to %%%u", key, @@ -172,6 +169,21 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m) return; } + /* 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); + return; + } + + /* Is this backspace? */ + if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) { + newkey = options_get_number(global_options, "backspace"); + if (newkey >= 0x7f) + newkey = '\177'; + key = newkey|(key & KEYC_MASK_MOD); + } + /* * 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. @@ -247,10 +259,10 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m) if ((mode & ALL_MOUSE_MODES) == 0) return; - if (!window_pane_visible(wp)) - return; if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) return; + if (!window_pane_visible(wp)) + return; /* If this pane is not in button or all mode, discard motion events. */ if (MOUSE_DRAG(m->b) && diff --git a/input.c b/input.c index e4aecf7d95..452eac7f87 100644 --- a/input.c +++ b/input.c @@ -30,7 +30,7 @@ /* * Based on the description by Paul Williams at: * - * http://vt100.net/emu/dec_ansi_parser + * https://vt100.net/emu/dec_ansi_parser * * With the following changes: * @@ -58,6 +58,19 @@ struct input_cell { int g1set; /* 1 if ACS */ }; +/* Input parser argument. */ +struct input_param { + enum { + INPUT_MISSING, + INPUT_NUMBER, + INPUT_STRING + } type; + union { + int num; + char *str; + }; +}; + /* Input parser context. */ struct input_ctx { struct window_pane *wp; @@ -68,6 +81,7 @@ struct input_ctx { struct input_cell old_cell; u_int old_cx; u_int old_cy; + int old_mode; u_char interm_buf[4]; size_t interm_len; @@ -80,11 +94,16 @@ struct input_ctx { u_char *input_buf; size_t input_len; size_t input_space; + enum { + INPUT_END_ST, + INPUT_END_BEL + } input_end; - int param_list[24]; /* -1 not present */ + struct input_param param_list[24]; u_int param_list_len; struct utf8_data utf8data; + int utf8started; int ch; int last; @@ -112,11 +131,11 @@ static void input_set_state(struct window_pane *, const struct input_transition *); static void input_reset_cell(struct input_ctx *); -static void input_osc_4(struct window_pane *, const char *); -static void input_osc_10(struct window_pane *, const char *); -static void input_osc_11(struct window_pane *, const char *); -static void input_osc_52(struct window_pane *, const char *); -static void input_osc_104(struct window_pane *, const char *); +static void input_osc_4(struct input_ctx *, const char *); +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 *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -146,9 +165,8 @@ static void input_csi_dispatch_sgr_256(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr_rgb(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr(struct input_ctx *); static int input_dcs_dispatch(struct input_ctx *); -static int input_utf8_open(struct input_ctx *); -static int input_utf8_add(struct input_ctx *); -static int input_utf8_close(struct input_ctx *); +static int input_top_bit_set(struct input_ctx *); +static int input_end_bel(struct input_ctx *); /* Command table comparison function. */ static int input_table_compare(const void *, const void *); @@ -226,6 +244,7 @@ enum input_csi_type { INPUT_CSI_RM, INPUT_CSI_RM_PRIVATE, INPUT_CSI_SCP, + INPUT_CSI_SD, INPUT_CSI_SGR, INPUT_CSI_SM, INPUT_CSI_SM_PRIVATE, @@ -252,8 +271,10 @@ static const struct input_table_entry input_csi_table[] = { { 'M', "", INPUT_CSI_DL }, { 'P', "", INPUT_CSI_DCH }, { 'S', "", INPUT_CSI_SU }, + { 'T', "", INPUT_CSI_SD }, { 'X', "", INPUT_CSI_ECH }, { 'Z', "", INPUT_CSI_CBT }, + { '`', "", INPUT_CSI_HPA }, { 'b', "", INPUT_CSI_REP }, { 'c', "", INPUT_CSI_DA }, { 'c', ">", INPUT_CSI_DA_TWO }, @@ -314,9 +335,6 @@ static const struct input_transition input_state_osc_string_table[]; static const struct input_transition input_state_apc_string_table[]; static const struct input_transition input_state_rename_string_table[]; static const struct input_transition input_state_consume_st_table[]; -static const struct input_transition input_state_utf8_three_table[]; -static const struct input_transition input_state_utf8_two_table[]; -static const struct input_transition input_state_utf8_one_table[]; /* ground state definition. */ static const struct input_state input_state_ground = { @@ -437,27 +455,6 @@ static const struct input_state input_state_consume_st = { input_state_consume_st_table }; -/* utf8_three state definition. */ -static const struct input_state input_state_utf8_three = { - "utf8_three", - NULL, NULL, - input_state_utf8_three_table -}; - -/* utf8_two state definition. */ -static const struct input_state input_state_utf8_two = { - "utf8_two", - NULL, NULL, - input_state_utf8_two_table -}; - -/* utf8_one state definition. */ -static const struct input_state input_state_utf8_one = { - "utf8_one", - NULL, NULL, - input_state_utf8_one_table -}; - /* ground state table. */ static const struct input_transition input_state_ground_table[] = { INPUT_STATE_ANYWHERE, @@ -467,11 +464,7 @@ static const struct input_transition input_state_ground_table[] = { { 0x1c, 0x1f, input_c0_dispatch, NULL }, { 0x20, 0x7e, input_print, NULL }, { 0x7f, 0x7f, NULL, NULL }, - { 0x80, 0xc1, NULL, NULL }, - { 0xc2, 0xdf, input_utf8_open, &input_state_utf8_one }, - { 0xe0, 0xef, input_utf8_open, &input_state_utf8_two }, - { 0xf0, 0xf4, input_utf8_open, &input_state_utf8_three }, - { 0xf5, 0xff, NULL, NULL }, + { 0x80, 0xff, input_top_bit_set, NULL }, { -1, -1, NULL, NULL } }; @@ -503,7 +496,7 @@ static const struct input_transition input_state_esc_enter_table[] = { { -1, -1, NULL, NULL } }; -/* esc_interm state table. */ +/* esc_intermediate state table. */ static const struct input_transition input_state_esc_intermediate_table[] = { INPUT_STATE_ANYWHERE, @@ -526,7 +519,7 @@ static const struct input_transition input_state_csi_enter_table[] = { { 0x1c, 0x1f, input_c0_dispatch, NULL }, { 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate }, { 0x30, 0x39, input_parameter, &input_state_csi_parameter }, - { 0x3a, 0x3a, NULL, &input_state_csi_ignore }, + { 0x3a, 0x3a, input_parameter, &input_state_csi_parameter }, { 0x3b, 0x3b, input_parameter, &input_state_csi_parameter }, { 0x3c, 0x3f, input_intermediate, &input_state_csi_parameter }, { 0x40, 0x7e, input_csi_dispatch, &input_state_ground }, @@ -544,7 +537,7 @@ static const struct input_transition input_state_csi_parameter_table[] = { { 0x1c, 0x1f, input_c0_dispatch, NULL }, { 0x20, 0x2f, input_intermediate, &input_state_csi_intermediate }, { 0x30, 0x39, input_parameter, NULL }, - { 0x3a, 0x3a, NULL, &input_state_csi_ignore }, + { 0x3a, 0x3a, input_parameter, NULL }, { 0x3b, 0x3b, input_parameter, NULL }, { 0x3c, 0x3f, NULL, &input_state_csi_ignore }, { 0x40, 0x7e, input_csi_dispatch, &input_state_ground }, @@ -618,7 +611,7 @@ static const struct input_transition input_state_dcs_parameter_table[] = { { -1, -1, NULL, NULL } }; -/* dcs_interm state table. */ +/* dcs_intermediate state table. */ static const struct input_transition input_state_dcs_intermediate_table[] = { INPUT_STATE_ANYWHERE, @@ -671,12 +664,12 @@ static const struct input_transition input_state_dcs_ignore_table[] = { static const struct input_transition input_state_osc_string_table[] = { INPUT_STATE_ANYWHERE, - { 0x00, 0x06, NULL, NULL }, - { 0x07, 0x07, NULL, &input_state_ground }, - { 0x08, 0x17, NULL, NULL }, - { 0x19, 0x19, NULL, NULL }, - { 0x1c, 0x1f, NULL, NULL }, - { 0x20, 0xff, input_input, NULL }, + { 0x00, 0x06, NULL, NULL }, + { 0x07, 0x07, input_end_bel, &input_state_ground }, + { 0x08, 0x17, NULL, NULL }, + { 0x19, 0x19, NULL, NULL }, + { 0x1c, 0x1f, NULL, NULL }, + { 0x20, 0xff, input_input, NULL }, { -1, -1, NULL, NULL } }; @@ -717,39 +710,6 @@ static const struct input_transition input_state_consume_st_table[] = { { -1, -1, NULL, NULL } }; -/* utf8_three state table. */ -static const struct input_transition input_state_utf8_three_table[] = { - /* No INPUT_STATE_ANYWHERE */ - - { 0x00, 0x7f, NULL, &input_state_ground }, - { 0x80, 0xbf, input_utf8_add, &input_state_utf8_two }, - { 0xc0, 0xff, NULL, &input_state_ground }, - - { -1, -1, NULL, NULL } -}; - -/* utf8_two state table. */ -static const struct input_transition input_state_utf8_two_table[] = { - /* No INPUT_STATE_ANYWHERE */ - - { 0x00, 0x7f, NULL, &input_state_ground }, - { 0x80, 0xbf, input_utf8_add, &input_state_utf8_one }, - { 0xc0, 0xff, NULL, &input_state_ground }, - - { -1, -1, NULL, NULL } -}; - -/* utf8_one state table. */ -static const struct input_transition input_state_utf8_one_table[] = { - /* No INPUT_STATE_ANYWHERE */ - - { 0x00, 0x7f, NULL, &input_state_ground }, - { 0x80, 0xbf, input_utf8_close, &input_state_ground }, - { 0xc0, 0xff, NULL, &input_state_ground }, - - { -1, -1, NULL, NULL } -}; - /* Input table compare. */ static int input_table_compare(const void *key, const void *value) @@ -780,7 +740,7 @@ input_timer_callback(__unused int fd, __unused short events, void *arg) static void input_start_timer(struct input_ctx *ictx) { - struct timeval tv = { .tv_usec = 100000 }; + struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; event_del(&ictx->timer); event_add(&ictx->timer, &tv); @@ -799,6 +759,32 @@ input_reset_cell(struct input_ctx *ictx) ictx->old_cy = 0; } +/* Save screen state. */ +static void +input_save_state(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + struct screen *s = sctx->s; + + memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell); + ictx->old_cx = s->cx; + ictx->old_cy = s->cy; + ictx->old_mode = s->mode; +} + +static void +input_restore_state(struct input_ctx *ictx) +{ + struct screen_write_ctx *sctx = &ictx->ctx; + + memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell); + if (ictx->old_mode & MODE_ORIGIN) + screen_write_mode_set(sctx, MODE_ORIGIN); + else + screen_write_mode_clear(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy, 0); +} + /* Initialise input parser. */ void input_init(struct window_pane *wp) @@ -811,6 +797,8 @@ input_init(struct window_pane *wp) ictx->input_buf = xmalloc(INPUT_BUF_START); ictx->since_ground = evbuffer_new(); + if (ictx->since_ground == NULL) + fatalx("out of memory"); evtimer_set(&ictx->timer, input_timer_callback, ictx); @@ -822,6 +810,12 @@ void input_free(struct window_pane *wp) { struct input_ctx *ictx = wp->ictx; + u_int i; + + for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) + free(ictx->param_list[i].str); + } event_del(&ictx->timer); @@ -837,16 +831,17 @@ void input_reset(struct window_pane *wp, int clear) { struct input_ctx *ictx = wp->ictx; + struct screen_write_ctx *sctx = &ictx->ctx; input_reset_cell(ictx); if (clear) { - if (wp->mode == NULL) - screen_write_start(&ictx->ctx, wp, &wp->base); + if (TAILQ_EMPTY(&wp->modes)) + screen_write_start(sctx, wp, &wp->base); else - screen_write_start(&ictx->ctx, NULL, &wp->base); - screen_write_reset(&ictx->ctx); - screen_write_stop(&ictx->ctx); + screen_write_start(sctx, NULL, &wp->base); + screen_write_reset(sctx); + screen_write_stop(sctx); } input_clear(ictx); @@ -880,35 +875,40 @@ input_set_state(struct window_pane *wp, const struct input_transition *itr) /* Parse input. */ void input_parse(struct window_pane *wp) +{ + 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; - const struct input_transition *itr; - struct evbuffer *evb = wp->event->input; - u_char *buf; - size_t len, off; + struct screen_write_ctx *sctx = &ictx->ctx; + const struct input_state *state = NULL; + const struct input_transition *itr = NULL; + size_t off = 0; - if (EVBUFFER_LENGTH(evb) == 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 (wp->mode == NULL) - screen_write_start(&ictx->ctx, wp, &wp->base); + if (TAILQ_EMPTY(&wp->modes)) + screen_write_start(sctx, wp, &wp->base); else - screen_write_start(&ictx->ctx, NULL, &wp->base); + screen_write_start(sctx, NULL, &wp->base); ictx->wp = wp; - buf = EVBUFFER_DATA(evb); - len = EVBUFFER_LENGTH(evb); - off = 0; - - notify_input(wp, evb); - log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id, ictx->state->name, len, (int)len, buf); @@ -917,16 +917,23 @@ input_parse(struct window_pane *wp) ictx->ch = buf[off++]; /* Find the transition. */ - itr = ictx->state->transitions; - while (itr->first != -1 && itr->last != -1) { - if (ictx->ch >= itr->first && ictx->ch <= itr->last) - break; - itr++; - } - if (itr->first == -1 || itr->last == -1) { - /* No transition? Eh? */ - fatalx("no transition from state"); + if (ictx->state != state || + itr == NULL || + ictx->ch < itr->first || + ictx->ch > itr->last) { + itr = ictx->state->transitions; + while (itr->first != -1 && itr->last != -1) { + if (ictx->ch >= itr->first && + ictx->ch <= itr->last) + break; + itr++; + } + if (itr->first == -1 || itr->last == -1) { + /* No transition? Eh? */ + fatalx("no transition from state"); + } } + state = ictx->state; /* * Any state except print stops the current collection. This is @@ -936,7 +943,7 @@ input_parse(struct window_pane *wp) * be the minority. */ if (itr->handler != input_print) - screen_write_collect_end(&ictx->ctx); + screen_write_collect_end(sctx); /* * Execute the handler, if any. Don't switch state if it @@ -955,38 +962,58 @@ input_parse(struct window_pane *wp) } /* Close the screen. */ - screen_write_stop(&ictx->ctx); - - evbuffer_drain(evb, len); + screen_write_stop(sctx); } /* Split the parameter list (if any). */ static int input_split(struct input_ctx *ictx) { - const char *errstr; - char *ptr, *out; - int n; + const char *errstr; + char *ptr, *out; + struct input_param *ip; + u_int i; + for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) + free(ictx->param_list[i].str); + } ictx->param_list_len = 0; + if (ictx->param_len == 0) return (0); + ip = &ictx->param_list[0]; ptr = ictx->param_buf; while ((out = strsep(&ptr, ";")) != NULL) { if (*out == '\0') - n = -1; + ip->type = INPUT_MISSING; else { - n = strtonum(out, 0, INT_MAX, &errstr); - if (errstr != NULL) - return (-1); + if (strchr(out, ':') != NULL) { + ip->type = INPUT_STRING; + ip->str = xstrdup(out); + } else { + ip->type = INPUT_NUMBER; + ip->num = strtonum(out, 0, INT_MAX, &errstr); + if (errstr != NULL) + return (-1); + } } - - ictx->param_list[ictx->param_list_len++] = n; + ip = &ictx->param_list[++ictx->param_list_len]; if (ictx->param_list_len == nitems(ictx->param_list)) return (-1); } + for (i = 0; i < ictx->param_list_len; i++) { + ip = &ictx->param_list[i]; + if (ip->type == INPUT_MISSING) + log_debug("parameter %u: missing", i); + else if (ip->type == INPUT_STRING) + log_debug("parameter %u: string %s", i, ip->str); + else if (ip->type == INPUT_NUMBER) + log_debug("parameter %u: number %d", i, ip->num); + } + return (0); } @@ -994,14 +1021,17 @@ input_split(struct input_ctx *ictx) static int input_get(struct input_ctx *ictx, u_int validx, int minval, int defval) { - int retval; + struct input_param *ip; + int retval; if (validx >= ictx->param_list_len) return (defval); - - retval = ictx->param_list[validx]; - if (retval == -1) + ip = &ictx->param_list[validx]; + if (ip->type == INPUT_MISSING) return (defval); + if (ip->type == INPUT_STRING) + return (-1); + retval = ip->num; if (retval < minval) return (minval); return (retval); @@ -1011,8 +1041,8 @@ 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; + va_list ap; + char *reply; va_start(ap, fmt); xvasprintf(&reply, fmt, ap); @@ -1037,6 +1067,8 @@ input_clear(struct input_ctx *ictx) *ictx->input_buf = '\0'; ictx->input_len = 0; + ictx->input_end = INPUT_END_ST; + ictx->flags &= ~INPUT_DISCARD; } @@ -1057,7 +1089,10 @@ input_ground(struct input_ctx *ictx) static int input_print(struct input_ctx *ictx) { - int set; + struct screen_write_ctx *sctx = &ictx->ctx; + int set; + + ictx->utf8started = 0; /* can't be valid UTF-8 */ set = ictx->cell.set == 0 ? ictx->cell.g0set : ictx->cell.g1set; if (set == 1) @@ -1066,7 +1101,7 @@ input_print(struct input_ctx *ictx) ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET; utf8_set(&ictx->cell.cell.data, ictx->ch); - screen_write_collect_add(&ictx->ctx, &ictx->cell.cell); + screen_write_collect_add(sctx, &ictx->cell.cell); ictx->last = ictx->ch; ictx->cell.cell.attr &= ~GRID_ATTR_CHARSET; @@ -1132,6 +1167,8 @@ input_c0_dispatch(struct input_ctx *ictx) struct window_pane *wp = ictx->wp; struct screen *s = sctx->s; + ictx->utf8started = 0; /* can't be valid UTF-8 */ + log_debug("%s: '%c'", __func__, ictx->ch); switch (ictx->ch) { @@ -1159,6 +1196,8 @@ input_c0_dispatch(struct input_ctx *ictx) case '\013': /* VT */ case '\014': /* FF */ screen_write_linefeed(sctx, 0, ictx->cell.cell.bg); + if (s->mode & MODE_CRLF) + screen_write_carriagereturn(sctx); break; case '\015': /* CR */ screen_write_carriagereturn(sctx); @@ -1224,13 +1263,10 @@ input_esc_dispatch(struct input_ctx *ictx) screen_write_mode_clear(sctx, MODE_KKEYPAD); break; case INPUT_ESC_DECSC: - memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell); - ictx->old_cx = s->cx; - ictx->old_cy = s->cy; + input_save_state(ictx); break; case INPUT_ESC_DECRC: - memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell); - screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy); + input_restore_state(ictx); break; case INPUT_ESC_DECALN: screen_write_alignmenttest(sctx); @@ -1264,7 +1300,7 @@ input_csi_dispatch(struct input_ctx *ictx) struct screen *s = sctx->s; struct input_table_entry *entry; int i, n, m; - u_int cx; + u_int cx, bg = ictx->cell.cell.bg; if (ictx->flags & INPUT_DISCARD) return (0); @@ -1289,6 +1325,8 @@ input_csi_dispatch(struct input_ctx *ictx) if (cx > screen_size_x(s) - 1) cx = screen_size_x(s) - 1; n = input_get(ictx, 0, 1, 1); + if (n == -1) + break; while (cx > 0 && n-- > 0) { do cx--; @@ -1297,35 +1335,52 @@ input_csi_dispatch(struct input_ctx *ictx) s->cx = cx; break; case INPUT_CSI_CUB: - screen_write_cursorleft(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorleft(sctx, n); break; case INPUT_CSI_CUD: - screen_write_cursordown(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursordown(sctx, n); break; case INPUT_CSI_CUF: - screen_write_cursorright(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorright(sctx, n); break; case INPUT_CSI_CUP: n = input_get(ictx, 0, 1, 1); m = input_get(ictx, 1, 1, 1); - screen_write_cursormove(sctx, m - 1, n - 1); + if (n != -1 && m != -1) + screen_write_cursormove(sctx, m - 1, n - 1, 1); break; case INPUT_CSI_WINOPS: input_csi_dispatch_winops(ictx); break; case INPUT_CSI_CUU: - screen_write_cursorup(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_cursorup(sctx, n); break; case INPUT_CSI_CNL: - screen_write_carriagereturn(sctx); - screen_write_cursordown(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) { + screen_write_carriagereturn(sctx); + screen_write_cursordown(sctx, n); + } break; case INPUT_CSI_CPL: - screen_write_carriagereturn(sctx); - screen_write_cursorup(sctx, input_get(ictx, 0, 1, 1)); + n = input_get(ictx, 0, 1, 1); + if (n != -1) { + screen_write_carriagereturn(sctx); + screen_write_cursorup(sctx, n); + } break; case INPUT_CSI_DA: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 0: input_reply(ictx, "\033[?1;2c"); break; @@ -1336,6 +1391,8 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_DA_TWO: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 0: input_reply(ictx, "\033[>84;0;0c"); break; @@ -1345,24 +1402,30 @@ input_csi_dispatch(struct input_ctx *ictx) } break; case INPUT_CSI_ECH: - screen_write_clearcharacter(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_clearcharacter(sctx, n, bg); break; case INPUT_CSI_DCH: - screen_write_deletecharacter(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_deletecharacter(sctx, n, bg); break; case INPUT_CSI_DECSTBM: n = input_get(ictx, 0, 1, 1); m = input_get(ictx, 1, 1, screen_size_y(s)); - screen_write_scrollregion(sctx, n - 1, m - 1); + if (n != -1 && m != -1) + screen_write_scrollregion(sctx, n - 1, m - 1); break; case INPUT_CSI_DL: - screen_write_deleteline(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_deleteline(sctx, n, bg); break; case INPUT_CSI_DSR: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 5: input_reply(ictx, "\033[0n"); break; @@ -1376,24 +1439,24 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_ED: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 0: - screen_write_clearendofscreen(sctx, ictx->cell.cell.bg); + screen_write_clearendofscreen(sctx, bg); break; case 1: - screen_write_clearstartofscreen(sctx, ictx->cell.cell.bg); + screen_write_clearstartofscreen(sctx, bg); break; case 2: - screen_write_clearscreen(sctx, ictx->cell.cell.bg); + screen_write_clearscreen(sctx, bg); break; case 3: - switch (input_get(ictx, 1, 0, 0)) { - case 0: + if (input_get(ictx, 1, 0, 0) == 0) { /* * Linux console extension to clear history * (for example before locking the screen). */ screen_write_clearhistory(sctx); - break; } break; default: @@ -1403,14 +1466,16 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_EL: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 0: - screen_write_clearendofline(sctx, ictx->cell.cell.bg); + screen_write_clearendofline(sctx, bg); break; case 1: - screen_write_clearstartofline(sctx, ictx->cell.cell.bg); + screen_write_clearstartofline(sctx, bg); break; case 2: - screen_write_clearline(sctx, ictx->cell.cell.bg); + screen_write_clearline(sctx, bg); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1419,28 +1484,33 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_HPA: n = input_get(ictx, 0, 1, 1); - screen_write_cursormove(sctx, n - 1, s->cy); + if (n != -1) + screen_write_cursormove(sctx, n - 1, -1, 1); break; case INPUT_CSI_ICH: - screen_write_insertcharacter(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_insertcharacter(sctx, n, bg); break; case INPUT_CSI_IL: - screen_write_insertline(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_insertline(sctx, n, bg); break; case INPUT_CSI_REP: + n = input_get(ictx, 0, 1, 1); + if (n == -1) + break; + if (ictx->last == -1) break; ictx->ch = ictx->last; - n = input_get(ictx, 0, 1, 1); for (i = 0; i < n; i++) input_print(ictx); break; case INPUT_CSI_RCP: - memcpy(&ictx->cell, &ictx->old_cell, sizeof ictx->cell); - screen_write_cursormove(sctx, ictx->old_cx, ictx->old_cy); + input_restore_state(ictx); break; case INPUT_CSI_RM: input_csi_dispatch_rm(ictx); @@ -1449,9 +1519,7 @@ input_csi_dispatch(struct input_ctx *ictx) input_csi_dispatch_rm_private(ictx); break; case INPUT_CSI_SCP: - memcpy(&ictx->old_cell, &ictx->cell, sizeof ictx->old_cell); - ictx->old_cx = s->cx; - ictx->old_cy = s->cy; + input_save_state(ictx); break; case INPUT_CSI_SGR: input_csi_dispatch_sgr(ictx); @@ -1463,11 +1531,19 @@ input_csi_dispatch(struct input_ctx *ictx) input_csi_dispatch_sm_private(ictx); break; case INPUT_CSI_SU: - screen_write_scrollup(sctx, input_get(ictx, 0, 1, 1), - ictx->cell.cell.bg); + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_scrollup(sctx, n, bg); + break; + case INPUT_CSI_SD: + n = input_get(ictx, 0, 1, 1); + if (n != -1) + screen_write_scrolldown(sctx, n, bg); break; case INPUT_CSI_TBC: switch (input_get(ictx, 0, 0, 0)) { + case -1: + break; case 0: if (s->cx < screen_size_x(s)) bit_clear(s->tabs, s->cx); @@ -1482,11 +1558,13 @@ input_csi_dispatch(struct input_ctx *ictx) break; case INPUT_CSI_VPA: n = input_get(ictx, 0, 1, 1); - screen_write_cursormove(sctx, s->cx, n - 1); + if (n != -1) + screen_write_cursormove(sctx, -1, n - 1, 1); break; case INPUT_CSI_DECSCUSR: n = input_get(ictx, 0, 0, 0); - screen_set_cursor_style(s, n); + if (n != -1) + screen_set_cursor_style(s, n); break; } @@ -1498,15 +1576,18 @@ input_csi_dispatch(struct input_ctx *ictx) static void input_csi_dispatch_rm(struct input_ctx *ictx) { - u_int i; + struct screen_write_ctx *sctx = &ictx->ctx; + u_int i; for (i = 0; i < ictx->param_list_len; i++) { switch (input_get(ictx, i, 0, -1)) { + case -1: + break; case 4: /* IRM */ - screen_write_mode_clear(&ictx->ctx, MODE_INSERT); + screen_write_mode_clear(sctx, MODE_INSERT); break; case 34: - screen_write_mode_set(&ictx->ctx, MODE_BLINKING); + screen_write_mode_set(sctx, MODE_BLINKING); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1519,42 +1600,48 @@ input_csi_dispatch_rm(struct input_ctx *ictx) static void input_csi_dispatch_rm_private(struct input_ctx *ictx) { + struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; u_int i; for (i = 0; i < ictx->param_list_len; i++) { switch (input_get(ictx, i, 0, -1)) { + case -1: + break; case 1: /* DECCKM */ - screen_write_mode_clear(&ictx->ctx, MODE_KCURSOR); + screen_write_mode_clear(sctx, MODE_KCURSOR); break; case 3: /* DECCOLM */ - screen_write_cursormove(&ictx->ctx, 0, 0); - screen_write_clearscreen(&ictx->ctx, - ictx->cell.cell.bg); + screen_write_cursormove(sctx, 0, 0, 1); + screen_write_clearscreen(sctx, ictx->cell.cell.bg); + break; + case 6: /* DECOM */ + screen_write_mode_clear(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, 0, 0, 1); break; case 7: /* DECAWM */ - screen_write_mode_clear(&ictx->ctx, MODE_WRAP); + screen_write_mode_clear(sctx, MODE_WRAP); break; case 12: - screen_write_mode_clear(&ictx->ctx, MODE_BLINKING); + screen_write_mode_clear(sctx, MODE_BLINKING); break; case 25: /* TCEM */ - screen_write_mode_clear(&ictx->ctx, MODE_CURSOR); + screen_write_mode_clear(sctx, MODE_CURSOR); break; case 1000: case 1001: case 1002: case 1003: - screen_write_mode_clear(&ictx->ctx, ALL_MOUSE_MODES); + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); break; case 1004: - screen_write_mode_clear(&ictx->ctx, MODE_FOCUSON); + screen_write_mode_clear(sctx, MODE_FOCUSON); break; case 1005: - screen_write_mode_clear(&ictx->ctx, MODE_MOUSE_UTF8); + screen_write_mode_clear(sctx, MODE_MOUSE_UTF8); break; case 1006: - screen_write_mode_clear(&ictx->ctx, MODE_MOUSE_SGR); + screen_write_mode_clear(sctx, MODE_MOUSE_SGR); break; case 47: case 1047: @@ -1564,7 +1651,7 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) window_pane_alternate_off(wp, &ictx->cell.cell, 1); break; case 2004: - screen_write_mode_clear(&ictx->ctx, MODE_BRACKETPASTE); + screen_write_mode_clear(sctx, MODE_BRACKETPASTE); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1577,15 +1664,18 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx) static void input_csi_dispatch_sm(struct input_ctx *ictx) { - u_int i; + struct screen_write_ctx *sctx = &ictx->ctx; + u_int i; for (i = 0; i < ictx->param_list_len; i++) { switch (input_get(ictx, i, 0, -1)) { + case -1: + break; case 4: /* IRM */ - screen_write_mode_set(&ictx->ctx, MODE_INSERT); + screen_write_mode_set(sctx, MODE_INSERT); break; case 34: - screen_write_mode_clear(&ictx->ctx, MODE_BLINKING); + screen_write_mode_clear(sctx, MODE_BLINKING); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1598,51 +1688,57 @@ input_csi_dispatch_sm(struct input_ctx *ictx) static void input_csi_dispatch_sm_private(struct input_ctx *ictx) { + struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; u_int i; for (i = 0; i < ictx->param_list_len; i++) { switch (input_get(ictx, i, 0, -1)) { + case -1: + break; case 1: /* DECCKM */ - screen_write_mode_set(&ictx->ctx, MODE_KCURSOR); + screen_write_mode_set(sctx, MODE_KCURSOR); break; case 3: /* DECCOLM */ - screen_write_cursormove(&ictx->ctx, 0, 0); - screen_write_clearscreen(&ictx->ctx, - ictx->cell.cell.bg); + screen_write_cursormove(sctx, 0, 0, 1); + screen_write_clearscreen(sctx, ictx->cell.cell.bg); + break; + case 6: /* DECOM */ + screen_write_mode_set(sctx, MODE_ORIGIN); + screen_write_cursormove(sctx, 0, 0, 1); break; case 7: /* DECAWM */ - screen_write_mode_set(&ictx->ctx, MODE_WRAP); + screen_write_mode_set(sctx, MODE_WRAP); break; case 12: - screen_write_mode_set(&ictx->ctx, MODE_BLINKING); + screen_write_mode_set(sctx, MODE_BLINKING); break; case 25: /* TCEM */ - screen_write_mode_set(&ictx->ctx, MODE_CURSOR); + screen_write_mode_set(sctx, MODE_CURSOR); break; case 1000: - screen_write_mode_clear(&ictx->ctx, ALL_MOUSE_MODES); - screen_write_mode_set(&ictx->ctx, MODE_MOUSE_STANDARD); + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_STANDARD); break; case 1002: - screen_write_mode_clear(&ictx->ctx, ALL_MOUSE_MODES); - screen_write_mode_set(&ictx->ctx, MODE_MOUSE_BUTTON); + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_BUTTON); break; case 1003: - screen_write_mode_clear(&ictx->ctx, ALL_MOUSE_MODES); - screen_write_mode_set(&ictx->ctx, MODE_MOUSE_ALL); + screen_write_mode_clear(sctx, ALL_MOUSE_MODES); + screen_write_mode_set(sctx, MODE_MOUSE_ALL); break; case 1004: - if (ictx->ctx.s->mode & MODE_FOCUSON) + if (sctx->s->mode & MODE_FOCUSON) break; - screen_write_mode_set(&ictx->ctx, MODE_FOCUSON); + screen_write_mode_set(sctx, MODE_FOCUSON); wp->flags |= PANE_FOCUSPUSH; /* force update */ break; case 1005: - screen_write_mode_set(&ictx->ctx, MODE_MOUSE_UTF8); + screen_write_mode_set(sctx, MODE_MOUSE_UTF8); break; case 1006: - screen_write_mode_set(&ictx->ctx, MODE_MOUSE_SGR); + screen_write_mode_set(sctx, MODE_MOUSE_SGR); break; case 47: case 1047: @@ -1652,7 +1748,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) window_pane_alternate_on(wp, &ictx->cell.cell, 1); break; case 2004: - screen_write_mode_set(&ictx->ctx, MODE_BRACKETPASTE); + screen_write_mode_set(sctx, MODE_BRACKETPASTE); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); @@ -1665,6 +1761,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx) static void input_csi_dispatch_winops(struct input_ctx *ictx) { + struct screen_write_ctx *sctx = &ictx->ctx; struct window_pane *wp = ictx->wp; int n, m; @@ -1693,11 +1790,32 @@ input_csi_dispatch_winops(struct input_ctx *ictx) /* FALLTHROUGH */ case 9: case 10: + m++; + if (input_get(ictx, m, 0, -1) == -1) + return; + break; case 22: + m++; + switch (input_get(ictx, m, 0, -1)) { + case -1: + return; + case 0: + case 2: + screen_push_title(sctx->s); + break; + } + break; case 23: m++; - if (input_get(ictx, m, 0, -1) == -1) + switch (input_get(ictx, m, 0, -1)) { + case -1: return; + case 0: + case 2: + screen_pop_title(sctx->s); + server_status_window(ictx->wp->window); + break; + } break; case 18: input_reply(ictx, "\033[8;%u;%ut", wp->sy, wp->sx); @@ -1710,16 +1828,13 @@ input_csi_dispatch_winops(struct input_ctx *ictx) } } -/* Handle CSI SGR for 256 colours. */ -static void -input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i) +/* Helper for 256 colour SGR. */ +static int +input_csi_dispatch_sgr_256_do(struct input_ctx *ictx, int fgbg, int c) { struct grid_cell *gc = &ictx->cell.cell; - int c; - (*i)++; - c = input_get(ictx, *i, 0, -1); - if (c == -1) { + if (c == -1 || c > 255) { if (fgbg == 38) gc->fg = 8; else if (fgbg == 48) @@ -1729,33 +1844,140 @@ input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i) gc->fg = c | COLOUR_FLAG_256; else if (fgbg == 48) gc->bg = c | COLOUR_FLAG_256; + else if (fgbg == 58) + gc->us = c | COLOUR_FLAG_256; } + return (1); } -/* Handle CSI SGR for RGB colours. */ +/* Handle CSI SGR for 256 colours. */ static void -input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i) +input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i) +{ + int c; + + c = input_get(ictx, (*i) + 1, 0, -1); + if (input_csi_dispatch_sgr_256_do(ictx, fgbg, c)) + (*i)++; +} + +/* Helper for RGB colour SGR. */ +static int +input_csi_dispatch_sgr_rgb_do(struct input_ctx *ictx, int fgbg, int r, int g, + int b) { struct grid_cell *gc = &ictx->cell.cell; - int r, g, b; - (*i)++; - r = input_get(ictx, *i, 0, -1); if (r == -1 || r > 255) - return; - (*i)++; - g = input_get(ictx, *i, 0, -1); + return (0); if (g == -1 || g > 255) - return; - (*i)++; - b = input_get(ictx, *i, 0, -1); + return (0); if (b == -1 || b > 255) - return; + return (0); if (fgbg == 38) gc->fg = colour_join_rgb(r, g, b); else if (fgbg == 48) gc->bg = colour_join_rgb(r, g, b); + else if (fgbg == 58) + gc->us = colour_join_rgb(r, g, b); + return (1); +} + +/* Handle CSI SGR for RGB colours. */ +static void +input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i) +{ + int r, g, b; + + r = input_get(ictx, (*i) + 1, 0, -1); + g = input_get(ictx, (*i) + 2, 0, -1); + b = input_get(ictx, (*i) + 3, 0, -1); + if (input_csi_dispatch_sgr_rgb_do(ictx, fgbg, r, g, b)) + (*i) += 3; +} + +/* Handle CSI SGR with a ISO parameter. */ +static void +input_csi_dispatch_sgr_colon(struct input_ctx *ictx, u_int i) +{ + struct grid_cell *gc = &ictx->cell.cell; + char *s = ictx->param_list[i].str, *copy, *ptr, *out; + int p[8]; + u_int n; + const char *errstr; + + for (n = 0; n < nitems(p); n++) + p[n] = -1; + n = 0; + + ptr = copy = xstrdup(s); + while ((out = strsep(&ptr, ":")) != NULL) { + if (*out != '\0') { + p[n++] = strtonum(out, 0, INT_MAX, &errstr); + if (errstr != NULL || n == nitems(p)) { + free(copy); + return; + } + } else + n++; + log_debug("%s: %u = %d", __func__, n - 1, p[n - 1]); + } + free(copy); + + if (n == 0) + return; + if (p[0] == 4) { + if (n != 2) + return; + switch (p[1]) { + case 0: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + break; + case 1: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE; + break; + case 2: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_2; + break; + case 3: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_3; + break; + case 4: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_4; + break; + case 5: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; + gc->attr |= GRID_ATTR_UNDERSCORE_5; + break; + } + return; + } + if (n < 2 || (p[0] != 38 && p[0] != 48 && p[0] != 58)) + return; + switch (p[1]) { + case 2: + if (n < 3) + break; + if (n == 5) + i = 2; + else + i = 3; + if (n < i + 3) + break; + input_csi_dispatch_sgr_rgb_do(ictx, p[0], p[i], p[i + 1], + p[i + 2]); + break; + case 5: + if (n < 3) + break; + input_csi_dispatch_sgr_256_do(ictx, p[0], p[2]); + break; + } } /* Handle CSI SGR. */ @@ -1772,9 +1994,15 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) } for (i = 0; i < ictx->param_list_len; i++) { + if (ictx->param_list[i].type == INPUT_STRING) { + input_csi_dispatch_sgr_colon(ictx, i); + continue; + } n = input_get(ictx, i, 0, 0); + if (n == -1) + continue; - if (n == 38 || n == 48) { + if (n == 38 || n == 48 || n == 58) { i++; switch (input_get(ictx, i, 0, -1)) { case 2: @@ -1789,7 +2017,6 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) switch (n) { case 0: - case 10: memcpy(gc, &grid_default_cell, sizeof *gc); break; case 1: @@ -1802,6 +2029,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) gc->attr |= GRID_ATTR_ITALICS; break; case 4: + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; gc->attr |= GRID_ATTR_UNDERSCORE; break; case 5: @@ -1823,7 +2051,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) gc->attr &= ~GRID_ATTR_ITALICS; break; case 24: - gc->attr &= ~GRID_ATTR_UNDERSCORE; + gc->attr &= ~GRID_ATTR_ALL_UNDERSCORE; break; case 25: gc->attr &= ~GRID_ATTR_BLINK; @@ -1863,6 +2091,15 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) case 49: gc->bg = 8; break; + case 53: + gc->attr |= GRID_ATTR_OVERLINE; + break; + case 55: + gc->attr &= ~GRID_ATTR_OVERLINE; + break; + case 59: + gc->us = 0; + break; case 90: case 91: case 92: @@ -1887,6 +2124,17 @@ input_csi_dispatch_sgr(struct input_ctx *ictx) } } +/* End of input with BEL. */ +static int +input_end_bel(struct input_ctx *ictx) +{ + log_debug("%s", __func__); + + ictx->input_end = INPUT_END_BEL; + + return (0); +} + /* DCS string started. */ static void input_enter_dcs(struct input_ctx *ictx) @@ -1902,20 +2150,19 @@ input_enter_dcs(struct input_ctx *ictx) static int input_dcs_dispatch(struct input_ctx *ictx) { - const char prefix[] = "tmux;"; - const u_int prefix_len = (sizeof prefix) - 1; + struct screen_write_ctx *sctx = &ictx->ctx; + u_char *buf = ictx->input_buf; + size_t len = ictx->input_len; + const char prefix[] = "tmux;"; + const u_int prefixlen = (sizeof prefix) - 1; if (ictx->flags & INPUT_DISCARD) return (0); - log_debug("%s: \"%s\"", __func__, ictx->input_buf); + log_debug("%s: \"%s\"", __func__, buf); - /* Check for tmux prefix. */ - if (ictx->input_len >= prefix_len && - strncmp(ictx->input_buf, prefix, prefix_len) == 0) { - screen_write_rawstring(&ictx->ctx, - ictx->input_buf + prefix_len, ictx->input_len - prefix_len); - } + if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) + screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen); return (0); } @@ -1935,15 +2182,17 @@ input_enter_osc(struct input_ctx *ictx) static void input_exit_osc(struct input_ctx *ictx) { - u_char *p = ictx->input_buf; - u_int option; + struct screen_write_ctx *sctx = &ictx->ctx; + u_char *p = ictx->input_buf; + u_int option; if (ictx->flags & INPUT_DISCARD) return; if (ictx->input_len < 1 || *p < '0' || *p > '9') return; - log_debug("%s: \"%s\"", __func__, p); + log_debug("%s: \"%s\" (end %s)", __func__, p, + ictx->input_end == INPUT_END_ST ? "ST" : "BEL"); option = 0; while (*p >= '0' && *p <= '9') @@ -1955,32 +2204,38 @@ input_exit_osc(struct input_ctx *ictx) case 0: case 2: if (utf8_isvalid(p)) { - screen_set_title(ictx->ctx.s, p); + screen_set_title(sctx->s, p); server_status_window(ictx->wp->window); } break; case 4: - input_osc_4(ictx->wp, p); + input_osc_4(ictx, p); + break; + case 7: + if (utf8_isvalid(p)) { + screen_set_path(sctx->s, p); + server_status_window(ictx->wp->window); + } break; case 10: - input_osc_10(ictx->wp, p); + input_osc_10(ictx, p); break; case 11: - input_osc_11(ictx->wp, p); + input_osc_11(ictx, p); break; case 12: if (utf8_isvalid(p) && *p != '?') /* ? is colour request */ - screen_set_cursor_colour(ictx->ctx.s, p); + screen_set_cursor_colour(sctx->s, p); break; case 52: - input_osc_52(ictx->wp, p); + input_osc_52(ictx, p); break; case 104: - input_osc_104(ictx->wp, p); + input_osc_104(ictx, p); break; case 112: if (*p == '\0') /* no arguments allowed */ - screen_set_cursor_colour(ictx->ctx.s, ""); + screen_set_cursor_colour(sctx->s, ""); break; default: log_debug("%s: unknown '%u'", __func__, option); @@ -2003,13 +2258,15 @@ input_enter_apc(struct input_ctx *ictx) static void input_exit_apc(struct input_ctx *ictx) { + struct screen_write_ctx *sctx = &ictx->ctx; + if (ictx->flags & INPUT_DISCARD) return; log_debug("%s: \"%s\"", __func__, ictx->input_buf); if (!utf8_isvalid(ictx->input_buf)) return; - screen_set_title(ictx->ctx.s, ictx->input_buf); + screen_set_title(sctx->s, ictx->input_buf); server_status_window(ictx->wp->window); } @@ -2028,14 +2285,24 @@ input_enter_rename(struct input_ctx *ictx) static void input_exit_rename(struct input_ctx *ictx) { + struct window_pane *wp = ictx->wp; + struct options_entry *oe; + if (ictx->flags & INPUT_DISCARD) return; - if (!options_get_number(ictx->wp->window->options, "allow-rename")) + if (!options_get_number(ictx->wp->options, "allow-rename")) return; log_debug("%s: \"%s\"", __func__, ictx->input_buf); if (!utf8_isvalid(ictx->input_buf)) return; + + if (ictx->input_len == 0) { + oe = options_get(wp->window->options, "automatic-rename"); + if (oe != NULL) + options_remove(oe); + 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); @@ -2043,64 +2310,96 @@ input_exit_rename(struct input_ctx *ictx) /* Open UTF-8 character. */ static int -input_utf8_open(struct input_ctx *ictx) +input_top_bit_set(struct input_ctx *ictx) { + struct screen_write_ctx *sctx = &ictx->ctx; struct utf8_data *ud = &ictx->utf8data; - if (utf8_open(ud, ictx->ch) != UTF8_MORE) - fatalx("UTF-8 open invalid %#x", ictx->ch); - - log_debug("%s %hhu", __func__, ud->size); ictx->last = -1; - return (0); -} + if (!ictx->utf8started) { + if (utf8_open(ud, ictx->ch) != UTF8_MORE) + return (0); + ictx->utf8started = 1; + return (0); + } -/* Append to UTF-8 character. */ -static int -input_utf8_add(struct input_ctx *ictx) -{ - struct utf8_data *ud = &ictx->utf8data; + switch (utf8_append(ud, ictx->ch)) { + case UTF8_MORE: + return (0); + case UTF8_ERROR: + ictx->utf8started = 0; + return (0); + case UTF8_DONE: + break; + } + ictx->utf8started = 0; - if (utf8_append(ud, ictx->ch) != UTF8_MORE) - fatalx("UTF-8 add invalid %#x", ictx->ch); + log_debug("%s %hhu '%*s' (width %hhu)", __func__, ud->size, + (int)ud->size, ud->data, ud->width); - log_debug("%s", __func__); + utf8_copy(&ictx->cell.cell.data, ud); + screen_write_collect_add(sctx, &ictx->cell.cell); return (0); } -/* Close UTF-8 string. */ +/* Parse colour from OSC. */ static int -input_utf8_close(struct input_ctx *ictx) +input_osc_parse_colour(const char *p, u_int *r, u_int *g, u_int *b) { - struct utf8_data *ud = &ictx->utf8data; + u_int rsize, gsize, bsize; + const char *cp, *s = p; - if (utf8_append(ud, ictx->ch) != UTF8_DONE) { - /* - * An error here could be invalid UTF-8 or it could be a - * nonprintable character for which we can't get the - * width. Drop it. - */ + if (sscanf(p, "rgb:%x/%x/%x", r, g, b) != 3) + return (0); + p += 4; + + 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); - } - log_debug("%s %hhu '%*s' (width %hhu)", __func__, ud->size, - (int)ud->size, ud->data, ud->width); + 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); - utf8_copy(&ictx->cell.cell.data, ud); - screen_write_collect_add(&ictx->ctx, &ictx->cell.cell); + 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); - return (0); + log_debug("%s: %s = %02x%02x%02x", __func__, s, *r, *g, *b); + return (1); } /* Handle the OSC 4 sequence for setting (multiple) palette entries. */ static void -input_osc_4(struct window_pane *wp, const char *p) +input_osc_4(struct input_ctx *ictx, const char *p) { - char *copy, *s, *next = NULL; - long idx; - u_int r, g, b; + struct window_pane *wp = ictx->wp; + char *copy, *s, *next = NULL; + long idx; + u_int r, g, b; copy = s = xstrdup(p); while (s != NULL && *s != '\0') { @@ -2111,7 +2410,7 @@ input_osc_4(struct window_pane *wp, const char *p) goto bad; s = strsep(&next, ";"); - if (sscanf(s, "rgb:%2x/%2x/%2x", &r, &g, &b) != 3) { + if (!input_osc_parse_colour(s, &r, &g, &b)) { s = next; continue; } @@ -2128,17 +2427,23 @@ input_osc_4(struct window_pane *wp, const char *p) free(copy); } -/* Handle the OSC 10 sequence for setting background colour. */ +/* Handle the OSC 10 sequence for setting foreground colour. */ static void -input_osc_10(struct window_pane *wp, const char *p) +input_osc_10(struct input_ctx *ictx, const char *p) { - u_int r, g, b; + struct window_pane *wp = ictx->wp; + u_int r, g, b; + char tmp[16]; - if (sscanf(p, "rgb:%2x/%2x/%2x", &r, &g, &b) != 3) - goto bad; + if (strcmp(p, "?") == 0) + return; - wp->colgc.fg = colour_join_rgb(r, g, b); - wp->flags |= PANE_REDRAW; + 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; @@ -2148,15 +2453,21 @@ input_osc_10(struct window_pane *wp, const char *p) /* Handle the OSC 11 sequence for setting background colour. */ static void -input_osc_11(struct window_pane *wp, const char *p) +input_osc_11(struct input_ctx *ictx, const char *p) { - u_int r, g, b; + struct window_pane *wp = ictx->wp; + u_int r, g, b; + char tmp[16]; - if (sscanf(p, "rgb:%2x/%2x/%2x", &r, &g, &b) != 3) - goto bad; + if (strcmp(p, "?") == 0) + return; - wp->colgc.bg = colour_join_rgb(r, g, b); - wp->flags |= PANE_REDRAW; + 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); return; @@ -2166,13 +2477,16 @@ input_osc_11(struct window_pane *wp, const char *p) /* Handle the OSC 52 sequence for setting the clipboard. */ static void -input_osc_52(struct window_pane *wp, const char *p) +input_osc_52(struct input_ctx *ictx, const char *p) { + struct window_pane *wp = ictx->wp; char *end; + const char *buf; size_t len; u_char *out; int outlen, state; struct screen_write_ctx ctx; + struct paste_buffer *pb; state = options_get_number(global_options, "set-clipboard"); if (state != 2) @@ -2183,6 +2497,31 @@ input_osc_52(struct window_pane *wp, const char *p) end++; if (*end == '\0') return; + log_debug("%s: %s", __func__, end); + + if (strcmp(end, "?") == 0) { + if ((pb = paste_get_top(NULL)) != NULL) { + buf = paste_buffer_data(pb, &len); + outlen = 4 * ((len + 2) / 3) + 1; + out = xmalloc(outlen); + if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { + free(out); + return; + } + } else { + outlen = 0; + out = NULL; + } + bufferevent_write(wp->event, "\033]52;;", 6); + if (outlen != 0) + bufferevent_write(wp->event, out, outlen); + if (ictx->input_end == INPUT_END_BEL) + bufferevent_write(wp->event, "\007", 1); + else + bufferevent_write(wp->event, "\033\\", 2); + free(out); + return; + } len = (strlen(end) / 4) * 3; if (len == 0) @@ -2199,15 +2538,16 @@ input_osc_52(struct window_pane *wp, const char *p) screen_write_stop(&ctx); notify_pane("pane-set-clipboard", wp); - paste_add(out, outlen); + paste_add(NULL, out, outlen); } /* Handle the OSC 104 sequence for unsetting (multiple) palette entries. */ static void -input_osc_104(struct window_pane *wp, const char *p) +input_osc_104(struct input_ctx *ictx, const char *p) { - char *copy, *s; - long idx; + struct window_pane *wp = ictx->wp; + char *copy, *s; + long idx; if (*p == '\0') { window_pane_reset_palette(wp); diff --git a/job.c b/job.c index d8f7a7d810..10883e8eae 100644 --- a/job.c +++ b/job.c @@ -36,14 +36,39 @@ static void job_read_callback(struct bufferevent *, void *); static void job_write_callback(struct bufferevent *, void *); static void job_error_callback(struct bufferevent *, short, void *); +/* A single job. */ +struct job { + enum { + JOB_RUNNING, + JOB_DEAD, + JOB_CLOSED + } state; + + int flags; + + char *cmd; + pid_t pid; + int status; + + int fd; + struct bufferevent *event; + + job_update_cb updatecb; + job_complete_cb completecb; + job_free_cb freecb; + void *data; + + LIST_ENTRY(job) entry; +}; + /* All jobs list. */ -struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); +static LIST_HEAD(joblist, job) all_jobs = LIST_HEAD_INITIALIZER(all_jobs); /* Start a job running, if it isn't already. */ 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) + void *data, int flags) { struct job *job; struct environ *env; @@ -54,6 +79,7 @@ job_run(const char *cmd, struct session *s, const char *cwd, if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, out) != 0) return (NULL); + log_debug("%s: cmd=%s, cwd=%s", __func__, cmd, cwd == NULL ? "" : cwd); /* * Do not set TERM during .tmux.conf, it is nice to be able to use @@ -91,7 +117,7 @@ job_run(const char *cmd, struct session *s, const char *cwd, close(out[0]); nullfd = open(_PATH_DEVNULL, O_RDWR, 0); - if (nullfd < 0) + if (nullfd == -1) fatal("open failed"); if (dup2(nullfd, STDERR_FILENO) == -1) fatal("dup2 failed"); @@ -110,6 +136,7 @@ job_run(const char *cmd, struct session *s, const char *cwd, job = xmalloc(sizeof *job); job->state = JOB_RUNNING; + job->flags = flags; job->cmd = xstrdup(cmd); job->pid = pid; @@ -127,6 +154,8 @@ job_run(const char *cmd, struct session *s, const char *cwd, job->event = bufferevent_new(job->fd, job_read_callback, job_write_callback, job_error_callback, job); + if (job->event == NULL) + fatalx("out of memory"); bufferevent_enable(job->event, EV_READ|EV_WRITE); log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); @@ -206,8 +235,16 @@ job_error_callback(__unused struct bufferevent *bufev, __unused short events, /* Job died (waitpid() returned its pid). */ void -job_died(struct job *job, int status) +job_check_died(pid_t pid, int status) { + struct job *job; + + LIST_FOREACH(job, &all_jobs, entry) { + if (pid == job->pid) + break; + } + if (job == NULL) + return; log_debug("job died %p: %s, pid %ld", job, job->cmd, (long) job->pid); job->status = status; @@ -221,3 +258,67 @@ job_died(struct job *job, int status) job->state = JOB_DEAD; } } + +/* Get job status. */ +int +job_get_status(struct job *job) +{ + return (job->status); +} + +/* Get job data. */ +void * +job_get_data(struct job *job) +{ + return (job->data); +} + +/* Get job event. */ +struct bufferevent * +job_get_event(struct job *job) +{ + return (job->event); +} + +/* Kill all jobs. */ +void +job_kill_all(void) +{ + struct job *job; + + LIST_FOREACH(job, &all_jobs, entry) { + if (job->pid != -1) + kill(job->pid, SIGTERM); + } +} + +/* Are any jobs still running? */ +int +job_still_running(void) +{ + struct job *job; + + LIST_FOREACH(job, &all_jobs, entry) { + if ((~job->flags & JOB_NOWAIT) && job->state == JOB_RUNNING) + return (1); + } + return (0); +} + +/* Print job summary. */ +void +job_print_summary(struct cmdq_item *item, int blank) +{ + struct job *job; + u_int n = 0; + + LIST_FOREACH(job, &all_jobs, entry) { + if (blank) { + cmdq_print(item, "%s", ""); + blank = 0; + } + cmdq_print(item, "Job %u: %s [fd=%d, pid=%ld, status=%d]", + n, job->cmd, job->fd, (long)job->pid, job->status); + n++; + } +} diff --git a/key-bindings.c b/key-bindings.c index badbc0f48c..175af8f4b4 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -24,17 +24,64 @@ #include "tmux.h" -RB_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp); -RB_GENERATE(key_tables, key_table, entry, key_table_cmp); -struct key_tables key_tables = RB_INITIALIZER(&key_tables); - -int -key_table_cmp(struct key_table *e1, struct key_table *e2) +#define DEFAULT_CLIENT_MENU \ + " 'Detach' 'd' {detach-client}" \ + " 'Detach & Kill' 'X' {detach-client -P}" \ + " 'Detach Others' 'o' {detach-client -a}" \ + " ''" \ + " 'Lock' 'l' {lock-client}" +#define DEFAULT_SESSION_MENU \ + " 'Next' 'n' {switch-client -n}" \ + " 'Previous' 'p' {switch-client -p}" \ + " ''" \ + " 'Renumber' 'N' {move-window -r}" \ + " '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}" \ + " '#{?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 -- '%%'\"}" \ + " ''" \ + " '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}\"}" \ + " ''" \ + " 'Horizontal Split' 'h' {split-window -h}" \ + " 'Vertical Split' 'v' {split-window -v}" \ + " ''" \ + " 'Swap Up' 'u' {swap-pane -U}" \ + " '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}" + +static int key_bindings_cmp(struct key_binding *, struct key_binding *); +RB_GENERATE_STATIC(key_bindings, key_binding, entry, key_bindings_cmp); +static int key_table_cmp(struct key_table *, struct key_table *); +RB_GENERATE_STATIC(key_tables, key_table, entry, key_table_cmp); +static struct key_tables key_tables = RB_INITIALIZER(&key_tables); + +static int +key_table_cmp(struct key_table *table1, struct key_table *table2) { - return (strcmp(e1->name, e2->name)); + return (strcmp(table1->name, table2->name)); } -int +static int key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) { if (bd1->key < bd2->key) @@ -64,6 +111,18 @@ key_bindings_get_table(const char *name, int create) return (table); } +struct key_table * +key_bindings_first_table(void) +{ + return (RB_MIN(key_tables, &key_tables)); +} + +struct key_table * +key_bindings_next_table(struct key_table *table) +{ + return (RB_NEXT(key_tables, &key_tables, table)); +} + void key_bindings_unref_table(struct key_table *table) { @@ -83,6 +142,27 @@ key_bindings_unref_table(struct key_table *table) free(table); } +struct key_binding * +key_bindings_get(struct key_table *table, key_code key) +{ + struct key_binding bd; + + bd.key = key; + return (RB_FIND(key_bindings, &table->key_bindings, &bd)); +} + +struct key_binding * +key_bindings_first(struct key_table *table) +{ + return (RB_MIN(key_bindings, &table->key_bindings)); +} + +struct key_binding * +key_bindings_next(__unused struct key_table *table, struct key_binding *bd) +{ + return (RB_NEXT(key_bindings, &table->key_bindings, bd)); +} + void key_bindings_add(const char *name, key_code key, int repeat, struct cmd_list *cmdlist) @@ -162,13 +242,13 @@ key_bindings_init(void) "bind ! break-pane", "bind '\"' split-window", "bind '#' list-buffers", - "bind '$' command-prompt -I'#S' \"rename-session '%%'\"", + "bind '$' command-prompt -I'#S' \"rename-session -- '%%'\"", "bind % split-window -h", "bind & confirm-before -p\"kill-window #W? (y/n)\" kill-window", "bind \"'\" command-prompt -pindex \"select-window -t ':%%'\"", "bind ( switch-client -p", "bind ) switch-client -n", - "bind , command-prompt -I'#W' \"rename-window '%%'\"", + "bind , command-prompt -I'#W' \"rename-window -- '%%'\"", "bind - delete-buffer", "bind . command-prompt \"move-window -t '%%'\"", "bind 0 select-window -t:=0", @@ -183,16 +263,17 @@ key_bindings_init(void) "bind 9 select-window -t:=9", "bind : command-prompt", "bind \\; last-pane", - "bind = choose-buffer", + "bind = choose-buffer -Z", "bind ? list-keys", - "bind D choose-client", + "bind D choose-client -Z", + "bind E select-layout -E", "bind L switch-client -l", "bind M select-pane -M", "bind [ copy-mode", "bind ] paste-buffer", "bind c new-window", "bind d detach-client", - "bind f command-prompt \"find-window '%%'\"", + "bind f command-prompt \"find-window -Z -- '%%'\"", "bind i display-message", "bind l last-window", "bind m select-pane -m", @@ -201,13 +282,13 @@ key_bindings_init(void) "bind p previous-window", "bind q display-panes", "bind r refresh-client", - "bind s choose-tree -s", + "bind s choose-tree -Zs", "bind t clock-mode", - "bind w choose-tree -w", + "bind w choose-tree -Zw", "bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane", "bind z resize-pane -Z", - "bind { swap-pane -U", - "bind } swap-pane -D", + "bind '{' swap-pane -U", + "bind '}' swap-pane -D", "bind '~' show-messages", "bind PPage copy-mode -u", "bind -r Up select-pane -U", @@ -222,6 +303,11 @@ key_bindings_init(void) "bind M-n next-window -a", "bind M-o rotate-window -D", "bind M-p previous-window -a", + "bind -r S-Up refresh-client -U 10", + "bind -r S-Down refresh-client -D 10", + "bind -r S-Left refresh-client -L 10", + "bind -r S-Right refresh-client -R 10", + "bind -r DC refresh-client -c", "bind -r M-Up resize-pane -U 5", "bind -r M-Down resize-pane -D 5", "bind -r M-Left resize-pane -L 5", @@ -230,14 +316,22 @@ key_bindings_init(void) "bind -r C-Down resize-pane -D", "bind -r C-Left resize-pane -L", "bind -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 MouseDown3Pane if-shell -Ft= '#{mouse_any_flag}' 'select-pane -t=; send-keys -M' 'select-pane -mt='", - "bind -n WheelUpPane if-shell -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= \"#{pane_in_mode}\" \"send-keys -M\" \"copy-mode -et=\"'", + "bind -n WheelUpPane if -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= \"#{pane_in_mode}\" \"send-keys -M\" \"copy-mode -et=\"'", + + "bind -n MouseDown3StatusRight display-menu -t= -xM -yS -T \"#[align=centre]#{client_name}\" " DEFAULT_CLIENT_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, "bind -Tcopy-mode C-Space send -X begin-selection", "bind -Tcopy-mode C-a send -X start-of-line", @@ -294,18 +388,22 @@ key_bindings_init(void) "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-{' 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", @@ -365,8 +463,9 @@ key_bindings_init(void) "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 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", @@ -384,16 +483,15 @@ key_bindings_init(void) "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_list *cmdlist; - char *cause; + u_int i; + struct cmd_parse_result *pr; for (i = 0; i < nitems(defaults); i++) { - cmdlist = cmd_string_parse(defaults[i], "", i, &cause); - if (cmdlist == NULL) + pr = cmd_parse_from_string(defaults[i], NULL); + if (pr->status != CMD_PARSE_SUCCESS) fatalx("bad default key: %s", defaults[i]); - cmdq_append(NULL, cmdq_get_command(cmdlist, NULL, NULL, 0)); - cmd_list_free(cmdlist); + cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL, NULL, 0)); + cmd_list_free(pr->cmdlist); } } @@ -404,7 +502,7 @@ key_bindings_read_only(struct cmdq_item *item, __unused void *data) return (CMD_RETURN_ERROR); } -void +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) { @@ -412,12 +510,16 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, struct cmdq_item *new_item; int readonly; - readonly = 1; - TAILQ_FOREACH(cmd, &bd->cmdlist->list, qentry) { - if (!(cmd->entry->flags & CMD_READONLY)) - readonly = 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; + } } - if (!readonly && (c->flags & CLIENT_READONLY)) + if (!readonly) new_item = cmdq_get_callback(key_bindings_read_only, NULL); else { new_item = cmdq_get_command(bd->cmdlist, fs, m, 0); @@ -428,4 +530,5 @@ key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item, cmdq_insert_after(item, new_item); else cmdq_append(c, new_item); + return (new_item); } diff --git a/key-string.c b/key-string.c index d630d7789a..0505623e08 100644 --- a/key-string.c +++ b/key-string.c @@ -43,7 +43,9 @@ static const struct { { "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 }, @@ -157,7 +159,7 @@ key_string_get_modifiers(const char **string) key_code key_string_lookup_string(const char *string) { - static const char *other = "!#()+,-.0123456789:;<=>?'\r\t"; + static const char *other = "!#()+,-.0123456789:;<=>'\r\t"; key_code key; u_int u; key_code modifiers; @@ -166,9 +168,11 @@ key_string_lookup_string(const char *string) enum utf8_state more; wchar_t wc; - /* Is this no key? */ + /* Is this no key or any key? */ if (strcasecmp(string, "None") == 0) return (KEYC_NONE); + if (strcasecmp(string, "Any") == 0) + return (KEYC_ANY); /* Is this a hexadecimal value? */ if (string[0] == '0' && string[1] == 'x') { @@ -192,7 +196,7 @@ key_string_lookup_string(const char *string) /* Is this a standard ASCII key? */ if (string[1] == '\0' && (u_char)string[0] <= 127) { key = (u_char)string[0]; - if (key < 32 || key == 127) + if (key < 32) return (KEYC_UNKNOWN); } else { /* Try as a UTF-8 key. */ @@ -222,6 +226,8 @@ key_string_lookup_string(const char *string) key -= 64; else if (key == 32) key = 0; + else if (key == '?') + key = 127; else if (key == 63) key = KEYC_BSPACE; else @@ -251,6 +257,8 @@ key_string_lookup_key(key_code key) /* Handle special keys. */ if (key == KEYC_UNKNOWN) return ("Unknown"); + if (key == KEYC_ANY) + return ("Any"); if (key == KEYC_FOCUS_IN) return ("FocusIn"); if (key == KEYC_FOCUS_OUT) @@ -267,6 +275,10 @@ key_string_lookup_key(key_code key) return ("MouseMovePane"); if (key == KEYC_MOUSEMOVE_STATUS) return ("MouseMoveStatus"); + if (key == KEYC_MOUSEMOVE_STATUS_LEFT) + return ("MouseMoveStatusLeft"); + if (key == KEYC_MOUSEMOVE_STATUS_RIGHT) + return ("MouseMoveStatusRight"); if (key == KEYC_MOUSEMOVE_BORDER) return ("MouseMoveBorder"); if (key >= KEYC_USER && key < KEYC_USER + KEYC_NUSER) { @@ -274,6 +286,12 @@ key_string_lookup_key(key_code key) return (out); } + /* Literal keys are themselves. */ + if (key & KEYC_LITERAL) { + snprintf(out, sizeof out, "%c", (int)(key & 0xff)); + return (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 @@ -313,7 +331,7 @@ key_string_lookup_key(key_code key) } /* Invalid keys are errors. */ - if (key == 127 || key > 255) { + if (key > 255) { snprintf(out, sizeof out, "Invalid#%llx", key); return (out); } @@ -327,7 +345,9 @@ key_string_lookup_key(key_code key) } else if (key >= 32 && key <= 126) { tmp[0] = key; tmp[1] = '\0'; - } else if (key >= 128) + } else if (key == 127) + xsnprintf(tmp, sizeof tmp, "C-?"); + else if (key >= 128) xsnprintf(tmp, sizeof tmp, "\\%llo", key); strlcat(out, tmp, sizeof out); diff --git a/layout-custom.c b/layout-custom.c index 1b8f576efe..097dabe6b0 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -60,7 +60,7 @@ layout_checksum(const char *layout) char * layout_dump(struct layout_cell *root) { - char layout[BUFSIZ], *out; + char layout[8192], *out; *layout = '\0'; if (layout_append(root, layout, sizeof layout) != 0) @@ -116,13 +116,49 @@ layout_append(struct layout_cell *lc, char *buf, size_t len) return (0); } +/* Check layout sizes fit. */ +static int +layout_check(struct layout_cell *lc) +{ + struct layout_cell *lcchild; + u_int n = 0; + + switch (lc->type) { + case LAYOUT_WINDOWPANE: + break; + case LAYOUT_LEFTRIGHT: + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->sy != lc->sy) + return (0); + if (!layout_check(lcchild)) + return (0); + n += lcchild->sx + 1; + } + if (n - 1 != lc->sx) + return (0); + break; + case LAYOUT_TOPBOTTOM: + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (lcchild->sx != lc->sx) + return (0); + if (!layout_check(lcchild)) + return (0); + n += lcchild->sy + 1; + } + if (n - 1 != lc->sy) + return (0); + break; + } + return (1); +} + /* Parse a layout string and arrange window as layout. */ int layout_parse(struct window *w, const char *layout) { struct layout_cell *lc, *lcchild; struct window_pane *wp; - u_int npanes, ncells, sx, sy; + u_int npanes, ncells, sx = 0, sy = 0; u_short csum; /* Check validity. */ @@ -153,9 +189,39 @@ layout_parse(struct window *w, const char *layout) layout_destroy_cell(w, lcchild, &lc); } - /* Save the old window size and resize to the layout size. */ - sx = w->sx; sy = w->sy; - window_resize(w, lc->sx, lc->sy); + /* + * It appears older versions of tmux were able to generate layouts with + * an incorrect top cell size - if it is larger than the top child then + * correct that (if this is still wrong the check code will catch it). + */ + switch (lc->type) { + case LAYOUT_WINDOWPANE: + break; + case LAYOUT_LEFTRIGHT: + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + sy = lcchild->sy + 1; + sx += lcchild->sx + 1; + } + break; + case LAYOUT_TOPBOTTOM: + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + sx = lcchild->sx + 1; + sy += lcchild->sy + 1; + } + break; + } + if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) { + log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy); + layout_print_cell(lc, __func__, 0); + lc->sx = sx - 1; lc->sy = sy - 1; + } + + /* Check the new layout. */ + if (!layout_check(lc)) + return (-1); + + /* Resize to the layout size. */ + window_resize(w, lc->sx, lc->sy, -1, -1); /* Destroy the old layout and swap to the new. */ layout_free_cell(w->layout_root); @@ -166,12 +232,9 @@ layout_parse(struct window *w, const char *layout) layout_assign(&wp, lc); /* Update pane offsets and sizes. */ - layout_fix_offsets(lc); - layout_fix_panes(w, lc->sx, lc->sy); - - /* Then resize the layout back to the original window size. */ - layout_resize(w, sx, sy); - window_resize(w, sx, sy); + layout_fix_offsets(w); + layout_fix_panes(w); + recalculate_sizes(); layout_print_cell(lc, __func__, 0); diff --git a/layout-set.c b/layout-set.c index 7ba18feae0..f712b0592f 100644 --- a/layout-set.c +++ b/layout-set.c @@ -115,11 +115,11 @@ layout_set_previous(struct window *w) } static void -layout_set_even_h(struct window *w) +layout_set_even(struct window *w, enum layout_type type) { struct window_pane *wp; struct layout_cell *lc, *lcnew; - u_int i, n, width, xoff; + u_int n, sx, sy; layout_print_cell(w->layout_root, __func__, 1); @@ -128,109 +128,64 @@ layout_set_even_h(struct window *w) if (n <= 1) return; - /* How many can we fit? */ - width = (w->sx - (n - 1)) / n; - if (width < PANE_MINIMUM) - width = PANE_MINIMUM; - /* Free the old root and construct a new. */ layout_free(w); lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, w->sx, w->sy, 0, 0); - layout_make_node(lc, LAYOUT_LEFTRIGHT); + if (type == LAYOUT_LEFTRIGHT) { + sx = (n * (PANE_MINIMUM + 1)) - 1; + if (sx < w->sx) + sx = w->sx; + sy = w->sy; + } else { + sy = (n * (PANE_MINIMUM + 1)) - 1; + if (sy < w->sy) + sy = w->sy; + sx = w->sx; + } + layout_set_size(lc, sx, sy, 0, 0); + layout_make_node(lc, type); /* Build new leaf cells. */ - i = xoff = 0; TAILQ_FOREACH(wp, &w->panes, entry) { - /* Create child cell. */ lcnew = layout_create_cell(lc); - layout_set_size(lcnew, width, w->sy, xoff, 0); layout_make_leaf(lcnew, wp); + lcnew->sx = w->sx; + lcnew->sy = w->sy; TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); - - i++; - xoff += width + 1; } - /* Allocate any remaining space. */ - if (w->sx > xoff - 1) { - lc = TAILQ_LAST(&lc->cells, layout_cells); - layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, - w->sx - (xoff - 1)); - } + /* Spread out cells. */ + layout_spread_cell(w, lc); /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, w->sx, w->sy); + layout_fix_offsets(w); + layout_fix_panes(w); layout_print_cell(w->layout_root, __func__, 1); + window_resize(w, lc->sx, lc->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } static void -layout_set_even_v(struct window *w) +layout_set_even_h(struct window *w) { - struct window_pane *wp; - struct layout_cell *lc, *lcnew; - u_int i, n, height, yoff; - - layout_print_cell(w->layout_root, __func__, 1); - - /* Get number of panes. */ - n = window_count_panes(w); - if (n <= 1) - return; - - /* How many can we fit? */ - height = (w->sy - (n - 1)) / n; - if (height < PANE_MINIMUM) - height = PANE_MINIMUM; - - /* Free the old root and construct a new. */ - layout_free(w); - lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, w->sx, w->sy, 0, 0); - layout_make_node(lc, LAYOUT_TOPBOTTOM); - - /* Build new leaf cells. */ - i = yoff = 0; - TAILQ_FOREACH(wp, &w->panes, entry) { - /* Create child cell. */ - lcnew = layout_create_cell(lc); - layout_set_size(lcnew, w->sx, height, 0, yoff); - layout_make_leaf(lcnew, wp); - TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); - - i++; - yoff += height + 1; - } - - /* Allocate any remaining space. */ - if (w->sy > yoff - 1) { - lc = TAILQ_LAST(&lc->cells, layout_cells); - layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, - w->sy - (yoff - 1)); - } - - /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, w->sx, w->sy); - - layout_print_cell(w->layout_root, __func__, 1); + layout_set_even(w, LAYOUT_LEFTRIGHT); +} - notify_window("window-layout-changed", w); - server_redraw_window(w); +static void +layout_set_even_v(struct window *w) +{ + layout_set_even(w, LAYOUT_TOPBOTTOM); } static void layout_set_main_h(struct window *w) { struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lcrow, *lcchild; - u_int n, mainheight, otherheight, width, height; - u_int used, i, j, columns, rows, totalrows; + struct layout_cell *lc, *lcmain, *lcother, *lcchild; + u_int n, mainh, otherh, sx, sy; layout_print_cell(w->layout_root, __func__, 1); @@ -240,110 +195,74 @@ layout_set_main_h(struct window *w) return; n--; /* take off main pane */ - /* How many rows and columns will be needed, not counting main? */ - columns = (w->sx + 1) / (PANE_MINIMUM + 1); /* maximum columns */ - if (columns == 0) - columns = 1; - rows = 1 + (n - 1) / columns; - columns = 1 + (n - 1) / rows; - width = (w->sx - (n - 1)) / columns; - - /* Get the main pane height and add one for separator line. */ - mainheight = options_get_number(w->options, "main-pane-height") + 1; - - /* Get the optional other pane height and add one for separator line. */ - otherheight = options_get_number(w->options, "other-pane-height") + 1; - - /* - * If an other pane height was specified, honour it so long as it - * doesn't shrink the main height to less than the main-pane-height - */ - if (otherheight > 1 && w->sy - otherheight > mainheight) - mainheight = w->sy - otherheight; - if (mainheight < PANE_MINIMUM + 1) - mainheight = PANE_MINIMUM + 1; - - /* Try and make everything fit. */ - totalrows = rows * (PANE_MINIMUM + 1) - 1; - if (mainheight + totalrows > w->sy) { - if (totalrows + PANE_MINIMUM + 1 > w->sy) - mainheight = PANE_MINIMUM + 2; + /* 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"); + if (mainh + PANE_MINIMUM >= sy) { + if (sy <= PANE_MINIMUM + PANE_MINIMUM) + mainh = PANE_MINIMUM; else - mainheight = w->sy - totalrows; - height = PANE_MINIMUM; - } else - height = (w->sy - mainheight - (rows - 1)) / rows; + mainh = sy - PANE_MINIMUM; + otherh = PANE_MINIMUM; + } else { + otherh = options_get_number(w->options, "other-pane-height"); + if (otherh == 0) + otherh = sy - mainh; + else if (otherh > sy || sy - otherh < mainh) + otherh = sy - mainh; + else + mainh = sy - otherh; + } + + /* Work out what width is needed. */ + sx = (n * (PANE_MINIMUM + 1)) - 1; + if (sx < w->sx) + sx = w->sx; /* Free old tree and create a new root. */ layout_free(w); lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, w->sx, mainheight + rows * (height + 1) - 1, 0, 0); + layout_set_size(lc, sx, mainh + otherh + 1, 0, 0); layout_make_node(lc, LAYOUT_TOPBOTTOM); /* Create the main pane. */ lcmain = layout_create_cell(lc); - layout_set_size(lcmain, w->sx, mainheight - 1, 0, 0); + layout_set_size(lcmain, sx, mainh, 0, 0); layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); - /* Create a grid of the remaining cells. */ - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); - for (j = 0; j < rows; j++) { - /* If this is the last cell, all done. */ - if (wp == NULL) - break; - - /* Create the new row. */ - lcrow = layout_create_cell(lc); - layout_set_size(lcrow, w->sx, height, 0, 0); - TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry); - - /* If only one column, just use the row directly. */ - if (columns == 1) { - layout_make_leaf(lcrow, wp); - wp = TAILQ_NEXT(wp, entry); - continue; - } - - /* Add in the columns. */ - layout_make_node(lcrow, LAYOUT_LEFTRIGHT); - for (i = 0; i < columns; i++) { - /* Create and add a pane cell. */ - lcchild = layout_create_cell(lcrow); - layout_set_size(lcchild, width, height, 0, 0); + /* Create the other pane. */ + lcother = layout_create_cell(lc); + layout_set_size(lcother, sx, otherh, 0, 0); + if (n == 1) { + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + layout_make_leaf(lcother, wp); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + } else { + layout_make_node(lcother, LAYOUT_LEFTRIGHT); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + + /* Add the remaining panes as children. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) + continue; + lcchild = layout_create_cell(lcother); + layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0); layout_make_leaf(lcchild, wp); - TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry); - - /* Move to the next cell. */ - if ((wp = TAILQ_NEXT(wp, entry)) == NULL) - break; + TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); } - - /* Adjust the row to fit the full width if necessary. */ - if (i == columns) - i--; - used = ((i + 1) * (width + 1)) - 1; - if (w->sx <= used) - continue; - lcchild = TAILQ_LAST(&lcrow->cells, layout_cells); - layout_resize_adjust(w, lcchild, LAYOUT_LEFTRIGHT, - w->sx - used); - } - - /* Adjust the last row height to fit if necessary. */ - used = mainheight + (rows * height) + rows - 1; - if (w->sy > used) { - lcrow = TAILQ_LAST(&lc->cells, layout_cells); - layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM, - w->sy - used); + layout_spread_cell(w, lcother); } /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, w->sx, w->sy); + layout_fix_offsets(w); + layout_fix_panes(w); layout_print_cell(w->layout_root, __func__, 1); + window_resize(w, lc->sx, lc->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -352,9 +271,8 @@ static void layout_set_main_v(struct window *w) { struct window_pane *wp; - struct layout_cell *lc, *lcmain, *lccolumn, *lcchild; - u_int n, mainwidth, otherwidth, width, height; - u_int used, i, j, columns, rows, totalcolumns; + struct layout_cell *lc, *lcmain, *lcother, *lcchild; + u_int n, mainw, otherw, sx, sy; layout_print_cell(w->layout_root, __func__, 1); @@ -364,110 +282,74 @@ layout_set_main_v(struct window *w) return; n--; /* take off main pane */ - /* How many rows and columns will be needed, not counting main? */ - rows = (w->sy + 1) / (PANE_MINIMUM + 1); /* maximum rows */ - if (rows == 0) - rows = 1; - columns = 1 + (n - 1) / rows; - rows = 1 + (n - 1) / columns; - height = (w->sy - (n - 1)) / rows; - - /* Get the main pane width and add one for separator line. */ - mainwidth = options_get_number(w->options, "main-pane-width") + 1; - - /* Get the optional other pane width and add one for separator line. */ - otherwidth = options_get_number(w->options, "other-pane-width") + 1; - - /* - * If an other pane width was specified, honour it so long as it - * doesn't shrink the main width to less than the main-pane-width - */ - if (otherwidth > 1 && w->sx - otherwidth > mainwidth) - mainwidth = w->sx - otherwidth; - if (mainwidth < PANE_MINIMUM + 1) - mainwidth = PANE_MINIMUM + 1; - - /* Try and make everything fit. */ - totalcolumns = columns * (PANE_MINIMUM + 1) - 1; - if (mainwidth + totalcolumns > w->sx) { - if (totalcolumns + PANE_MINIMUM + 1 > w->sx) - mainwidth = PANE_MINIMUM + 2; + /* 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"); + if (mainw + PANE_MINIMUM >= sx) { + if (sx <= PANE_MINIMUM + PANE_MINIMUM) + mainw = PANE_MINIMUM; else - mainwidth = w->sx - totalcolumns; - width = PANE_MINIMUM; - } else - width = (w->sx - mainwidth - (columns - 1)) / columns; + mainw = sx - PANE_MINIMUM; + otherw = PANE_MINIMUM; + } else { + otherw = options_get_number(w->options, "other-pane-width"); + if (otherw == 0) + otherw = sx - mainw; + else if (otherw > sx || sx - otherw < mainw) + otherw = sx - mainw; + else + mainw = sx - otherw; + } + + /* Work out what height is needed. */ + sy = (n * (PANE_MINIMUM + 1)) - 1; + if (sy < w->sy) + sy = w->sy; /* Free old tree and create a new root. */ layout_free(w); lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, mainwidth + columns * (width + 1) - 1, w->sy, 0, 0); + layout_set_size(lc, mainw + otherw + 1, sy, 0, 0); layout_make_node(lc, LAYOUT_LEFTRIGHT); /* Create the main pane. */ lcmain = layout_create_cell(lc); - layout_set_size(lcmain, mainwidth - 1, w->sy, 0, 0); + layout_set_size(lcmain, mainw, sy, 0, 0); layout_make_leaf(lcmain, TAILQ_FIRST(&w->panes)); TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry); - /* Create a grid of the remaining cells. */ - wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); - for (j = 0; j < columns; j++) { - /* If this is the last cell, all done. */ - if (wp == NULL) - break; - - /* Create the new column. */ - lccolumn = layout_create_cell(lc); - layout_set_size(lccolumn, width, w->sy, 0, 0); - TAILQ_INSERT_TAIL(&lc->cells, lccolumn, entry); - - /* If only one row, just use the row directly. */ - if (rows == 1) { - layout_make_leaf(lccolumn, wp); - wp = TAILQ_NEXT(wp, entry); - continue; - } - - /* Add in the rows. */ - layout_make_node(lccolumn, LAYOUT_TOPBOTTOM); - for (i = 0; i < rows; i++) { - /* Create and add a pane cell. */ - lcchild = layout_create_cell(lccolumn); - layout_set_size(lcchild, width, height, 0, 0); + /* Create the other pane. */ + lcother = layout_create_cell(lc); + layout_set_size(lcother, otherw, sy, 0, 0); + if (n == 1) { + wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry); + layout_make_leaf(lcother, wp); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + } else { + layout_make_node(lcother, LAYOUT_TOPBOTTOM); + TAILQ_INSERT_TAIL(&lc->cells, lcother, entry); + + /* Add the remaining panes as children. */ + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp == TAILQ_FIRST(&w->panes)) + continue; + lcchild = layout_create_cell(lcother); + layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0); layout_make_leaf(lcchild, wp); - TAILQ_INSERT_TAIL(&lccolumn->cells, lcchild, entry); - - /* Move to the next cell. */ - if ((wp = TAILQ_NEXT(wp, entry)) == NULL) - break; + TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry); } - - /* Adjust the column to fit the full height if necessary. */ - if (i == rows) - i--; - used = ((i + 1) * (height + 1)) - 1; - if (w->sy <= used) - continue; - lcchild = TAILQ_LAST(&lccolumn->cells, layout_cells); - layout_resize_adjust(w, lcchild, LAYOUT_TOPBOTTOM, - w->sy - used); - } - - /* Adjust the last column width to fit if necessary. */ - used = mainwidth + (columns * width) + columns - 1; - if (w->sx > used) { - lccolumn = TAILQ_LAST(&lc->cells, layout_cells); - layout_resize_adjust(w, lccolumn, LAYOUT_LEFTRIGHT, - w->sx - used); + layout_spread_cell(w, lcother); } /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, w->sx, w->sy); + layout_fix_offsets(w); + layout_fix_panes(w); layout_print_cell(w->layout_root, __func__, 1); + window_resize(w, lc->sx, lc->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } @@ -477,7 +359,7 @@ layout_set_tiled(struct window *w) { struct window_pane *wp; struct layout_cell *lc, *lcrow, *lcchild; - u_int n, width, height, used; + u_int n, width, height, used, sx, sy; u_int i, j, columns, rows; layout_print_cell(w->layout_root, __func__, 1); @@ -506,8 +388,13 @@ layout_set_tiled(struct window *w) /* Free old tree and create a new root. */ layout_free(w); lc = w->layout_root = layout_create_cell(NULL); - layout_set_size(lc, (width + 1) * columns - 1, - (height + 1) * rows - 1, 0, 0); + sx = ((width + 1) * columns) - 1; + if (sx < w->sx) + sx = w->sx; + sy = ((height + 1) * rows) - 1; + if (sy < w->sy) + sy = w->sy; + layout_set_size(lc, sx, sy, 0, 0); layout_make_node(lc, LAYOUT_TOPBOTTOM); /* Create a grid of the cells. */ @@ -566,11 +453,12 @@ layout_set_tiled(struct window *w) } /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, w->sx, w->sy); + layout_fix_offsets(w); + layout_fix_panes(w); layout_print_cell(w->layout_root, __func__, 1); + window_resize(w, lc->sx, lc->sy, -1, -1); notify_window("window-layout-changed", w); server_redraw_window(w); } diff --git a/layout.c b/layout.c index 5c2224bbd4..37214d02cc 100644 --- a/layout.c +++ b/layout.c @@ -39,7 +39,6 @@ static int layout_resize_pane_grow(struct window *, struct layout_cell *, enum layout_type, int, int); static int layout_resize_pane_shrink(struct window *, struct layout_cell *, enum layout_type, int); -static int layout_need_status(struct layout_cell *, int); static u_int layout_new_pane_size(struct window *, u_int, struct layout_cell *, enum layout_type, u_int, u_int, u_int); @@ -97,9 +96,24 @@ void layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) { struct layout_cell *lcchild; + const char *type; - log_debug("%s:%*s%p type %u [parent %p] wp=%p [%u,%u %ux%u]", hdr, n, - " ", lc, lc->type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx, + switch (lc->type) { + case LAYOUT_LEFTRIGHT: + type = "LEFTRIGHT"; + break; + case LAYOUT_TOPBOTTOM: + type = "TOPBOTTOM"; + break; + case LAYOUT_WINDOWPANE: + type = "WINDOWPANE"; + break; + default: + type = "UNKNOWN"; + break; + } + log_debug("%s:%*s%p type %s [parent %p] wp=%p [%u,%u %ux%u]", hdr, n, + " ", lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx, lc->sy); switch (lc->type) { case LAYOUT_LEFTRIGHT: @@ -112,6 +126,42 @@ layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n) } } +struct layout_cell * +layout_search_by_border(struct layout_cell *lc, u_int x, u_int y) +{ + struct layout_cell *lcchild, *last = NULL; + + TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (x >= lcchild->xoff && x < lcchild->xoff + lcchild->sx && + y >= lcchild->yoff && y < lcchild->yoff + lcchild->sy) { + /* Inside the cell - recurse. */ + return (layout_search_by_border(lcchild, x, y)); + } + + if (last == NULL) { + last = lcchild; + continue; + } + + switch (lc->type) { + case LAYOUT_LEFTRIGHT: + if (x < lcchild->xoff && x >= last->xoff + last->sx) + return (last); + break; + case LAYOUT_TOPBOTTOM: + if (y < lcchild->yoff && y >= last->yoff + last->sy) + return (last); + break; + case LAYOUT_WINDOWPANE: + break; + } + + last = lcchild; + } + + return (NULL); +} + void layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, u_int xoff, u_int yoff) @@ -148,9 +198,9 @@ layout_make_node(struct layout_cell *lc, enum layout_type type) lc->wp = NULL; } -/* Fix cell offsets based on their sizes. */ -void -layout_fix_offsets(struct layout_cell *lc) +/* Fix cell offsets for a child cell. */ +static void +layout_fix_offsets1(struct layout_cell *lc) { struct layout_cell *lcchild; u_int xoff, yoff; @@ -161,7 +211,7 @@ layout_fix_offsets(struct layout_cell *lc) lcchild->xoff = xoff; lcchild->yoff = lc->yoff; if (lcchild->type != LAYOUT_WINDOWPANE) - layout_fix_offsets(lcchild); + layout_fix_offsets1(lcchild); xoff += lcchild->sx + 1; } } else { @@ -170,103 +220,92 @@ layout_fix_offsets(struct layout_cell *lc) lcchild->xoff = lc->xoff; lcchild->yoff = yoff; if (lcchild->type != LAYOUT_WINDOWPANE) - layout_fix_offsets(lcchild); + layout_fix_offsets1(lcchild); yoff += lcchild->sy + 1; } } } -/* - * Returns 1 if we need to reserve space for the pane status line. This is the - * case for the most upper panes only. - */ -static int -layout_need_status(struct layout_cell *lc, int at_top) +/* Update cell offsets based on their sizes. */ +void +layout_fix_offsets(struct window *w) { - struct layout_cell *first_lc; + struct layout_cell *lc = w->layout_root; - if (lc->parent) { - if (lc->parent->type == LAYOUT_LEFTRIGHT) - return (layout_need_status(lc->parent, at_top)); + lc->xoff = 0; + lc->yoff = 0; - if (at_top) - first_lc = TAILQ_FIRST(&lc->parent->cells); - else - first_lc = TAILQ_LAST(&lc->parent->cells,layout_cells); - if (lc == first_lc) - return (layout_need_status(lc->parent, at_top)); - return (0); + layout_fix_offsets1(lc); +} + +/* Is this a top cell? */ +static int +layout_cell_is_top(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *next; + + while (lc != w->layout_root) { + next = lc->parent; + if (next->type == LAYOUT_TOPBOTTOM && + lc != TAILQ_FIRST(&next->cells)) + return (0); + lc = next; + } + return (1); +} + +/* Is this a bottom cell? */ +static int +layout_cell_is_bottom(struct window *w, struct layout_cell *lc) +{ + struct layout_cell *next; + + while (lc != w->layout_root) { + next = lc->parent; + if (next->type == LAYOUT_TOPBOTTOM && + lc != TAILQ_LAST(&next->cells, layout_cells)) + return (0); + lc = next; } return (1); } +/* + * Returns 1 if we need to add an extra line for the pane status line. This is + * the case for the most upper or lower panes only. + */ +static int +layout_add_border(struct window *w, struct layout_cell *lc, int status) +{ + if (status == PANE_STATUS_TOP) + return (layout_cell_is_top(w, lc)); + if (status == PANE_STATUS_BOTTOM) + return (layout_cell_is_bottom(w, lc)); + return (0); +} + /* Update pane offsets and sizes based on their cells. */ void -layout_fix_panes(struct window *w, u_int wsx, u_int wsy) +layout_fix_panes(struct window *w) { struct window_pane *wp; struct layout_cell *lc; - u_int sx, sy; - int shift, status, at_top; + int status; status = options_get_number(w->options, "pane-border-status"); - at_top = (status == 1); TAILQ_FOREACH(wp, &w->panes, entry) { if ((lc = wp->layout_cell) == NULL) continue; - if (status != 0) - shift = layout_need_status(lc, at_top); - else - shift = 0; - wp->xoff = lc->xoff; wp->yoff = lc->yoff; - if (shift && at_top) - wp->yoff += 1; - - /* - * Layout cells are limited by the smallest size of other cells - * within the same row or column; if this isn't the case - * resizing becomes difficult. - * - * However, panes do not have to take up their entire cell, so - * they can be cropped to the window edge if the layout - * overflows and they are partly visible. - * - * This stops cells being hidden unnecessarily. - */ - - /* - * Work out the horizontal size. If the pane is actually - * outside the window or the entire pane is already visible, - * don't crop. - */ - if (lc->xoff >= wsx || lc->xoff + lc->sx < wsx) - sx = lc->sx; - else { - sx = wsx - lc->xoff; - if (sx < 1) - sx = lc->sx; - } - - /* - * Similarly for the vertical size; the minimum vertical size - * is two because scroll regions cannot be one line. - */ - if (lc->yoff >= wsy || lc->yoff + lc->sy < wsy) - sy = lc->sy; - else { - sy = wsy - lc->yoff; - if (sy < 2) - sy = lc->sy; - } - - if (shift) - sy -= 1; - - window_pane_resize(wp, sx, sy); + if (layout_add_border(w, lc, status)) { + if (status == PANE_STATUS_TOP) + wp->yoff++; + window_pane_resize(wp, lc->sx, lc->sy - 1); + } else + window_pane_resize(wp, lc->sx, lc->sy); } } @@ -298,17 +337,20 @@ layout_resize_check(struct window *w, struct layout_cell *lc, { struct layout_cell *lcchild; u_int available, minimum; + int status; + status = options_get_number(w->options, "pane-border-status"); if (lc->type == LAYOUT_WINDOWPANE) { /* Space available in this cell only. */ - minimum = PANE_MINIMUM; - if (type == LAYOUT_LEFTRIGHT) + if (type == LAYOUT_LEFTRIGHT) { available = lc->sx; - else { + minimum = PANE_MINIMUM; + } else { available = lc->sy; - minimum += layout_need_status(lc, - options_get_number(w->options, - "pane-border-status") == 1); + if (layout_add_border(w, lc, status)) + minimum = PANE_MINIMUM + 1; + else + minimum = PANE_MINIMUM; } if (available > minimum) available -= minimum; @@ -404,9 +446,9 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lcother = TAILQ_NEXT(lc, entry); else lcother = TAILQ_PREV(lc, layout_cells, entry); - if (lcparent->type == LAYOUT_LEFTRIGHT) + if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT) layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1); - else + else if (lcother != NULL) layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1); /* Remove this from the parent's list. */ @@ -440,8 +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, w->sx, w->sy); + layout_fix_panes(w); } void @@ -470,7 +511,7 @@ layout_resize(struct window *w, u_int sx, u_int sy) * out proportionately - this should leave the layout fitting the new * window size. */ - xchange = sx - w->sx; + xchange = sx - lc->sx; xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT); if (xchange < 0 && xchange < -xlimit) xchange = -xlimit; @@ -484,7 +525,7 @@ layout_resize(struct window *w, u_int sx, u_int sy) layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange); /* Adjust vertically in a similar fashion. */ - ychange = sy - w->sy; + ychange = sy - lc->sy; ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM); if (ychange < 0 && ychange < -ylimit) ychange = -ylimit; @@ -498,8 +539,8 @@ layout_resize(struct window *w, u_int sx, u_int sy) layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange); /* Fix cell offsets. */ - layout_fix_offsets(lc); - layout_fix_panes(w, sx, sy); + layout_fix_offsets(w); + layout_fix_panes(w); } /* Resize a pane to an absolute size. */ @@ -535,14 +576,40 @@ layout_resize_pane_to(struct window_pane *wp, enum layout_type type, layout_resize_pane(wp, type, change, 1); } +void +layout_resize_layout(struct window *w, struct layout_cell *lc, + enum layout_type type, int change, int opposite) +{ + int needed, size; + + /* Grow or shrink the cell. */ + needed = change; + while (needed != 0) { + if (change > 0) { + size = layout_resize_pane_grow(w, lc, type, needed, + opposite); + needed -= size; + } else { + size = layout_resize_pane_shrink(w, lc, type, needed); + needed += size; + } + + if (size == 0) /* no more change possible */ + break; + } + + /* Fix cell offsets. */ + layout_fix_offsets(w); + layout_fix_panes(w); + notify_window("window-layout-changed", w); +} + /* Resize a single pane within the layout. */ void layout_resize_pane(struct window_pane *wp, enum layout_type type, int change, int opposite) { - struct window *w = wp->window; struct layout_cell *lc, *lcparent; - int needed, size; lc = wp->layout_cell; @@ -559,26 +626,7 @@ layout_resize_pane(struct window_pane *wp, enum layout_type type, int change, if (lc == TAILQ_LAST(&lcparent->cells, layout_cells)) lc = TAILQ_PREV(lc, layout_cells, entry); - /* Grow or shrink the cell. */ - needed = change; - while (needed != 0) { - if (change > 0) { - size = layout_resize_pane_grow(w, lc, type, needed, - opposite); - needed -= size; - } else { - size = layout_resize_pane_shrink(w, lc, type, needed); - needed += size; - } - - if (size == 0) /* no more change possible */ - break; - } - - /* Fix cell offsets. */ - layout_fix_offsets(wp->window->layout_root); - layout_fix_panes(wp->window, wp->window->sx, wp->window->sy); - notify_window("window-layout-changed", wp->window); + layout_resize_layout(wp->window, lc, type, change, opposite); } /* Helper function to grow pane. */ @@ -659,7 +707,7 @@ void layout_assign_pane(struct layout_cell *lc, struct window_pane *wp) { layout_make_leaf(lc, wp); - layout_fix_panes(wp->window, wp->window->sx, wp->window->sy); + layout_fix_panes(wp->window); } /* Calculate the new pane size for resized parent. */ @@ -706,7 +754,7 @@ layout_set_size_check(struct window *w, struct layout_cell *lc, enum layout_type type, int size) { struct layout_cell *lcchild; - u_int new_size, available, previous, count, idx; + u_int new_size, available, previous, count, idx; /* Cells with no children must just be bigger than minimum. */ if (lc->type == LAYOUT_WINDOWPANE) @@ -720,6 +768,9 @@ layout_set_size_check(struct window *w, struct layout_cell *lc, /* Check new size will work for each child. */ if (lc->type == type) { + if (available < (count * 2) - 1) + return (0); + if (type == LAYOUT_LEFTRIGHT) previous = lc->sx; else @@ -729,13 +780,17 @@ layout_set_size_check(struct window *w, struct layout_cell *lc, TAILQ_FOREACH(lcchild, &lc->cells, entry) { new_size = layout_new_pane_size(w, previous, lcchild, type, size, count - idx, available); - if (new_size > available) - return (0); - - available -= (new_size + 1); + if (idx == count - 1) { + if (new_size > available) + return (0); + available -= new_size; + } else { + if (new_size + 1 > available) + return (0); + available -= new_size + 1; + } if (!layout_set_size_check(w, lcchild, type, new_size)) return (0); - idx++; } } else { @@ -808,11 +863,12 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) */ struct layout_cell * layout_split_pane(struct window_pane *wp, enum layout_type type, int size, - int insert_before, int full_size) + int flags) { struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2; - u_int sx, sy, xoff, yoff, size1, size2; + u_int sx, sy, xoff, yoff, size1, size2, minimum; u_int new_size, saved_size, resize_first = 0; + int full_size = (flags & SPAWN_FULLSIZE), status; /* * If full_size is specified, add a new cell at the top of the window @@ -822,6 +878,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, lc = wp->window->layout_root; else lc = wp->layout_cell; + status = options_get_number(wp->window->options, "pane-border-status"); /* Copy the old cell size. */ sx = lc->sx; @@ -836,7 +893,11 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, return (NULL); break; case LAYOUT_TOPBOTTOM: - if (sy < PANE_MINIMUM * 2 + 1) + if (layout_add_border(wp->window, lc, status)) + minimum = PANE_MINIMUM * 2 + 2; + else + minimum = PANE_MINIMUM * 2 + 1; + if (sy < minimum) return (NULL); break; default: @@ -853,7 +914,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, saved_size = sy; if (size < 0) size2 = ((saved_size + 1) / 2) - 1; - else if (insert_before) + else if (flags & SPAWN_BEFORE) size2 = saved_size - size - 1; else size2 = size; @@ -864,7 +925,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, size1 = saved_size - 1 - size2; /* Which size are we using? */ - if (insert_before) + if (flags & SPAWN_BEFORE) new_size = size2; else new_size = size1; @@ -880,7 +941,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, */ lcparent = lc->parent; lcnew = layout_create_cell(lcparent); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_BEFORE(lc, lcnew, entry); else TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); @@ -909,7 +970,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, layout_set_size(lcnew, size, sy, 0, 0); else if (lc->type == LAYOUT_TOPBOTTOM) layout_set_size(lcnew, sx, size, 0, 0); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry); else TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); @@ -933,12 +994,12 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, /* Create the new child cell. */ lcnew = layout_create_cell(lcparent); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry); else TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); } - if (insert_before) { + if (flags & SPAWN_BEFORE) { lc1 = lcnew; lc2 = lc; } else { @@ -960,7 +1021,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, if (full_size) { if (!resize_first) layout_resize_child_cells(wp->window, lc); - layout_fix_offsets(wp->window->layout_root); + layout_fix_offsets(wp->window); } else layout_make_leaf(lc, wp); @@ -978,8 +1039,78 @@ layout_close_pane(struct window_pane *wp) /* Fix pane offsets and sizes. */ if (w->layout_root != NULL) { - layout_fix_offsets(w->layout_root); - layout_fix_panes(w, w->sx, w->sy); + layout_fix_offsets(w); + layout_fix_panes(w); } notify_window("window-layout-changed", w); } + +int +layout_spread_cell(struct window *w, struct layout_cell *parent) +{ + struct layout_cell *lc; + u_int number, each, size, this; + int change, changed, status; + + number = 0; + TAILQ_FOREACH (lc, &parent->cells, entry) + number++; + if (number <= 1) + return (0); + status = options_get_number(w->options, "pane-border-status"); + + if (parent->type == LAYOUT_LEFTRIGHT) + size = parent->sx; + else if (parent->type == LAYOUT_TOPBOTTOM) { + if (layout_add_border(w, parent, status)) + size = parent->sy - 1; + else + size = parent->sy; + } else + return (0); + if (size < number - 1) + return (0); + each = (size - (number - 1)) / number; + if (each == 0) + return (0); + + changed = 0; + TAILQ_FOREACH (lc, &parent->cells, entry) { + if (TAILQ_NEXT(lc, entry) == NULL) + each = size - ((each + 1) * (number - 1)); + change = 0; + if (parent->type == LAYOUT_LEFTRIGHT) { + change = each - (int)lc->sx; + layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change); + } else if (parent->type == LAYOUT_TOPBOTTOM) { + if (layout_add_border(w, lc, status)) + this = each + 1; + else + this = each; + change = this - (int)lc->sy; + layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change); + } + if (change != 0) + changed = 1; + } + return (changed); +} + +void +layout_spread_out(struct window_pane *wp) +{ + struct layout_cell *parent; + struct window *w = wp->window; + + parent = wp->layout_cell->parent; + if (parent == NULL) + return; + + do { + if (layout_spread_cell(w, parent)) { + layout_fix_offsets(w); + layout_fix_panes(w); + break; + } + } while ((parent = parent->parent) != NULL); +} diff --git a/log.c b/log.c index 8998cf2a5a..f87cab92e5 100644 --- a/log.c +++ b/log.c @@ -130,6 +130,9 @@ log_debug(const char *msg, ...) { va_list ap; + if (log_file == NULL) + return; + va_start(ap, msg); log_vwrite(msg, ap); va_end(ap); diff --git a/menu.c b/menu.c new file mode 100644 index 0000000000..6024ba022f --- /dev/null +++ b/menu.c @@ -0,0 +1,323 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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" + +struct menu_data { + struct cmdq_item *item; + int flags; + + struct cmd_find_state fs; + struct screen s; + + u_int px; + u_int py; + + struct menu *menu; + int choice; + + menu_choice_cb cb; + void *data; +}; + +void +menu_add_items(struct menu *menu, const struct menu_item *items, + struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs) +{ + const struct menu_item *loop; + + for (loop = items; loop->name != NULL; loop++) + menu_add_item(menu, loop, qitem, c, fs); +} + +void +menu_add_item(struct menu *menu, const struct menu_item *item, + struct cmdq_item *qitem, struct client *c, struct cmd_find_state *fs) +{ + struct menu_item *new_item; + const char *key, *cmd; + char *s, *name; + u_int width; + int line; + + line = (item == NULL || item->name == NULL || *item->name == '\0'); + if (line && menu->count == 0) + return; + + menu->items = xreallocarray(menu->items, menu->count + 1, + sizeof *menu->items); + new_item = &menu->items[menu->count++]; + memset(new_item, 0, sizeof *new_item); + + if (line) + return; + + if (fs != NULL) + s = format_single(qitem, item->name, c, fs->s, fs->wl, fs->wp); + else + s = format_single(qitem, item->name, c, NULL, NULL, NULL); + if (*s == '\0') { /* no item if empty after format expanded */ + menu->count--; + return; + } + if (*s != '-' && item->key != KEYC_UNKNOWN && item->key != KEYC_NONE) { + key = key_string_lookup_key(item->key); + xasprintf(&name, "%s#[default] #[align=right](%s)", s, key); + } else + xasprintf(&name, "%s", s); + new_item->name = name; + free(s); + + cmd = item->command; + if (cmd != NULL) { + if (fs != NULL) + s = format_single(qitem, cmd, c, fs->s, fs->wl, fs->wp); + else + s = format_single(qitem, cmd, c, NULL, NULL, NULL); + } else + s = NULL; + new_item->command = s; + new_item->key = item->key; + + width = format_width(new_item->name); + if (width > menu->width) + menu->width = width; +} + +struct menu * +menu_create(const char *title) +{ + struct menu *menu; + + menu = xcalloc(1, sizeof *menu); + menu->title = xstrdup(title); + + return (menu); +} + +void +menu_free(struct menu *menu) +{ + u_int i; + + for (i = 0; i < menu->count; i++) { + free((void *)menu->items[i].name); + free((void *)menu->items[i].command); + } + free(menu->items); + + free((void *)menu->title); + free(menu); +} + +static void +menu_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) +{ + struct menu_data *md = c->overlay_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; + + screen_write_start(&ctx, NULL, s); + screen_write_clearscreen(&ctx, 8); + screen_write_menu(&ctx, menu, md->choice); + 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); +} + +static void +menu_free_cb(struct client *c) +{ + struct menu_data *md = c->overlay_data; + + if (md->item != NULL) + cmdq_continue(md->item); + + if (md->cb != NULL) + md->cb(md->menu, UINT_MAX, KEYC_NONE, md->data); + + screen_free(&md->s); + menu_free(md->menu); + free(md); +} + +static int +menu_key_cb(struct client *c, struct key_event *event) +{ + struct menu_data *md = c->overlay_data; + struct menu *menu = md->menu; + struct mouse_event *m = &event->m; + u_int i; + int count = menu->count, old = md->choice; + const struct menu_item *item; + struct cmdq_item *new_item; + struct cmd_parse_result *pr; + const char *name; + + if (KEYC_IS_MOUSE(event->key)) { + if (md->flags & MENU_NOMOUSE) { + if (MOUSE_BUTTONS(m->b) != 0) + return (1); + return (0); + } + if (m->x < md->px || + 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->choice != -1) { + md->choice = -1; + c->flags |= CLIENT_REDRAWOVERLAY; + } + return (0); + } + if (MOUSE_RELEASE(m->b)) + goto chosen; + md->choice = m->y - (md->py + 1); + if (md->choice != old) + c->flags |= CLIENT_REDRAWOVERLAY; + return (0); + } + for (i = 0; i < (u_int)count; i++) { + name = menu->items[i].name; + if (name == NULL || *name == '-') + continue; + if (event->key == menu->items[i].key) { + md->choice = i; + goto chosen; + } + } + switch (event->key) { + case KEYC_UP: + case 'k': + if (old == -1) + old = 0; + do { + if (md->choice == -1 || md->choice == 0) + md->choice = count - 1; + else + md->choice--; + name = menu->items[md->choice].name; + } while ((name == NULL || *name == '-') && md->choice != old); + c->flags |= CLIENT_REDRAWOVERLAY; + return (0); + case KEYC_DOWN: + case 'j': + if (old == -1) + old = 0; + do { + if (md->choice == -1 || md->choice == count - 1) + md->choice = 0; + else + md->choice++; + name = menu->items[md->choice].name; + } while ((name == NULL || *name == '-') && md->choice != old); + c->flags |= CLIENT_REDRAWOVERLAY; + return (0); + case '\r': + goto chosen; + case '\033': /* Escape */ + case '\003': /* C-c */ + case '\007': /* C-g */ + case 'q': + return (1); + } + return (0); + +chosen: + if (md->choice == -1) + return (1); + item = &menu->items[md->choice]; + if (item->name == NULL || *item->name == '-') + 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; + } + return (1); +} + +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, + void *data) +{ + struct menu_data *md; + + if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) + 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); + return (0); +} diff --git a/mode-tree.c b/mode-tree.c index ad783bb455..054989fb6b 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -31,18 +31,20 @@ TAILQ_HEAD(mode_tree_list, mode_tree_item); struct mode_tree_data { int dead; u_int references; + int zoomed; struct window_pane *wp; void *modedata; + const struct menu_item *menu; const char **sort_list; u_int sort_size; - u_int sort_type; + struct mode_tree_sort_criteria sort_crit; - void (*buildcb)(void *, u_int, uint64_t *, - const char *); - struct screen *(*drawcb)(void *, void *, u_int, u_int); - int (*searchcb)(void*, void *, const char *); + mode_tree_build_cb buildcb; + mode_tree_draw_cb drawcb; + mode_tree_search_cb searchcb; + mode_tree_menu_cb menucb; struct mode_tree_list children; struct mode_tree_list saved; @@ -63,6 +65,7 @@ struct mode_tree_data { int preview; char *search; char *filter; + int no_matches; }; struct mode_tree_item { @@ -88,8 +91,24 @@ struct mode_tree_line { int flat; }; +struct mode_tree_menu { + struct mode_tree_data *data; + struct client *c; + u_int line; + void *itemdata; +}; + static void mode_tree_free_items(struct mode_tree_list *); +static const struct menu_item mode_tree_menu_items[] = { + { "Scroll Left", '<', NULL }, + { "Scroll Right", '>', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + static struct mode_tree_item * mode_tree_find_item(struct mode_tree_list *mtl, uint64_t tag) { @@ -192,27 +211,6 @@ mode_tree_clear_tagged(struct mode_tree_list *mtl) } static void -mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) -{ - u_int i; - - for (i = 0; i < mtd->line_size; i++) { - if (mtd->line_list[i].item->tag == tag) - break; - } - if (i != mtd->line_size) { - mtd->current = i; - if (mtd->current > mtd->height - 1) - mtd->offset = mtd->current - mtd->height + 1; - else - mtd->offset = 0; - } else { - mtd->current = 0; - mtd->offset = 0; - } -} - -void mode_tree_up(struct mode_tree_data *mtd, int wrap) { if (mtd->current == 0) { @@ -249,6 +247,36 @@ mode_tree_get_current(struct mode_tree_data *mtd) return (mtd->line_list[mtd->current].item->itemdata); } +void +mode_tree_expand_current(struct mode_tree_data *mtd) +{ + if (!mtd->line_list[mtd->current].item->expanded) { + mtd->line_list[mtd->current].item->expanded = 1; + mode_tree_build(mtd); + } +} + +void +mode_tree_set_current(struct mode_tree_data *mtd, uint64_t tag) +{ + u_int i; + + for (i = 0; i < mtd->line_size; i++) { + if (mtd->line_list[i].item->tag == tag) + break; + } + if (i != mtd->line_size) { + mtd->current = i; + if (mtd->current > mtd->height - 1) + mtd->offset = mtd->current - mtd->height + 1; + else + mtd->offset = 0; + } else { + mtd->current = 0; + mtd->offset = 0; + } +} + u_int mode_tree_count_tagged(struct mode_tree_data *mtd) { @@ -265,8 +293,8 @@ mode_tree_count_tagged(struct mode_tree_data *mtd) } void -mode_tree_each_tagged(struct mode_tree_data *mtd, void (*cb)(void *, void *, - key_code), key_code key, int current) +mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, + struct client *c, key_code key, int current) { struct mode_tree_item *mti; u_int i; @@ -277,21 +305,21 @@ mode_tree_each_tagged(struct mode_tree_data *mtd, void (*cb)(void *, void *, mti = mtd->line_list[i].item; if (mti->tagged) { fired = 1; - cb(mtd->modedata, mti->itemdata, key); + cb(mtd->modedata, mti->itemdata, c, key); } } if (!fired && current) { mti = mtd->line_list[mtd->current].item; - cb(mtd->modedata, mti->itemdata, key); + cb(mtd->modedata, mti->itemdata, c, key); } } struct mode_tree_data * mode_tree_start(struct window_pane *wp, struct args *args, - void (*buildcb)(void *, u_int, uint64_t *, const char *), - struct screen *(*drawcb)(void *, void *, u_int, u_int), - int (*searchcb)(void *, void *, const char *), void *modedata, - const char **sort_list, u_int sort_size, struct screen **s) + mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, + mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata, + const struct menu_item *menu, const char **sort_list, u_int sort_size, + struct screen **s) { struct mode_tree_data *mtd; const char *sort; @@ -302,10 +330,10 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->wp = wp; mtd->modedata = modedata; + mtd->menu = menu; mtd->sort_list = sort_list; mtd->sort_size = sort_size; - mtd->sort_type = 0; mtd->preview = !args_has(args, 'N'); @@ -313,9 +341,10 @@ mode_tree_start(struct window_pane *wp, struct args *args, if (sort != NULL) { for (i = 0; i < sort_size; i++) { if (strcasecmp(sort, sort_list[i]) == 0) - mtd->sort_type = i; + mtd->sort_crit.field = i; } } + mtd->sort_crit.reversed = args_has(args, 'r'); if (args_has(args, 'f')) mtd->filter = xstrdup(args_get(args, 'f')); @@ -325,6 +354,7 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->buildcb = buildcb; mtd->drawcb = drawcb; mtd->searchcb = searchcb; + mtd->menucb = menucb; TAILQ_INIT(&mtd->children); @@ -335,6 +365,19 @@ mode_tree_start(struct window_pane *wp, struct args *args, return (mtd); } +void +mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) +{ + struct window_pane *wp = mtd->wp; + + if (args_has(args, 'Z')) { + mtd->zoomed = (wp->window->flags & WINDOW_ZOOMED); + if (!mtd->zoomed && window_zoom(wp) == 0) + server_redraw_window(wp->window); + } else + mtd->zoomed = -1; +} + void mode_tree_build(struct mode_tree_data *mtd) { @@ -344,14 +387,15 @@ mode_tree_build(struct mode_tree_data *mtd) if (mtd->line_list != NULL) tag = mtd->line_list[mtd->current].item->tag; else - tag = 0; + tag = UINT64_MAX; TAILQ_CONCAT(&mtd->saved, &mtd->children, entry); TAILQ_INIT(&mtd->children); - mtd->buildcb(mtd->modedata, mtd->sort_type, &tag, mtd->filter); - if (TAILQ_EMPTY(&mtd->children)) - mtd->buildcb(mtd->modedata, mtd->sort_type, &tag, NULL); + mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, mtd->filter); + mtd->no_matches = TAILQ_EMPTY(&mtd->children); + if (mtd->no_matches) + mtd->buildcb(mtd->modedata, &mtd->sort_crit, &tag, NULL); mode_tree_free_items(&mtd->saved); TAILQ_INIT(&mtd->saved); @@ -359,6 +403,8 @@ mode_tree_build(struct mode_tree_data *mtd) mode_tree_clear_lines(mtd); mode_tree_build_lines(mtd, &mtd->children, 0); + if (tag == UINT64_MAX) + tag = mtd->line_list[mtd->current].item->tag; mode_tree_set_current(mtd, tag); mtd->width = screen_size_x(s); @@ -385,6 +431,11 @@ mode_tree_remove_ref(struct mode_tree_data *mtd) void mode_tree_free(struct mode_tree_data *mtd) { + struct window_pane *wp = mtd->wp; + + if (mtd->zoomed == 0) + server_unzoom_window(wp->window); + mode_tree_free_items(&mtd->children); mode_tree_clear_lines(mtd); screen_free(&mtd->screen); @@ -429,7 +480,7 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, saved = mode_tree_find_item(&mtd->saved, tag); if (saved != NULL) { - if (parent == NULL || (parent != NULL && parent->expanded)) + if (parent == NULL || parent->expanded) mti->tagged = saved->tagged; mti->expanded = saved->expanded; } else if (expanded == -1) @@ -463,16 +514,16 @@ void mode_tree_draw(struct mode_tree_data *mtd) { struct window_pane *wp = mtd->wp; - struct screen *s = &mtd->screen, *box = NULL; + struct screen *s = &mtd->screen; struct mode_tree_line *line; struct mode_tree_item *mti; struct options *oo = wp->window->options; struct screen_write_ctx ctx; struct grid_cell gc0, gc; - u_int w, h, i, j, sy, box_x, box_y; + u_int w, h, i, j, sy, box_x, box_y, width; char *text, *start, key[7]; const char *tag, *symbol; - size_t size; + size_t size, n; int keylen; if (mtd->line_size == 0) @@ -502,7 +553,7 @@ mode_tree_draw(struct mode_tree_data *mtd) line = &mtd->line_list[i]; mti = line->item; - screen_write_cursormove(&ctx, 0, i - mtd->offset); + screen_write_cursormove(&ctx, 0, i - mtd->offset, 0); if (i < 10) snprintf(key, sizeof key, "(%c) ", '0' + i); @@ -544,8 +595,9 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s: %s", keylen, key, start, - mti->name, tag, mti->text); + xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, + tag); + width = utf8_cstrwidth(text); free(start); if (mti->tagged) { @@ -554,10 +606,14 @@ mode_tree_draw(struct mode_tree_data *mtd) } if (i != mtd->current) { - screen_write_puts(&ctx, &gc0, "%.*s", w, text); screen_write_clearendofline(&ctx, 8); - } else - screen_write_puts(&ctx, &gc, "%-*.*s", w, w, text); + screen_write_puts(&ctx, &gc0, "%s", text); + format_draw(&ctx, &gc0, w - width, mti->text, NULL); + } else { + screen_write_clearendofline(&ctx, gc.bg); + screen_write_puts(&ctx, &gc, "%s", text); + format_draw(&ctx, &gc, w - width, mti->text, NULL); + } free(text); if (mti->tagged) { @@ -575,27 +631,37 @@ mode_tree_draw(struct mode_tree_data *mtd) line = &mtd->line_list[mtd->current]; mti = line->item; - screen_write_cursormove(&ctx, 0, h); + screen_write_cursormove(&ctx, 0, h, 0); screen_write_box(&ctx, w, sy - h); - xasprintf(&text, " %s (sort: %s) ", mti->name, - mtd->sort_list[mtd->sort_type]); + xasprintf(&text, " %s (sort: %s%s)", mti->name, + mtd->sort_list[mtd->sort_crit.field], + mtd->sort_crit.reversed ? ", reversed" : ""); if (w - 2 >= strlen(text)) { - screen_write_cursormove(&ctx, 1, h); + screen_write_cursormove(&ctx, 1, h, 0); screen_write_puts(&ctx, &gc0, "%s", text); + + if (mtd->no_matches) + n = (sizeof "no matches") - 1; + else + n = (sizeof "active") - 1; + if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) { + screen_write_puts(&ctx, &gc0, " (filter: "); + if (mtd->no_matches) + screen_write_puts(&ctx, &gc, "no matches"); + else + screen_write_puts(&ctx, &gc0, "active"); + screen_write_puts(&ctx, &gc0, ") "); + } } free(text); box_x = w - 4; box_y = sy - h - 2; - if (box_x != 0 && box_y != 0) - box = mtd->drawcb(mtd->modedata, mti->itemdata, box_x, box_y); - if (box != NULL) { - screen_write_cursormove(&ctx, 2, h + 1); - screen_write_copy(&ctx, box, 0, 0, box_x, box_y, NULL, NULL); - - screen_free(box); + if (box_x != 0 && box_y != 0) { + screen_write_cursormove(&ctx, 2, h + 1, 0); + mtd->drawcb(mtd->modedata, mti->itemdata, &ctx, box_x, box_y); } screen_write_stop(&ctx); @@ -720,9 +786,73 @@ mode_tree_filter_free(void *data) mode_tree_remove_ref(data); } +static void +mode_tree_menu_callback(__unused struct menu *menu, __unused u_int idx, + key_code key, void *data) +{ + struct mode_tree_menu *mtm = data; + struct mode_tree_data *mtd = mtm->data; + struct mode_tree_item *mti; + + if (mtd->dead || key == KEYC_NONE) + goto out; + + if (mtm->line >= mtd->line_size) + goto out; + mti = mtd->line_list[mtm->line].item; + if (mti->itemdata != mtm->itemdata) + goto out; + mtd->current = mtm->line; + mtd->menucb (mtd->modedata, mtm->c, key); + +out: + mode_tree_remove_ref(mtd); + free(mtm); +} + +static void +mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x, + u_int y, int outside) +{ + struct mode_tree_item *mti; + struct menu *menu; + const struct menu_item *items; + struct mode_tree_menu *mtm; + char *title; + u_int line; + + if (mtd->offset + y > mtd->line_size - 1) + line = mtd->current; + else + line = mtd->offset + y; + mti = mtd->line_list[line].item; + + if (!outside) { + items = mtd->menu; + xasprintf(&title, "#[align=centre]%s", mti->name); + } else { + items = mode_tree_menu_items; + title = xstrdup(""); + } + menu = menu_create(title); + menu_add_items(menu, items, NULL, NULL, NULL); + free(title); + + mtm = xmalloc(sizeof *mtm); + mtm->data = mtd; + mtm->c = c; + mtm->line = line; + mtm->itemdata = mti->itemdata; + mtd->references++; + + if (menu_display(menu, 0, NULL, x, y, c, NULL, mode_tree_menu_callback, + mtm) != 0) + menu_free(menu); +} + int mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, - struct mouse_event *m) + struct mouse_event *m, u_int *xp, u_int *yp) { struct mode_tree_line *line; struct mode_tree_item *current, *parent; @@ -730,20 +860,40 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, int choice; key_code tmp; - if (*key == KEYC_MOUSEDOWN1_PANE) { + if (KEYC_IS_MOUSE(*key) && m != NULL) { if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) { *key = KEYC_NONE; return (0); } + if (xp != NULL) + *xp = x; + if (yp != NULL) + *yp = y; if (x > mtd->width || y > mtd->height) { - *key = KEYC_NONE; + if (*key == KEYC_MOUSEDOWN3_PANE) + mode_tree_display_menu(mtd, c, x, y, 1); + if (!mtd->preview) + *key = KEYC_NONE; return (0); } if (mtd->offset + y < mtd->line_size) { - mtd->current = mtd->offset + y; - *key = '\r'; - return (0); + if (*key == KEYC_MOUSEDOWN1_PANE || + *key == KEYC_MOUSEDOWN3_PANE || + *key == KEYC_DOUBLECLICK1_PANE) + mtd->current = mtd->offset + y; + if (*key == KEYC_DOUBLECLICK1_PANE) + *key = '\r'; + else { + if (*key == KEYC_MOUSEDOWN3_PANE) + mode_tree_display_menu(mtd, c, x, y, 0); + *key = KEYC_NONE; + } + } else { + if (*key == KEYC_MOUSEDOWN3_PANE) + mode_tree_display_menu(mtd, c, x, y, 0); + *key = KEYC_NONE; } + return (0); } line = &mtd->line_list[mtd->current]; @@ -770,17 +920,21 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, switch (*key) { case 'q': case '\033': /* Escape */ + case '\007': /* C-g */ return (1); case KEYC_UP: case 'k': case KEYC_WHEELUP_PANE: + case '\020': /* C-p */ mode_tree_up(mtd, 1); break; case KEYC_DOWN: case 'j': case KEYC_WHEELDOWN_PANE: + case '\016': /* C-n */ mode_tree_down(mtd, 1); break; + case 'g': case KEYC_PPAGE: case '\002': /* C-b */ for (i = 0; i < mtd->height; i++) { @@ -789,6 +943,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_up(mtd, 1); } break; + case 'G': case KEYC_NPAGE: case '\006': /* C-f */ for (i = 0; i < mtd->height; i++) { @@ -823,7 +978,8 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, current->tagged = 1; } else current->tagged = 0; - mode_tree_down(mtd, 0); + if (m != NULL) + mode_tree_down(mtd, 0); break; case 'T': for (i = 0; i < mtd->line_size; i++) @@ -838,12 +994,17 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, } break; case 'O': - mtd->sort_type++; - if (mtd->sort_type == mtd->sort_size) - mtd->sort_type = 0; + mtd->sort_crit.field++; + if (mtd->sort_crit.field == mtd->sort_size) + mtd->sort_crit.field = 0; + mode_tree_build(mtd); + break; + case 'r': + mtd->sort_crit.reversed = !mtd->sort_crit.reversed; mode_tree_build(mtd); break; case KEYC_LEFT: + case 'h': case '-': if (line->flat || !current->expanded) current = current->parent; @@ -856,6 +1017,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, } break; case KEYC_RIGHT: + case 'l': case '+': if (line->flat || current->expanded) mode_tree_down(mtd, 0); @@ -864,6 +1026,8 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, mode_tree_build(mtd); } break; + case '?': + case '/': case '\023': /* C-s */ mtd->references++; status_prompt_set(c, "(search) ", "", @@ -894,8 +1058,8 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs, const char *template, const char *name) { struct cmdq_item *new_item; - struct cmd_list *cmdlist; - char *command, *cause; + char *command; + struct cmd_parse_result *pr; command = cmd_template_replace(template, name, 1); if (command == NULL || *command == '\0') { @@ -903,17 +1067,22 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs, return; } - cmdlist = cmd_string_parse(command, NULL, 0, &cause); - if (cmdlist == NULL) { - if (cause != NULL && c != NULL) { - *cause = toupper((u_char)*cause); - status_message_set(c, "%s", cause); + 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); } - free(cause); - } else { - new_item = cmdq_get_command(cmdlist, fs, NULL, 0); + 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(cmdlist); + cmd_list_free(pr->cmdlist); + break; } free(command); diff --git a/notify.c b/notify.c index 05b9d84279..8bd8f98f75 100644 --- a/notify.c +++ b/notify.c @@ -35,13 +35,34 @@ struct notify_entry { }; static void -notify_hook(struct cmdq_item *item, struct notify_entry *ne) +notify_hook_formats(struct cmdq_item *item, struct session *s, struct window *w, + int pane) { - struct cmd_find_state fs; - struct hook *hook; - struct cmdq_item *new_item; - struct session *s = ne->session; - struct window *w = ne->window; + 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 options_entry *o; + struct options_array_item *a; + struct cmd_list *cmdlist; + + log_debug("%s: %s", __func__, ne->name); cmd_find_clear_state(&fs, 0); if (cmd_find_empty_state(&ne->fs) || !cmd_find_valid_state(&ne->fs)) @@ -49,26 +70,31 @@ notify_hook(struct cmdq_item *item, struct notify_entry *ne) else cmd_find_copy_state(&fs, &ne->fs); - hook = hooks_find(hooks_get(fs.s), ne->name); - if (hook == NULL) + if (fs.s == NULL) + oo = global_s_options; + else + oo = fs.s->options; + o = options_get(oo, ne->name); + if (o == NULL) return; - log_debug("notify hook %s", ne->name); - new_item = cmdq_get_command(hook->cmdlist, &fs, NULL, CMDQ_NOHOOKS); - cmdq_format(new_item, "hook", "%s", ne->name); + 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 (s != NULL) { - cmdq_format(new_item, "hook_session", "$%u", s->id); - cmdq_format(new_item, "hook_session_name", "%s", s->name); - } - if (w != NULL) { - cmdq_format(new_item, "hook_window", "@%u", w->id); - cmdq_format(new_item, "hook_window_name", "%s", w->name); - } - if (ne->pane != -1) - cmdq_format(new_item, "hook_pane", "%%%d", ne->pane); + 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); + + cmdq_insert_after(item, new_item); + item = new_item; - cmdq_insert_after(item, new_item); + a = options_array_next(a); + } } static enum cmd_retval @@ -101,7 +127,7 @@ notify_callback(struct cmdq_item *item, void *data) if (strcmp(ne->name, "session-window-changed") == 0) control_notify_session_window_changed(ne->session); - notify_hook(item, ne); + notify_insert_hook(item, ne); if (ne->client != NULL) server_client_unref(ne->client); @@ -154,13 +180,31 @@ notify_add(const char *name, struct cmd_find_state *fs, struct client *c, } void -notify_input(struct window_pane *wp, struct evbuffer *input) +notify_hook(struct cmdq_item *item, const char *name) +{ + struct notify_entry ne; + + memset(&ne, 0, sizeof ne); + + ne.name = name; + cmd_find_copy_state(&ne.fs, &item->target); + + ne.client = item->client; + ne.session = item->target.s; + ne.window = item->target.w; + ne.pane = item->target.wp->id; + + 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, input); + control_notify_input(c, wp, buf, len); } } diff --git a/options-table.c b/options-table.c index f5e973e50d..be0d220d2c 100644 --- a/options-table.c +++ b/options-table.c @@ -38,6 +38,9 @@ static const char *options_table_mode_keys_list[] = { static const char *options_table_clock_mode_style_list[] = { "12", "24", NULL }; +static const char *options_table_status_list[] = { + "off", "on", "2", "3", "4", "5", NULL +}; static const char *options_table_status_keys_list[] = { "emacs", "vi", NULL }; @@ -59,9 +62,102 @@ static const char *options_table_pane_status_list[] = { 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 +}; + +/* Status line format. */ +#define OPTIONS_TABLE_STATUS_FORMAT1 \ + "#[align=left range=left #{status-left-style}]" \ + "#[push-default]" \ + "#{T;=/#{status-left-length}:status-left}" \ + "#[pop-default]" \ + "#[norange default]" \ + "#[list=on align=#{status-justify}]" \ + "#[list=left-marker]<#[list=right-marker]>#[list=on]" \ + "#{W:" \ + "#[range=window|#{window_index} " \ + "#{window-status-style}" \ + "#{?#{&&:#{window_last_flag}," \ + "#{!=:#{window-status-last-style},default}}, " \ + "#{window-status-last-style}," \ + "}" \ + "#{?#{&&:#{window_bell_flag}," \ + "#{!=:#{window-status-bell-style},default}}, " \ + "#{window-status-bell-style}," \ + "#{?#{&&:#{||:#{window_activity_flag}," \ + "#{window_silence_flag}}," \ + "#{!=:" \ + "#{window-status-activity-style}," \ + "default}}, " \ + "#{window-status-activity-style}," \ + "}" \ + "}" \ + "]" \ + "#[push-default]" \ + "#{T:window-status-format}" \ + "#[pop-default]" \ + "#[norange default]" \ + "#{?window_end_flag,,#{window-status-separator}}" \ + "," \ + "#[range=window|#{window_index} list=focus " \ + "#{?#{!=:#{window-status-current-style},default}," \ + "#{window-status-current-style}," \ + "#{window-status-style}" \ + "}" \ + "#{?#{&&:#{window_last_flag}," \ + "#{!=:#{window-status-last-style},default}}, " \ + "#{window-status-last-style}," \ + "}" \ + "#{?#{&&:#{window_bell_flag}," \ + "#{!=:#{window-status-bell-style},default}}, " \ + "#{window-status-bell-style}," \ + "#{?#{&&:#{||:#{window_activity_flag}," \ + "#{window_silence_flag}}," \ + "#{!=:" \ + "#{window-status-activity-style}," \ + "default}}, " \ + "#{window-status-activity-style}," \ + "}" \ + "}" \ + "]" \ + "#[push-default]" \ + "#{T:window-status-current-format}" \ + "#[pop-default]" \ + "#[norange list=on default]" \ + "#{?window_end_flag,,#{window-status-separator}}" \ + "}" \ + "#[nolist align=right range=right #{status-right-style}]" \ + "#[push-default]" \ + "#{T;=/#{status-right-length}:status-right}" \ + "#[pop-default]" \ + "#[norange default]" +#define OPTIONS_TABLE_STATUS_FORMAT2 \ + "#[align=centre]#{P:#{?pane_active,#[reverse],}" \ + "#{pane_index}[#{pane_width}x#{pane_height}]#[default] }" +static const char *options_table_status_format_default[] = { + OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, NULL +}; + +/* Helper for hook options. */ +#define OPTIONS_TABLE_HOOK(hook_name, default_value) \ + { .name = hook_name, \ + .type = OPTIONS_TABLE_COMMAND, \ + .scope = OPTIONS_TABLE_SESSION, \ + .flags = OPTIONS_TABLE_IS_ARRAY|OPTIONS_TABLE_IS_HOOK, \ + .default_str = default_value, \ + .separator = "" \ + } /* Top-level options. */ const struct options_table_entry options_table[] = { + /* Server options. */ + { .name = "backspace", + .type = OPTIONS_TABLE_KEY, + .scope = OPTIONS_TABLE_SERVER, + .default_num = '\177', + }, + { .name = "buffer-limit", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, @@ -71,8 +167,9 @@ const struct options_table_entry options_table[] = { }, { .name = "command-alias", - .type = OPTIONS_TABLE_ARRAY, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "split-pane=split-window," "splitp=split-window," "server-info=show-messages -JT," @@ -96,6 +193,12 @@ const struct options_table_entry options_table[] = { .default_num = 500 }, + { .name = "exit-empty", + .type = OPTIONS_TABLE_FLAG, + .scope = OPTIONS_TABLE_SERVER, + .default_num = 1 + }, + { .name = "exit-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, @@ -130,8 +233,9 @@ const struct options_table_entry options_table[] = { }, { .name = "terminal-overrides", - .type = OPTIONS_TABLE_ARRAY, + .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", @@ -139,12 +243,14 @@ const struct options_table_entry options_table[] = { }, { .name = "user-keys", - .type = OPTIONS_TABLE_ARRAY, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, + .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", .separator = "," }, + /* Session options. */ { .name = "activity-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, @@ -187,6 +293,13 @@ const struct options_table_entry options_table[] = { .default_str = _PATH_BSHELL }, + { .name = "default-size", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SESSION, + .pattern = "[0-9]*x[0-9]*", + .default_str = "80x24" + }, + { .name = "destroy-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, @@ -255,54 +368,12 @@ const struct options_table_entry options_table[] = { .default_str = "lock -np" }, - { .name = "message-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "message-style" - }, - - { .name = "message-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 3, - .style = "message-style" - }, - - { .name = "message-command-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "message-command-style" - }, - - { .name = "message-command-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "message-command-style" - }, - - { .name = "message-command-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 3, - .style = "message-command-style" - }, - { .name = "message-command-style", .type = OPTIONS_TABLE_STYLE, .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=black,fg=yellow" }, - { .name = "message-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "message-style" - }, - { .name = "message-style", .type = OPTIONS_TABLE_STYLE, .scope = OPTIONS_TABLE_SESSION, @@ -361,30 +432,29 @@ const struct options_table_entry options_table[] = { }, { .name = "status", - .type = OPTIONS_TABLE_FLAG, + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, + .choices = options_table_status_list, .default_num = 1 }, - { .name = "status-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "status-style" - }, - { .name = "status-bg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 2, - .style = "status-style" }, { .name = "status-fg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 0, - .style = "status-style" + }, + + { .name = "status-format", + .type = OPTIONS_TABLE_STRING, + .scope = OPTIONS_TABLE_SESSION, + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_arr = options_table_status_format_default, }, { .name = "status-interval", @@ -415,27 +485,6 @@ const struct options_table_entry options_table[] = { .default_str = "[#S] " }, - { .name = "status-left-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "status-left-style" - }, - - { .name = "status-left-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 8, - .style = "status-left-style" - }, - - { .name = "status-left-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 8, - .style = "status-left-style" - }, - { .name = "status-left-length", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SESSION, @@ -460,28 +509,9 @@ const struct options_table_entry options_table[] = { { .name = "status-right", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " \"#{=21:pane_title}\" %H:%M %d-%b-%y" - }, - - { .name = "status-right-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 0, - .style = "status-right-style" - }, - - { .name = "status-right-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 8, - .style = "status-right-style" - }, - - { .name = "status-right-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_SESSION, - .default_num = 8, - .style = "status-right-style" + .default_str = "#{?window_bigger," + "[#{window_offset_x}#,#{window_offset_y}] ,}" + "\"#{=21:pane_title}\" %H:%M %d-%b-%y" }, { .name = "status-right-length", @@ -505,10 +535,11 @@ const struct options_table_entry options_table[] = { }, { .name = "update-environment", - .type = OPTIONS_TABLE_ARRAY, + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID " - "SSH_CONNECTION WINDOWID XAUTHORITY" + .flags = OPTIONS_TABLE_IS_ARRAY, + .default_str = "DISPLAY KRB5CCNAME SSH_ASKPASS SSH_AUTH_SOCK " + "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY" }, { .name = "visual-activity", @@ -535,9 +566,10 @@ const struct options_table_entry options_table[] = { { .name = "word-separators", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " -_@" + .default_str = " " }, + /* Window options. */ { .name = "aggressive-resize", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, @@ -546,13 +578,13 @@ const struct options_table_entry options_table[] = { { .name = "allow-rename", .type = OPTIONS_TABLE_FLAG, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_num = 0 }, { .name = "alternate-screen", .type = OPTIONS_TABLE_FLAG, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_num = 1 }, @@ -582,22 +614,6 @@ const struct options_table_entry options_table[] = { .default_num = 1 }, - { .name = "force-height", - .type = OPTIONS_TABLE_NUMBER, - .scope = OPTIONS_TABLE_WINDOW, - .minimum = 0, - .maximum = INT_MAX, - .default_num = 0 - }, - - { .name = "force-width", - .type = OPTIONS_TABLE_NUMBER, - .scope = OPTIONS_TABLE_WINDOW, - .minimum = 0, - .maximum = INT_MAX, - .default_num = 0 - }, - { .name = "main-pane-height", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_WINDOW, @@ -614,27 +630,6 @@ const struct options_table_entry options_table[] = { .default_num = 80 }, - { .name = "mode-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0, - .style = "mode-style" - }, - - { .name = "mode-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 3, - .style = "mode-style" - }, - - { .name = "mode-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0, - .style = "mode-style" - }, - { .name = "mode-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, @@ -684,20 +679,6 @@ const struct options_table_entry options_table[] = { .default_num = 0 }, - { .name = "pane-active-border-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "pane-active-border-style" - }, - - { .name = "pane-active-border-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 2, - .style = "pane-active-border-style" - }, - { .name = "pane-active-border-style", .type = OPTIONS_TABLE_STYLE, .scope = OPTIONS_TABLE_WINDOW, @@ -712,20 +693,6 @@ const struct options_table_entry options_table[] = { .default_num = 0 }, - { .name = "pane-border-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "pane-border-style" - }, - - { .name = "pane-border-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "pane-border-style" - }, - { .name = "pane-border-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, @@ -737,7 +704,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_status_list, - .default_num = 0 + .default_num = PANE_STATUS_OFF }, { .name = "pane-border-style", @@ -748,7 +715,7 @@ const struct options_table_entry options_table[] = { { .name = "remain-on-exit", .type = OPTIONS_TABLE_FLAG, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_num = 0 }, @@ -760,35 +727,21 @@ const struct options_table_entry options_table[] = { { .name = "window-active-style", .type = OPTIONS_TABLE_STYLE, - .scope = OPTIONS_TABLE_WINDOW, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default" }, - { .name = "window-style", - .type = OPTIONS_TABLE_STYLE, - .scope = OPTIONS_TABLE_WINDOW, - .default_str = "default" - }, - - { .name = "window-status-activity-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = GRID_ATTR_REVERSE, - .style = "window-status-activity-style" - }, - - { .name = "window-status-activity-bg", - .type = OPTIONS_TABLE_COLOUR, + { .name = "window-size", + .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-activity-style" + .choices = options_table_window_size_list, + .default_num = WINDOW_SIZE_LATEST }, - { .name = "window-status-activity-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-activity-style" + { .name = "window-style", + .type = OPTIONS_TABLE_STYLE, + .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, + .default_str = "default" }, { .name = "window-status-activity-style", @@ -797,68 +750,12 @@ const struct options_table_entry options_table[] = { .default_str = "reverse" }, - { .name = "window-status-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0, - .style = "window-status-style" - }, - - { .name = "window-status-bell-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = GRID_ATTR_REVERSE, - .style = "window-status-bell-style" - }, - - { .name = "window-status-bell-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-bell-style" - }, - - { .name = "window-status-bell-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-bell-style" - }, - { .name = "window-status-bell-style", .type = OPTIONS_TABLE_STYLE, .scope = OPTIONS_TABLE_WINDOW, .default_str = "reverse" }, - { .name = "window-status-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-style" - }, - - { .name = "window-status-current-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0, - .style = "window-status-current-style" - }, - - { .name = "window-status-current-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-current-style" - }, - - { .name = "window-status-current-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-current-style" - }, - { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, @@ -871,40 +768,12 @@ const struct options_table_entry options_table[] = { .default_str = "default" }, - { .name = "window-status-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-style" - }, - { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#I:#W#{?window_flags,#{window_flags}, }" }, - { .name = "window-status-last-attr", - .type = OPTIONS_TABLE_ATTRIBUTES, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0, - .style = "window-status-last-style" - }, - - { .name = "window-status-last-bg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-last-style" - }, - - { .name = "window-status-last-fg", - .type = OPTIONS_TABLE_COLOUR, - .scope = OPTIONS_TABLE_WINDOW, - .default_num = 8, - .style = "window-status-last-style" - }, - { .name = "window-status-last-style", .type = OPTIONS_TABLE_STYLE, .scope = OPTIONS_TABLE_WINDOW, @@ -935,5 +804,67 @@ const struct options_table_entry options_table[] = { .default_num = 1 }, + /* Hook options. */ + OPTIONS_TABLE_HOOK("after-bind-key", ""), + OPTIONS_TABLE_HOOK("after-capture-pane", ""), + OPTIONS_TABLE_HOOK("after-copy-mode", ""), + OPTIONS_TABLE_HOOK("after-display-message", ""), + OPTIONS_TABLE_HOOK("after-display-panes", ""), + OPTIONS_TABLE_HOOK("after-kill-pane", ""), + OPTIONS_TABLE_HOOK("after-list-buffers", ""), + OPTIONS_TABLE_HOOK("after-list-clients", ""), + OPTIONS_TABLE_HOOK("after-list-keys", ""), + OPTIONS_TABLE_HOOK("after-list-panes", ""), + OPTIONS_TABLE_HOOK("after-list-sessions", ""), + OPTIONS_TABLE_HOOK("after-list-windows", ""), + OPTIONS_TABLE_HOOK("after-load-buffer", ""), + OPTIONS_TABLE_HOOK("after-lock-server", ""), + OPTIONS_TABLE_HOOK("after-new-session", ""), + OPTIONS_TABLE_HOOK("after-new-window", ""), + OPTIONS_TABLE_HOOK("after-paste-buffer", ""), + OPTIONS_TABLE_HOOK("after-pipe-pane", ""), + OPTIONS_TABLE_HOOK("after-queue", ""), + OPTIONS_TABLE_HOOK("after-refresh-client", ""), + OPTIONS_TABLE_HOOK("after-rename-session", ""), + OPTIONS_TABLE_HOOK("after-rename-window", ""), + OPTIONS_TABLE_HOOK("after-resize-pane", ""), + OPTIONS_TABLE_HOOK("after-resize-window", ""), + OPTIONS_TABLE_HOOK("after-save-buffer", ""), + OPTIONS_TABLE_HOOK("after-select-layout", ""), + OPTIONS_TABLE_HOOK("after-select-pane", ""), + OPTIONS_TABLE_HOOK("after-select-window", ""), + OPTIONS_TABLE_HOOK("after-send-keys", ""), + OPTIONS_TABLE_HOOK("after-set-buffer", ""), + OPTIONS_TABLE_HOOK("after-set-environment", ""), + OPTIONS_TABLE_HOOK("after-set-hook", ""), + OPTIONS_TABLE_HOOK("after-set-option", ""), + OPTIONS_TABLE_HOOK("after-show-environment", ""), + OPTIONS_TABLE_HOOK("after-show-messages", ""), + OPTIONS_TABLE_HOOK("after-show-options", ""), + OPTIONS_TABLE_HOOK("after-split-window", ""), + OPTIONS_TABLE_HOOK("after-unbind-key", ""), + OPTIONS_TABLE_HOOK("alert-activity", ""), + OPTIONS_TABLE_HOOK("alert-bell", ""), + OPTIONS_TABLE_HOOK("alert-silence", ""), + OPTIONS_TABLE_HOOK("client-attached", ""), + OPTIONS_TABLE_HOOK("client-detached", ""), + 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_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_HOOK("window-linked", ""), + OPTIONS_TABLE_HOOK("window-pane-changed", ""), + OPTIONS_TABLE_HOOK("window-renamed", ""), + OPTIONS_TABLE_HOOK("window-unlinked", ""), + { .name = NULL } }; diff --git a/options.c b/options.c index a607dab776..6bc54ef898 100644 --- a/options.c +++ b/options.c @@ -30,23 +30,30 @@ * a red-black tree. */ +struct options_array_item { + u_int index; + union options_value value; + RB_ENTRY(options_array_item) entry; +}; +static int +options_array_cmp(struct options_array_item *a1, struct options_array_item *a2) +{ + if (a1->index < a2->index) + return (-1); + if (a1->index > a2->index) + return (1); + return (0); +} +RB_GENERATE_STATIC(options_array, options_array_item, entry, options_array_cmp); + struct options_entry { - struct options *owner; - - const char *name; - const struct options_table_entry *tableentry; - - union { - char *string; - long long number; - struct grid_cell style; - struct { - const char **array; - u_int arraysize; - }; - }; - - RB_ENTRY(options_entry) entry; + struct options *owner; + + const char *name; + const struct options_table_entry *tableentry; + union options_value value; + + RB_ENTRY(options_entry) entry; }; struct options { @@ -56,8 +63,6 @@ struct options { static struct options_entry *options_add(struct options *, const char *); -#define OPTIONS_ARRAY_LIMIT 1000 - #define OPTIONS_IS_STRING(o) \ ((o)->tableentry == NULL || \ (o)->tableentry->type == OPTIONS_TABLE_STRING) @@ -66,15 +71,18 @@ static struct options_entry *options_add(struct options *, const char *); ((o)->tableentry->type == OPTIONS_TABLE_NUMBER || \ (o)->tableentry->type == OPTIONS_TABLE_KEY || \ (o)->tableentry->type == OPTIONS_TABLE_COLOUR || \ - (o)->tableentry->type == OPTIONS_TABLE_ATTRIBUTES || \ (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_ARRAY(o) \ +#define OPTIONS_IS_COMMAND(o) \ + ((o)->tableentry != NULL && \ + (o)->tableentry->type == OPTIONS_TABLE_COMMAND) + +#define OPTIONS_IS_ARRAY(o) \ ((o)->tableentry != NULL && \ - (o)->tableentry->type == OPTIONS_TABLE_ARRAY) + ((o)->tableentry->flags & OPTIONS_TABLE_IS_ARRAY)) static int options_cmp(struct options_entry *, struct options_entry *); RB_GENERATE_STATIC(options_tree, options_entry, entry, options_cmp); @@ -92,12 +100,63 @@ options_parent_table_entry(struct options *oo, const char *s) if (oo->parent == NULL) fatalx("no parent options for %s", s); - o = options_get_only(oo->parent, s); + o = options_get(oo->parent, s); if (o == NULL) fatalx("%s not in parent options", s); return (o->tableentry); } +static void +options_value_free(struct options_entry *o, union options_value *ov) +{ + if (OPTIONS_IS_STRING(o)) + free(ov->string); + if (OPTIONS_IS_COMMAND(o) && ov->cmdlist != NULL) + cmd_list_free(ov->cmdlist); +} + +static char * +options_value_tostring(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)); + break; + case OPTIONS_TABLE_COLOUR: + s = xstrdup(colour_tostring(ov->number)); + break; + case OPTIONS_TABLE_FLAG: + if (numeric) + xasprintf(&s, "%lld", ov->number); + else + s = xstrdup(ov->number ? "on" : "off"); + break; + case OPTIONS_TABLE_CHOICE: + s = xstrdup(o->tableentry->choices[ov->number]); + break; + case OPTIONS_TABLE_STRING: + case OPTIONS_TABLE_STYLE: + case OPTIONS_TABLE_COMMAND: + fatalx("not a number option type"); + } + return (s); + } + if (OPTIONS_IS_STRING(o)) + return (xstrdup(ov->string)); + return (xstrdup("")); +} + struct options * options_create(struct options *parent) { @@ -119,6 +178,12 @@ options_free(struct options *oo) free(oo); } +void +options_set_parent(struct options *oo, struct options *parent) +{ + oo->parent = parent; +} + struct options_entry * options_first(struct options *oo) { @@ -163,6 +228,9 @@ options_empty(struct options *oo, const struct options_table_entry *oe) o = options_add(oo, oe->name); o->tableentry = oe; + if (oe->flags & OPTIONS_TABLE_IS_ARRAY) + RB_INIT(&o->value.array); + return (o); } @@ -170,17 +238,34 @@ struct options_entry * options_default(struct options *oo, const struct options_table_entry *oe) { struct options_entry *o; + union options_value *ov; + u_int i; o = options_empty(oo, oe); - if (oe->type == OPTIONS_TABLE_ARRAY) - options_array_assign(o, oe->default_str); - else if (oe->type == OPTIONS_TABLE_STRING) - o->string = xstrdup(oe->default_str); - else if (oe->type == OPTIONS_TABLE_STYLE) { - memcpy(&o->style, &grid_default_cell, sizeof o->style); - style_parse(&grid_default_cell, &o->style, oe->default_str); - } else - o->number = oe->default_num; + ov = &o->value; + + if (oe->flags & OPTIONS_TABLE_IS_ARRAY) { + if (oe->default_arr == NULL) { + options_array_assign(o, oe->default_str, NULL); + return (o); + } + for (i = 0; oe->default_arr[i] != NULL; i++) + options_array_set(o, i, oe->default_arr[i], 0, NULL); + return (o); + } + + switch (oe->type) { + 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; + } return (o); } @@ -205,17 +290,13 @@ void options_remove(struct options_entry *o) { struct options *oo = o->owner; - u_int i; - - if (OPTIONS_IS_STRING(o)) - free((void *)o->string); - else if (OPTIONS_IS_ARRAY(o)) { - for (i = 0; i < o->arraysize; i++) - free((void *)o->array[i]); - free(o->array); - } + if (OPTIONS_IS_ARRAY(o)) + options_array_clear(o); + else + options_value_free(o, &o->value); RB_REMOVE(options_tree, &oo->tree, o); + free((void *)o->name); free(o); } @@ -231,67 +312,127 @@ options_table_entry(struct options_entry *o) return (o->tableentry); } +static struct options_array_item * +options_array_item(struct options_entry *o, u_int idx) +{ + struct options_array_item a; + + a.index = idx; + return (RB_FIND(options_array, &o->value.array, &a)); +} + +static struct options_array_item * +options_array_new(struct options_entry *o, u_int idx) +{ + struct options_array_item *a; + + a = xcalloc(1, sizeof *a); + a->index = idx; + RB_INSERT(options_array, &o->value.array, a); + return (a); +} + +static void +options_array_free(struct options_entry *o, struct options_array_item *a) +{ + options_value_free(o, &a->value); + RB_REMOVE(options_array, &o->value.array, a); + free(a); +} + void options_array_clear(struct options_entry *o) { - if (OPTIONS_IS_ARRAY(o)) - o->arraysize = 0; + struct options_array_item *a, *a1; + + if (!OPTIONS_IS_ARRAY(o)) + return; + + RB_FOREACH_SAFE(a, options_array, &o->value.array, a1) + options_array_free(o, a); } -const char * +union options_value * options_array_get(struct options_entry *o, u_int idx) { + struct options_array_item *a; + if (!OPTIONS_IS_ARRAY(o)) return (NULL); - if (idx >= o->arraysize) + a = options_array_item(o, idx); + if (a == NULL) return (NULL); - return (o->array[idx]); + return (&a->value); } int options_array_set(struct options_entry *o, u_int idx, const char *value, - int append) + int append, char **cause) { - char *new; - u_int i; + struct options_array_item *a; + char *new; + struct cmd_parse_result *pr; - if (!OPTIONS_IS_ARRAY(o)) + if (!OPTIONS_IS_ARRAY(o)) { + if (cause != NULL) + *cause = xstrdup("not an array"); return (-1); + } - if (idx >= OPTIONS_ARRAY_LIMIT) - return (-1); - if (idx >= o->arraysize) { - o->array = xreallocarray(o->array, idx + 1, sizeof *o->array); - for (i = o->arraysize; i < idx + 1; i++) - o->array[i] = NULL; - o->arraysize = idx + 1; + if (value == NULL) { + a = options_array_item(o, idx); + if (a != NULL) + options_array_free(o, a); + return (0); } - new = NULL; - if (value != NULL) { - if (o->array[idx] != NULL && append) - xasprintf(&new, "%s%s", o->array[idx], 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; + else + free(pr->error); + return (-1); + case CMD_PARSE_SUCCESS: + break; + } + + a = options_array_item(o, idx); + if (a == NULL) + a = options_array_new(o, idx); + else + options_value_free(o, &a->value); + a->value.cmdlist = pr->cmdlist; + return (0); + } + + if (OPTIONS_IS_STRING(o)) { + a = options_array_item(o, idx); + if (a != NULL && append) + xasprintf(&new, "%s%s", a->value.string, value); else new = xstrdup(value); + if (a == NULL) + a = options_array_new(o, idx); + else + options_value_free(o, &a->value); + a->value.string = new; + return (0); } - free((void *)o->array[idx]); - o->array[idx] = new; - return (0); + if (cause != NULL) + *cause = xstrdup("wrong array type"); + return (-1); } int -options_array_size(struct options_entry *o, u_int *size) -{ - if (!OPTIONS_IS_ARRAY(o)) - return (-1); - if (size != NULL) - *size = o->arraysize; - return (0); -} - -void -options_array_assign(struct options_entry *o, const char *s) +options_array_assign(struct options_entry *o, const char *s, char **cause) { const char *separator; char *copy, *next, *string; @@ -300,81 +441,89 @@ options_array_assign(struct options_entry *o, const char *s) separator = o->tableentry->separator; if (separator == NULL) separator = " ,"; + if (*separator == '\0') { + if (*s == '\0') + return (0); + for (i = 0; i < UINT_MAX; i++) { + if (options_array_item(o, i) == NULL) + break; + } + return (options_array_set(o, i, s, 0, cause)); + } + if (*s == '\0') + return (0); copy = string = xstrdup(s); while ((next = strsep(&string, separator)) != NULL) { if (*next == '\0') continue; - for (i = 0; i < OPTIONS_ARRAY_LIMIT; i++) { - if (i >= o->arraysize || o->array[i] == NULL) + for (i = 0; i < UINT_MAX; i++) { + if (options_array_item(o, i) == NULL) break; } - if (i == OPTIONS_ARRAY_LIMIT) + if (i == UINT_MAX) break; - options_array_set(o, i, next, 0); + if (options_array_set(o, i, next, 0, cause) != 0) { + free(copy); + return (-1); + } } free(copy); + return (0); +} + +struct options_array_item * +options_array_first(struct options_entry *o) +{ + if (!OPTIONS_IS_ARRAY(o)) + return (NULL); + return (RB_MIN(options_array, &o->value.array)); +} + +struct options_array_item * +options_array_next(struct options_array_item *a) +{ + return (RB_NEXT(options_array, &o->value.array, a)); +} + +u_int +options_array_item_index(struct options_array_item *a) +{ + return (a->index); +} + +union options_value * +options_array_item_value(struct options_array_item *a) +{ + return (&a->value); +} + +int +options_isarray(struct options_entry *o) +{ + return (OPTIONS_IS_ARRAY(o)); } int options_isstring(struct options_entry *o) { - if (o->tableentry == NULL) - return (1); - return (OPTIONS_IS_STRING(o) || OPTIONS_IS_ARRAY(o)); + return (OPTIONS_IS_STRING(o)); } -const char * +char * options_tostring(struct options_entry *o, int idx, int numeric) { - static char s[1024]; - const char *tmp; + struct options_array_item *a; if (OPTIONS_IS_ARRAY(o)) { if (idx == -1) - return (NULL); - if ((u_int)idx >= o->arraysize || o->array[idx] == NULL) - return (""); - return (o->array[idx]); - } - if (OPTIONS_IS_STYLE(o)) - return (style_tostring(&o->style)); - if (OPTIONS_IS_NUMBER(o)) { - tmp = NULL; - switch (o->tableentry->type) { - case OPTIONS_TABLE_NUMBER: - xsnprintf(s, sizeof s, "%lld", o->number); - break; - case OPTIONS_TABLE_KEY: - tmp = key_string_lookup_key(o->number); - break; - case OPTIONS_TABLE_COLOUR: - tmp = colour_tostring(o->number); - break; - case OPTIONS_TABLE_ATTRIBUTES: - tmp = attributes_tostring(o->number); - break; - case OPTIONS_TABLE_FLAG: - if (numeric) - xsnprintf(s, sizeof s, "%lld", o->number); - else - tmp = (o->number ? "on" : "off"); - break; - case OPTIONS_TABLE_CHOICE: - tmp = o->tableentry->choices[o->number]; - break; - case OPTIONS_TABLE_STRING: - case OPTIONS_TABLE_STYLE: - case OPTIONS_TABLE_ARRAY: - break; - } - if (tmp != NULL) - xsnprintf(s, sizeof s, "%s", tmp); - return (s); + return (xstrdup("")); + a = options_array_item(o, idx); + if (a == NULL) + return (xstrdup("")); + return (options_value_tostring(o, &a->value, numeric)); } - if (OPTIONS_IS_STRING(o)) - return (o->string); - return (NULL); + return (options_value_tostring(o, &o->value, numeric)); } char * @@ -420,7 +569,7 @@ options_parse_get(struct options *oo, const char *s, int *idx, int only) } char * -options_match(const char *s, int *idx, int* ambiguous) +options_match(const char *s, int *idx, int *ambiguous) { const struct options_table_entry *oe, *found; char *name; @@ -488,7 +637,7 @@ options_get_string(struct options *oo, const char *name) fatalx("missing option %s", name); if (!OPTIONS_IS_STRING(o)) fatalx("option %s is not a string", name); - return (o->string); + return (o->value.string); } long long @@ -501,10 +650,10 @@ options_get_number(struct options *oo, const char *name) fatalx("missing option %s", name); if (!OPTIONS_IS_NUMBER(o)) fatalx("option %s is not a number", name); - return (o->number); + return (o->value.number); } -const struct grid_cell * +struct style * options_get_style(struct options *oo, const char *name) { struct options_entry *o; @@ -514,7 +663,7 @@ options_get_style(struct options *oo, const char *name) fatalx("missing option %s", name); if (!OPTIONS_IS_STYLE(o)) fatalx("option %s is not a style", name); - return (&o->style); + return (&o->value.style); } struct options_entry * @@ -531,7 +680,7 @@ 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->string, s); + xasprintf(&value, "%s%s", o->value.string, s); free(s); } else value = s; @@ -545,8 +694,8 @@ options_set_string(struct options *oo, const char *name, int append, if (!OPTIONS_IS_STRING(o)) fatalx("option %s is not a string", name); - free(o->string); - o->string = value; + free(o->value.string); + o->value.string = value; return (o); } @@ -567,7 +716,7 @@ options_set_number(struct options *oo, const char *name, long long value) if (!OPTIONS_IS_NUMBER(o)) fatalx("option %s is not a number", name); - o->number = value; + o->value.number = value; return (o); } @@ -576,17 +725,17 @@ options_set_style(struct options *oo, const char *name, int append, const char *value) { struct options_entry *o; - struct grid_cell gc; + 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)) - memcpy(&gc, &o->style, sizeof gc); + style_copy(&sy, &o->value.style); else - memcpy(&gc, &grid_default_cell, sizeof gc); - if (style_parse(&grid_default_cell, &gc, value) == -1) + 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)); @@ -596,24 +745,106 @@ options_set_style(struct options *oo, const char *name, int append, if (!OPTIONS_IS_STYLE(o)) fatalx("option %s is not a style", name); - memcpy(&o->style, &gc, sizeof o->style); + style_copy(&o->value.style, &sy); return (o); } -enum options_table_scope +int +options_scope_from_name(struct args *args, int window, + const char *name, struct cmd_find_state *fs, struct options **oo, + char **cause) +{ + struct session *s = fs->s; + struct winlink *wl = fs->wl; + struct window_pane *wp = fs->wp; + const char *target = args_get(args, 't'); + const struct options_table_entry *oe; + int scope = OPTIONS_TABLE_NONE; + + if (*name == '@') + return (options_scope_from_flags(args, window, fs, oo, cause)); + + for (oe = options_table; oe->name != NULL; oe++) { + if (strcmp(oe->name, name) == 0) + break; + } + if (oe->name == NULL) { + xasprintf(cause, "unknown option: %s", name); + return (OPTIONS_TABLE_NONE); + } + switch (oe->scope) { + case OPTIONS_TABLE_SERVER: + *oo = global_options; + scope = OPTIONS_TABLE_SERVER; + break; + case OPTIONS_TABLE_SESSION: + if (args_has(args, 'g')) { + *oo = global_s_options; + scope = OPTIONS_TABLE_SESSION; + } else if (s == NULL && target != NULL) + xasprintf(cause, "no such session: %s", target); + else if (s == NULL) + xasprintf(cause, "no current session"); + else { + *oo = s->options; + scope = OPTIONS_TABLE_SESSION; + } + break; + case OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE: + if (args_has(args, 'p')) { + if (wp == NULL && target != NULL) + xasprintf(cause, "no such pane: %s", target); + else if (wp == NULL) + xasprintf(cause, "no current pane"); + else { + *oo = wp->options; + scope = OPTIONS_TABLE_PANE; + } + break; + } + /* FALLTHROUGH */ + case OPTIONS_TABLE_WINDOW: + if (args_has(args, 'g')) { + *oo = global_w_options; + scope = OPTIONS_TABLE_WINDOW; + } else if (wl == NULL && target != NULL) + xasprintf(cause, "no such window: %s", target); + else if (wl == NULL) + xasprintf(cause, "no current window"); + else { + *oo = wl->window->options; + scope = OPTIONS_TABLE_WINDOW; + } + break; + } + return (scope); +} + +int options_scope_from_flags(struct args *args, int window, struct cmd_find_state *fs, struct options **oo, char **cause) { - struct session *s = fs->s; - struct winlink *wl = fs->wl; - const char *target= args_get(args, 't'); + struct session *s = fs->s; + struct winlink *wl = fs->wl; + struct window_pane *wp = fs->wp; + const char *target = args_get(args, 't'); if (args_has(args, 's')) { *oo = global_options; return (OPTIONS_TABLE_SERVER); } - if (window || args_has(args, 'w')) { + if (args_has(args, 'p')) { + if (wp == NULL) { + if (target != NULL) + xasprintf(cause, "no such pane: %s", target); + else + xasprintf(cause, "no current pane"); + return (OPTIONS_TABLE_NONE); + } + *oo = wp->options; + return (OPTIONS_TABLE_PANE); + } else if (window || args_has(args, 'w')) { if (args_has(args, 'g')) { *oo = global_w_options; return (OPTIONS_TABLE_WINDOW); @@ -643,44 +874,3 @@ options_scope_from_flags(struct args *args, int window, return (OPTIONS_TABLE_SESSION); } } - -void -options_style_update_new(struct options *oo, struct options_entry *o) -{ - const char *newname = o->tableentry->style; - struct options_entry *new; - - if (newname == NULL) - return; - new = options_get_only(oo, newname); - if (new == NULL) - new = options_set_style(oo, newname, 0, "default"); - - if (strstr(o->name, "-bg") != NULL) - new->style.bg = o->number; - else if (strstr(o->name, "-fg") != NULL) - new->style.fg = o->number; - else if (strstr(o->name, "-attr") != NULL) - new->style.attr = o->number; -} - -void -options_style_update_old(struct options *oo, struct options_entry *o) -{ - char newname[128]; - int size; - - size = strrchr(o->name, '-') - o->name; - - xsnprintf(newname, sizeof newname, "%.*s-bg", size, o->name); - if (options_get(oo, newname) != NULL) - options_set_number(oo, newname, o->style.bg); - - xsnprintf(newname, sizeof newname, "%.*s-fg", size, o->name); - if (options_get(oo, newname) != NULL) - options_set_number(oo, newname, o->style.fg); - - xsnprintf(newname, sizeof newname, "%.*s-attr", size, o->name); - if (options_get(oo, newname) != NULL) - options_set_number(oo, newname, o->style.attr); -} diff --git a/osdep-darwin.c b/osdep-darwin.c index 9956ab4588..d4a88028e9 100644 --- a/osdep-darwin.c +++ b/osdep-darwin.c @@ -30,7 +30,9 @@ char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); +#ifndef __unused #define __unused __attribute__ ((__unused__)) +#endif char * osdep_get_name(int fd, __unused char *tty) @@ -47,6 +49,7 @@ osdep_get_name(int fd, __unused char *tty) &bsdinfo, sizeof bsdinfo); if (ret == sizeof bsdinfo && *bsdinfo.pbsi_comm != '\0') return (strdup(bsdinfo.pbsi_comm)); + return (NULL); #else int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; size_t size; @@ -88,11 +91,17 @@ osdep_get_cwd(int fd) struct event_base * osdep_event_init(void) { + struct event_base *base; + /* * On OS X, kqueue and poll are both completely broken and don't * work on anything except socket file descriptors (yes, really). */ setenv("EVENT_NOKQUEUE", "1", 1); setenv("EVENT_NOPOLL", "1", 1); - return (event_init()); + + base = event_init(); + unsetenv("EVENT_NOKQUEUE"); + unsetenv("EVENT_NOPOLL"); + return (base); } diff --git a/osdep-freebsd.c b/osdep-freebsd.c index 067ba56533..a7d0293035 100644 --- a/osdep-freebsd.c +++ b/osdep-freebsd.c @@ -31,6 +31,8 @@ #include #include +#include "compat.h" + struct kinfo_proc *cmp_procs(struct kinfo_proc *, struct kinfo_proc *); char *osdep_get_name(int, char *); char *osdep_get_cwd(int); @@ -193,10 +195,15 @@ osdep_get_cwd(int fd) struct event_base * osdep_event_init(void) { + struct event_base *base; + /* * On some versions of FreeBSD, kqueue doesn't work properly on tty * file descriptors. This is fixed in recent FreeBSD versions. */ setenv("EVENT_NOKQUEUE", "1", 1); - return (event_init()); + + base = event_init(); + unsetenv("EVENT_NOKQUEUE"); + return (base); } diff --git a/osdep-linux.c b/osdep-linux.c index 42712dea30..5f0d9352ce 100644 --- a/osdep-linux.c +++ b/osdep-linux.c @@ -92,7 +92,12 @@ osdep_get_cwd(int fd) struct event_base * osdep_event_init(void) { + struct event_base *base; + /* On Linux, epoll doesn't work on /dev/null (yes, really). */ setenv("EVENT_NOEPOLL", "1", 1); - return (event_init()); + + base = event_init(); + unsetenv("EVENT_NOEPOLL"); + return (base); } diff --git a/osdep-netbsd.c b/osdep-netbsd.c index 823eeebf87..6789417502 100644 --- a/osdep-netbsd.c +++ b/osdep-netbsd.c @@ -23,14 +23,17 @@ #include #include +#include #include #include #include +#include "tmux.h" + #define is_runnable(p) \ - ((p)->p_stat == LSRUN || (p)->p_stat == SIDL) + ((p)->p_stat == LSRUN || (p)->p_stat == SIDL) #define is_stopped(p) \ - ((p)->p_stat == SSTOP || (p)->p_stat == SZOMB) + ((p)->p_stat == SSTOP || (p)->p_stat == SZOMB) struct kinfo_proc2 *cmp_procs(struct kinfo_proc2 *, struct kinfo_proc2 *); char *osdep_get_name(int, char *); @@ -129,6 +132,37 @@ osdep_get_name(int fd, __unused char *tty) char * osdep_get_cwd(int fd) { + static char target[PATH_MAX + 1]; + pid_t pgrp; +#ifdef KERN_PROC_CWD + int mib[4]; + size_t len; +#else + char *path; + ssize_t n; +#endif + + if ((pgrp = tcgetpgrp(fd)) == -1) + return (NULL); + +#ifdef KERN_PROC_CWD + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = pgrp; + mib[3] = KERN_PROC_CWD; + len = sizeof(target); + if (sysctl(mib, __arraycount(mib), target, &len, NULL, 0) == 0) + return (target); +#else + xasprintf(&path, "/proc/%lld/cwd", (long long) pgrp); + n = readlink(path, target, sizeof(target) - 1); + free(path); + if (n > 0) { + target[n] = '\0'; + return (target); + } +#endif + return (NULL); } diff --git a/paste.c b/paste.c index 2f2c043b6b..c1036ad968 100644 --- a/paste.c +++ b/paste.c @@ -157,11 +157,14 @@ paste_free(struct paste_buffer *pb) * that the caller is responsible for allocating data. */ void -paste_add(char *data, size_t size) +paste_add(const char *prefix, char *data, size_t size) { struct paste_buffer *pb, *pb1; u_int limit; + if (prefix == NULL) + prefix = "buffer"; + if (size == 0) { free(data); return; @@ -180,7 +183,7 @@ paste_add(char *data, size_t size) pb->name = NULL; do { free(pb->name); - xasprintf(&pb->name, "buffer%04u", paste_next_index); + xasprintf(&pb->name, "%s%u", prefix, paste_next_index); paste_next_index++; } while (paste_get_name(pb->name) != NULL); @@ -262,7 +265,7 @@ paste_set(char *data, size_t size, const char *name, char **cause) return (0); } if (name == NULL) { - paste_add(data, size); + paste_add(NULL, data, size); return (0); } diff --git a/regress/Makefile b/regress/Makefile index 3775a711c2..e6c3619fc4 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -7,3 +7,4 @@ all: $(TESTS) $(TESTS): sh $*.sh + sleep 1 diff --git a/regress/command-order.sh b/regress/command-order.sh new file mode 100644 index 0000000000..04046f0d07 --- /dev/null +++ b/regress/command-order.sh @@ -0,0 +1,51 @@ +#!/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 + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +cat <$TMP +new -sfoo -nfoo0; neww -nfoo1; neww -nfoo2 +new -sbar -nbar0; neww -nbar1; neww -nbar2 +EOF +$TMUX -f$TMP start $TMP || exit 1 +$TMUX kill-server 2>/dev/null +cat <$TMP +new -sfoo -nfoo0 +neww -nfoo1 +neww -nfoo2 +new -sbar -nbar0 +neww -nbar1 +neww -nbar2 +EOF +$TMUX -f$TMP start $TMP || exit 1 +$TMUX kill-server 2>/dev/null +cat </dev/null + +for i in conf/*.conf; do + $TMUX -f/dev/null start \; source -n $i || exit 1 +done + +exit 0 diff --git a/regress/conf/21867280ff7e99631046f9cc669b80d2.conf b/regress/conf/21867280ff7e99631046f9cc669b80d2.conf new file mode 100644 index 0000000000..43b142b409 --- /dev/null +++ b/regress/conf/21867280ff7e99631046f9cc669b80d2.conf @@ -0,0 +1,8 @@ +%if #{l:1} +set -g status-style fg=cyan,bg='#001040' +%elif #{l:1} +set -g status-style fg=white,bg='#400040' +%else +set -g status-style fg=white,bg='#800000' +%endif +bind ^X last-window diff --git a/regress/conf/29813ff35544434e2e64dc879a8dd274.conf b/regress/conf/29813ff35544434e2e64dc879a8dd274.conf new file mode 100644 index 0000000000..d0bda4a2ce --- /dev/null +++ b/regress/conf/29813ff35544434e2e64dc879a8dd274.conf @@ -0,0 +1,58 @@ +set -g prefix C-g +# needed for e.g. mutt +bind C-g send-prefix + +set -g set-titles on +set -g status-position top +set -g status-keys vi +set -g mode-keys vi +set -g base-index 1 +set -g pane-base-index 1 +set -g focus-events on + +set history-file ~/.tmux_SSH_history +set focus-events on +set -g history-limit 100000 +set -s set-clipboard on +set -g display-time 3000 +set -g display-panes-time 3000 + +set -g pane-border-status top + +setw -g window-status-current-style bg=colour240,fg=colour250 +setw -g window-status-separator "|" +set -g status-bg colour235 +set -g status-fg colour245 + +set -g window-status-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" +set -g window-status-current-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" +set -g pane-border-format " #P: #{s/ //:pane_title} " + +set -g renumber-windows on +set -g status-right-length 0 +############################################################## + +# I prefer not to have a status for my tabbed term +set -g status-right "" +set -g status-right-length 0 +set -g status-left-length 0 +set -g status-left "" + +# some settings for "navigation" +bind -n C-PageUp copy-mode -u +unbind -n C-Left +unbind -n C-Right +bind -n C-Left select-window -t :- +bind -n C-Right select-window -t :+ + +# I prefer a tiled layout and easy joining of current active pane via windows' +# index +bind F1 join-pane -s 1.\; select-layout tiled +bind F2 join-pane -s 2.\; select-layout tiled +bind F3 join-pane -s 3.\; select-layout tiled +bind F4 join-pane -s 4.\; select-layout tiled +bind F5 join-pane -s 5.\; select-layout tiled +bind F6 join-pane -s 6.\; select-layout tiled +bind F7 join-pane -s 7.\; select-layout tiled +bind F8 join-pane -s 8.\; select-layout tiled +bind F9 join-pane -s 9.\; select-layout tiled diff --git a/regress/conf/2eae5d47049c1f6d0bef3db4e171aed7.conf b/regress/conf/2eae5d47049c1f6d0bef3db4e171aed7.conf new file mode 100644 index 0000000000..c09adc24fe --- /dev/null +++ b/regress/conf/2eae5d47049c1f6d0bef3db4e171aed7.conf @@ -0,0 +1,56 @@ +# 256 colors for vim +set -g default-terminal "screen-256color" + +# Set default shell to zsh +set-option -g default-shell /bin/zsh + +# Start window numbering at 1 +set-option -g base-index 1 +set-window-option -g pane-base-index 1 + +# Cycle panes with C-b C-b +unbind ^B +bind ^B select-pane -t :.+ + +# Reload config wtih a key +bind-key r source-file ~/.tmux.conf \; display "Config reloaded!" + +# Mouse works as expected +# set -g mode-mouse on +# set -g mouse-select-pane on +# set -g mouse-resize-pane on +# set -g mouse-select-window on + +# Scrolling works as expected +set -g terminal-overrides 'xterm*:smcup@:rmcup@' + +# Use the system clipboard +# set-option -g default-command "reattach-to-user-namespace -l zsh" + +# Clear the pane and its history +bind -n C-k send-keys C-l \; clear-history + +# smart pane switching with awareness of vim splits +bind -n C-h run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-h) || tmux select-pane -L" +bind -n C-j run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-j) || tmux select-pane -D" +bind -n C-k run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-k) || tmux select-pane -U" +bind -n C-l run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-l) || tmux select-pane -R" +bind -n C-\ run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys 'C-\\') || tmux select-pane -l" + +# C-l is taken oer by vim style pane navigation +bind C-l send-keys 'C-l' + +# Use vim keybindings in copy mode +setw -g mode-keys vi + +# Setup 'v' to begin selection as in Vim +# bind-key -t vi-copy v begin-selection +# bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy" + +# Update default binding of `Enter` to also use copy-pipe +# unbind -t vi-copy Enter +# bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy" + +# Powerline +run-shell "powerline-daemon -q" +source "/Users/adamcooper/Library/Python/3.7/lib/python/site-packages/powerline/bindings/tmux/powerline.conf" \ No newline at end of file diff --git a/regress/conf/327af72ad372255817b585a74da06eda.conf b/regress/conf/327af72ad372255817b585a74da06eda.conf new file mode 100644 index 0000000000..2a719c8b31 --- /dev/null +++ b/regress/conf/327af72ad372255817b585a74da06eda.conf @@ -0,0 +1,30 @@ +set -sg escape-time 10 + +set -g default-terminal tmux-256color +set -g prefix ^X +set -g history-limit 10000 +setw -g mode-keys vi +setw -g xterm-keys off + +# black, red, green, yellow, blue, magenta, cyan, white, default. +setw -g message-command-style fg=yellow,bg=black +setw -g message-style fg=black,bg=yellow + +%if #{m:*mydomain*,#{host}} +set -g status-style fg=cyan,bg='#001040' +setw -g window-status-current-style fg='#f0f0f0',bg='#001040' +%elif #{||:#{m:*somedomain*,#{host}},#{m:*otherdomain*,#{host}}} +set -g status-style fg=white,bg='#400040' +setw -g window-status-current-style fg=yellow,bg='#400040',bright +%else +set -g status-style fg=white,bg='#800000' +setw -g window-status-current-style fg=brightwhite,bg='#800000' +%endif + +unbind ^B +bind ^X last-window +bind x send-prefix +bind ^C new-window +bind ^D detach-client +bind ^N next-window +bind ^P previous-window diff --git a/regress/conf/58304907c117cab9898ea0b070bccde3.conf b/regress/conf/58304907c117cab9898ea0b070bccde3.conf new file mode 100644 index 0000000000..c9ce3fa48c --- /dev/null +++ b/regress/conf/58304907c117cab9898ea0b070bccde3.conf @@ -0,0 +1,118 @@ +# +# Tureba's tmux.conf +# +# To use it, either: +# a) link ~/.tmux.conf to it; or +# b) create a ~/.tmux.conf that sources it. +# +# who: Arthur Nascimento +# where: github.com/tureba/myconfigfiles +# + +# defaults +set -g default-shell /bin/zsh +set -g default-command zsh +# tmux sets screen/screen-256, but has no codes for italics +set -g default-terminal tmux-256color +# linux terminal doesn't need this, but xterm does +set -g terminal-overrides 'xterm*:smcup@:rmcup@,*256col*:colors=256,xterm*:XT' +# xterm-style function key sequences +setw -g xterm-keys on + +# 1, 2 and 3 are closer together than 0, 1 and 2 +set -g base-index 1 +set -g pane-base-index 1 + +# easier to type than C-b +set -g prefix C-a +set -g prefix2 C-b +unbind C-b +bind C-a send-prefix + +# for repeatable keys +set -g repeat-time 170 + +# status bar +set -g status-style fg=green,bg=colour234 +set -g status-right-style bg=colour236 +set -g status-right "#[bold,fg=blue][#[fg=default]#T#[fg=blue]]#[nobold,fg=default] | #[fg=yellow]%F %R" +set -g status-right-length 120 +set -g status-left-style bg=colour236,bright +set -g status-left "#[fg=blue][#[fg=default]#h#[fg=cyan]:#[fg=default]#S#[fg=blue]]" +set -g status-left-length 30 +setw -g window-status-style fg=green +setw -g window-status-format " #I#[nobold]:#W " +setw -g window-status-current-style fg=green,bright +setw -g window-status-current-format "#[fg=red][#[fg=default]#I:#W#[fg=red]]" +setw -g window-status-separator "|" +setw -g window-status-activity-style blink +setw -g window-status-bell-style blink +setw -g window-status-last-style bright + +# enable wm window titles +set -g set-titles on + +# auto window rename +setw -g automatic-rename on +# auto window resize +setw -g aggressive-resize on + +# mouse settings +set -g mouse on + +# var|bind \ cmd | vim | less | copy | zsh +# pane_in_mode | 0 | 0 | 1 | 0 +# mouse_any_flag | 1 | 0 | 0 | 0 +# alternate_on | 1 | 1 | 0 | 0 +# WheelUpPane | send -M | send Up | * | send Up (** or copy-mode -e) +# WheelDownPane | send -M | send Down | * | send Down +# * panes in copy mode have scroll handled by different bindings + +# ** cycle over shell history +#bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Up' + +# ** enter copy mode +bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'if -Ft= "#{alternate_on}" "send -t= Up" "copy-mode -et="' + +bind -T root WheelDownPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Down' + +# sensible v/h splits +unbind % +unbind '"' +bind | split-window -h +bind - split-window -v + +# hjkl pane traversal +bind -r h select-pane -L +bind -r j select-pane -D +bind -r k select-pane -U +bind -r l select-pane -R + +# window navigation +unbind p +bind -r [ previous-window +unbind n +bind -r ] next-window + +# Vi copypaste mode +setw -g mode-keys vi +bind C-c copy-mode +bind p paste-buffer +bind -T copy-mode-vi v send-keys -X begin-selection +bind -T copy-mode-vi y send-keys -X copy-selection +bind -T copy-mode-vi V send-keys -X rectangle-toggle + +# toggle window activity monitoring +bind m setw monitor-activity + +# reload the configuration +bind r source-file ~/.tmux.conf + +# toggle synchronize-panes +bind S setw synchronize-panes + +# create a new window with exactly this command +bind C command-prompt "new-window 'exec %%'" + +# (toggle) mark this pane for easier joins and swaps +bind . select-pane -m diff --git a/regress/conf/91378fd400b0444eb8cac471e30642b3.conf b/regress/conf/91378fd400b0444eb8cac471e30642b3.conf new file mode 100644 index 0000000000..4081007657 --- /dev/null +++ b/regress/conf/91378fd400b0444eb8cac471e30642b3.conf @@ -0,0 +1,30 @@ +### + +if-shell " \ + tmux -V \ + | awk '{print $2}' \ + | awk -F - '{print $1}' \ + | awk '{ \ + if ($1 ~ /^[[:digit:].]+$/) { \ + exit !($1 >= 2.6) \ + } else { \ + exit !($1 == \"master\" || $1 == \"next\") \ + } \ + }'" \ + "source-file ~/.tmux/v2rc" \ + "source-file ~/.tmux/v1rc" \ + ; + +### + +set-option -qg status-left \ + "[#[fg=yellow]#{session_name}#[default]] #[fg=colour060]#{host_short}#[default]:#[fg=colour151]#{b:pane_current_path} #[fg=colour099]#(git -C #{pane_current_path} symbolic-ref --short HEAD) #[fg=green]#(git -C #{pane_current_path} status --porcelain --untracked-files=no | cut -b 1-1 | sort | uniq | awk '/^[^[:space:]]/ {printf\(\"%%s\", $0\)}')#[fg=red]#(git -C #{pane_current_path} status --porcelain --untracked-files=no | cut -b 2-2 | sort | uniq | awk '/^[^[:space:]]/ {printf\(\"%%s\", $0\)}')#[fg=colour113]#(git -C #{pane_current_path} stash list 2>/dev/null | wc -l | tr -d '\n' | sed s,^0\$,,) #[default]" + +set-option -qg status-right \ + "#[default] ┊ #[fg=colour065]#(grep ^MemFree /proc/meminfo | awk '{print rshift\($2, 10\)}')#[fg=colour071]m #[default]┊ #[fg=colour101]#(echo \"\(`awk '{print \$1}' /proc/loadavg` / `grep ^processor /proc/cpuinfo | wc -l`\) \* 100\" | bc -ql | sed 's,\\..*,,' | awk '{printf\(\"%%2u\", $0\)}')#[fg=colour102]%% " + +set-option -qwg window-status-current-format \ + "#[fg=colour208]»#[fg=colour190]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" + +set-option -qwg window-status-format \ + "#[default]»#[fg=colour066]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" diff --git a/regress/conf/99749670b62bcb99a9b2e3d59708e357.conf b/regress/conf/99749670b62bcb99a9b2e3d59708e357.conf new file mode 100644 index 0000000000..dd1700b0c6 --- /dev/null +++ b/regress/conf/99749670b62bcb99a9b2e3d59708e357.conf @@ -0,0 +1,93 @@ +# ----------------------------------------------------------------------------- +# This config is targeted for tmux 2.1+ and should be placed in $HOME. +# +# Read the "Plugin Manager" section (bottom) before trying to use this config! +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Global options +# ----------------------------------------------------------------------------- + +# Set a new prefix / leader key. +set -g prefix ` +bind ` send-prefix + +# Allow opening multiple terminals to view the same session at different sizes. +setw -g aggressive-resize on + +# Remove delay when switching between Vim modes. +set -s escape-time 0 + +# Allow Vim's FocusGained to work when your terminal gains focus. +# Requires Vim plugin: https://github.com/tmux-plugins/vim-tmux-focus-events +set -g focus-events on + +# Add a bit more scroll history in the buffer. +set -g history-limit 50000 + +# Enable color support inside of tmux. +set -g default-terminal "screen-256color" + +# Ensure window titles get renamed automatically. +setw -g automatic-rename + +# Start windows and panes index at 1, not 0. +set -g base-index 1 +setw -g pane-base-index 1 + +# Enable full mouse support. +set -g mouse on + +# Status bar optimized for Gruvbox. +set -g status-fg colour244 +set -g status-bg default +set -g status-left '' +set -g status-right-length 0 +#set -g status-right-length 20 +#set -g status-right '%a %Y-%m-%d %H:%M' + +set -g pane-border-fg default +set -g pane-border-bg default +set -g pane-active-border-fg colour250 +set -g pane-active-border-bg default + +set-window-option -g window-status-current-attr bold +set-window-option -g window-status-current-fg colour223 + +# ----------------------------------------------------------------------------- +# Key bindings +# ----------------------------------------------------------------------------- + +# Unbind default keys +unbind C-b +unbind '"' +unbind % + +# Reload the tmux config. +bind-key r source-file ~/.tmux.conf + +# Split panes. +bind-key h split-window -v +bind-key v split-window -h + +# Move around panes with ALT + arrow keys. +bind-key -n M-Up select-pane -U +bind-key -n M-Left select-pane -L +bind-key -n M-Down select-pane -D +bind-key -n M-Right select-pane -R + +# ----------------------------------------------------------------------------- +# Plugin Manager - https://github.com/tmux-plugins/tpm +# In order to use the plugins below you need to install TPM and the plugins. +# Step 1) git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm +# Step 2) Reload tmux if it's already started with `r +# Step 3) Launch tmux and hit `I (capital i) to fetch any plugins +# ----------------------------------------------------------------------------- + +# List of plugins. +set -g @plugin 'tmux-plugins/tpm' +set -g @plugin 'tmux-plugins/tmux-resurrect' +set -g @plugin 'tmux-plugins/tmux-yank' + +# Initialize TPM (keep this line at the very bottom of your tmux.conf). +run -b '~/.tmux/plugins/tpm/tpm' diff --git a/regress/conf/a46e6e84cd1071105aa807256dbc158d.conf b/regress/conf/a46e6e84cd1071105aa807256dbc158d.conf new file mode 100644 index 0000000000..bfbb2d3e78 --- /dev/null +++ b/regress/conf/a46e6e84cd1071105aa807256dbc158d.conf @@ -0,0 +1,432 @@ +# Dynamic configuration file generated by ~/Makefile from /home/sunny/.tmux.conf.erb +# +# DO NOT EDIT THIS FILE BY HAND -- +# YOUR CHANGES WILL BE OVERWRITTEN +# + + + +bind-key R source ~/.tmux.conf \; display-message 'config reloaded!' + +#----------------------------------------------------------------------------- +# terminal +#----------------------------------------------------------------------------- + +# enable mouse support for general selection and control +set-option -g mouse on + +# auto-set terminal title to current window pane's title +set-option -g set-titles on + +# enable 256-color support for pretty colorschemes in Vim +set-option -g default-terminal 'screen-256color' + +# allow Vim to receive focus events from terminal window +set-option -g focus-events on + +# allow Vim to recieve modifier keys: Shift, Control, Alt +set-window-option -g xterm-keys on + +# prevent tmux from catching modifier keys meant for Vim +set-option -s escape-time 0 + +# enable 24-bit true color RGB escape sequences under st +# https://sunaku.github.io/tmux-24bit-color.html +set-option -ga terminal-overrides ',st-256color:Tc' +set-option -ga terminal-overrides ',xterm-256color:Tc' # hterm (ChromeOS) + +# allow set-titles to change the window title under XTerm +# http://opennomad.com/content/goodbye-screen-hello-tmux +set-option -ga terminal-overrides ',xterm*:XT' + +# allow set-titles to change the window title under XTerm +# http://opennomad.com/content/goodbye-screen-hello-tmux +# http://stackoverflow.com/questions/15195624 +set-option -ga terminal-overrides ',st-256color:smkx=\E=' + +# yank to system clipboard rather than primary selection +# http://invisible-island.net/xterm/terminfo-contents.html#tic-xterm_tmux +set-option -ga terminal-overrides ',xterm*:Ms=\E]52;c;%p2%s\007' + +# KiTTY always appends to clipboard; must clear it first +# https://sw.kovidgoyal.net/kitty/protocol-extensions.html#pasting-to-clipboard +set-option -ga terminal-overrides ',xterm-kitty:Ms=\E]52;c;!\007\E]52;c;%p2%s\007' + +# prevent standout from appearing as italics under URxvt +# http://comments.gmane.org/gmane.comp.terminal-emulators.tmux.user/1927 +set-option -ga terminal-overrides ',rxvt-unicode*:sitm@' + +#----------------------------------------------------------------------------- +# appearance +#----------------------------------------------------------------------------- + +# Colors from the "lucius" and "gruvbox" themes in the vim-airline plugin: +# https://github.com/bling/vim-airline/blob/master/autoload/airline/themes/lucius.vim +# https://github.com/morhetz/gruvbox/blob/master/autoload/airline/themes/gruvbox.vim + +set-option -g status-style fg=colour246,bg=colour237 +set-window-option -g window-status-current-style fg=colour214,bg=colour239 +set-option -g pane-border-style fg=colour239 +set-option -g pane-active-border-style fg=colour208 +set-option -g message-style fg=colour214,bg=colour239 +set-window-option -g mode-style fg=colour214,bg=colour239,bold,reverse + +# Common UI interaction cues from Blueprint CSS: +# http://blueprintcss.org/tests/parts/forms.html +set-window-option -g window-status-bell-style 'bg=#205791,fg=#d5edf8' # info (blue) +set-window-option -g window-status-activity-style 'bg=#8a1f11,fg=#fbe3e4' # error (red) + +#----------------------------------------------------------------------------- +# status bar +#----------------------------------------------------------------------------- + +# toggle status bar visibility +bind-key -n M-` set-option -g status + +# toggle status bar position +bind-key -n M-~ \ + if-shell 'tmux show-option -g status-position | grep -q top$' \ + 'set-option -g status-position bottom' \ + 'set-option -g status-position top' + +# put status bar at the top of the screen +set-option -g status-position top + +# list windows on left side of status bar +set-option -g status-left-length 0 + +# make window list easier to scan +set-window-option -g window-status-format ' #[bold]#I#F#[nobold]#W ' +set-window-option -g window-status-current-format ' #[bold]#I#F#[nobold]#W ' +set-window-option -g window-status-separator '' + +# show pane title, pane identifier, and hostname on right side of status bar +set-option -g status-right-length 64 +set-option -g status-right '#{=32:pane_title} \ +#[fg=colour214,bg=colour239] #S:#I.#P \ +#(test -n "$SSH_TTY" && echo "#[fg=colour214,bg=colour239,bold,reverse] #H ")' + +#----------------------------------------------------------------------------- +# windows +#----------------------------------------------------------------------------- + +# create window +bind-key -n M-e new-window + +# rename window +bind-key -n M-E command-prompt -I '#W' 'rename-window "%%%"' + +set-window-option -g automatic-rename off + + +# break off pane to a new window +bind-key -n M-x \ + command-prompt -p 'break-pane:' -I '#W' \ + 'break-pane ; rename-window "%%%"' +bind-key -n M-X break-pane + +# focus window +bind-key -n M-, previous-window +bind-key -n M-. next-window +bind-key -n M-o last-window + +# focus by number +set-option -g base-index 1 +set-window-option -g pane-base-index 1 +set-option -g renumber-windows on +bind-key -n M-0 choose-window +bind-key -n M-1 select-window -t :1 +bind-key -n M-2 select-window -t :2 +bind-key -n M-3 select-window -t :3 +bind-key -n M-4 select-window -t :4 +bind-key -n M-5 select-window -t :5 +bind-key -n M-6 select-window -t :6 +bind-key -n M-7 select-window -t :7 +bind-key -n M-8 select-window -t :8 +bind-key -n M-9 select-window -t :1 \; select-window -t :-1 + +# swap window +bind-key -n M-< swap-window -t :-1 +bind-key -n M-> swap-window -t :+1 + +# monitor window +set-option -g visual-activity on +set-option -g visual-silence on + + +bind-key -n M-k \ + set-window-option monitor-activity \;\ + display-message 'monitor-activity #{?monitor-activity,on,off}' + +bind-key -n M-K \ + if-shell 'tmux show-window-option -g monitor-activity | grep -q off$' \ + 'set-window-option -g monitor-activity on' \ + 'set-window-option -g monitor-activity off' \;\ + display-message 'monitor-activity #{?monitor-activity,on,off} (global)' + +bind-key -n M-j \ + command-prompt -p 'monitor-silence (seconds):' -I '#{monitor-silence}' \ + 'set-window-option monitor-silence %% ;\ + display-message "monitor-silence #{?monitor-silence,on,off}"' + +#----------------------------------------------------------------------------- +# panes +#----------------------------------------------------------------------------- + +# send input to all panes in window (toggle) +bind-key C-a \ + set-option synchronize-panes \;\ + display-message 'synchronize-panes #{?synchronize-panes,on,off}' + +# clear the screen in all panes in window +bind-key C-l \ + set-option synchronize-panes on \;\ + send-keys C-l \;\ + set-option synchronize-panes off + +# create pane (below, above, left, right) +bind-key -n M-c split-window -c '#{pane_current_path}' +bind-key -n M-C split-window -c '#{pane_current_path}' -b +bind-key -n M-R split-window -c '#{pane_current_path}' -b -h +bind-key -n M-r split-window -c '#{pane_current_path}' -h + +# join pane (above, left, below, right) +bind-key -n M-g move-pane -t .-1 -s . # join pane at bottom of prev pane +bind-key -n M-l move-pane -t .-1 -s . -h # join pane at right of prev pane +bind-key -n M-G move-pane -d -s .+1 -t . # join next pane at bottom +bind-key -n M-L move-pane -d -s .+1 -t . -h # join next pane at right + +# Intelligently navigate tmux panes and Vim splits using the same keys. +# See https://sunaku.github.io/tmux-select-pane.html for documentation. +# +# +-------------+------------+-----------------------------+ +# | inside Vim? | is Zoomed? | Action taken by key binding | +# +-------------+------------+-----------------------------+ +# | No | No | Focus directional tmux pane | +# | No | Yes | Nothing: ignore key binding | +# | Yes | No | Seamlessly focus Vim / tmux | +# | Yes | Yes | Focus directional Vim split | +# +-------------+------------+-----------------------------+ +# +vim_navigation_timeout=0.05 # number of seconds we give Vim to navigate +navigate=' \ + pane_is_zoomed() { \ + test #{window_zoomed_flag} -eq 1; \ + }; \ + pane_title_changed() { \ + test "#{pane_title}" != "$(tmux display -p "##{pane_title}")"; \ + }; \ + command_is_vim() { \ + case "${1%% *}" in \ + (vi|?vi|vim*|?vim*|view|?view|vi??*) true ;; \ + (*) false ;; \ + esac; \ + }; \ + pane_contains_vim() { \ + case "#{=3:pane_current_command}" in \ + (git|ssh|sh) command_is_vim "#{=5:pane_title}" ;; \ + (*) command_is_vim "#{=5:pane_current_command}" ;; \ + esac; \ + }; \ + pane_contains_neovim_terminal() { \ + test "#{=12:pane_title}" = "nvim term://"; \ + }; \ + navigate() { \ + tmux_navigation_command=$1; \ + vim_navigation_command=$2; \ + vim_navigation_only_if=${3:-true}; \ + if pane_contains_vim && eval "$vim_navigation_only_if"; then \ + if pane_contains_neovim_terminal; then \ + tmux send-keys C-\\ C-n; \ + fi; \ + eval "$vim_navigation_command"; \ + if ! pane_is_zoomed; then \ + sleep $vim_navigation_timeout; : wait for Vim to change title; \ + if ! pane_title_changed; then \ + eval "$tmux_navigation_command"; \ + fi; \ + fi; \ + elif ! pane_is_zoomed; then \ + eval "$tmux_navigation_command"; \ + fi; \ + }; \ +navigate ' +navigate_left=" $navigate 'tmux select-pane -L' 'tmux send-keys C-w h'" +navigate_down=" $navigate 'tmux select-pane -D' 'tmux send-keys C-w j'" +navigate_up=" $navigate 'tmux select-pane -U' 'tmux send-keys C-w k'" +navigate_right="$navigate 'tmux select-pane -R' 'tmux send-keys C-w l'" +navigate_back=" $navigate 'tmux select-pane -l || tmux select-pane -t1'\ + 'tmux send-keys C-w p' \ + 'pane_is_zoomed' " + +## QWERTY keys - comment these out if you don't use QWERTY layout! +#bind-key -n M-h run-shell -b "$navigate_left" +#bind-key -n M-j run-shell -b "$navigate_down" +#bind-key -n M-k run-shell -b "$navigate_up" +#bind-key -n M-l run-shell -b "$navigate_right" +#bind-key -n M-\ run-shell -b "$navigate_back" + +# Dvorak keys - comment these out if you don't use Dvorak layout! +bind-key -n M-d run-shell -b "$navigate_back" +bind-key -n M-h run-shell -b "$navigate_left" +bind-key -n M-t run-shell -b "$navigate_up" +bind-key -n M-n run-shell -b "$navigate_down" +bind-key -n M-s run-shell -b "$navigate_right" + +# resize pane +bind-key -r H resize-pane -L 5 +bind-key -r T resize-pane -U 5 +bind-key -r N resize-pane -D 5 +bind-key -r S resize-pane -R 5 + +# zoom pane +bind-key -n M-m resize-pane -Z + +# swap pane +bind-key -n M-- swap-pane -D +bind-key -n M-_ swap-pane -U +bind-key -n M-D run-shell 'tmux select-pane -l \; swap-pane -d -s #D' +bind-key -n M-H run-shell 'tmux select-pane -L \; swap-pane -d -s #D' +bind-key -n M-T run-shell 'tmux select-pane -U \; swap-pane -d -s #D' +bind-key -n M-N run-shell 'tmux select-pane -D \; swap-pane -d -s #D' +bind-key -n M-S run-shell 'tmux select-pane -R \; swap-pane -d -s #D' + +# attach by number +bind-key -n 'M-!' join-pane -t :1 +bind-key -n 'M-@' join-pane -t :2 +bind-key -n 'M-#' join-pane -t :3 +bind-key -n 'M-$' join-pane -t :4 +bind-key -n 'M-%' join-pane -t :5 +bind-key -n 'M-^' join-pane -t :6 +bind-key -n 'M-&' join-pane -t :7 +bind-key -n 'M-*' join-pane -t :8 +bind-key -n 'M-(' run-shell 'tmux select-window -t :1 \;\ + select-window -t :-1 \;\ + join-pane -s "#{pane_id}"' +bind-key -n 'M-)' choose-window 'join-pane -t "%%%"' + +# promote pane (toggle) +bind-key -n M-Enter \ + if-shell 'test #P -ne 1' \ + 'select-pane -t 1' \ + 'last-pane; swap-pane -s 1' + +# rotate panes +bind-key -n M-a rotate-window -D +bind-key -n M-A rotate-window -U + +#----------------------------------------------------------------------------- +# layouts +#----------------------------------------------------------------------------- + +bind-key M-w select-layout main-horizontal +bind-key M-W select-layout even-vertical +bind-key M-v select-layout main-vertical +bind-key M-V select-layout even-horizontal +bind-key M-z select-layout tiled + +# half-screen tiling layouts (horizontal, vertical) +# https://sunaku.github.io/tmux-half-screen-tiling-layouts.html +bind-key -n M-w select-layout main-horizontal \;\ + run-shell 'tmux resize-pane -t 1 -y $(( #{window_height} / 2 ))' +bind-key -n M-v select-layout main-vertical \;\ + run-shell 'tmux resize-pane -t 1 -x $(( #{window_width} / 2 ))' + +# binary space partitioned layouts (dwindle, spiral) +# https://sunaku.github.io/tmux-layout-dwindle.html +bind-key -n M-w run-shell 'tmux-layout-dwindle brhc && tmux-redraw-vim' +bind-key -n M-W run-shell 'tmux-layout-dwindle trhc && tmux-redraw-vim' +bind-key -n M-v run-shell 'tmux-layout-dwindle brvc && tmux-redraw-vim' +bind-key -n M-V run-shell 'tmux-layout-dwindle blvc && tmux-redraw-vim' +bind-key -n M-z select-layout tiled + +#----------------------------------------------------------------------------- +# scrollback buffer +#----------------------------------------------------------------------------- + +# buffer length +set-option -g history-limit 32767 + +# search buffer using copy mode +bind-key -n M-/ copy-mode \;\ + command-prompt -p 'search-backward (press up):' \ + -i 'send-keys -X search-backward-incremental "%%%"' + +# search buffer using Vim or less +bind-key -n M-| \ + capture-pane -J -S - \; \ + new-window -n '#S:#I.#P' -a ' \ + tmux save-buffer - \; delete-buffer | { \ + if command -v vim; \ + then vim -R -c "set nofen is hls ic" -; \ + else less; \ + fi; \ + }; \ + ' \; \ + run-shell 'tmux send-keys G \?' + +# search colored buffer using less +bind-key -n M-? \ + capture-pane -e -J -S - \; \ + new-window -n '#S:#I.#P' -a ' \ + tmux save-buffer - \; delete-buffer | \ + less -R \ + ' \; \ + run-shell 'tmux send-keys G \?' + +# scroll buffer +# NOTE: set "URxvt.saveLines: 0" in ~/.Xdefaults to make Shift+PageUp bindable +# NOTE: see http://aperiodic.net/screen/interface for doing the same in XTerm +bind-key -n S-PPage copy-mode -u + +# copy text from buffer +bind-key -n M-u copy-mode +set-window-option -g mode-keys vi +bind-key -T copy-mode-vi v send-keys -X begin-selection +bind-key -T copy-mode-vi y send-keys -X copy-selection +bind-key -T copy-mode-vi - send-keys -X jump-again +bind-key -T copy-mode-vi _ send-keys -X jump-reverse +bind-key -T copy-mode-vi ? command-prompt -p 'search-backward:' -I '#{pane_search_string}' -i 'send-keys -X search-backward-incremental "%%%"' +bind-key -T copy-mode-vi / command-prompt -p 'search-forward:' -I '#{pane_search_string}' -i 'send-keys -X search-forward-incremental "%%%"' + +# transfer copied text to attached terminal with yank: +# https://github.com/sunaku/home/blob/master/bin/yank +bind-key -T copy-mode-vi Y send-keys -X copy-pipe 'yank > #{pane_tty}' +# open the visual selection with xdg-open(1) +bind-key -T copy-mode-vi O send-keys -X copy-pipe 'xargs -r xdg-open' + +# paste most-recently copied text +bind-key -n M-i paste-buffer + +# paste previously copied text (chosen from a menu) +bind-key -n M-I choose-buffer + +# transfer most-recently copied text to attached terminal with yank: +# https://github.com/sunaku/home/blob/master/bin/yank +bind-key -n M-y run-shell 'tmux save-buffer - | yank > #{pane_tty}' + +# transfer previously copied text (chosen from a menu) to attached terminal: +# https://github.com/sunaku/home/blob/master/bin/yank +bind-key -n M-Y choose-buffer 'run-shell "tmux save-buffer -b \"%%%\" - | yank > #{pane_tty}"' + +#----------------------------------------------------------------------------- +# TMUX plugin manager https://github.com/tmux-plugins/tpm +#----------------------------------------------------------------------------- + +set -g @plugin 'tmux-plugins/tmux-resurrect' +set -g @resurrect-capture-pane-contents on + +set -g @plugin 'Morantron/tmux-fingers' +set -g @fingers-key '-n M-U' +set -g @fingers-compact-hints 1 +set -g @fingers-hint-format '#[fg=yellow,bold,reverse]%s' +set -g @fingers-hint-labels ' \ + a o e u i d h t n s \ + p y f g c r l \ + q j k x b m w v z \ + A O E U I D H T N S \ + P Y F G C R L \ + Q J K X B M W V Z \ +' + +run-shell ~/.tmux/plugins/tpm/tpm diff --git a/regress/conf/a4789a6782859c66aa8c9614ee6fabfa.conf b/regress/conf/a4789a6782859c66aa8c9614ee6fabfa.conf new file mode 100644 index 0000000000..7f4a8cd19d --- /dev/null +++ b/regress/conf/a4789a6782859c66aa8c9614ee6fabfa.conf @@ -0,0 +1,80 @@ +set -g default-command "if [ \"$(uname)\" = 'Darwin' ]; then exec reattach-to-user-namespace $SHELL; else exec $SHELL; fi" +set -g history-limit 32000 +set -g update-environment "DISPLAY WINDOWID SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION SSH_CLIENT SSH_TTY KRB5CCNAME Apple_PubSub_Socket_Render Apple_Ubiquity_Message" + +# Reset SHLVL (otherwise it is 2 inside tmux) +setenv -g SHLVL 0 + +# Send esc faster so that neovim won't get so laggy +# https://github.com/neovim/neovim/issues/2093 +set -g escape-time 100 + +# Disable paste detection +set -g assume-paste-time 0 + +# Titles and window names +set -g set-titles on +set -g set-titles-string "#T" + +# Make it not so annoying/sticky to switch windows +set -g repeat-time 170 + +# Don't deattach me when a session ends +set -g detach-on-destroy off + +# Make shift+keys work +setw -g xterm-keys on + +# Prefix +set -g prefix ^A +unbind ^B +bind ^A send-prefix +bind a send-prefix + +# Last window +bind ^a last + +# Next & prev +bind ' ' next +bind '^ ' next +bind ^p prev + +# Status +set -g status off +# Need more (cow)bells! +set -g bell-action any +set -g bell-on-alert on + +# Detach +bind ^d detach + +# Control the a tmux in a tmux +bind A send-prefix \; send-prefix +bind C send-prefix \; send-keys c +bind n send-prefix \; send-keys ' ' +bind bspace send-prefix \; send-keys p +bind '#' send-prefix \; send-keys '"' + +# Other key bindings. +bind ^r command-prompt "find-window '%%'" +bind '"' choose-tree -w +bind w split-window +bind W split-window -c "#{pane_current_path}" +bind ^w split-window +bind I list-windows +bind i list-windows +bind D neww 'if who | grep -q "$USER.* via mosh"; then tmux lsc -F "#{client_activity} #{client_tty}" | sort | head -n -1 | awk "{print \$2}" | xargs -n1 tmux detach -t; else for i in $(tmux lsc | cut -d: -f1 | grep -v "^$SSH_TTY$"); do tmux detach -t $i; done; fi' +bind S neww -t 999 'window=`tmux display -p "#{pane_title}"`; i=0; tmux list-windows | cut -d: -f1 | while read j; do if [ $j != $i ]; then tmux move-window -s $j -t $i; fi; i=$(($i+1)); done' # ; tmux find-window -T "$window" +bind ^s command-prompt "rename-session '%%'" +# Make the default HOME always ~ +bind c neww -c ~ +bind ^c new -c ~ +bind escape copy-mode +# Copy to the OS clipboard +bind -T copy-mode-vi y send -X copy-pipe-and-cancel "if [ \"$(uname)\" = 'Darwin' ]; then reattach-to-user-namespace pbcopy; else xclip; fi" +bind j command-prompt "join-pane -s '%%'" +bind ! break-pane -d +bind - command-prompt "move-pane -t '%%'" + +# Makes `tmux a` work even when there isn't a session going on +new-session -A -c ~ diff --git a/regress/conf/ad0537c4e83d7a25d5dc4f3a3c571349.conf b/regress/conf/ad0537c4e83d7a25d5dc4f3a3c571349.conf new file mode 100644 index 0000000000..ce68c4abe5 --- /dev/null +++ b/regress/conf/ad0537c4e83d7a25d5dc4f3a3c571349.conf @@ -0,0 +1,65 @@ +set-option -g allow-rename on +set-option -g automatic-rename off +set-option -g base-index 1 +set-option -g default-command "$SHELL" +set-option -g default-terminal "tmux-256color" +set-option -g history-limit 25000 +set-option -g mode-keys vi +set-option -g prefix C-f +set-option -g renumber-windows yes +set-option -g set-titles on +set-option -g set-titles-string "#T" +set-option -g xterm-keys on + +set-option -g status-interval 1 +set-option -g status-left "#(tmux-status-left)" +set-option -g status-left-length 40 +set-option -g status-right "" + +set-option -g window-status-current-attr bold +set-option -g window-status-current-format "[#I#F#{?window_zoomed_flag, ,}#{=40:pane_title}]" +set-option -g window-status-format "#I#{?window_zoomed_flag, ,}#F#{?window_flags,, }#{?window_zoomed_flag, ,}#{=20:pane_title}" + +set-option -g pane-active-border-fg colour247 +set-option -g pane-border-fg colour235 +set-option -g status-bg colour7 +set-option -g status-fg colour16 +set-option -g status-left-bg colour4 +set-option -g status-left-fg colour15 +set-option -g window-status-current-bg colour15 +set-option -g window-status-current-fg colour16 + +set-option -g update-environment "DBUS_SESSION_BUS_ADDRESS DISPLAY KRB5CCNAME \ + SESSION_MANAGER SSH_AGENT_PID SSH_ASKPASS SSH_AUTH_SOCK SSH_CONNECTION \ + WINDOWID XAUTHORITY SSH_TTY" + +bind-key w break-pane -d +bind-key l clear-history \; display "Pane history cleared." +bind-key C-f if-shell "test #{window_panes} -eq 1" last-window last-pane +bind-key N new-session +bind-key t new-window +bind-key z resize-pane -Z +bind-key C-r rotate-window -D +bind-key -n C-t run-shell "metamux new-shell-in-pane #{window_panes}" +bind-key n run-shell "metamux rotate-pane next" +bind-key p run-shell "metamux rotate-pane prev" +bind-key q run-shell "metamux pane-buster" +bind-key S run-shell "metamux join-hidden-pane -v" +bind-key u run-shell "metamux open-last-url-printed" +bind-key | run-shell "metamux join-hidden-pane -h" +bind-key f send-prefix +bind-key r source "$HOME/.tmux.conf" \; display "Configuration reloaded." + +# When the current window is split, Ctrl+Tab and Ctrl+Shift+Tab should rotate +# between the split windows. If there is only one pane in the current window, +# Ctrl+Tab and Ctrl+Shift+Tab will cycle between windows as though they were +# tabs in modern desktop UIs. +bind-key -n C-Tab if-shell "test #{window_panes} -eq 1" next-window "select-pane -t :.+" +bind-key -n C-S-Tab if-shell "test #{window_panes} -eq 1" previous-window "select-pane -t :.-" + +# Binding to mark and swap panes; if no pane is marked, the shortcut will mark +# the active pane, but if a pane is already marked, active pane will be swapped +# with the marked pane. +bind-key m if-shell 'test -z "$PANE_IS_MARKED"' \ + "select-pane -m; set-env PANE_IS_MARKED 1" \ + "swap-pane; select-pane -M; set-env -u PANE_IS_MARKED" diff --git a/regress/conf/ad21dbb0893240563ddfdd954b9903a1.conf b/regress/conf/ad21dbb0893240563ddfdd954b9903a1.conf new file mode 100644 index 0000000000..27d8f31090 --- /dev/null +++ b/regress/conf/ad21dbb0893240563ddfdd954b9903a1.conf @@ -0,0 +1,580 @@ +# Time-stamp: <2018-05-31 17:10:05 kmodi> +# https://github.com/tmux/tmux +# Hi-lock: (("\\(^\\s< \\**\\)\\(\\* *.*\\)" (1 'org-hide prepend) (2 '(:inherit org-level-1 :height 1.3 :weight bold :overline t :underline t) prepend))) +# Hi-Lock: end + +# Running tmux built from master branch on tcsh in uxterm +# tmux version 2.5-RC+ dev + +# Contents: +# +# PREFIX +# Source config +# Pane Management +# Window <-join/split-> Pane +# Select Panes +# Resize Panes +# Dynamic Split +# Window Management +# Window Navigation +# Swap Windows +# Split Window +# Layout +# Session Management +# Mouse +# Drag pane border to resize +# Left click on pane +# Middle click on pane +# Right click on pane +# Wheel scroll in pane +# Wheel scroll in pane WHILE in copy-mode +# Left click on status +# Middle click on status +# Other mouse settings +# Window Title +# Status Bar +# Left Status +# Right Status +# Pane Status +# Colors +# Status Bar Colors +# Message Colors +# Window Status Colors +# Pane Colors +# Mode Info Colors +# Activity +# Command Prompt +# Audible and Visual Bells +# Copy & Paste +# Synchronize commands to panes/windows/sessions +# Terminal Setting +# Other Options +# Server Options +# Session Options +# Window Options +# Notes + +# * PREFIX +set -g prefix C-z +unbind C-b # unbind the default binding to send prefix key to the application +# Often you'll run a tmux inside another tmux and need a command sequence to +# send things to the inner session. With below binding that can be accomplished +# using "PREFIX Z " +bind Z send-prefix + +# * Source config +unbind r # unbind default binding to force redraw of attached client +bind r source-file ~/.tmux.conf \; display "Finished sourcing ~/.tmux.conf ." + +# * Pane Management + +set -g pane-base-index 1 # start pane indices at 1 +set -g main-pane-width 100 # used by selectl main-vertical +bind z resize-pane -Z # zoom/unzoom the current pane +# If the window has >1 panes kill them without confirming. But confirm before kill +# the last pane (along with its window) in a window +bind x if "tmux display -p \"#{window_panes}\" | grep ^1\$" \ + "confirm-before -p \"Kill the only pane in window? It will kill this window too! (y/n)\" kill-pane" \ + "kill-pane" +bind C clear-history \; display "Cleared history of the current pane." +unbind C-p +bind C-p run -b "tmux display -p -F '#{pane_current_path}' | xclip -i -sel pri" \; display "Copied current path '#{pane_current_path}' to the primary selection." + +# Hooks need tmux 2.3+ +# set-hook -g -u after-kill-pane # Remove after hook for kill-pane +set-hook -g after-kill-pane "selectl main-vertical" +# If -g options is used when setting the hook, it has to be used when +# removing (-u option) the hook too. + +# ** Window <-join/split-> Pane +# Join a pane *from* a different window (of same or different session) into the CURRENT window +# Binding mnemonic: F for (F)etch/pull (as in git) from a different window +bind F command-prompt -p "Join pane from [sess:]win#[.pane#] (ex: kmodi:3.1) into current window:" "join-pane -s '%%'" +# Join CURRENT pane *to* a different window +# Binding mnemonic: P for (P)ush (as in git) to a different window +bind P command-prompt -p "Send CURRENT pane to [sess:]win# (ex: kmodi:3):" "join-pane -t '%%'" +# PREFIX ! : break-pane, convert the current pane to a window + +# ** Select Panes +bind o select-pane -t :.+ # cycle to the next pane number +bind O select-pane -t :.- # cycle to the previous pane number +# PREFIX ; : last-pane or select-pane -l, switch to the last active pane +# PREFIX ← : select-pane -L, switch to the pane on the left +# PREFIX → : select-pane -R, switch to the pane on the right +# PREFIX ↑ : select-pane -U, switch to the pane on the top +# PREFIX ↓ : select-pane -D, switch to the pane on the bottom +# PREFIX { : swap-pane -U, swap current pane with the pane above (not literally above) +# PREFIX } : swap-pane -D, swap current pane with the pane below (not literally below) + +# ** Resize Panes +bind -r h resize-pane -L 2 +bind -r C-h resize-pane -L 2 +bind -r j resize-pane -D 2 +bind -r C-j resize-pane -D 2 +bind -r k resize-pane -U 2 +bind -r C-k resize-pane -U 2 +unbind l # unbind default binding for `last-window` +bind -r l resize-pane -R 2 +bind -r C-l resize-pane -R 2 + +# ** Dynamic Split +# Key-chaining example, analogous to prefix maps in emacs +bind / switch-client -Tlauncher +# Run below -Tlauncher commands using "PREFIX / " +# Open calendar in a split window "PREFIX / c" +# FIXME: Below does not work; cal pane quits as soon as it launches (before "&& sleep .." +# was added). To make better of the situation, I now auto-close that pane after 3 seconds. +# bind -Tlauncher c split-window -h 'cal && sleep 3' +bind -Tlauncher c run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'cal && sleep 3'" +# Start emacsclient in terminal mode in a split window "PREFIX / e" +# Use the emacs binding "C-x 5 0" to quit from that pane gracefully. +bind -Tlauncher e run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'emacsclient -a \"\" -t'" +# Open man page "PREFIX / m" +# PREFIX / m will bring up the tmux command prompt. Enter the command for which +# you want to see the man page, example: ls. That man page will open in a split +# pane. When you are done reviewing the man page, hit q and the split pane +# closes by itself. Beautiful! +bind -Tlauncher m command-prompt -p "man" "run \"/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'man %1'\"" +# Open python interpreter in a split window for quick calculations "PREFIX / p" +# Ctrl-D in python quits python and thus closes the split window too. +bind -Tlauncher p run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'ipython --profile=default --no-confirm-exit'" +# PREFIX Up, Down, Right, Left : Move cursor from one pane to another +# PREFIX Space : Cycle through different pane layouts +# PREFIX C-o : rotate-window, rotate panes in the current window + +# * Window Management +set -g base-index 1 # start window indices at 1 +# automatically renumber the windows +# http://unix.stackexchange.com/questions/21742/renumbering-windows-in-tmux +set -g renumber-windows on + +bind C-f command-prompt -p "New window:" "new-window -c '#{pane_current_path}' -n %1" +bind C-r command-prompt -p "New name for this window:" "rename-window '%%'" +unbind L # unbind default binding for `switch-client -l` +bind L list-windows -F '#{window_index}:#{window_name}: #{?pane_dead, (dead), (not dead)}' +unbind & # unbind default binding for `kill-window` +bind C-c confirm-before -p "Kill this window? (y/n)" kill-window +# Move the current window to another window index in the same or any other session +bind m command-prompt -p "Move window to sess or sess:win# or win# (ex: kmodi or kmodi:3 or 2(of current session)):" "move-window -t '%%'" +# Move or bring a window from a different session to the current one +bind M command-prompt -p "Move the window from sess:win# (ex: kmodi:3):" "move-window -s '%%'" + +# ** Window Navigation +bind C-z last-window # switch to last active window +# Allow repeats for next/previous-window +bind -r p previous-window +bind -r n next-window +# switch to another window by name +bind W split-window "tmux lsw | peco --initial-index `tmux lsw | awk '/active.$/ {print NR-1}'` | cut -d':' -f 1 | xargs tmux select-window -t" +# PREFIX : switches to window with index=N + +# ** Swap Windows +bind N move-window -r # renumber the windows +unbind , # unbind default binding for `rename-window` +bind -r , swap-window -t -1 # move window one position to the left +bind -r < swap-window -t -1 # move window one position to the left +unbind . # unbind default binding to move window to user provided index +bind -r . swap-window -t +1 # move window one position to the right +bind -r > swap-window -t +1 # move window one position to the right +unbind t # unbind default binding to show time +bind t swap-window -t 1 # swap the current window's position with window # 1, move it to the top + +# ** Split Window +unbind & # unbind default binding for `split-window -h` +bind - split-window -v -c '#{pane_current_path}' # vertical split +bind _ split-window -v -c '#{pane_current_path}' -f # full vertical split (v2.3+) +bind \ split-window -h -c '#{pane_current_path}' # horizontal split +bind | split-window -h -c '#{pane_current_path}' -f # full horizontal split (v2.3+) +# https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/cw5wy00 +bind w switch-client -Tsplit_wind +bind -Tsplit_wind v split-window -v -c '#{pane_current_path}' +bind -Tsplit_wind V split-window -v -c '#{pane_current_path}'\; swap-pane -U +bind -Tsplit_wind h split-window -h -c '#{pane_current_path}' +bind -Tsplit_wind H split-window -h -c '#{pane_current_path}'\; swap-pane -U + +# ** Layout +bind Space next-layout +bind C-Space select-layout -o # undo only the last layout change #v2.1 + +# * Session Management +bind C-t command-prompt -p "New name for this session:" "rename-session '%%'" +bind b switch-client -l # switch to previously selected session +# switch to another session by name +bind S split-window "tmux ls | peco --initial-index `tmux ls | awk '/attached.$/ {print NR-1}'` | cut -d':' -f 1 | xargs tmux switch-client -t" +# switch to ANY window in ANY session by name +bind s split-window "tmux ls | cut -d: -f1 | xargs -I SESSION tmux lsw -F 'SESSION:#{window_name}' -t SESSION | peco --initial-index `tmux ls | cut -d: -f1 | xargs -I SESSION tmux lsw -F '___#{session_attached}#{window_active}___' -t SESSION | awk '/___11___/ {print NR-1}'` | xargs tmux switch-client -t" +# tmux kill-session -t NAME/SESSIONNUMBER # Kill session + +# * Mouse +# setw -g mode-mouse on # incompatible in tmux 2.1+ +set -g mouse on + +# ** Drag pane border to resize +# set -g mouse-resize-pane off # incompatible in tmux 2.1+ +bind -T root MouseDrag1Border resize-pane -M # default +# unbind -T root MouseDrag1Border # disable drag pane border to resize + +bind -T root MouseDrag1Pane if -Ft= '#{mouse_any_flag}' 'if -Ft= "#{pane_in_mode}" "copy-mode -M" "send-keys -M"' 'copy-mode -M' # default + +# ** Left click on pane +# set -g mouse-select-pane on # incompatible in tmux 2.1+ +# Left click on a pane selects it +# bind -T root MouseDown1Pane select-pane -t=\; send-keys -M # default +bind -T root MouseDown1Pane select-pane -t= + +# Sun Feb 19 11:31:34 EST 2017 - kmodi +# Below break in tmux 2.4 +# # Fri Aug 26 18:35:21 EDT 2016 - kmodi +# # FIXME Need to remember why I unbound the below 2 bindings +# unbind -temacs-copy MouseDown1Pane +# unbind -temacs-copy MouseUp1Pane +# # + +# https://groups.google.com/forum/#!topic/tmux-users/mHhdx7Au0Ds +# Fri Aug 26 18:30:15 EDT 2016 - kmodi +# Do not do the below!! That will update the primary selection with the top-most +# tmux buffer each time you left click on a pane. +# bind -T root MouseUp1Pane run -b "tmux show-buffer | xclip -i -sel pri" +# + +# Left click in the pane *followed after a region selection* copies that to the +# secondary selection +bind -T root MouseUp1Pane run -b "tmux show-buffer | xclip -i -sel sec" +# Fri Aug 26 19:03:57 EDT 2016 - kmodi +# FIXME: As of today it needs to be figured out how to best paste the content +# from secondary selection + +# ** Middle click on pane +# Middle click in a pane to paste from the primary selection +bind -T root MouseDown2Pane run -b "xclip -o -sel pri | tmux load-buffer - && tmux paste-buffer -s ' '" + +# ** Right click on pane +# Right click on a pane selects and marks it *if not in copy-mode*; else +# passes on the mode keys +# bind -T root MouseDown3Pane select-pane -t= -m # default +bind -T root MouseDown3Pane if -Ft= '#{pane_in_mode}' 'send-keys -M' 'select-pane -t= -m' + +# Sun Feb 19 11:32:00 EST 2017 - kmodi +# Below breaks in tmux 2.4 +# # Right click *release* on a pane *in copy-mode* quits copy-mode +# bind -temacs-copy MouseUp3Pane cancel + +# ** Wheel scroll in pane +unbind -T root WheelUpPane +unbind -T root WheelDownPane +# Do mouse wheel-up to enter copy mode and do page-up +# https://groups.google.com/d/msg/tmux-users/XTrSVUR15Zk/3iyJLMyQ7PwJ +# Below binding did not work +# bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'if -Ft= "#{pane_in_mode}" "copy-mode -u" "send-keys -M"' 'copy-mode -u' +# Below works and allows the WheelUpPane binding in emacs-copy table to be effective +bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= "#{pane_in_mode}" "send-keys -M" "copy-mode -u"' +# |---------------------+-----------------------------------------+--------------------------------| +# | using mouse? AND .. | #{pane_in_mode} (already in copy-mode?) | action | +# |---------------------+-----------------------------------------+--------------------------------| +# | Yes | Don't care | Send the mode keys | +# | No | Yes | Send the mode keys | +# | No | No | Enable copy-mode and do PageUp | +# |---------------------+-----------------------------------------+--------------------------------| + +# *** Wheel scroll in pane WHILE in copy-mode +# Sun Feb 19 11:32:16 EST 2017 - kmodi +# Below breaks in tmux 2.4 +# # Once in copy-mode, mouse wheel scrolls scrolls by half pages +# bind -temacs-copy WheelUpPane halfpage-up +# bind -temacs-copy WheelDownPane halfpage-down + +# ** Left click on status +# set -g mouse-select-window on # incompatible in tmux 2.1+ +# Left click on a window name in status bar to select it +bind -T root MouseDown1Status select-window -t= # default + +# ** Middle click on status +# Middle click on a window name in status bar to kill it +bind -T root MouseDown2Status kill-window + +# ** Other mouse settings +# The special token ‘{mouse}’ or ‘=’ may be used as target-window or target-pane in +# commands bound to mouse key bindings. Example: -t = + +# * Window Title +set -g set-titles on +set -g set-titles-string '#h :: #S:W#I(#W).P#P' + +# * Status Bar +set -g status-interval 5 # default = 15 seconds +set -g status-justify centre + +# ** Left Status +set -g status-left-length 20 +# Change the left status when prefix is pressed. +# https://www.reddit.com/r/tmux/comments/5cm2ca/post_you_favourite_tmux_tricks_here/d9ziuy9/ +set -g status-left "#{?client_prefix,#[fg=yellow]prefix pressed ..,[#S]}" + +# ** Right Status +set -g status-right-length 20 +set -g status-right "%l:%M %b %d %a " + +# ** Pane Status +setw -g pane-border-status "bottom" +setw -g pane-border-format " #P #T " + +# # tmux-powerline +# # https://github.com/erikw/tmux-powerline +# set -g status-left-length 30 +# set -g status-right-length 30 +# set -g status-left "#(~/usr_local/scripts/tmux-powerline/powerline.sh left)" +# set -g status-right "#(~/usr_local/scripts/tmux-powerline/powerline.sh right)" + +# * Colors + +# ** Status Bar Colors +set -g status-style fg=colour246,bg=colour233 # default for whole status line +set -g status-left-style fg=white,bold,bg=colour233 +set -g status-right-style fg=colour75,none,bg=colour233 + +# ** Message Colors +set -g message-style fg=colour2,bold,bg=default + +# ** Window Status Colors +setw -g window-status-style default # default for all window statuses +setw -g window-status-last-style fg=default,bg=colour235 +setw -g window-status-current-style fg=white,bold,bg=colour63 +setw -g window-status-bell-style default +setw -g window-status-activity-style fg=white,none,bg=colour196 +# setw -g window-status-content-style fg=black,none,bg=green # incompatible with tmux 2.0+ + +# ** Pane Colors +setw -g pane-active-border-style fg=colour63,bg=default +setw -g pane-border-style fg=colour235,bg=default +setw -g window-active-style 'bg=#330000' # bg color of active pane +setw -g window-style 'bg=black' # bg color of inactive pane(s) + +# ** Mode Info Colors +# Color of display shown on top-right in copy-mode, highlighting +setw -g mode-style fg=black,bg=colour244 + +# * Activity +# Notify when a window has activity +# This quick snippet will have tmux notify you in the status area when a +# window has activity: +setw -g monitor-activity on +set -g visual-activity off # Display message telling that an activity happened (on/off) +# It lets me know that there is activity in a non-active window +# To try this, enter `sleep 10 && echo “Hi”` in a window and switch to +# another window. + +# # Notify when a window has a content alert +# setw -g monitor-content "--[A-Za-z][A-Za-z]sim Done--" # This string appears when a sim finishes, alert then # incompatible with tmux 2.0+ +# # setw -g monitor-content "" # Disable monitor-content +# set -g visual-content on # Display message telling that a content alert was triggered (on/off) # incompatible with tmux 2.0+ + +# * Command Prompt +# Move focus to command prompt. tmux commands can be entered there directly +# without using the `tmux` prefix and it also supports auto-complete. +bind C-x command-prompt # default command-prompt binding "PREFIX :" also works + +# * Audible and Visual Bells +set -g bell-action any +set -g bell-on-alert off +set -g visual-bell on + +# * Copy & Paste +set -g set-clipboard off # default is on + +# Copy tmux buffer to primary and clipboard selections +# run -b runs a shell command in background +# http://grota.github.io/blog/2012/05/08/tmux-clipboard-integration/ +bind C-w run -b "tmux show-buffer | xclip -i -sel pri && tmux show-buffer | xclip -i -sel cli" +# Fri Aug 26 18:41:30 EDT 2016 - kmodi +# Below binding was suggested by Nicholas Marriott +# But the my older binding works fine so I am commenting out below for now. +# bind C-w run "tmux saveb - | xclip -i -sel pri; tmux saveb - | xclip -i -sel cli" +# Paste into tmux; also replace LF characters with +# space as separator characters (-s) when pasting. +# Yank from primary +bind C-y run -b "xclip -o -sel pri | tmux load-buffer - && tmux paste-buffer -s ' '" +# Yank from clipboard +bind M-y run -b "xclip -o -sel cli | tmux load-buffer - && tmux paste-buffer -s ' '" +# Open the file/dir path that was copied by selection in existing emacs client +# Usage: Highlight a file name in ls output and press "PREFIX e" +bind e run -b "tmux show-buffer | xclip -i -sel pri; (emacsclient -a '' `tmux display -p '#{pane_current_path}'`/`xclip -o -sel pri `&)" + +# * Synchronize commands to panes/windows/sessions +# Send the same command to all panes in the same window +bind C-a command-prompt -p "Command to all panes in this window:" \ + "run \"tmux list-panes -F '##{pane_index}' | xargs -I PANE \ + tmux send-keys -t PANE '%1' Enter\"" +# Alternative to using the above "C-a" binding is to enable pane synchronization, +# type the command you want to execute in all panes in the same window and disable +# pane synchronization +# Also turn the pane borders red while pane synchronization is enabled. +# - https://www.reddit.com/r/tmux/comments/5cm2ca/post_you_favourite_tmux_tricks_here/d9y6jzu/ +bind C-s if -F '#{pane_synchronized}' \ + 'setw synchronize-panes off; \ + setw pane-active-border-style fg=colour63,bg=default; \ + setw pane-border-format " #P #T "' \ + 'setw synchronize-panes on; \ + setw pane-active-border-style fg=red; \ + setw pane-border-format " #P - Pane Synchronization ON "' +# So it would be: C-s C-s + +# https://scripter.co/command-to-every-pane-window-session-in-tmux/ +# Send the same command to all panes/windows in the current session +bind C-e command-prompt -p "Command:" \ + "run \"tmux list-panes -s -F '##{session_name}:##{window_index}.##{pane_index}' \ + | xargs -I PANE tmux send-keys -t PANE '%1' Enter\"" + +# Send the same command to all panes/windows/sessions +bind E command-prompt -p "Command:" \ + "run \"tmux list-panes -a -F '##{session_name}:##{window_index}.##{pane_index}' \ + | xargs -I PANE tmux send-keys -t PANE '%1' Enter\"" + +# * Terminal Setting + +# From `man tmux', about `default-terminal' +# Set the default terminal for new windows created in this session - the default +# value of the TERM environment variable. For tmux to work correctly, this must +# be set to ‘screen’, ‘tmux’ or a derivative of them. +# set -g default-terminal "screen" +set -g default-terminal "screen-256color" +# Mon May 22 11:43:56 EDT 2017 - kmodi +# Blinking text (useful to show broken symlinks in ls) does not work when using tmux-24bits. +# set -g default-terminal "tmux-24bits" +# tmux-24bits is a custom terminfo profile created using the steps explained +# on https://github.com/ThomasAdam/tmux/blob/master/FAQ to support italics and +# 256 colors. + +# Enable 24-bit color +# https://sunaku.github.io/tmux-24bit-color.html +set -ga terminal-overrides ",screen-256color:Tc" +# set -ga terminal-overrides ",tmux-24bits:Tc" + +# Thu May 31 17:10:04 EDT 2018 - kmodi +# TODO: Try the 24-bit emacs+tmux config for ST +# https://www.reddit.com/r/emacs/comments/8ndm2x/gnu_emacs_261_24bit_colors_suckless_st_terminal/dzwh4vv/ +# set -g default-terminal "tmux-256color" +# set -ga terminal-overrides ",*256col*:Tc" +# + +setw -g xterm-keys on + +# Uncomment below when using st (by suckless.org) +# set -g default-terminal "st-256color" +# # https://sunaku.github.io/tmux-24bit-color.html +# # st supports 24-bit color, so enable support for that in tmux +# set -ga terminal-overrides ",st-256color:Tc" +# setw -g xterm-keys off + +bind R refresh-client +# bind R refresh-client \; display "Refreshed the client." + +# * Other Options + +# ** Server Options +set -s escape-time 0 # Allows for faster key repetition + +# ** Session Options +# Set the default shell to /bin/sh. If the default is tcsh, doing a split-window takes a long +# time as my tcsh init is loaded first (which takes really long). +set -g default-shell /bin/sh +# If I am doing a new-window or split-window without a specified command, start the tcsh +# shell by default. +set -g default-command tcsh +set -g history-limit 100000 +set -g display-time 1000 # Duration of tmux display messages in milliseconds + +# ** Window Options +# When a smaller terminal connects to a tmux client, it shrinks to fit it. The +# clients attached with bigger displays see this constrained view. +# aggressive-resize makes it such that the window is only resized if the smaller +# client is actively looking at it. +setw -g aggressive-resize on +setw -g mode-keys emacs # Use emacs keybindings in copy mode +setw -g status-keys emacs + +# * Notes + +# |-------------------+------------| +# | tmux command | short form | +# |-------------------+------------| +# | set-option | set | +# | set-window-option | setw | +# | bind-key | bind | +# | unbind-key | unbind | +# | display-message | display | +# | run-shell | run | +# | if-shell | if | +# |-------------------+------------| + +# Colo'u'r table +# http://guns.github.io/xterm-color-table.vim/images/xterm-color-table.png + +# CHARACTER PAIR REPLACED WITH +# #(command) First line of command’s output +# #[attributes] Colour or attribute change +# #H Hostname of local host +# #I Current window index +# #P Current pane index +# #S Session name +# #T Current window title +# #W Current window name +# ## A literal ‘#’ + +# Variables used in time format +# Source: http://docs.splunk.com/Documentation/Splunk/5.0.2/SearchReference/Commontimeformatvariables +# %y = year in numbers (2-digit) +# %Y = year in numbers (4-digit) +# %m = month in number (eg: 12) + # %B = full month name (eg: December)sho +# %b = short month name (eg: Dec) +# %d = day in numbers, with leading zeros (eg: 08) +# %e = day in numbers, no leading zeros (eg: 8) +# %A = full weekday name (eg: Sunday) +# %a = short weekday name (eg: Sun) +# %H = hours in 24-clock, with leading zeros +# %k = hours in 24-clock, no leading zeros +# %l = hours in 12-clock, with leading zeros +# %p = am/pm +# %T = time in 24-hour notation (%H:%M:%S) + +# PREFIX ? : list-keys, display key bindings + +# In command-prompt: show-options -g shows the global options +# In command-prompt: show-window-options -g shows the global windows options + +# How do I know which tmux version I am running? +# tmux -V + +# How to set bindings that don't need the prefix? +# bind -n .. or +# bind -T root .. + +# Changelog: https://github.com/tmux/tmux/blob/master/CHANGES + +# style colors: default, black, red, green, yellow, blue, magenta, cyan, white, +# colour0-colour255, hexdecimal RGB string '#ffffff' +# Use $SCRIPTS/bash/256-colors.sh to figure out the color number you want +# style attributes: none, bold/bright, dim, underscore, blink, reverse, hidden, +# or italics + +# https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/cw552qd + +# tmux buffers +# PREFIX # : List all paste buffers +# PREFIX - : Delete the most recently copied buffer of text +# PREFIX = : Choose which buffer to paste interactively from a list +# PREFIX ] : Paste the most recently copied buffer of text + +# How to start a temporary tmux server in addition to an existing running one? +# > tmux -L temp + +# In a shell environment in a terminal in tmux, the env var $TMUX will be +# defined to something like "/tmp/tmux-23273/default,31101,0". Outside tmux, +# $TMUX will be undefined. + +# Notation to address a specific pane +# SESSION_NAME:WINDOW_INDEX.PANE_NUMBER (Example: foo:2.1 i.e. Pane 1 in Window 2 of Session foo) + +# To print a message containing tmux variable values to stdout use '-p' option in display-message +# tmux display-message -p '#{session_name}:#{window_name}.#{pane_index}', or +# tmux display -p '#{session_name}:#{window_name}.#{pane_index}' diff --git a/regress/conf/b9f0ce1976ec62ec60dc5da7dd92c160.conf b/regress/conf/b9f0ce1976ec62ec60dc5da7dd92c160.conf new file mode 100644 index 0000000000..0a878369cb --- /dev/null +++ b/regress/conf/b9f0ce1976ec62ec60dc5da7dd92c160.conf @@ -0,0 +1,84 @@ +# none of these attempts worked, to bind keys, except sometimes during the sesssion. Oh well. +# I thought maybe that was because F1 is handled differently in a console than in X, but +# even just C-1 didnt work. Using just "a" or "x" as the key did, but not yet sure why not "C-". +#bind-key -T root C-1 attach-session -t$0 +#But this one works now, only picks the wrong one? Mbe need2understand what "$1" or $0 mean, better, +#but with the stub maybe this doesn't matter: +bind-key "`" switch-client -t$1 + +new-session #$0, stub, for keystroke convenience + +#$1 for root +new-session #a stub I guess, where keyboard convenience is concerned +new-window +send-keys -l pgup +send-keys Enter +send-keys -l "less /root/.tmux.conf &" +send-keys -l "echo; echo; echo Put something here for ssa or just run manly?" +send-keys Enter +new-window +new-window +new-window sul #for lcall, like man pages +send-keys -l "man tmux&" +send-keys Enter +select-window -t :=1 + +#$2 for om, so, can do C-b C-s 2 to get to the session, then C-b <#s> to get ~"tabs" +new-session sula ; send-keys -l q #0 +send-keys Enter Escape Escape +new-window sula ; send-keys -l q #1 +send-keys Enter Escape Escape +new-window sula ; send-keys -l q #2 +send-keys Enter Escape Enter Enter +new-window sula ; send-keys q #3, to start: +send-keys Enter Escape +# %%need a sleep here & .. ? +send-keys Enter Enter Enter Enter +new-window sula ; send-keys -l q #4 +send-keys Enter Escape Escape +new-window sula +new-window sula +new-window sula +new-window sula +new-window sula +select-window -t :=2 +select-window -t :=3 + +#$3 for email (mutt) +new-session sula +new-window sula ; send-keys mutt Enter +#nah, probly betr not?: +#send-keys -l z +#send-keys -l "thepassifdecide" +#send-keys Enter +new-window sula ; send-keys mutt Enter +send-keys -l "c!=sent" +send-keys Enter +new-window sula ; send-keys -l "cd mail/config; less mailsig.txt&" +send-keys Enter +send-keys "less macros&" +send-keys Enter +select-window -t :=1 + +#$4 for lacall-net: links etc +new-session suln +new-window suln +#send-keys -l "lkslfx" #; et; links ksl.com" +#send-keys asdafdfadfadfadfadf +#%%does opening links break subsequent cmds? With this Enter, the switch-client etc dont work: +#send-keys Enter +#send-keys Space Space Space +new-window suln +new-window suln +select-window -t :=1 +#send-keys Space Space Space + +#$5 for lacall-secnet, links?: +#new-session sulsn + +# then, where to start: +#%%need a sleep here, or ck a debug thing? +switch-client -t"$0" +send-keys -l "sleep 2" +send-keys Enter +switch-client -t$2 diff --git a/regress/conf/d0040b2e097f1e3d31d78eed6ce8d461.conf b/regress/conf/d0040b2e097f1e3d31d78eed6ce8d461.conf new file mode 100644 index 0000000000..3ae7444a6a --- /dev/null +++ b/regress/conf/d0040b2e097f1e3d31d78eed6ce8d461.conf @@ -0,0 +1,108 @@ +# Put the status bar on top +#set -g status-position "top" + +# Basic colours, safer for dumb terminals. +#set -g status-style "bg=white,fg=black" +#set -g status-right-style "bg=green,fg=black" +#set -g window-status-current-style "bg=yellow,fg=black" +#set -g message-style "bg=white,fg=black" +#set -g window-status-activity-style "fg=blue" +#set -g window-status-bell-style "fg=red" + +## Moar colours! Not recommended if attaching from dumber terminals with 8 or 16 colours. +#set -g default-terminal "tmux-256color" +# A more compatible XTERM var. +set -g default-terminal "screen-256color" +set -g message-style "bg=#485548 fg=#ffffff" +set -g pane-border-style "fg=#424954" +set -g pane-active-border-style "fg=#ffffff" +set -g status-style "bg=#424954 fg=#ffffff" +set -g status-right-style "bg=#303338 fg=colour87" +set -g window-status-current-style "bg=#303338" +set -g window-status-last-style "bg=#364146" +set -g window-status-format ' #I:#W#[fg=colour201]#F ' +set -g window-status-current-format ' #[fg=colour226]#I#[fg=#ffffff]:#[fg=colour119]#W#[fg=colour202]#F ' +set -g window-status-separator "" + +# Uncomment and reload settings for sanity in a console with 8 colours. +#set -g status-style "bg=white,fg=black" +#set -g window-status-last-style "bg=white" + +# Might help when graphical characters used for vertical and horizontal lines are drawn as x and q. +#set-option -ga terminal-overrides ',*:enacs@:smacs@:rmacs@:acsc@' + +# Count panes starting from 1. +set -g base-index 1 + +# With this you set the window name in the status line. +# Beware of outrageous prompts, such as the default one in RHEL 7. +set -g set-titles on +# Let status right consists of only the pane title (removes date and time). +# Usually shows current path. +set -g status-right ' #T ' +# Increase the default length of 40. +set -g status-right-length 80 + +# Scroll up with the mouse. +set -g mouse + +# Clipboard integration, use this in tandem with the recommended xterm settings. +set -g set-clipboard on +# Pass through modifier keys, xterm style. You'll want this in vim. +set -g xterm-keys on +# Reduce time to wait for Escape key. You'll want this for neovim. +set-option escape-time 40 +# Leave ESC alone... +#set-option -s escape-time 0 + +# New-style mouse scroll (>2.1) +bind -n WheelUpPane select-pane -t= \; copy-mode -e \; send-keys -M +bind -n WheelDownPane select-pane -t= \; send-keys -M + +# This is for scrolling up with the terminal using keys, but has issues... +#set -ga terminal-overrides ',xterm*:smcup@:rmcup@' + +# 10x more history. +set -g history-limit 20000 + +# Swap the default Control-b with Control-s which usually stops the output in a shell. +unbind C-b +set-option -g terminal-overrides "xterm-rightclick:krightclick=^[[29~" +set -g prefix C-s +bind C-s send-prefix + +# For renumbering windows when you get gaps in numbering. +bind R \ + move-window -r\; \ + display-message "Windows reordered..." + +# My shortcuts. +#bind-key -n C-S-t new-window # Doesn't work :-/ +bind-key -n C-t new-window +bind-key -n C-PgUp prev +bind-key -n C-PgDn next +#bind-key -n C-S-PgUp swap-window -t -1 # Doesn't work :-/ +#bind-key -n C-S-PgDn swap-window -t +1 # Doesn't work :-/ +bind-key -n C-S-Left swap-window -t -1 +bind-key -n C-S-Right swap-window -t +1 +bind-key -n M-` select-window -t 0 +bind-key -n M-1 select-window -t 1 +bind-key -n M-2 select-window -t 2 +bind-key -n M-3 select-window -t 3 +bind-key -n M-4 select-window -t 4 +bind-key -n M-5 select-window -t 5 +bind-key -n M-6 select-window -t 6 +bind-key -n M-7 select-window -t 7 +bind-key -n M-8 select-window -t 8 +bind-key -n M-9 select-window -t 9 +bind-key -n M-0 select-window -t 10 + +# switch panes without prefix using Alt-arrow +bind -n M-Left select-pane -L +bind -n M-Right select-pane -R +bind -n M-Up select-pane -U +bind -n M-Down select-pane -D + +# join pane from inputted window (horizontally or vertically) +#bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -h" +bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -v" diff --git a/regress/conf/d2e576f947e108eb9903679b65c81fbc.conf b/regress/conf/d2e576f947e108eb9903679b65c81fbc.conf new file mode 100644 index 0000000000..392a69f089 --- /dev/null +++ b/regress/conf/d2e576f947e108eb9903679b65c81fbc.conf @@ -0,0 +1,198 @@ +### GENERAL + +set-option -g prefix C-a # Set prefix to +bind a send-prefix # Send with a + +bind R source-file ~/.tmux.conf \; display "~/.tmux.conf reloaded" +bind -n M-R source-file ~/.tmux.conf \; display "~/.tmux.conf reloaded" + +set -g history-limit 10000 # lines to keep in hisoty +set-option -g display-panes-time 3000 # Timeout for pane-numbering in ms +bind -n M-q display-panes +set-option -sg escape-time 0 # speed up commands +set -g mouse on # enable mouse (tmux 2.1+) +set -g base-index 1 # start window numbering at 1 +set -g pane-base-index 1 # start pane numbering at 1 +set -g renumber-windows on # renumber windows automatically +setw -g automatic-rename on # rename window after process + +# Clear window name before renaming +bind , rename-window "" \; command-prompt "rename-window '%%'" + +#### APPEARANCE + +set -g default-terminal "screen-256color" # use 256 colors +setw -g aggressive-resize on # resize window to smallest client + +set -g pane-border-style fg=colour238 # border color for inactive panes +set -g pane-active-border-style fg=colour247 # border color for active panes + +# Status bar colors and format +setw -g window-status-format ' #[fg=#999999]#I #[fg=$666666]#W ' +setw -g window-status-current-format '#[fg=#ffffff] #I #W#[fg=#ffffff] ' +setw -g window-status-separator '#[fg=#292929]|#[fg=default]' +set -g status-bg default # background color for status bar +set -g status-position bottom # put status bar on top or bottom +set -g status-interval 2 # interval in s to update status +set -g status-justify left # horizontal alignment +set -g message-style fg=white,bg=black # appearance of status messages +set -g message-command-style fg=white # appearance of status message cmds + +# Left section of status bar +set -g status-left "" + +# Status bar visibility +set -g status off +bind -r -n M-t set status on +bind -r -n M-T set status off +bind t set status on +bind T set status off + +# Right section of status bar +if-shell 'uname | grep -qi Darwin' "set -g status-right \"#[fg=#81a2be]#(/usr/local/bin/mpc | head -n 1 | sed 's/volume.*$//') #[fg=cyan]#(~/bin/battery-osx) #(~/bin/mailstatus.sh) #[fg=yellow]#(uptime|sed 's/.* //') #[fg=#666666]%F #[fg=#bababa]%R\"" + +if-shell 'uname | grep -qi Linux' "set -g status-right \"#[fg=cyan]#(~/bin/battery-linux) #(~/bin/mailstatus.sh) #[fg=yellow]#(cat /proc/loadavg|awk '{print $1;}') #[fg=#666666]%F #[fg=#bababa]%R\"" + +# Scaling of status-bar sections +set -g status-right-length 40 + + +#### NAVIGATION + +# With C-a prefix +bind h select-pane -L # navigate left with h +bind j select-pane -D # navigate down with j +bind k select-pane -U # navigate up with k +bind l select-pane -R # navigate right with l +bind -r H resize-pane -L 5 # resize pane left with H +bind -r J resize-pane -D 5 # resize pane down with J +bind -r K resize-pane -U 5 # resize pane up with K +bind -r L resize-pane -R 5 # resize pane right with L + +# Navigate panes with Meta (alt) modifier + hjkl +bind -r -n M-h select-pane -L # navigate left with M-h +bind -r -n M-j select-pane -D # navigate down with M-j +bind -r -n M-k select-pane -U # navigate up with M-k +bind -r -n M-l select-pane -R # navigate right with M-l +bind -r -n M-H resize-pane -L 5 # resize pane left with M-H +bind -r -n M-J resize-pane -D 5 # resize pane down with M-J +bind -r -n M-K resize-pane -U 5 # resize pane up with M-K +bind -r -n M-L resize-pane -R 5 # resize pane right with M-L + +# Navigate windows with Meta (alt) modifier + number keys +bind -n M-1 select-window -t :=1 +bind -n M-2 select-window -t :=2 +bind -n M-3 select-window -t :=3 +bind -n M-4 select-window -t :=4 +bind -n M-5 select-window -t :=5 +bind -n M-6 select-window -t :=6 +bind -n M-7 select-window -t :=7 +bind -n M-8 select-window -t :=8 +bind -n M-9 select-window -t :=9 +bind -n M-0 select-window -t :=10 + +bind C-s last-window # go to last window with +bind C-a last-pane # go to last pane with +bind -n M-s last-window # go to last pane with M-s +bind -n M-a last-pane # go to last pane with M-a +bind -r n next-window # next window with n +bind -r b previous-window # next window with p + +bind -n -r M-n next-window # next window with +bind -n -r M-b previous-window # previous window with +#bind -n M-, run-shell "tmux list-panes -as -F \"##I.##P ##{pane_current_command} . #{pane_current_path} (#W)#F\" | fzf-tmux | cut -d \" \" -f 1 | xargs tmux select-pane -t" +bind -n M-, run-shell "tmux list-windows -F \"##I:##W\" | fzf-tmux | cut -d \":\" -f 1 | xargs tmux select-window -t" +bind -n M-. run-shell "tmux list-sessions -F \"##S\" | fzf-tmux | xargs tmux switch -t" + + +#### LAYOUT CHANGING BINDINGS + +# create panes in same directory +bind '"' split-window -c "#{pane_current_path}" +bind '%' split-window -h -c "#{pane_current_path}" + +bind -r z resize-pane -Z # toggle pane zoom with z +bind -r y next-layout # cycle to next pane layout with y +bind -r Y previous-layout # cycle to previous pane layout with Y +bind -r r rotate-window # rotate panes with r + +bind -n M-z resize-pane -Z # toggle pane zoom with +bind -n -r M-y next-layout # cycle to next pane layout with +bind -n -r M-Y previous-layout # cycle to previous pane layout with +bind -n -r M-r rotate-window # rotate panes with + +bind -r Left swap-window -t -1 # Swap window left +bind -r Right swap-window -t +1 # Swap window right + +bind -r B swap-window -t -1 # Swap window left +bind -r N swap-window -t +1 # Swap window right +bind -n -r M-B swap-window -t -1 # Swap window left +bind -n -r M-N swap-window -t +1 # Swap window right + +#### CLIPBOARD + +# enable reattach-to-user-namespace which fixes pasteboard access and launchctl +bind Space copy-mode # enter copy mode with +bind -n M-u copy-mode # enter copy mode with M-u +bind -T copy-mode-vi M-u send -X halfpage-up # scroll up with M-u +bind -T copy-mode-vi M-d send -X halfpage-down # scroll down with M-d +bind -T copy-mode-vi v send -X begin-selection # start "visual" with v + +# Copy (yank) with y +if-shell 'uname | grep -qi Linux && which xclip > /dev/null' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "DISPLAY=:0 xclip -i -sel clipboard"' +if-shell 'uname | grep -qi Darwin' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"' +if-shell 'uname | grep -qi Cygwin' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "cat > /dev/clipboard"' + +# Paste with C-a p or M-p +if-shell 'uname | grep -qi Linux && which xclip > /dev/null' 'bind p run "DISPLAY=:0 xclip -o | tmux load-buffer - ; tmux paste-buffer"' +if-shell 'uname | grep -qi Darwin && which reattach-to-user-namespace > /dev/null' 'bind p run "pbpaste | tmux load-buffer - ; tmux paste-buffer"' +if-shell 'uname | grep -qi Darwin' 'bind -n M-p run "pbpaste | tmux load-buffer - ; tmux paste-buffer"' +if-shell 'uname | grep -qi Cygwin' 'bind p run "cat /dev/clipboard | tmux load-buffer - ; tmux paste-buffer"' +if-shell 'uname | grep -qi Cygwin' 'bind -n M-p run "cat /dev/clipboard | tmux load-buffer - ; tmux paste-buffer"' + + +#### LAUNCH PROCESSES + +# use urlview to follow URLs in current pane +bind u capture-pane -J \; \ + save-buffer "/tmp/active_tmux_buffer" \; \ + delete-buffer \; \ + split-window -l 10 "urlview '/tmp/active_tmux_buffer' && rm /tmp/active_tmux_buffer" + +# Launch offlineimap in inactive splits +bind o split-window -p 25 '$SHELL -c "offlineimap -qf INBOX"' \; select-pane -l +bind O split-window -p 25 '$SHELL -c "offlineimap"' \; select-pane -l + +# Use nested bindings (l) for grouping process launch bindings +bind -n C-M-v new-window -n vim "/usr/local/bin/vim" +bind -n C-M-w new-window -n weather \ + "curl 'wttr.in/?m'; echo -e '\nPress to quit'; read -n 1 -s" + +# Open new window and resize status accordingly (should be a hook instead) +bind Enter new-window -c "#{pane_current_path}" "$SHELL" +bind -n M-Enter new-window \ + "tmux set status-right-length `echo $(tput cols)/2|bc|tr -d '\n'`; zsh" + +# Use nested bindings (m) for grouping music-control bindings +bind m switchc -Tmpd +bind -n M-m switchc -Tmpd +bind -Tmpd v new-window -n vimpc "vimpc" +bind -Tmpd p display "#(mpc toggle | tr '\n' ' ')" +bind -Tmpd s display "#(mpc stop | tr '\n' ' ')" +bind -Tmpd n display "#(mpc next | tr '\n' ' ')" +bind -Tmpd b display "#(mpc prev | tr '\n' ' ')" +bind -Tmpd r display "#(mpc clear && mpc ls | mpc add && mpc random on && mpc play | tr '\n' ' ')" +#bind -Tmpd r new-window -n mpc "mpc clear && mpc ls | mpc add && mpc shuffle && mpc play" + +bind -n C-M-p display "#(mpc toggle | tr '\n' ' ')" +bind -n C-M-s display "#(mpc stop | tr '\n' ' ')" +bind -n C-M-n display "#(mpc next | tr '\n' ' ')" +bind -n C-M-b display "#(mpc prev | tr '\n' ' ')" +#bind -n C-M-r display "#(mpc clear && mpc ls | mpc add && mpc random on && mpc play | tr '\n' ' ')" +bind -n C-M-a split-window -p 50 "source ~/code/fzf-mpd/fzf-mpd.zsh && fm" + +# fzf-locate from entire file system and insert result in current pane (Alt-`) +bind -n 'M-`' run "tmux split-window -p 40 'tmux send-keys -t #{pane_id} \"$(locate / | fzf -m | paste -sd\\ -)\"'" + +# Change to the previous pane, repeat the last command, change back +bind -n M-! last-pane \; send-keys C-p C-m \; last-pane diff --git a/regress/conf/d41d8cd98f00b204e9800998ecf8427e.conf b/regress/conf/d41d8cd98f00b204e9800998ecf8427e.conf new file mode 100644 index 0000000000..79bcdb5d81 --- /dev/null +++ b/regress/conf/d41d8cd98f00b204e9800998ecf8427e.conf @@ -0,0 +1,148 @@ +set-option -g prefix C-a +unbind-key C-b +bind-key C-a send-prefix + +set-option -s set-clipboard on +set -sg escape-time 0 +set -g bell-action other +set -g lock-after-time 1800 +set -g lock-command 'tput civis && read -s -n1' +set -g history-limit 10000 +set -g default-terminal "screen-256color" +set -g pane-border-style fg=white,bg=default +set -g pane-active-border-style fg=red,bg=default +set -g repeat-time 100 +set -g terminal-overrides "xterm*:kLFT5=\eOD:kRIT5=\eOC:kUP5=\eOA:kDN5=\eOB:smkx@:rmkx@:Tc" + +#set -g terminal-overrides '*88col*:colors=88,*256col*:colors=256,rxvt-uni*:Tc:XT:Ms=\E]52;%p1%s;%p2%s\007:Cc=\E]12;%p1%s\007:Cr=\E]12;green\007:Cs=\E]777;Cs;%p1%d\007' +set -g mouse on +set -g status-style bg=blue,fg=cyan + +set-hook -g alert-bell 'run -b "notify-send \"Bell in session #{session_name}:#{window_index}:#{window_name}\""' + +unbind-key / +unbind-key c +unbind-key d +unbind-key f +unbind-key i +unbind-key l +unbind-key n +unbind-key o +unbind-key p +unbind-key r +unbind-key s +unbind-key t +unbind-key w +unbind-key x +unbind-key | +unbind-key - +unbind-key A +unbind-key S +unbind-key . +unbind-key "'" +unbind-key '#' +unbind-key ' ' +unbind-key z +unbind-key ^z + +bind a send-prefix +bind c new-window -a -c '#{pane_current_path}' +bind d detach-client +bind "/" command-prompt "find-window '%%'" +bind i display-message +bind a last-window +bind n next-window +bind o select-pane -D +bind p previous-window +bind r respawn-window +bind s choose-tree -Z +bind t clock-mode +bind w choose-window +bind k confirm-before kill-pane +bind x set lock-command '/usr/bin/vlock' \; lock-client \; set lock-command 'tput civis && read -s -n1' +bind "|" split-window -v -c '#{pane_current_path}' +bind "-" split-window -h -c '#{pane_current_path}' +bind l command-prompt "rename-window '%%'" +bind S command-prompt "rename-session '%%'" +bind . display-panes +bind "'" command-prompt -p "SSH: " "new-window -n %1 'ssh %1'" +bind ' ' choose-window +bind z resize-pane -Z + +bind ^a last-window +bind ^c new-window -a -c '#{pane_current_path}' +bind ^d detach-client +bind ^i display-message +bind a last-window +bind ^n next-window +bind ^o select-pane -D +bind ^p previous-window +bind ^r respawn-window +bind ^s choose-session +bind ^t clock-mode +bind ^w choose-window +bind ^k confirm-before kill-pane +bind ^x lock-client +bind ^S command-prompt "rename-session '%%'" +bind ^z resize-pane -Z + +bind -n C-Left previous-window +bind -n C-Right next-window +bind -n C-s set status + +bind -r C-Left swapw -t:- +bind -r C-Right swapw -t:+ + +# Status stuff. +set -g status-left-style "fg=white, bg=magenta" +set -g status-left-length 30 +set -g status-left "#S " +set -g status-right-length 30 +set -g status-right-fg white +set -g status-right-bg blue +set -g status-right "#{?client_prefix,#[reverse][^a]#[noreverse],}[%a %d/%m %H:%M]" +set -g display-panes-time 4000 +set -g window-status-bell-style reverse + +#setw -g window-status-current-fg white +#setw -g window-status-current-bg colour34 +setw -g mode-keys vi + +setw -g window-status-separator "| " +#setw -g window-status-format "#[bg=blue]#I:#W:#{window_flags}#[bg=default]" +#setw -g window-status-current-format "#[fg=black,bg=green]#I:#W:#{window_flags}" + +setw -g window-status-format "#[bg=blue]#I:#W:#{?window_linked,+#{window_flags},#{window_flags} }#[bg=default]" +setw -g window-status-current-format "#[fg=black,bg=green]#I:#W:#{?window_linked,+#{window_flags},#{window_flags}}" + +set-window-option -g clock-mode-colour green + +# Sessions +new -d -sspecial +new -d -swork -d -nmutt 'exec neomutt' +neww -d +neww -d +neww -d +neww -d + +# FIXME -- the entire block below is required for taskwarrior. +#new -d -stask -ntask -x237 -y 79 +#selectl -ttask tiled +#set -ttask status off +#splitw -ttask:task +#splitw -ttask:task +#splitw -ttask:task +#splitw -ttask:task +#splitw -ttask:task +#selectl -ttask:task 4ada,237x79,0,0[237x67,0,0{156x67,0,0,5,80x67,157,0[80x27,157,0,19,80x22,157,28,20,80x16,157,51,21]},237x11,0,68,22] +#send -ttask:task.0 'cyclenext list' 'C-m' +#send -ttask:task.1 'clear ; tasksh' 'C-m' +#send -ttask:task.2 'cyclenext summary' 'C-m' +#send -ttask:task.3 'cyclenext burndown.daily' 'C-m' +#send -ttask:task.4 'cyclenext ghistory.monthly' 'C-m' +#selectp -ttask:task.1 +#linkw -stask:task -twork +#set -t task:task remain-on-exit on + +set -t work:irc remain-on-exit on +set -t work:mutt remain-on-exit on diff --git a/regress/conf/dfd579a114a8366b5a665c264e29c084.conf b/regress/conf/dfd579a114a8366b5a665c264e29c084.conf new file mode 100644 index 0000000000..7ad12c953a --- /dev/null +++ b/regress/conf/dfd579a114a8366b5a665c264e29c084.conf @@ -0,0 +1,52 @@ +set -as terminal-overrides '\e\r\n\t\u12ab\U000012ab' +set -as terminal-overrides "\e\r\n\t\u12ab\U000012ab" + +# format #{abc #{def}} +# abc + +set -g status-left \ +"\u007c \ +abc" + +%if #{TMUX} +set -g status-bg red +%endif + +X=1 +Y=2 set -g status-bg blue; Z=3 set -g status-bg magenta + +set -g status-left "~/abcdef"$HOME # abcdef + +%if #{l:1} set -g status-bg red %endif + +%if #{l:0} +X=1 +%elif #{l:1} +Y=1 +%if #{l:0} +Y=2 +%else +Y=3 +%endif +%endif + +bind x display-message \"hello\" + +bind c neww -c ~ +bind ';' lsk + +set -g status-left "a""b" +set -g status-left ~ + +set -g status-left 'a $HOME b ~ c \e\e\e' +set -g status-left "a $HOME b ~ c \e\e\e" + +set -s command-alias[99] "foo=lsk;neww" +bind-key -n C-s if-shell 'true' 'display-message hello' + +set -g status-left-style \ +bg=red +set -g status-left \\\ +abc + +set -g status-left 'xyz' ; %if #{l:1} set -g status-bg red %endif ; bind x lsk diff --git a/regress/conf/e2661d67d0d45a8647fb95de76ec8174.conf b/regress/conf/e2661d67d0d45a8647fb95de76ec8174.conf new file mode 100644 index 0000000000..79f46df157 --- /dev/null +++ b/regress/conf/e2661d67d0d45a8647fb95de76ec8174.conf @@ -0,0 +1,78 @@ +# Scott Rochford's tmux configuration +# +# change the prefix to the GNU screen default (avoids clash with page up in vi) +set -g prefix C-a +unbind-key C-b +bind-key C-a send-prefix +# toggle sending input to all panes +bind-key b set-window-option synchronize-panes +# alternative to ',' which doesn't pre-fill the prompt with the existing name +bind-key < command-prompt "rename-window '%%'" + +# Disabled all of these in favour of changing 'default-command' below. +#bind-key C-p pipe-pane -o 'cat >>~/tmux_logs/output.$(echo #I-#P-#W-#T | sed "s/[^[:alnum:].-]/_/g")' \; display-message 'Toggled logging' +# From http://unix.stackexchange.com/questions/5832/is-there-an-equivalent-of-gnu-screens-log-command-in-tmux +# bind-key H pipe-pane -o "exec cat >>$HOME/'#W-tmux.log'" \; display-message 'Toggled logging to $HOME/#W-tmux.log' +#bind-key H pipe-pane "exec cat >>$HOME/'#W-tmux.log'" \; display-message 'Started logging to $HOME/#W-tmux.log' +#bind-key h pipe-pane \; display-message 'Ended logging to $HOME/#W-tmux.log' + +#set -g utf8 on + +set-option -g history-limit 32768 + +# no longer available in 2.2 +#set-option -g mouse-select-pane on +#set-option -g mouse-select-window on +set-option -g mouse on + +# increase the amount of time status bar messages are displayed for (default 1000 I think) +set-option -g display-time 1500 +# unfortunately this seems to have no effect in putty :-( +set-option -g set-clipboard on +set-option -g default-command 'tmux pipe-pane -o "cat >>~/tmux_logs/output-`date +%Y%m%d-%H%M%S-$$`" ; /bin/ksh -l' +# +# allow yank into system clipboard +# from http://stackoverflow.com/questions/17255031/how-to-copy-from-tmux-running-in-putty-to-windows-clipbard +# +# for some reason this is wrapping at 80 cols, using save- instead of show- helps +# -b for background is needed because xclip continues to run to service the clipboard paste reqeusts until the +# clipboard buffer is replaced with some new contents +#bind C-y run-shell -b "tmux save-buffer - | DISPLAY=$(<~/.xdisplay) xclip -selection clipboard -in && tmux display-message 'xclipped successfully'" +bind C-y save-buffer ~/etc/clipboard.pipe +# +# this was just for testing, but interestingly for some reason tmux-show-buffer >/tmp/t never terminates, writing to a pipe works fine?? +#bind C-z run-shell "tmux show-buffer | cat >/tmp/t" +# move x clipboard into tmux paste buffer +#bind C-p run-shell -b "xclip -o -selection clipboard | tmux load-buffer - ; tmux paste-buffer" +bind C-p run-shell "DISPLAY=$(<~/.xdisplay) xclip -o -selection clipboard | tmux load-buffer - ; tmux paste-buffer" + +# switch to last-but-one window (like prefix-l but last, last) +# only works on tmux-2.4 + with Nicholas Marriott's patch from my feature request, unless it reached mainline.... +#bind k run-shell "tmux select-window -t $(tmux list-windows -F '#{session_stack}' | awk -F, '{print $3;exit}END{print $1}')" +bind k run-shell "tmux select-window -t $(echo #{session_stack} | awk -F, '{w=$1}NF>=3{w=$3;exit}END{print w}')" + +# switch to oldest window (for clean-up), not sure why brackets are required around (NF) here... +bind K run-shell "tmux select-window -t $(echo #{session_stack} | awk -F, '{print $(NF)}')" + +# prompt for hosts to connect to, open a new synchronized window with horizontally split panes for each host, supports brace expansion +bind N command-prompt -p hosts: 'run-shell -b "bash -c \"~/lbin/nw %% >/dev/null\""' + +# seems to cause unexpected resizes when focussing on putty :-( +#set-option mouse-resize-pane on + +#05:59 < Celti> annihilannic: I believe the #{pane_in_mode} format does what you want +#05:59 < Celti> put it in your statusline +#05:59 < Celti> annihilannic: No, my mistake, I should have read farther down, you want #{pane_synchronized} +# only works in tmux 2.0?, higher than 1.6.3 anyawy +set-option -g window-status-format ' #I:#W#F#{?pane_synchronized,S,}' +#set-option -g window-status-current-format ' #I:#W#{?pane_synchronized,[sync],}#F' +# to highlight in red when sync is on... not sure why I did this with set-window-option instead of set-option, perhaps +# both work? +set-window-option -g window-status-current-format "#{?pane_synchronized,#[bg=red],}#{?window_zoomed_flag,#[bg=yellow],} #I:#W#F#{?pane_synchronized,S,}" +# +# also only in 2.0? if I use this, don't need #F in window-status-*-format? - actually, nah, +# still useful for showing [Z]oomed, or - last active, etc. +set-option -g window-status-current-style bg=blue + +# Toggle input on a pane (from Thomas Sattler) +bind-key R if -F '#{pane_input_off}' "select-pane -e; select-pane -P fg=default" "select-pane -d; select-pane -P fg=yellow" \ No newline at end of file diff --git a/regress/conf/ed08995f38b5a3079262a88d2563abe4.conf b/regress/conf/ed08995f38b5a3079262a88d2563abe4.conf new file mode 100644 index 0000000000..a0fd150069 --- /dev/null +++ b/regress/conf/ed08995f38b5a3079262a88d2563abe4.conf @@ -0,0 +1,283 @@ +#---------------------------------------------------------------------------# +# .tmux.conf +# Helmut K. C. Tessarek, Last update 2018-10-16 +#---------------------------------------------------------------------------# + +#---------------------------------------------------------------------------# +# set prefix key to ctrl+a / ctrl-b is used in vi for going back one page +#---------------------------------------------------------------------------# +unbind C-b +set -g prefix C-a + +#---------------------------------------------------------------------------# +# send the prefix to client inside window (nested sessions) +#---------------------------------------------------------------------------# +bind-key a send-prefix + +#---------------------------------------------------------------------------# +# toggle last window like screen +#---------------------------------------------------------------------------# +bind-key C-a last-window + +#---------------------------------------------------------------------------# +# start window indexing at one instead of zero +#---------------------------------------------------------------------------# +#set -g base-index 1 + +#---------------------------------------------------------------------------# +# default terminal - we want 256 colors !!! +#---------------------------------------------------------------------------# +set -g default-terminal "screen-256color" + +#---------------------------------------------------------------------------# +# on-screen time for status messages in ms +#---------------------------------------------------------------------------# +set -g display-time 2000 + +#---------------------------------------------------------------------------# +# on-screen time for display-panes in ms +#---------------------------------------------------------------------------# +set -g display-panes-time 2000 + +#---------------------------------------------------------------------------# +# color for display pane indicator +#---------------------------------------------------------------------------# +set -g display-panes-colour "cyan" +#set -g display-panes-active-colour "#0087ff" +#set -g display-panes-active-colour "red" + +#---------------------------------------------------------------------------# +# open a man page in new window +#---------------------------------------------------------------------------# +unbind m +bind m command-prompt "split-window 'exec man %%'" + +#---------------------------------------------------------------------------# +# quick view of processes +#---------------------------------------------------------------------------# +#bind '~' split-window "exec htop" + +#---------------------------------------------------------------------------# +# scrollback buffer n lines +#---------------------------------------------------------------------------# +set -g history-limit 5000 + +#---------------------------------------------------------------------------# +# toggle status bar +#---------------------------------------------------------------------------# +unbind b +bind-key b set-option status + +#---------------------------------------------------------------------------# +# resize panes like vim +# feel free to change the "1" to however many lines you want to resize by, +# only one at a time can be slow +#---------------------------------------------------------------------------# +unbind < +unbind > +unbind - +unbind + +bind -r < resize-pane -L 1 +bind -r > resize-pane -R 1 +bind -r - resize-pane -D 1 +bind -r + resize-pane -U 1 + +#---------------------------------------------------------------------------# +# toggle mouse helpers +#---------------------------------------------------------------------------# +unbind Enter +unbind C-m +bind C-m set-option mouse \; display-message 'mouse -> #{?mouse,on,off}' + +#---------------------------------------------------------------------------# +# Reload config file +#---------------------------------------------------------------------------# +unbind R +bind-key R source-file ~/.tmux.conf \; display-message "Reloading configuration done" + +#---------------------------------------------------------------------------# +# start ssh session in new window +#---------------------------------------------------------------------------# +unbind S +bind-key S command-prompt "new-window -n %1 'ssh %1'" + +#---------------------------------------------------------------------------# +# start new session +#---------------------------------------------------------------------------# +unbind C +bind-key C command-prompt "new-session -s %1" + +#---------------------------------------------------------------------------# +# Keys to switch session +#---------------------------------------------------------------------------# +bind Q switchc -t0 +bind W switchc -t compile +bind E switchc -t config + +#---------------------------------------------------------------------------# +# break pane in background +#---------------------------------------------------------------------------# +unbind '@' +bind '@' break-pane -d + +#---------------------------------------------------------------------------# +# join pane with target window +#---------------------------------------------------------------------------# +unbind ^ +bind ^ command-prompt "join-pane -t %1" + +#---------------------------------------------------------------------------# +# move around panes with hjkl, as one would in vim after pressing ctrl-w +#---------------------------------------------------------------------------# +#bind h select-pane -L +#bind j select-pane -D +#bind k select-pane -U +#bind l select-pane -R + +#---------------------------------------------------------------------------# +# bind : to command-prompt like vim +# this is the default in tmux already +#---------------------------------------------------------------------------# +bind : command-prompt + +#---------------------------------------------------------------------------# +# Remain on exit +#---------------------------------------------------------------------------# +#setw -g remain-on-exit on + +#---------------------------------------------------------------------------# +# vi-style controls for copy mode +#---------------------------------------------------------------------------# +setw -g mode-keys vi + +#---------------------------------------------------------------------------# +# Make mouse useful in copy mode +#---------------------------------------------------------------------------# +#setw -g mode-mouse on + +#---------------------------------------------------------------------------# +# More straight forward key bindings for splitting +#---------------------------------------------------------------------------# +unbind % +unbind v +#bind | split-window -h +bind v split-window -h +unbind '"' +unbind h +#bind - split-window -v +bind h split-window -v + +#---------------------------------------------------------------------------# +# Synchronize panes +#---------------------------------------------------------------------------# +unbind y +bind y set-window-option synchronize-panes \; display-message 'synchronize-panes -> #{?synchronize-panes,on,off}' + +#---------------------------------------------------------------------------# +# Other key codes: Tab, BTab, Escape +#---------------------------------------------------------------------------# + +#---------------------------------------------------------------------------# +# Clock +#---------------------------------------------------------------------------# +setw -g clock-mode-colour green +setw -g clock-mode-style 24 + +#---------------------------------------------------------------------------# +# Terminal emulator window title +#---------------------------------------------------------------------------# +set -g set-titles on +set -g set-titles-string '#S:#I.#P #W' + +#---------------------------------------------------------------------------# +# Status Bar +#---------------------------------------------------------------------------# +set -g status-bg black +set -g status-fg white +set -g status-interval 1 +set -g status-left-length 30 +set -g status-left '#[fg=green]#h#[default] ' +#set -g status-right '#[fg=yellow]#(cut -d " " -f 1-4 /proc/loadavg)#[default] #[fg=cyan,bold]%Y-%m-%d %H:%M:%S#[default]' +#set -g status-right '#[fg=yellow,bold]%Y-%m-%d %H:%M#[default]' +set -g status-right '#[fg=yellow]%Y-%m-%d %H:%M %Z#[default]' +#set -g status-justify center +#set -g status-keys vi + +set -g allow-rename off +setw -g automatic-rename on + +#---------------------------------------------------------------------------# +# Highlighting the active window in status bar +#---------------------------------------------------------------------------# +#setw -g window-status-current-bg red +set-option -g window-status-format "#I:#W#F#{?pane_synchronized,S,}" +set-window-option -g window-status-current-format "#{?pane_synchronized,#[bg=red],}#{?window_zoomed_flag,#[bg=colour130],}#I:#W#F#{?pane_synchronized,S,}" +set-option -g window-status-current-style bg=blue + +#---------------------------------------------------------------------------# +# global update environment +#---------------------------------------------------------------------------# +set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY TZ" + +#---------------------------------------------------------------------------# +# settings for AIX +# terminal overrides to enable colors +# set default terminal to vt100 or xterm (screen does not exist on AIX) +#---------------------------------------------------------------------------# +if-shell "uname|grep AIX" 'set -g terminal-overrides "xterm*:XT,xterm*:setab=\\E[4%p1%dm,xterm*:setaf=\\E[3%p1%dm"' +#if-shell "uname|grep AIX" "set -g default-terminal vt100" +if-shell "uname|grep AIX" "set -g default-terminal xterm" + +#---------------------------------------------------------------------------# +# settings for macOS +#---------------------------------------------------------------------------# +if-shell "uname|grep Darwin" 'set -g default-command "/bin/bash -l"' + +#---------------------------------------------------------------------------# +# Pane coloring +# set inactive/active window styles +#---------------------------------------------------------------------------# +set -g window-style "fg=colour247,bg=colour234" +set -g window-active-style "fg=colour250,bg=black" +set -g @TPCS "1" + +#---------------------------------------------------------------------------# +# pane border - different style / use cyan +#---------------------------------------------------------------------------# +#set -g pane-border-bg colour235 +#set -g pane-border-fg colour238 +#set -g pane-active-border-bg colour234 +#set -g pane-active-border-fg colour51 + +#---------------------------------------------------------------------------# +# toggle pane coloring on/off +#---------------------------------------------------------------------------# +unbind C-b +bind C-b if -F '#{@TPCS}' \ + 'set -g window-style "fg=default,bg=default" ; set -g window-active-style "fg=default,bg=default" ; set -g @TPCS "0"; display-message "Pane coloring -> off"' \ + 'set -g window-style "fg=colour247,bg=colour234" ; set -g window-active-style "fg=colour250,bg=black" ; set -g @TPCS "1"; display-message "Pane coloring -> on"' + +#---------------------------------------------------------------------------# +# List of plugins +#---------------------------------------------------------------------------# +set -g @plugin 'tmux-plugins/tpm' +#set -g @plugin 'tmux-plugins/tmux-sensible' +set -g @plugin 'tmux-plugins/tmux-resurrect' +set -g @plugin 'tmux-plugins/tmux-logging' + +set -g @resurrect-capture-pane-contents 'on' +set -g @resurrect-save-bash-history 'on' + +set -g @logging-path $HOME +set -g @screen-capture-path $HOME +set -g @save-complete-history-path $HOME + +# Other examples: +# set -g @plugin 'github_username/plugin_name' +# set -g @plugin 'git@github.com/user/plugin' +# set -g @plugin 'git@bitbucket.com/user/plugin' + +#---------------------------------------------------------------------------# +# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) +#---------------------------------------------------------------------------# +run '~/.tmux/plugins/tpm/tpm' diff --git a/regress/control-client-sanity.sh b/regress/control-client-sanity.sh new file mode 100644 index 0000000000..bf76b4d5a9 --- /dev/null +++ b/regress/control-client-sanity.sh @@ -0,0 +1,42 @@ +#!/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 + +TMP=$(mktemp) +OUT=$(mktemp) +trap "rm -f $TMP $OUT" 0 1 15 + +$TMUX -f/dev/null new -d -x200 -y200 || exit 1 +$TMUX -f/dev/null splitw || exit 1 +sleep 1 +cat <$TMP +selectp -t%0 +splitw +neww +splitw +selectp -t%0 +killp -t%1 +swapp -t%2 -s%3 +neww +splitw +splitw +selectl tiled +killw +EOF +sleep 1 +$TMUX has || exit 1 +$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1 +cat </dev/null + +exit 0 diff --git a/regress/control-client-size.sh b/regress/control-client-size.sh index cef48a0ed0..5847ede3e0 100644 --- a/regress/control-client-size.sh +++ b/regress/control-client-size.sh @@ -18,32 +18,32 @@ trap "rm -f $TMP $OUT" 0 1 15 $TMUX -f/dev/null new -d || exit 1 sleep 1 cat <$TMP -ls -F':#{session_width} #{session_height}' +ls -F':#{window_width} #{window_height}' refresh -C 100,50 -ls -F':#{session_width} #{session_height}' +ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT -printf ":80 24\n:100 50\n"|cmp -s $OUT || exit 1 +printf ":80 24\n:100 50\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null $TMUX -f/dev/null new -d || exit 1 sleep 1 -cat <$TMP -ls -F':#{session_width} #{session_height}' +cat <$TMP +ls -F':#{window_width} #{window_height}' refresh -C 80,24 -ls -F':#{session_width} #{session_height}' +ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT -printf ":80 24\n:80 24\n"|cmp -s $OUT || exit 1 +printf ":80 24\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null -cat <$TMP -ls -F':#{session_width} #{session_height}' +cat <$TMP +ls -F':#{window_width} #{window_height}' refresh -C 80,24 -ls -F':#{session_width} #{session_height}' +ls -F':#{window_width} #{window_height}' EOF grep ^: $TMP >$OUT -printf ":100 50\n:80 24\n"|cmp -s $OUT || exit 1 +printf ":100 50\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null exit 0 diff --git a/regress/cursor-test.txt b/regress/cursor-test.txt new file mode 100644 index 0000000000..67ed52c8fe --- /dev/null +++ b/regress/cursor-test.txt @@ -0,0 +1,6 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute +irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat +nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia +deserunt mollit anim id est laborum. diff --git a/regress/cursor-test1.result b/regress/cursor-test1.result new file mode 100644 index 0000000000..71b9a4b63e --- /dev/null +++ b/regress/cursor-test1.result @@ -0,0 +1,33 @@ +14 8 t +0 ud exercitation ullamco laboris nisi ut +1 aliquip ex ea +2 commodo consequat. Duis aute +3 irure dolor in reprehenderit in voluptat +4 e velit esse cillum dolore eu fugiat +5 nulla pariatur. Excepteur sint occaecat +6 cupidatat non proident, sunt in culpa qu +7 i officia +8 deserunt mollit anim id est laborum. +9 +4 6 t +0 cupidatat +1 non proide +2 nt, sunt i +3 n culpa qu +4 i officia +5 deserunt m +6 ollit anim +7 id est la +8 borum. +9 +14 8 t +0 incididunt ut labore et dolore magna aliqua. Ut en +1 im ad minim veniam, quis nostrud exercitation ulla +2 mco laboris nisi ut aliquip ex ea +3 commodo consequat. Duis aute +4 irure dolor in reprehenderit in voluptate velit es +5 se cillum dolore eu fugiat +6 nulla pariatur. Excepteur sint occaecat cupidatat +7 non proident, sunt in culpa qui officia +8 deserunt mollit anim id est laborum. +9 diff --git a/regress/cursor-test1.sh b/regress/cursor-test1.sh new file mode 100644 index 0000000000..2dc205392b --- /dev/null +++ b/regress/cursor-test1.sh @@ -0,0 +1,29 @@ +#!/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 + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +$TMUX -f/dev/null new -d -x40 -y10 \ + "cat cursor-test.txt; printf '\e[9;15H'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x10 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x50 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP + +cmp -s $TMP cursor-test1.result || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cursor-test2.result b/regress/cursor-test2.result new file mode 100644 index 0000000000..dc005d2b41 --- /dev/null +++ b/regress/cursor-test2.result @@ -0,0 +1,33 @@ +9 7 a +0 cupidatat +1 non proide +2 nt, sunt i +3 n culpa qu +4 i officia +5 deserunt m +6 ollit anim +7 id est la +8 borum. +9 +4 6 a +0 icia +1 deser +2 unt m +3 ollit +4 anim +5 id e +6 st la +7 borum +8 . +9 +29 8 a +0 incididunt ut labore et dolore magna aliqua. Ut en +1 im ad minim veniam, quis nostrud exercitation ulla +2 mco laboris nisi ut aliquip ex ea +3 commodo consequat. Duis aute +4 irure dolor in reprehenderit in voluptate velit es +5 se cillum dolore eu fugiat +6 nulla pariatur. Excepteur sint occaecat cupidatat +7 non proident, sunt in culpa qui officia +8 deserunt mollit anim id est laborum. +9 diff --git a/regress/cursor-test2.sh b/regress/cursor-test2.sh new file mode 100644 index 0000000000..9791f56708 --- /dev/null +++ b/regress/cursor-test2.sh @@ -0,0 +1,29 @@ +#!/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 + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +$TMUX -f/dev/null new -d -x10 -y10 \ + "cat cursor-test.txt; printf '\e[8;10H'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x5 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x50 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP + +cmp -s $TMP cursor-test2.result || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cursor-test3.result b/regress/cursor-test3.result new file mode 100644 index 0000000000..e72b1a9bbb --- /dev/null +++ b/regress/cursor-test3.result @@ -0,0 +1,9 @@ +6 1 b +0 abcdefa +1 bcdefab +3 1 b +0 fabcd +1 efab +6 1 b +0 abcdefa +1 bcdefab diff --git a/regress/cursor-test3.sh b/regress/cursor-test3.sh new file mode 100644 index 0000000000..8bb4bd6fb5 --- /dev/null +++ b/regress/cursor-test3.sh @@ -0,0 +1,29 @@ +#!/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 + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +$TMUX -f/dev/null new -d -x7 -y2 \ + "printf 'abcdefabcdefab'; printf '\e[2;7H'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x5 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x7 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP + +cmp -s $TMP cursor-test3.result || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cursor-test4.result b/regress/cursor-test4.result new file mode 100644 index 0000000000..db26e4faa5 --- /dev/null +++ b/regress/cursor-test4.result @@ -0,0 +1,16 @@ +0 1 +0 abcdef +1 +2 +0 1 +0 abcdef +1 +2 +0 1 +0 def +1 +2 +0 1 +0 abcdef +1 +2 diff --git a/regress/cursor-test4.sh b/regress/cursor-test4.sh new file mode 100644 index 0000000000..2bf1bb0efa --- /dev/null +++ b/regress/cursor-test4.sh @@ -0,0 +1,31 @@ +#!/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 + +TMP=$(mktemp) +trap "rm -f $TMP" 0 1 15 + +$TMUX -f/dev/null new -d -x10 -y3 "printf 'abcdef\n'; cat" || exit 1 +$TMUX set -g window-size manual || exit 1 + +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x20 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x3 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP +$TMUX resizew -x10 || exit 1 +$TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP +$TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP + +cmp -s $TMP cursor-test4.result || exit 1 + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/format-strings.sh b/regress/format-strings.sh new file mode 100644 index 0000000000..726b46bc8e --- /dev/null +++ b/regress/format-strings.sh @@ -0,0 +1,183 @@ +#!/bin/sh + +# Tests of formats as described in tmux(1) FORMATS + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest" + +# test_format $format $expected_result +test_format() +{ + fmt="$1" + exp="$2" + + out=$($TMUX display-message -p "$fmt") + + if [ "$out" != "$exp" ]; then + echo "Format test failed for '$fmt'." + echo "Expected: '$exp'" + echo "But got '$out'" + exit 1 + fi +} + +# test_conditional_with_pane_in_mode $format $exp1 $exp2 +# +# Tests the format string $format to yield $exp1 if #{pane_in_mode} is true and +# $exp2 when #{pane_in_mode} is false. +test_conditional_with_pane_in_mode() +{ + fmt="$1" + exp_true="$2" + exp_false="$3" + + $TMUX copy-mode # enter copy mode + test_format "$fmt" "$exp_true" + $TMUX send-keys -X cancel # leave copy mode + test_format "$fmt" "$exp_false" +} + +# test_conditional_with_session_name #format $exp_summer $exp_winter +# +# Tests the format string $format to yield $exp_summer if the session name is +# 'Summer' and $exp_winter if the session name is 'Winter'. +test_conditional_with_session_name() +{ + fmt="$1" + exp_summer="$2" + exp_winter="$3" + + $TMUX rename-session "Summer" + test_format "$fmt" "$exp_summer" + $TMUX rename-session "Winter" + test_format "$fmt" "$exp_winter" + $TMUX rename-session "Summer" # restore default +} + + +$TMUX kill-server 2>/dev/null +$TMUX -f/dev/null new-session -d || exit 1 +$TMUX rename-session "Summer" || exit 1 # used later in conditionals + +# Plain string without substitutions et al +test_format "abc xyz" "abc xyz" + +# Test basic escapes for "#", "{", "#{" "}", "#}", "," +test_format "##" "#" +test_format "#," "," +test_format "{" "{" +test_format "##{" "#{" +test_format "#}" "}" +test_format "###}" "#}" # not a "basic" one but interesting nevertheless + +# Simple expansion +test_format "#{pane_in_mode}" "0" + +# Simple conditionals +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,xyz}" "abc" "xyz" + +# Expansion in conditionals +test_conditional_with_pane_in_mode "#{?pane_in_mode,#{session_name},xyz}" "Summer" "xyz" + +# Basic escapes in conditionals +# First argument +test_conditional_with_pane_in_mode "#{?pane_in_mode,##,xyz}" "#" "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,#,,xyz}" "," "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,{,xyz}" "{" "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,##{,xyz}" "#{" "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,#},xyz}" "}" "xyz" +# not a "basic" one but interesting nevertheless +test_conditional_with_pane_in_mode "#{?pane_in_mode,###},xyz}" "#}" "xyz" +# Second argument +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,##}" "abc" "#" +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#,}" "abc" "," +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,{}" "abc" "{" +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,##{}" "abc" "#{" +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#}}" "abc" "}" +# not a "basic" one but interesting nevertheless +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,###}}" "abc" "#}" +# mixed +test_conditional_with_pane_in_mode "#{?pane_in_mode,{,#}}" "{" "}" +test_conditional_with_pane_in_mode "#{?pane_in_mode,#},{}" "}" "{" +test_conditional_with_pane_in_mode "#{?pane_in_mode,##{,###}}" "#{" "#}" +test_conditional_with_pane_in_mode "#{?pane_in_mode,###},##{}" "#}" "#{" + +# Conditionals split on the second comma (this is not documented) +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,xyz,bonus}" "abc" "xyz,bonus" + +# Curly brackets {...} do not capture a comma inside of conditionals as the +# conditional ends on the first '}' +test_conditional_with_pane_in_mode "#{?pane_in_mode,{abc,xyz},bonus}" "{abc,bonus}" "xyz,bonus}" + +# Substitutions '#{...}' capture the comma +# invalid format: #{abc,xyz} is not a known variable name. +#test_conditional_with_pane_in_mode "#{?pane_in_mode,#{abc,xyz},bonus}" "" "bonus" + +# Parenthesis (...) do not captura a comma +test_conditional_with_pane_in_mode "#{?pane_in_mode,(abc,xyz),bonus}" "(abc" "xyz),bonus" +test_conditional_with_pane_in_mode "#{?pane_in_mode,(abc#,xyz),bonus}" "(abc,xyz)" "bonus" + +# Brackets [...] do not captura a comma +test_conditional_with_pane_in_mode "#{?pane_in_mode,[abc,xyz],bonus}" "[abc" "xyz],bonus" +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>" +test_format "#{?pane_in_mode,#(echo #,),xyz}" "xyz" +test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "<'echo ,' not ready>" "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" +#test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "," "xyz" + +# invalid format: '#(' is not closed in the first argument of #{?,,}. +#test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo ,),xyz}" "" "),xyz" + +# Escape comma inside of #[...] +test_conditional_with_pane_in_mode "#{?pane_in_mode,#[fg=default#,bg=default]abc,xyz}" "#[fg=default,bg=default]abc" "xyz" +# invalid format: '#[' is not closed in the first argument of #{?,,} +#test_conditional_with_pane_in_mode "#{?pane_in_mode,#[fg=default,bg=default]abc,xyz}" "" "bg=default]abc,xyz" + +# Conditionals with comparison +test_conditional_with_session_name "#{?#{==:#{session_name},Summer},abc,xyz}" "abc" "xyz" +# Conditionals with comparison and escaped commas +$TMUX rename-session "," +test_format "#{?#{==:#,,#{session_name}},abc,xyz}" "abc" +$TMUX rename-session "Summer" # reset to default + +# Conditional in conditional +test_conditional_with_pane_in_mode "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" "ABC" "xyz" +test_conditional_with_session_name "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" "xyz" "xyz" + +test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#{?#{==:#{session_name},Summer},ABC,XYZ}}" "abc" "ABC" +test_conditional_with_session_name "#{?pane_in_mode,abc,#{?#{==:#{session_name},Summer},ABC,XYZ}}" "ABC" "XYZ" + +# Some fancy stackings +test_conditional_with_pane_in_mode "#{?#{==:#{?pane_in_mode,#{session_name},#(echo Spring)},Summer},abc,xyz}" "abc" "xyz" + + + +# Format test for the literal option +# Note: The behavior for #{l:...} with escapes is sometimes weird as #{l:...} +# respects the escapes. +test_format "#{l:#{}}" "#{}" +test_format "#{l:#{pane_in_mode}}" "#{pane_in_mode}" +test_format "#{l:#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}}" "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" + +# With escapes (which escape but are returned literally) +test_format "#{l:##{}" "##{" +test_format "#{l:#{#}}}" "#{#}}" + +# Invalid formats: +#test_format "#{l:#{}" "" +#test_format "#{l:#{#}}" "" + +exit 0 diff --git a/regress/kill-session-process-exit.sh b/regress/kill-session-process-exit.sh index 27d883e2dc..69ee27a231 100644 --- a/regress/kill-session-process-exit.sh +++ b/regress/kill-session-process-exit.sh @@ -9,9 +9,9 @@ TERM=screen TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null -$TMUX new -d 'sleep 1000' || exit 1 +$TMUX -f/dev/null new -d 'sleep 1000' || exit 1 P=$($TMUX display -pt0:0.0 '#{pane_pid}') -$TMUX new -d || exit 1 +$TMUX -f/dev/null new -d || exit 1 sleep 1 $TMUX kill-session -t0: sleep 1 diff --git a/regress/new-session-size.sh b/regress/new-session-size.sh index f9394f35b1..89fc580d61 100644 --- a/regress/new-session-size.sh +++ b/regress/new-session-size.sh @@ -14,14 +14,14 @@ trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d $TMP -printf "80 24\n"|cmp -s $TMP || exit 1 +$TMUX ls -F "#{window_width} #{window_height}" >$TMP +printf "80 24\n"|cmp -s $TMP - || exit 1 $TMUX kill-server 2>/dev/null $TMUX -f/dev/null new -d -x 100 -y 50 $TMP -printf "100 50\n"|cmp -s $TMP || exit 1 +$TMUX ls -F "#{window_width} #{window_height}" >$TMP +printf "100 50\n"|cmp -s $TMP - || exit 1 $TMUX kill-server 2>/dev/null exit 0 diff --git a/regress/xenl-terminal.sh b/regress/xenl-terminal.sh new file mode 100644 index 0000000000..07469ceb2c --- /dev/null +++ b/regress/xenl-terminal.sh @@ -0,0 +1,29 @@ +#!/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 + +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 -g status-right 'RRR' || exit 1 +$TMUX2 set -g status-left 'LLL' || exit 1 +$TMUX2 set -g window-status-current-format 'WWW' || exit 1 +$TMUX -f/dev/null new -x20 -y2 -d "$TMUX2 attach" || exit 1 +sleep 1 +$TMUX capturep -p|tail -1 >$TMP || exit 1 +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null +cat < + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +static void +regsub_copy(char **buf, size_t *len, const char *text, size_t start, + size_t end) +{ + size_t add = end - start; + + *buf = xrealloc(*buf, (*len) + add + 1); + memcpy((*buf) + *len, text + start, add); + (*len) += add; +} + +static void +regsub_expand(char **buf, size_t *len, const char *with, const char *text, + regmatch_t *m, u_int n) +{ + const char *cp; + u_int i; + + for (cp = with; *cp != '\0'; cp++) { + if (*cp == '\\') { + cp++; + if (*cp >= '0' && *cp <= '9') { + i = *cp - '0'; + if (i < n && m[i].rm_so != m[i].rm_eo) { + regsub_copy(buf, len, text, m[i].rm_so, + m[i].rm_eo); + continue; + } + } + } + *buf = xrealloc(*buf, (*len) + 2); + (*buf)[(*len)++] = *cp; + } +} + +char * +regsub(const char *pattern, const char *with, const char *text, int flags) +{ + regex_t r; + regmatch_t m[10]; + ssize_t start, end, last, len = 0; + int empty = 0; + char *buf = NULL; + + if (*text == '\0') + return (xstrdup("")); + if (regcomp(&r, pattern, flags) != 0) + return (NULL); + + start = 0; + last = 0; + end = strlen(text); + + while (start <= end) { + if (regexec(&r, text + start, nitems(m), m, 0) != 0) { + regsub_copy(&buf, &len, text, start, end); + break; + } + + /* + * Append any text not part of this match (from the end of the + * last match). + */ + regsub_copy(&buf, &len, text, last, m[0].rm_so + start); + + /* + * If the last match was empty and this one isn't (it is either + * later or has matched text), expand this match. If it is + * empty, move on one character and try again from there. + */ + if (empty || + start + m[0].rm_so != last || + m[0].rm_so != m[0].rm_eo) { + regsub_expand(&buf, &len, with, text + start, m, + nitems(m)); + + last = start + m[0].rm_eo; + start += m[0].rm_eo; + empty = 0; + } else { + last = start + m[0].rm_eo; + start += m[0].rm_eo + 1; + empty = 1; + } + + /* Stop now if anchored to start. */ + if (*pattern == '^') { + regsub_copy(&buf, &len, text, start, end); + break; + } + } + buf[len] = '\0'; + + regfree(&r); + return (buf); +} diff --git a/resize.c b/resize.c index 0fd30ba006..054b025f21 100644 --- a/resize.c +++ b/resize.c @@ -22,147 +22,358 @@ #include "tmux.h" -/* - * Recalculate window and session sizes. - * - * Every session has the size of the smallest client it is attached to and - * every window the size of the smallest session it is attached to. - * - * So, when a client is resized or a session attached to or detached from a - * client, the window sizes must be recalculated. For each session, find the - * smallest client it is attached to, and resize it to that size. Then for - * every window, find the smallest session it is attached to, resize it to that - * size and clear and redraw every client with it as the current window. - * - * This is quite inefficient - better/additional data structures are needed - * to make it better. - * - * As a side effect, this function updates the SESSION_UNATTACHED flag. This - * flag is necessary to make sure unattached sessions do not limit the size of - * windows that are attached both to them and to other (attached) sessions. - */ - void -recalculate_sizes(void) +resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) { - struct session *s; - struct client *c; - struct window *w; - struct window_pane *wp; - u_int ssx, ssy, has, limit; - int flag, has_status, is_zoomed, forced; + int zoomed; - RB_FOREACH(s, sessions, &sessions) { - has_status = options_get_number(s->options, "status"); + /* Check size limits. */ + if (sx < WINDOW_MINIMUM) + sx = WINDOW_MINIMUM; + if (sx > WINDOW_MAXIMUM) + sx = WINDOW_MAXIMUM; + if (sy < WINDOW_MINIMUM) + sy = WINDOW_MINIMUM; + if (sy > WINDOW_MAXIMUM) + sy = WINDOW_MAXIMUM; - s->attached = 0; - ssx = ssy = UINT_MAX; - TAILQ_FOREACH(c, &clients, entry) { - if (c->flags & CLIENT_SUSPENDED) + /* If the window is zoomed, unzoom. */ + zoomed = w->flags & WINDOW_ZOOMED; + if (zoomed) + window_unzoom(w); + + /* Resize the layout first. */ + layout_resize(w, sx, sy); + + /* Resize the window, it can be no smaller than the layout. */ + if (sx < w->layout_root->sx) + sx = w->layout_root->sx; + 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, + sx, sy, w->layout_root->sx, w->layout_root->sy); + + /* Restore the window zoom state. */ + if (zoomed) + window_zoom(w->active); + + tty_update_window_offset(w); + server_redraw_window(w); + notify_window("window-layout-changed", w); +} + +static int +ignore_client_size(struct client *c) +{ + if (c->session == NULL) + return (1); + if (c->flags & CLIENT_NOSIZEFLAGS) + return (1); + if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED)) + 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) +{ + struct client *loop; + u_int cx, cy, n; + const char *value; + + 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 ((c->flags & (CLIENT_CONTROL|CLIENT_SIZECHANGED)) == - CLIENT_CONTROL) + if (w != NULL && !session_has(loop->session, w)) continue; - if (c->session == s) { - if (c->tty.sx < ssx) - ssx = c->tty.sx; - if (has_status && - !(c->flags & CLIENT_CONTROL) && - c->tty.sy > 1 && c->tty.sy - 1 < ssy) - ssy = c->tty.sy - 1; - else if (c->tty.sy < ssy) - ssy = c->tty.sy; - s->attached++; + 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 (ssx == UINT_MAX || ssy == UINT_MAX) { - s->flags |= SESSION_UNATTACHED; - continue; - } - s->flags &= ~SESSION_UNATTACHED; + 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; - if (has_status && ssy == 0) - ssy = 1; + cx = loop->tty.sx; + cy = loop->tty.sy - status_line_size(loop); - if (s->sx == ssx && s->sy == ssy) - continue; + if (cx < *sx) + *sx = cx; + if (cy < *sy) + *sy = cy; - log_debug("session $%u size %u,%u (was %u,%u)", s->id, ssx, ssy, - s->sx, s->sy); + 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); - s->sx = ssx; - s->sy = ssy; + if (cx < *sx) + *sx = cx; + if (cy < *sy) + *sy = cy; - status_update_saved(s); + 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; } + goto done; - RB_FOREACH(w, windows, &windows) { - if (w->active == NULL) - continue; - flag = options_get_number(w->options, "aggressive-resize"); +manual: + value = options_get_string(s->options, "default-size"); + if (sscanf(value, "%ux%u", sx, sy) != 2) { + *sx = 80; + *sy = 24; + } + +done: + if (*sx < WINDOW_MINIMUM) + *sx = WINDOW_MINIMUM; + if (*sx > WINDOW_MAXIMUM) + *sx = WINDOW_MAXIMUM; + if (*sy < WINDOW_MINIMUM) + *sy = WINDOW_MINIMUM; + if (*sy > WINDOW_MAXIMUM) + *sy = WINDOW_MAXIMUM; +} + +void +recalculate_size(struct window *w) +{ + struct session *s; + struct client *c; + u_int sx, sy, cx, cy, xpixel = 0, ypixel = 0, n; + int type, current, has, changed; - ssx = ssy = UINT_MAX; - RB_FOREACH(s, sessions, &sessions) { - if (s->flags & SESSION_UNATTACHED) + if (w->active == NULL) + return; + log_debug("%s: @%u is %u,%u", __func__, w->id, w->sx, w->sy); + + 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; - if (flag) - has = s->curw->window == w; + s = c->session; + + if (current) + has = (s->curw->window == w); else has = session_has(s, w); - if (has) { - if (s->sx < ssx) - ssx = s->sx; - if (s->sy < ssy) - ssy = s->sy; + 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 (ssx == UINT_MAX || ssy == UINT_MAX) - continue; + if (sx == 0 || sy == 0) + 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; - forced = 0; - limit = options_get_number(w->options, "force-width"); - if (limit >= PANE_MINIMUM && ssx > limit) { - ssx = limit; - forced |= WINDOW_FORCEWIDTH; + 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; + } } - limit = options_get_number(w->options, "force-height"); - if (limit >= PANE_MINIMUM && ssy > limit) { - ssy = limit; - forced |= WINDOW_FORCEHEIGHT; + if (sx == UINT_MAX || sy == UINT_MAX) + 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 (w->sx == ssx && w->sy == ssy) - continue; - log_debug("window @%u size %u,%u (was %u,%u)", w->id, ssx, ssy, - w->sx, w->sy); - - w->flags &= ~(WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT); - w->flags |= forced; - - is_zoomed = w->flags & WINDOW_ZOOMED; - if (is_zoomed) - window_unzoom(w); - layout_resize(w, ssx, ssy); - window_resize(w, ssx, ssy); - if (is_zoomed && window_pane_visible(w->active)) - window_zoom(w->active); - - /* - * If the current pane is now not visible, move to the next - * that is. - */ - wp = w->active; - while (!window_pane_visible(w->active)) { - w->active = TAILQ_PREV(w->active, window_panes, entry); - if (w->active == NULL) - w->active = TAILQ_LAST(&w->panes, window_panes); - if (w->active == wp) - break; + 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 (w->active == w->last) - w->last = NULL; + 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 (!changed) { + 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); +} + +void +recalculate_sizes(void) +{ + struct session *s; + struct client *c; + struct window *w; - server_redraw_window(w); - notify_window("window-layout-changed", w); + /* + * Clear attached count and update saved status line information for + * each session. + */ + RB_FOREACH(s, sessions, &sessions) { + s->attached = 0; + status_update_cache(s); } + + /* + * Increment attached count and check the status line size for each + * client. + */ + TAILQ_FOREACH(c, &clients, entry) { + if (ignore_client_size(c)) + continue; + s = c->session; + if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL)) + c->flags |= CLIENT_STATUSOFF; + else + c->flags &= ~CLIENT_STATUSOFF; + s->attached++; + } + + /* Walk each window and adjust the size. */ + RB_FOREACH(w, windows, &windows) + recalculate_size(w); } diff --git a/screen-redraw.c b/screen-redraw.c index 6c4d84d868..e7f4f0770d 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -18,26 +18,16 @@ #include +#include #include #include "tmux.h" -static int screen_redraw_cell_border1(struct window_pane *, u_int, u_int); -static int screen_redraw_cell_border(struct client *, u_int, u_int); -static int screen_redraw_check_cell(struct client *, u_int, u_int, int, - struct window_pane **); -static int screen_redraw_check_is(u_int, u_int, int, int, struct window *, - struct window_pane *, struct window_pane *); - -static int screen_redraw_make_pane_status(struct client *, struct window *, +static void screen_redraw_draw_borders(struct screen_redraw_ctx *); +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_draw_pane_status(struct client *, int); - -static void screen_redraw_draw_borders(struct client *, int, int, u_int); -static void screen_redraw_draw_panes(struct client *, u_int); -static void screen_redraw_draw_status(struct client *, u_int); -static void screen_redraw_draw_number(struct client *, struct window_pane *, - u_int); #define CELL_INSIDE 0 #define CELL_LEFTRIGHT 1 @@ -55,10 +45,6 @@ static void screen_redraw_draw_number(struct client *, struct window_pane *, #define CELL_BORDERS " xqlkmjwvtun~" -#define CELL_STATUS_OFF 0 -#define CELL_STATUS_TOP 1 -#define CELL_STATUS_BOTTOM 2 - /* 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) @@ -122,12 +108,12 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, if (px > w->sx || py > w->sy) return (CELL_OUTSIDE); - if (pane_status != CELL_STATUS_OFF) { + if (pane_status != PANE_STATUS_OFF) { TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; - if (pane_status == CELL_STATUS_TOP) + if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else line = wp->yoff + wp->sy; @@ -163,7 +149,7 @@ screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status, borders |= 8; if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py)) borders |= 4; - if (pane_status == CELL_STATUS_TOP) { + if (pane_status == PANE_STATUS_TOP) { if (py != 0 && screen_redraw_cell_border(c, px, py - 1)) borders |= 2; } else { @@ -218,9 +204,9 @@ screen_redraw_check_is(u_int px, u_int py, int type, int pane_status, border = screen_redraw_cell_border1(wantwp, px, py); if (border == 0 || border == -1) return (0); - if (pane_status == CELL_STATUS_TOP && border == 4) + if (pane_status == PANE_STATUS_TOP && border == 4) return (0); - if (pane_status == CELL_STATUS_BOTTOM && border == 3) + if (pane_status == PANE_STATUS_BOTTOM && border == 3) return (0); /* If there are more than two panes, that's enough. */ @@ -232,7 +218,7 @@ screen_redraw_check_is(u_int px, u_int py, int type, int pane_status, return (1); /* With status lines mark the entire line. */ - if (pane_status != CELL_STATUS_OFF) + if (pane_status != PANE_STATUS_OFF) return (1); /* Check if the pane covers the whole width. */ @@ -268,8 +254,8 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, struct grid_cell gc; const char *fmt; struct format_tree *ft; - char *out; - size_t outlen; + char *expanded; + u_int width, i; struct screen_write_ctx ctx; struct screen old; @@ -280,29 +266,33 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, fmt = options_get_string(w->options, "pane-border-format"); - ft = format_create(c, NULL, FORMAT_PANE|wp->id, 0); + ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS); format_defaults(ft, c, NULL, NULL, wp); + expanded = format_expand_time(ft, fmt); + if (wp->sx < 4) + wp->status_size = width = 0; + else + wp->status_size = width = wp->sx - 4; + memcpy(&old, &wp->status_screen, sizeof old); - screen_init(&wp->status_screen, wp->sx, 1, 0); + screen_init(&wp->status_screen, width, 1, 0); wp->status_screen.mode = 0; - out = format_expand(ft, fmt); - outlen = screen_write_cstrlen("%s", out); - if (outlen > wp->sx - 4) - outlen = wp->sx - 4; - screen_resize(&wp->status_screen, outlen, 1, 0); - screen_write_start(&ctx, NULL, &wp->status_screen); - screen_write_cursormove(&ctx, 0, 0); - screen_write_clearline(&ctx, 8); - screen_write_cnputs(&ctx, outlen, &gc, "%s", out); + + gc.attr |= GRID_ATTR_CHARSET; + for (i = 0; i < width; i++) + screen_write_putc(&ctx, &gc, 'q'); + gc.attr &= ~GRID_ATTR_CHARSET; + + screen_write_cursormove(&ctx, 0, 0, 0); + format_draw(&ctx, &gc, width, expanded, NULL); screen_write_stop(&ctx); + free(expanded); format_free(ft); - wp->status_size = outlen; - if (grid_compare(wp->status_screen.grid, old.grid) == 0) { screen_free(&old); return (0); @@ -313,35 +303,67 @@ screen_redraw_make_pane_status(struct client *c, struct window *w, /* Draw pane status. */ static void -screen_redraw_draw_pane_status(struct client *c, int pane_status) +screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx) { + struct client *c = ctx->c; struct window *w = c->session->curw->window; - struct options *oo = c->session->options; struct tty *tty = &c->tty; struct window_pane *wp; - int spos; - u_int yoff; + struct screen *s; + u_int i, x, width, xoff, yoff, size; + + log_debug("%s: %s @%u", __func__, c->name, w->id); - spos = options_get_number(oo, "status-position"); TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; - if (pane_status == CELL_STATUS_TOP) + s = &wp->status_screen; + + size = wp->status_size; + if (ctx->pane_status == PANE_STATUS_TOP) yoff = wp->yoff - 1; else yoff = wp->yoff + wp->sy; - if (spos == 0) - yoff += 1; + xoff = wp->xoff + 2; - tty_draw_line(tty, NULL, &wp->status_screen, 0, wp->xoff + 2, - yoff); + if (xoff + size <= ctx->ox || + xoff >= ctx->ox + ctx->sx || + yoff < ctx->oy || + yoff >= ctx->oy + ctx->sy) + continue; + + if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) { + /* All visible. */ + i = 0; + x = xoff - ctx->ox; + width = size; + } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) { + /* Both left and right not visible. */ + i = ctx->ox; + x = 0; + width = ctx->sx; + } else if (xoff < ctx->ox) { + /* Left not visible. */ + i = ctx->ox - xoff; + x = 0; + width = size - i; + } else { + /* Right not visible. */ + i = 0; + x = xoff - ctx->ox; + width = size - x; + } + + if (ctx->statustop) + yoff += ctx->statuslines; + tty_draw_line(tty, NULL, s, i, 0, width, x, yoff - ctx->oy); } tty_cursor(tty, 0, 0); } /* Update status line and change flags if unchanged. */ -void -screen_redraw_update(struct client *c) +static int +screen_redraw_update(struct client *c, int flags) { struct window *w = c->session->curw->window; struct window_pane *wp; @@ -354,125 +376,147 @@ screen_redraw_update(struct client *c) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); - if (!redraw) - c->flags &= ~CLIENT_STATUS; + if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS)) + flags &= ~CLIENT_REDRAWSTATUS; + + if (c->overlay_draw != NULL) + flags |= CLIENT_REDRAWOVERLAY; - if (options_get_number(wo, "pane-border-status") != CELL_STATUS_OFF) { + if (options_get_number(wo, "pane-border-status") != PANE_STATUS_OFF) { redraw = 0; TAILQ_FOREACH(wp, &w->panes, entry) { if (screen_redraw_make_pane_status(c, w, wp)) redraw = 1; } if (redraw) - c->flags |= CLIENT_BORDERS; + flags |= CLIENT_REDRAWBORDERS; } + return (flags); +} + +/* Set up redraw context. */ +static void +screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx) +{ + struct session *s = c->session; + struct options *oo = s->options; + struct window *w = s->curw->window; + struct options *wo = w->options; + u_int lines; + + memset(ctx, 0, sizeof *ctx); + ctx->c = c; + + lines = status_line_size(c); + if (c->message_string != NULL || c->prompt_string != NULL) + lines = (lines == 0) ? 1 : lines; + if (lines != 0 && options_get_number(oo, "status-position") == 0) + ctx->statustop = 1; + ctx->statuslines = lines; + + ctx->pane_status = options_get_number(wo, "pane-border-status"); + + tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy); + + log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name, + w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines, + ctx->statustop); } /* Redraw entire screen. */ void -screen_redraw_screen(struct client *c, int draw_panes, int draw_status, - int draw_borders) +screen_redraw_screen(struct client *c) { - struct options *oo = c->session->options; - struct tty *tty = &c->tty; - struct window *w = c->session->curw->window; - struct options *wo = w->options; - u_int top; - int status, pane_status, spos; + struct screen_redraw_ctx ctx; + int flags; - /* Suspended clients should not be updated. */ if (c->flags & CLIENT_SUSPENDED) return; - /* Get status line, er, status. */ - spos = options_get_number(oo, "status-position"); - if (c->message_string != NULL || c->prompt_string != NULL) - status = 1; - else - status = options_get_number(oo, "status"); - top = 0; - if (status && spos == 0) - top = 1; - if (!status) - draw_status = 0; - - /* Draw the elements. */ - if (draw_borders) { - pane_status = options_get_number(wo, "pane-border-status"); - screen_redraw_draw_borders(c, status, pane_status, top); - if (pane_status != CELL_STATUS_OFF) - screen_redraw_draw_pane_status(c, pane_status); + flags = screen_redraw_update(c, c->flags); + screen_redraw_set_context(c, &ctx); + + if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) { + if (ctx.pane_status != PANE_STATUS_OFF) + screen_redraw_draw_pane_status(&ctx); + screen_redraw_draw_borders(&ctx); } - if (draw_panes) - screen_redraw_draw_panes(c, top); - if (draw_status) - screen_redraw_draw_status(c, top); - tty_reset(tty); + if (flags & CLIENT_REDRAWWINDOW) + screen_redraw_draw_panes(&ctx); + if (ctx.statuslines != 0 && + (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) + screen_redraw_draw_status(&ctx); + if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) + c->overlay_draw(c, &ctx); + tty_reset(&c->tty); } -/* Draw a single pane. */ +/* Redraw a single pane. */ void screen_redraw_pane(struct client *c, struct window_pane *wp) { - u_int i, yoff; + struct screen_redraw_ctx ctx; - if (!window_pane_visible(wp)) + if (c->overlay_draw != NULL || !window_pane_visible(wp)) return; - yoff = wp->yoff; - if (status_at_line(c) == 0) - yoff++; + screen_redraw_set_context(c, &ctx); - log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id, - wp->xoff, yoff); - - for (i = 0; i < wp->sy; i++) - tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff); + screen_redraw_draw_pane(&ctx, wp); tty_reset(&c->tty); } -/* Draw the borders. */ +/* Draw a border cell. */ static void -screen_redraw_draw_borders(struct client *c, int status, int pane_status, - u_int top) +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) { + 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 window_pane *wp; - struct grid_cell m_active_gc, active_gc, m_other_gc, other_gc; - struct grid_cell msg_gc; - u_int i, j, type, msgx = 0, msgy = 0; - int active, small, flags; - char msg[256]; - const char *tmp; - size_t msglen = 0; - - small = (tty->sy - status + top > w->sy) || (tty->sx > w->sx); - if (small) { - flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT); - if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT)) - tmp = "force-width, force-height"; - else if (flags == WINDOW_FORCEWIDTH) - tmp = "force-width"; - else if (flags == WINDOW_FORCEHEIGHT) - tmp = "force-height"; + 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; + + 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 (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 - tmp = "a smaller client"; - xsnprintf(msg, sizeof msg, "(size %ux%u from %s)", - w->sx, w->sy, tmp); - msglen = strlen(msg); - - if (tty->sy - 1 - status + top > w->sy && tty->sx >= msglen) { - msgx = tty->sx - msglen; - msgy = tty->sy - 1 - status + top; - } else if (tty->sx - w->sx > msglen) { - msgx = tty->sx - msglen; - msgy = tty->sy - 1 - status + top; - } else - small = 0; - } + tty_attributes(tty, m_other_gc, NULL); + } else if (flag) + tty_attributes(tty, active_gc, NULL); + else + tty_attributes(tty, other_gc, NULL); + if (ctx->statustop) + tty_cursor(tty, i, ctx->statuslines + j); + else + tty_cursor(tty, i, j); + tty_putc(tty, CELL_BORDERS[type]); +} + +/* Draw the borders. */ +static void +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; + 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"); @@ -483,147 +527,101 @@ screen_redraw_draw_borders(struct client *c, int status, int pane_status, memcpy(&m_active_gc, &active_gc, sizeof m_active_gc); m_active_gc.attr ^= GRID_ATTR_REVERSE; - for (j = 0; j < tty->sy - status; j++) { + for (j = 0; j < tty->sy - ctx->statuslines; j++) { for (i = 0; i < tty->sx; i++) { - type = screen_redraw_check_cell(c, i, j, pane_status, - &wp); - if (type == CELL_INSIDE) - continue; - if (type == CELL_OUTSIDE && small && - i > msgx && j == msgy) - continue; - active = screen_redraw_check_is(i, j, type, pane_status, - w, w->active, wp); - if (server_is_marked(s, s->curw, marked_pane.wp) && - screen_redraw_check_is(i, j, type, pane_status, w, - marked_pane.wp, wp)) { - if (active) - tty_attributes(tty, &m_active_gc, NULL); - else - tty_attributes(tty, &m_other_gc, NULL); - } else if (active) - tty_attributes(tty, &active_gc, NULL); - else - tty_attributes(tty, &other_gc, NULL); - tty_cursor(tty, i, top + j); - tty_putc(tty, CELL_BORDERS[type]); + screen_redraw_draw_borders_cell(ctx, i, j, + &m_active_gc, &active_gc, &m_other_gc, &other_gc); } } - - if (small) { - memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc); - tty_attributes(tty, &msg_gc, NULL); - tty_cursor(tty, msgx, msgy); - tty_puts(tty, msg); - } } /* Draw the panes. */ static void -screen_redraw_draw_panes(struct client *c, u_int top) +screen_redraw_draw_panes(struct screen_redraw_ctx *ctx) { + struct client *c = ctx->c; struct window *w = c->session->curw->window; - struct tty *tty = &c->tty; struct window_pane *wp; - u_int i; + + log_debug("%s: %s @%u", __func__, c->name, w->id); TAILQ_FOREACH(wp, &w->panes, entry) { - if (!window_pane_visible(wp)) - continue; - for (i = 0; i < wp->sy; i++) - tty_draw_pane(tty, wp, i, wp->xoff, top + wp->yoff); - if (c->flags & CLIENT_IDENTIFY) - screen_redraw_draw_number(c, wp, top); + if (window_pane_visible(wp)) + screen_redraw_draw_pane(ctx, wp); } } /* Draw the status line. */ static void -screen_redraw_draw_status(struct client *c, u_int top) +screen_redraw_draw_status(struct screen_redraw_ctx *ctx) { + struct client *c = ctx->c; + struct window *w = c->session->curw->window; struct tty *tty = &c->tty; + struct screen *s = c->status.active; + u_int i, y; + + log_debug("%s: %s @%u", __func__, c->name, w->id); - if (top) - tty_draw_line(tty, NULL, &c->status, 0, 0, 0); + if (ctx->statustop) + y = 0; else - tty_draw_line(tty, NULL, &c->status, 0, 0, tty->sy - 1); + 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); } -/* Draw number on a pane. */ +/* Draw one pane. */ static void -screen_redraw_draw_number(struct client *c, struct window_pane *wp, u_int top) +screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp) { - struct tty *tty = &c->tty; - 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; - int colour, active_colour; - char buf[16], *ptr; - size_t len; + struct client *c = ctx->c; + struct window *w = c->session->curw->window; + struct tty *tty = &c->tty; + struct screen *s; + u_int i, j, top, x, y, width; - if (window_pane_index(wp, &idx) != 0) - fatalx("index not found"); - len = xsnprintf(buf, sizeof buf, "%u", idx); + log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id); - if (wp->sx < len) + if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx) return; - colour = options_get_number(oo, "display-panes-colour"); - active_colour = options_get_number(oo, "display-panes-active-colour"); - - px = wp->sx / 2; py = wp->sy / 2; - xoff = wp->xoff; yoff = wp->yoff; - - if (top) - yoff++; - - if (wp->sx < len * 6 || wp->sy < 5) { - tty_cursor(tty, xoff + px - len / 2, yoff + py); - goto draw_text; - } - - px -= len * 3; - py -= 2; - - memcpy(&gc, &grid_default_cell, sizeof gc); - if (w->active == wp) - gc.bg = active_colour; + if (ctx->statustop) + top = ctx->statuslines; else - gc.bg = colour; - gc.flags |= GRID_FLAG_NOPALETTE; + top = 0; - tty_attributes(tty, &gc, wp); - for (ptr = buf; *ptr != '\0'; ptr++) { - if (*ptr < '0' || *ptr > '9') + s = wp->screen; + for (j = 0; j < wp->sy; j++) { + if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy) continue; - idx = *ptr - '0'; - - for (j = 0; j < 5; j++) { - for (i = px; i < px + 5; i++) { - tty_cursor(tty, xoff + i, yoff + py + j); - if (window_clock_table[idx][j][i - px]) - tty_putc(tty, ' '); - } + y = top + wp->yoff + j - ctx->oy; + + if (wp->xoff >= ctx->ox && + wp->xoff + wp->sx <= ctx->ox + ctx->sx) { + /* All visible. */ + i = 0; + x = wp->xoff - ctx->ox; + width = wp->sx; + } else if (wp->xoff < ctx->ox && + wp->xoff + wp->sx > ctx->ox + ctx->sx) { + /* Both left and right not visible. */ + i = ctx->ox; + x = 0; + width = ctx->sx; + } else if (wp->xoff < ctx->ox) { + /* Left not visible. */ + i = ctx->ox - wp->xoff; + x = 0; + width = wp->sx - i; + } else { + /* Right not visible. */ + i = 0; + x = wp->xoff - ctx->ox; + width = ctx->sx - x; } - px += 6; - } - - len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy); - if (wp->sx < len || wp->sy < 6) - return; - tty_cursor(tty, xoff + wp->sx - len, yoff); + log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u", + __func__, c->name, wp->id, i, j, x, y, width); -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); - - tty_cursor(tty, 0, 0); + tty_draw_line(tty, wp, s, i, j, width, x, y); + } } diff --git a/screen-write.c b/screen-write.c index 15f8d07f78..9aaedba9ec 100644 --- a/screen-write.c +++ b/screen-write.c @@ -36,7 +36,7 @@ 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 = { - GRID_FLAG_PADDING, 0, 8, 8, { { 0 }, 0, 0, 0 } + { { 0 }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 0, 8, 8 }; struct screen_write_collect_item { @@ -54,12 +54,52 @@ struct screen_write_collect_line { TAILQ_HEAD(, screen_write_collect_item) items; }; +static void +screen_write_offset_timer(__unused int fd, __unused short events, void *data) +{ + struct window *w = data; + + tty_update_window_offset(w); +} + +/* Set cursor position. */ +static void +screen_write_set_cursor(struct screen_write_ctx *ctx, int cx, int cy) +{ + struct window_pane *wp = ctx->wp; + struct window *w; + struct screen *s = ctx->s; + struct timeval tv = { .tv_usec = 10000 }; + + if (cx != -1 && (u_int)cx == s->cx && cy != -1 && (u_int)cy == s->cy) + return; + + if (cx != -1) { + if ((u_int)cx > screen_size_x(s)) /* allow last column */ + cx = screen_size_x(s) - 1; + s->cx = cx; + } + if (cy != -1) { + if ((u_int)cy > screen_size_y(s) - 1) + cy = screen_size_y(s) - 1; + s->cy = cy; + } + + if (wp == NULL) + return; + w = wp->window; + + if (!event_initialized(&w->offset_timer)) + evtimer_set(&w->offset_timer, screen_write_offset_timer, w); + if (!evtimer_pending(&w->offset_timer, NULL)) + evtimer_add(&w->offset_timer, &tv); +} + /* Initialize writing with a window. */ void screen_write_start(struct screen_write_ctx *ctx, struct window_pane *wp, struct screen *s) { - char tmp[16]; u_int y; memset(ctx, 0, sizeof *ctx); @@ -78,10 +118,17 @@ screen_write_start(struct screen_write_ctx *ctx, struct window_pane *wp, ctx->scrolled = 0; ctx->bg = 8; - if (wp != NULL) - snprintf(tmp, sizeof tmp, "pane %%%u", wp->id); - log_debug("%s: size %ux%u, %s", __func__, screen_size_x(ctx->s), - screen_size_y(ctx->s), wp == NULL ? "no pane" : tmp); + 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)); + } + } } /* Finish writing. */ @@ -107,11 +154,10 @@ screen_write_reset(struct screen_write_ctx *ctx) screen_reset_tabs(s); screen_write_scrollregion(ctx, 0, screen_size_y(s) - 1); - s->mode &= ~(MODE_INSERT|MODE_KCURSOR|MODE_KKEYPAD|MODE_FOCUSON); - s->mode &= ~(ALL_MOUSE_MODES|MODE_MOUSE_UTF8|MODE_MOUSE_SGR); + s->mode = MODE_CURSOR | MODE_WRAP; screen_write_clearscreen(ctx, 8); - screen_write_cursormove(ctx, 0, 0); + screen_write_set_cursor(ctx, 0, 0); } /* Write character. */ @@ -127,41 +173,6 @@ screen_write_putc(struct screen_write_ctx *ctx, const struct grid_cell *gcp, screen_write_cell(ctx, &gc); } -/* Calculate string length, with embedded formatting. */ -size_t -screen_write_cstrlen(const char *fmt, ...) -{ - va_list ap; - char *msg, *msg2, *ptr, *ptr2; - size_t size; - - va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); - va_end(ap); - msg2 = xmalloc(strlen(msg) + 1); - - ptr = msg; - ptr2 = msg2; - while (*ptr != '\0') { - if (ptr[0] == '#' && ptr[1] == '[') { - while (*ptr != ']' && *ptr != '\0') - ptr++; - if (*ptr == ']') - ptr++; - continue; - } - *ptr2++ = *ptr++; - } - *ptr2 = '\0'; - - size = screen_write_strlen("%s", msg2); - - free(msg); - free(msg2); - - return (size); -} - /* Calculate string length. */ size_t screen_write_strlen(const char *fmt, ...) @@ -280,110 +291,72 @@ screen_write_vnputs(struct screen_write_ctx *ctx, ssize_t maxlen, free(msg); } -/* Write string, similar to nputs, but with embedded formatting (#[]). */ +/* Copy from another screen. Assumes target region is big enough. */ void -screen_write_cnputs(struct screen_write_ctx *ctx, ssize_t maxlen, - const struct grid_cell *gcp, const char *fmt, ...) +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; - struct utf8_data *ud = &gc.data; - va_list ap; - char *msg; - u_char *ptr, *last; - size_t left, size = 0; - enum utf8_state more; - - memcpy(&gc, gcp, sizeof gc); - - va_start(ap, fmt); - xvasprintf(&msg, fmt, ap); - va_end(ap); - - ptr = msg; - while (*ptr != '\0') { - if (ptr[0] == '#' && ptr[1] == '[') { - ptr += 2; - last = ptr + strcspn(ptr, "]"); - if (*last == '\0') { - /* No ]. Not much point in doing anything. */ - break; - } - *last = '\0'; - - style_parse(gcp, &gc, ptr); - ptr = last + 1; - continue; - } + u_int xx, yy, cx, cy, b; - if (*ptr > 0x7f && utf8_open(ud, *ptr) == UTF8_MORE) { - ptr++; + if (nx == 0 || ny == 0) + return; - left = strlen(ptr); - if (left < (size_t)ud->size - 1) - break; - while ((more = utf8_append(ud, *ptr)) == UTF8_MORE) - ptr++; - ptr++; + cx = s->cx; + cy = s->cy; - if (more != UTF8_DONE) - continue; - if (maxlen > 0 && size + ud->width > (size_t)maxlen) { - while (size < (size_t)maxlen) { - screen_write_putc(ctx, &gc, ' '); - size++; + 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; } - break; - } - size += ud->width; - screen_write_cell(ctx, &gc); - } else { - if (maxlen > 0 && size + 1 > (size_t)maxlen) - break; - - if (*ptr > 0x1f && *ptr < 0x7f) { - size++; - screen_write_putc(ctx, &gc, *ptr); } - ptr++; + if (xx + gc.data.width <= px + nx) + screen_write_cell(ctx, &gc); } + cy++; + screen_write_set_cursor(ctx, cx, cy); } - - free(msg); } -/* Copy from another screen. */ +/* + * Copy from another screen but without the selection stuff. Also assumes the + * target region is already 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 *markbs, - const struct grid_cell *markgc) +screen_write_fast_copy(struct screen_write_ctx *ctx, struct screen *src, + u_int px, u_int py, u_int nx, u_int ny) { struct screen *s = ctx->s; struct grid *gd = src->grid; struct grid_cell gc; - u_int xx, yy, cx, cy, b; + u_int xx, yy, cx, cy; if (nx == 0 || ny == 0) return; - cx = s->cx; cy = s->cy; - for (yy = py; yy < py + ny; yy++) { + if (yy >= gd->hsize + gd->sy) + break; + cx = s->cx; for (xx = px; xx < px + nx; xx++) { + if (xx >= grid_get_line(gd, yy)->cellsize) + break; grid_get_cell(gd, xx, yy, &gc); - if (markbs != NULL) { - b = (yy * screen_size_x(src)) + xx; - if (bit_test(markbs, b)) { - gc.attr = markgc->attr; - gc.fg = markgc->fg; - gc.bg = markgc->bg; - } - } - screen_write_cell(ctx, &gc); + if (xx + gc.data.width > px + nx) + break; + grid_view_set_cell(ctx->s->grid, cx, cy, &gc); + cx++; } - cy++; - screen_write_cursormove(ctx, cx, cy); } } @@ -406,7 +379,7 @@ screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right) screen_write_putc(ctx, &gc, 'q'); screen_write_putc(ctx, &gc, right ? 'u' : 'q'); - screen_write_cursormove(ctx, cx, cy); + screen_write_set_cursor(ctx, cx, cy); } /* Draw a horizontal line on screen. */ @@ -425,13 +398,58 @@ screen_write_vline(struct screen_write_ctx *ctx, u_int ny, int top, int bottom) screen_write_putc(ctx, &gc, top ? 'w' : 'x'); for (i = 1; i < ny - 1; i++) { - screen_write_cursormove(ctx, cx, cy + i); + screen_write_set_cursor(ctx, cx, cy + i); screen_write_putc(ctx, &gc, 'x'); } - screen_write_cursormove(ctx, cx, cy + ny); + screen_write_set_cursor(ctx, cx, cy + ny - 1); screen_write_putc(ctx, &gc, bottom ? 'v' : 'x'); - screen_write_cursormove(ctx, cx, cy); + screen_write_set_cursor(ctx, cx, cy); +} + +/* Draw a menu on screen. */ +void +screen_write_menu(struct screen_write_ctx *ctx, struct menu *menu, int choice) +{ + struct screen *s = ctx->s; + struct grid_cell gc; + u_int cx, cy, i, j; + const char *name; + + cx = s->cx; + cy = s->cy; + + memcpy(&gc, &grid_default_cell, sizeof 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); + + for (i = 0; i < menu->count; i++) { + name = menu->items[i].name; + if (name == NULL) { + screen_write_cursormove(ctx, cx, cy + 1 + i, 0); + screen_write_hline(ctx, menu->width + 4, 1, 1); + } else { + if (choice >= 0 && i == (u_int)choice && *name != '-') + gc.attr |= GRID_ATTR_REVERSE; + screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); + for (j = 0; j < menu->width; j++) + 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; + } else + format_draw(ctx, &gc, menu->width, name, NULL); + if (choice >= 0 && i == (u_int)choice) + gc.attr &= ~GRID_ATTR_REVERSE; + } + } + + screen_write_set_cursor(ctx, cx, cy); } /* Draw a box on screen. */ @@ -453,25 +471,28 @@ screen_write_box(struct screen_write_ctx *ctx, u_int nx, u_int ny) screen_write_putc(ctx, &gc, 'q'); screen_write_putc(ctx, &gc, 'k'); - screen_write_cursormove(ctx, cx, cy + ny - 1); + screen_write_set_cursor(ctx, cx, cy + ny - 1); screen_write_putc(ctx, &gc, 'm'); for (i = 1; i < nx - 1; i++) screen_write_putc(ctx, &gc, 'q'); screen_write_putc(ctx, &gc, 'j'); for (i = 1; i < ny - 1; i++) { - screen_write_cursormove(ctx, cx, cy + i); + screen_write_set_cursor(ctx, cx, cy + i); screen_write_putc(ctx, &gc, 'x'); } for (i = 1; i < ny - 1; i++) { - screen_write_cursormove(ctx, cx + nx - 1, cy + i); + screen_write_set_cursor(ctx, cx + nx - 1, cy + i); screen_write_putc(ctx, &gc, 'x'); } - screen_write_cursormove(ctx, cx, cy); + screen_write_set_cursor(ctx, cx, cy); } -/* Write a preview version of a window. */ +/* + * Write a preview version of a window. Assumes target area is big enough and + * already cleared. + */ void screen_write_preview(struct screen_write_ctx *ctx, struct screen *src, u_int nx, u_int ny) @@ -515,13 +536,12 @@ screen_write_preview(struct screen_write_ctx *ctx, struct screen *src, u_int nx, py = 0; } - screen_write_copy(ctx, src, px, src->grid->hsize + py, nx, ny, NULL, - NULL); + screen_write_fast_copy(ctx, src, px, src->grid->hsize + py, nx, ny); if (src->mode & MODE_CURSOR) { grid_view_get_cell(src->grid, src->cx, src->cy, &gc); gc.attr |= GRID_ATTR_REVERSE; - screen_write_cursormove(ctx, cx + (src->cx - px), + screen_write_set_cursor(ctx, cx + (src->cx - px), cy + (src->cy - py)); screen_write_cell(ctx, &gc); } @@ -567,25 +587,26 @@ void screen_write_cursorup(struct screen_write_ctx *ctx, u_int ny) { struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; if (ny == 0) ny = 1; - if (s->cy < s->rupper) { + if (cy < s->rupper) { /* Above region. */ - if (ny > s->cy) - ny = s->cy; + if (ny > cy) + ny = cy; } else { /* Below region. */ - if (ny > s->cy - s->rupper) - ny = s->cy - s->rupper; + if (ny > cy - s->rupper) + ny = cy - s->rupper; } - if (s->cx == screen_size_x(s)) - s->cx--; - if (ny == 0) - return; + if (cx == screen_size_x(s)) + cx--; - s->cy -= ny; + cy -= ny; + + screen_write_set_cursor(ctx, cx, cy); } /* Cursor down by ny. */ @@ -593,25 +614,28 @@ void screen_write_cursordown(struct screen_write_ctx *ctx, u_int ny) { struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; if (ny == 0) ny = 1; - if (s->cy > s->rlower) { + if (cy > s->rlower) { /* Below region. */ - if (ny > screen_size_y(s) - 1 - s->cy) - ny = screen_size_y(s) - 1 - s->cy; + if (ny > screen_size_y(s) - 1 - cy) + ny = screen_size_y(s) - 1 - cy; } else { /* Above region. */ - if (ny > s->rlower - s->cy) - ny = s->rlower - s->cy; + if (ny > s->rlower - cy) + ny = s->rlower - cy; } - if (s->cx == screen_size_x(s)) - s->cx--; - if (ny == 0) + if (cx == screen_size_x(s)) + cx--; + else if (ny == 0) return; - s->cy += ny; + cy += ny; + + screen_write_set_cursor(ctx, cx, cy); } /* Cursor right by nx. */ @@ -619,16 +643,19 @@ void screen_write_cursorright(struct screen_write_ctx *ctx, u_int nx) { struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; if (nx == 0) nx = 1; - if (nx > screen_size_x(s) - 1 - s->cx) - nx = screen_size_x(s) - 1 - s->cx; + if (nx > screen_size_x(s) - 1 - cx) + nx = screen_size_x(s) - 1 - cx; if (nx == 0) return; - s->cx += nx; + cx += nx; + + screen_write_set_cursor(ctx, cx, cy); } /* Cursor left by nx. */ @@ -636,16 +663,19 @@ void screen_write_cursorleft(struct screen_write_ctx *ctx, u_int nx) { struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; if (nx == 0) nx = 1; - if (nx > s->cx) - nx = s->cx; + if (nx > cx) + nx = cx; if (nx == 0) return; - s->cx -= nx; + cx -= nx; + + screen_write_set_cursor(ctx, cx, cy); } /* Backspace; cursor left unless at start of wrapped line when can move up. */ @@ -654,17 +684,20 @@ screen_write_backspace(struct screen_write_ctx *ctx) { struct screen *s = ctx->s; struct grid_line *gl; + u_int cx = s->cx, cy = s->cy; - if (s->cx == 0) { - if (s->cy == 0) + if (cx == 0) { + if (cy == 0) return; - gl = &s->grid->linedata[s->grid->hsize + s->cy - 1]; + gl = grid_get_line(s->grid, s->grid->hsize + cy - 1); if (gl->flags & GRID_LINE_WRAPPED) { - s->cy--; - s->cx = screen_size_x(s) - 1; + cy--; + cx = screen_size_x(s) - 1; } } else - s->cx--; + cx--; + + screen_write_set_cursor(ctx, cx, cy); } /* VT100 alignment test. */ @@ -676,8 +709,6 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) struct grid_cell gc; u_int xx, yy; - screen_write_initctx(ctx, &ttyctx); - memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); @@ -686,12 +717,13 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) grid_view_set_cell(s->grid, xx, yy, &gc); } - s->cx = 0; - s->cy = 0; + screen_write_set_cursor(ctx, 0, 0); s->rupper = 0; s->rlower = screen_size_y(s) - 1; + screen_write_initctx(ctx, &ttyctx); + screen_write_collect_clear(ctx, 0, screen_size_y(s) - 1); tty_write(tty_cmd_alignmenttest, &ttyctx); } @@ -881,8 +913,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s); - gl = &s->grid->linedata[s->grid->hsize + s->cy]; - if (gl->cellsize == 0 && bg == 8) + gl = grid_get_line(s->grid, s->grid->hsize + s->cy); + if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) return; screen_write_initctx(ctx, &ttyctx); @@ -904,8 +936,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) struct tty_ctx ttyctx; u_int sx = screen_size_x(s); - gl = &s->grid->linedata[s->grid->hsize + s->cy]; - if (s->cx > sx - 1 || (s->cx >= gl->cellsize && bg == 8)) + gl = grid_get_line(s->grid, s->grid->hsize + s->cy); + if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) return; screen_write_initctx(ctx, &ttyctx); @@ -943,17 +975,24 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) /* Move cursor to px,py. */ void -screen_write_cursormove(struct screen_write_ctx *ctx, u_int px, u_int py) +screen_write_cursormove(struct screen_write_ctx *ctx, int px, int py, + int origin) { struct screen *s = ctx->s; - if (px > screen_size_x(s) - 1) + if (origin && py != -1 && (s->mode & MODE_ORIGIN)) { + if ((u_int)py > s->rlower - s->rupper) + py = s->rlower; + else + py += s->rupper; + } + + if (px != -1 && (u_int)px > screen_size_x(s) - 1) px = screen_size_x(s) - 1; - if (py > screen_size_y(s) - 1) + if (py != -1 && (u_int)py > screen_size_y(s) - 1) py = screen_size_y(s) - 1; - s->cx = px; - s->cy = py; + screen_write_set_cursor(ctx, px, py); } /* Reverse index (up with scroll). */ @@ -969,7 +1008,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) if (s->cy == s->rupper) grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); else if (s->cy > 0) - s->cy--; + screen_write_set_cursor(ctx, -1, s->cy - 1); screen_write_collect_flush(ctx, 0); tty_write(tty_cmd_reverseindex, &ttyctx); @@ -992,8 +1031,7 @@ screen_write_scrollregion(struct screen_write_ctx *ctx, u_int rupper, screen_write_collect_flush(ctx, 0); /* Cursor moves to top-left. */ - s->cx = 0; - s->cy = 0; + screen_write_set_cursor(ctx, 0, 0); s->rupper = rupper; s->rlower = rlower; @@ -1007,7 +1045,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) struct grid *gd = s->grid; struct grid_line *gl; - gl = &gd->linedata[gd->hsize + s->cy]; + gl = grid_get_line(gd, gd->hsize + s->cy); if (wrapped) gl->flags |= GRID_LINE_WRAPPED; else @@ -1026,7 +1064,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) screen_write_collect_scroll(ctx); ctx->scrolled++; } else if (s->cy < screen_size_y(s) - 1) - s->cy++; + screen_write_set_cursor(ctx, -1, s->cy + 1); } /* Scroll up. */ @@ -1054,13 +1092,36 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) ctx->scrolled += lines; } -/* Carriage return (cursor to start of line). */ +/* Scroll down. */ void -screen_write_carriagereturn(struct screen_write_ctx *ctx) +screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) { struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct tty_ctx ttyctx; + u_int i; - s->cx = 0; + screen_write_initctx(ctx, &ttyctx); + ttyctx.bg = bg; + + if (lines == 0) + lines = 1; + else if (lines > s->rlower - s->rupper + 1) + lines = s->rlower - s->rupper + 1; + + for (i = 0; i < lines; i++) + grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg); + + screen_write_collect_flush(ctx, 0); + ttyctx.num = lines; + tty_write(tty_cmd_scrolldown, &ttyctx); +} + +/* Carriage return (cursor to start of line). */ +void +screen_write_carriagereturn(struct screen_write_ctx *ctx) +{ + screen_write_set_cursor(ctx, 0, -1); } /* Clear to end of screen from cursor. */ @@ -1137,11 +1198,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) void screen_write_clearhistory(struct screen_write_ctx *ctx) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - - grid_move_lines(gd, 0, gd->hsize, gd->sy, 8); - gd->hscrolled = gd->hsize = 0; + grid_clear_history(ctx->s->grid); } /* Clear a collected line. */ @@ -1152,7 +1209,7 @@ screen_write_collect_clear(struct screen_write_ctx *ctx, u_int y, u_int n) u_int i; size_t size; - for (i = y ; i < y + n; i++) { + for (i = y; i < y + n; i++) { if (TAILQ_EMPTY(&ctx->list[i].items)) continue; size = 0; @@ -1181,7 +1238,6 @@ screen_write_collect_scroll(struct screen_write_ctx *ctx) for (y = s->rupper; y < s->rlower; y++) { cl = &ctx->list[y + 1]; TAILQ_CONCAT(&ctx->list[y].items, &cl->items, entry); - TAILQ_INIT(&cl->items); } } @@ -1215,7 +1271,7 @@ 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) { - screen_write_cursormove(ctx, ci->x, y); + screen_write_set_cursor(ctx, ci->x, y); screen_write_initctx(ctx, &ttyctx); ttyctx.cell = &ci->gc; ttyctx.wrapped = ci->wrapped; @@ -1243,6 +1299,7 @@ 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; if (ci->used == 0) return; @@ -1255,9 +1312,29 @@ screen_write_collect_end(struct screen_write_ctx *ctx) log_debug("%s: %u %s (at %u,%u)", __func__, ci->used, ci->data, s->cx, s->cy); - memcpy(&gc, &ci->gc, sizeof gc); - grid_view_set_cells(s->grid, s->cx, s->cy, &gc, ci->data, ci->used); - s->cx += ci->used; + if (s->cx != 0) { + for (xx = s->cx; xx > 0; xx--) { + grid_view_get_cell(s->grid, xx, s->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + grid_view_set_cell(s->grid, xx, s->cy, + &grid_default_cell); + } + if (gc.data.width > 1) { + grid_view_set_cell(s->grid, xx, s->cy, + &grid_default_cell); + } + } + + grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, ci->data, ci->used); + screen_write_set_cursor(ctx, s->cx + ci->used, -1); + + for (xx = s->cx; xx < screen_size_x(s); xx++) { + grid_view_get_cell(s->grid, xx, s->cy, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell); + } } /* Write cell data, collecting if necessary. */ @@ -1273,12 +1350,11 @@ screen_write_collect_add(struct screen_write_ctx *ctx, /* * Don't need to check that the attributes and whatnot are still the * same - input_parse will end the collection when anything that isn't - * a plain character is encountered. Also nothing should make it here - * that isn't a single ASCII character. + * a plain character is encountered. */ collect = 1; - if (gc->data.width != 1 || gc->data.size != 1) + if (gc->data.width != 1 || gc->data.size != 1 || *gc->data.data >= 0x7f) collect = 0; else if (gc->attr & GRID_ATTR_CHARSET) collect = 0; @@ -1286,7 +1362,7 @@ screen_write_collect_add(struct screen_write_ctx *ctx, collect = 0; else if (s->mode & MODE_INSERT) collect = 0; - else if (s->sel.flag) + else if (s->sel != NULL) collect = 0; if (!collect) { screen_write_collect_end(ctx); @@ -1304,7 +1380,7 @@ screen_write_collect_add(struct screen_write_ctx *ctx, log_debug("%s: wrapped at %u,%u", __func__, s->cx, s->cy); ci->wrapped = 1; screen_write_linefeed(ctx, 1, 8); - s->cx = 0; + screen_write_set_cursor(ctx, 0, -1); } if (ci->used == 0) @@ -1338,7 +1414,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) screen_write_collect_flush(ctx, 0); if ((gc = screen_write_combine(ctx, &gc->data, &xx)) != 0) { cx = s->cx; cy = s->cy; - screen_write_cursormove(ctx, xx, s->cy); + screen_write_set_cursor(ctx, xx, s->cy); screen_write_initctx(ctx, &ttyctx); ttyctx.cell = gc; tty_write(tty_cmd_cell, &ttyctx); @@ -1366,7 +1442,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) if ((s->mode & MODE_WRAP) && s->cx > sx - width) { log_debug("%s: wrapped at %u,%u", __func__, s->cx, s->cy); screen_write_linefeed(ctx, 1, 8); - s->cx = 0; + screen_write_set_cursor(ctx, 0, -1); screen_write_collect_flush(ctx, 1); } @@ -1376,7 +1452,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) screen_write_initctx(ctx, &ttyctx); /* Handle overwriting of UTF-8 characters. */ - gl = &s->grid->linedata[s->grid->hsize + s->cy]; + gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->flags & GRID_LINE_EXTENDED) { grid_view_get_cell(gd, s->cx, s->cy, &now_gc); if (screen_write_overwrite(ctx, &now_gc, width)) @@ -1388,6 +1464,7 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) * already ensured there is enough room. */ 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); skip = 0; } @@ -1438,9 +1515,9 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) */ last = !(s->mode & MODE_WRAP); if (s->cx <= sx - last - width) - s->cx += width; + screen_write_set_cursor(ctx, s->cx + width, -1); else - s->cx = sx - last; + screen_write_set_cursor(ctx, sx - last, -1); /* Create space for character in insert mode. */ if (s->mode & MODE_INSERT) { @@ -1537,10 +1614,12 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc, grid_view_get_cell(gd, xx, s->cy, &tmp_gc); if (~tmp_gc.flags & GRID_FLAG_PADDING) break; + log_debug("%s: padding at %u,%u", __func__, xx, s->cy); grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); } /* Overwrite the character at the start of this padding. */ + log_debug("%s: character at %u,%u", __func__, xx, s->cy); grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); done = 1; } @@ -1557,6 +1636,8 @@ screen_write_overwrite(struct screen_write_ctx *ctx, struct grid_cell *gc, grid_view_get_cell(gd, xx, s->cy, &tmp_gc); if (~tmp_gc.flags & GRID_FLAG_PADDING) break; + log_debug("%s: overwrite at %u,%u", __func__, xx, + s->cy); grid_view_set_cell(gd, xx, s->cy, &grid_default_cell); done = 1; } @@ -1590,3 +1671,44 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len) tty_write(tty_cmd_rawstring, &ttyctx); } + +/* Write a SIXEL image */ +void +screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si) +{ + struct tty_ctx ttyctx; + struct screen *s = ctx->s, *image; + u_int sx = screen_size_x(s), sy = screen_size_y(s); + + image = sixel_to_screen(si); + + if (image == NULL) + return; + + /* Reserve some space in the terminal for the sixel */ + for (u_int i = 0; i < image->grid->sy; i++) + screen_write_linefeed(ctx, 0, 8); + + /* Go to its beginning */ + screen_write_set_cursor(ctx, ctx->s->cx, ctx->s->cy - image->grid->sy); + + /* Flush to ensure scrolling is done at that point */ + screen_write_collect_flush(ctx, 0); + + /* Fill up */ + sx = sx - s->cx; + if (sx > screen_size_x(image)) + sx = screen_size_x(image); + sy = sy - s->cx; + if (sy > screen_size_x(image)) + sy = screen_size_x(image); + screen_write_fast_copy(ctx, image, 0, 0, sx, sy); + + screen_write_initctx(ctx, &ttyctx); + ttyctx.ptr = si; + tty_write(tty_cmd_sixelimage, &ttyctx); + + /* Go to its end */ + screen_write_set_cursor(ctx, ctx->s->cx, ctx->s->cy + image->grid->sy); + +} diff --git a/screen.c b/screen.c index 11e47938eb..405932ef0f 100644 --- a/screen.c +++ b/screen.c @@ -24,21 +24,64 @@ #include "tmux.h" -static void screen_resize_x(struct screen *, u_int); +/* Selected area in screen. */ +struct screen_sel { + int hidden; + int rectangle; + int modekeys; + + u_int sx; + u_int sy; + + u_int ex; + u_int ey; + + struct grid_cell cell; +}; + +/* Entry on title stack. */ +struct screen_title_entry { + char *text; + + TAILQ_ENTRY(screen_title_entry) 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); +/* Free titles stack. */ +static void +screen_free_titles(struct screen *s) +{ + struct screen_title_entry *title_entry; + + if (s->titles == NULL) + return; + + while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) { + TAILQ_REMOVE(s->titles, title_entry, entry); + free(title_entry->text); + free(title_entry); + } + + free(s->titles); + s->titles = NULL; +} + /* Create a new screen. */ void screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) { s->grid = grid_create(sx, sy, hlimit); s->title = xstrdup(""); + s->titles = NULL; s->cstyle = 0; s->ccolour = xstrdup(""); s->tabs = NULL; + s->sel = NULL; screen_reinit(s); } @@ -60,16 +103,21 @@ screen_reinit(struct screen *s) grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); screen_clear_selection(s); + screen_free_titles(s); } /* Destroy a screen. */ void screen_free(struct screen *s) { + free(s->sel); free(s->tabs); free(s->title); free(s->ccolour); + grid_destroy(s->grid); + + screen_free_titles(s); } /* Reset tabs to default, eight spaces apart. */ @@ -110,6 +158,51 @@ screen_set_title(struct screen *s, const char *title) utf8_stravis(&s->title, title, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); } +/* Set screen path. */ +void +screen_set_path(struct screen *s, const char *path) +{ + free(s->path); + utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); +} + +/* Push the current title onto the stack. */ +void +screen_push_title(struct screen *s) +{ + struct screen_title_entry *title_entry; + + if (s->titles == NULL) { + s->titles = xmalloc(sizeof *s->titles); + TAILQ_INIT(s->titles); + } + title_entry = xmalloc(sizeof *title_entry); + title_entry->text = xstrdup(s->title); + TAILQ_INSERT_HEAD(s->titles, title_entry, entry); +} + +/* + * Pop a title from the stack and set it as the screen title. If the stack is + * empty, do nothing. + */ +void +screen_pop_title(struct screen *s) +{ + struct screen_title_entry *title_entry; + + if (s->titles == NULL) + return; + + title_entry = TAILQ_FIRST(s->titles); + if (title_entry != NULL) { + screen_set_title(s, title_entry->text); + + TAILQ_REMOVE(s->titles, title_entry, entry); + free(title_entry->text); + free(title_entry); + } +} + /* Resize screen. */ void screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) @@ -120,15 +213,10 @@ screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) sy = 1; if (sx != screen_size_x(s)) { - screen_resize_x(s, sx); - - /* - * It is unclear what should happen to tabs on resize. xterm - * seems to try and maintain them, rxvt resets them. Resetting - * is simpler and more reliable so let's do that. - */ + s->grid->sx = sx; screen_reset_tabs(s); - } + } else + reflow = 0; if (sy != screen_size_y(s)) screen_resize_y(s, sy); @@ -137,28 +225,6 @@ screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) screen_reflow(s, sx); } -static void -screen_resize_x(struct screen *s, u_int sx) -{ - struct grid *gd = s->grid; - - if (sx == 0) - fatalx("zero size"); - - /* - * Treat resizing horizontally simply: just ensure the cursor is - * on-screen and change the size. Don't bother to truncate any lines - - * then the data should be accessible if the size is then increased. - * - * The only potential wrinkle is if UTF-8 double-width characters are - * left in the last column, but UTF-8 terminals should deal with this - * sanely. - */ - if (s->cx >= sx) - s->cx = sx - 1; - gd->sx = sx; -} - static void screen_resize_y(struct screen *s, u_int sy) { @@ -211,9 +277,8 @@ screen_resize_y(struct screen *s, u_int sy) s->cy -= needed; } - /* Resize line arrays. */ - gd->linedata = xreallocarray(gd->linedata, gd->hsize + sy, - sizeof *gd->linedata); + /* Resize line array. */ + grid_adjust_lines(gd, gd->hsize + sy); /* Size increasing. */ if (sy > oldy) { @@ -236,7 +301,7 @@ screen_resize_y(struct screen *s, u_int sy) /* Then fill the rest in with blanks. */ for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) - memset(&gd->linedata[i], 0, sizeof gd->linedata[i]); + memset(grid_get_line(gd, i), 0, sizeof(struct grid_line)); } /* Set the new size, and reset the scroll region. */ @@ -248,51 +313,49 @@ screen_resize_y(struct screen *s, u_int sy) /* Set selection. */ void screen_set_selection(struct screen *s, u_int sx, u_int sy, - u_int ex, u_int ey, u_int rectflag, struct grid_cell *gc) + u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc) { - struct screen_sel *sel = &s->sel; - - memcpy(&sel->cell, gc, sizeof sel->cell); - sel->flag = 1; - sel->hidden = 0; - - sel->rectflag = rectflag; - - sel->sx = sx; sel->sy = sy; - sel->ex = ex; sel->ey = ey; + if (s->sel == NULL) + s->sel = xcalloc(1, sizeof *s->sel); + + memcpy(&s->sel->cell, gc, sizeof s->sel->cell); + s->sel->hidden = 0; + s->sel->rectangle = rectangle; + s->sel->modekeys = modekeys; + + s->sel->sx = sx; + s->sel->sy = sy; + s->sel->ex = ex; + s->sel->ey = ey; } /* Clear selection. */ void screen_clear_selection(struct screen *s) { - struct screen_sel *sel = &s->sel; - - sel->flag = 0; - sel->hidden = 0; - sel->lineflag = LINE_SEL_NONE; + free(s->sel); + s->sel = NULL; } /* Hide selection. */ void screen_hide_selection(struct screen *s) { - struct screen_sel *sel = &s->sel; - - sel->hidden = 1; + if (s->sel != NULL) + s->sel->hidden = 1; } /* Check if cell in selection. */ int screen_check_selection(struct screen *s, u_int px, u_int py) { - struct screen_sel *sel = &s->sel; + struct screen_sel *sel = s->sel; u_int xx; - if (!sel->flag || sel->hidden) + if (sel == NULL || sel->hidden) return (0); - if (sel->rectflag) { + if (sel->rectangle) { if (sel->sy < sel->ey) { /* start line < end line -- downward selection. */ if (py < sel->sy || py > sel->ey) @@ -340,7 +403,11 @@ screen_check_selection(struct screen *s, u_int px, u_int py) if (py == sel->sy && px < sel->sx) return (0); - if (py == sel->ey && px > sel->ex) + if (sel->modekeys == MODEKEY_EMACS) + xx = (sel->ex == 0 ? 0 : sel->ex - 1); + else + xx = sel->ex; + if (py == sel->ey && px > xx) return (0); } else if (sel->sy > sel->ey) { /* starting line > ending line -- upward selection. */ @@ -371,7 +438,11 @@ screen_check_selection(struct screen *s, u_int px, u_int py) return (0); } else { /* selection start (sx) is on the left */ - if (px < sel->sx || px > sel->ex) + if (sel->modekeys == MODEKEY_EMACS) + xx = (sel->ex == 0 ? 0 : sel->ex - 1); + else + xx = sel->ex; + if (px < sel->sx || px > xx) return (0); } } @@ -385,10 +456,10 @@ void screen_select_cell(struct screen *s, struct grid_cell *dst, const struct grid_cell *src) { - if (!s->sel.flag || s->sel.hidden) + if (s->sel == NULL || s->sel->hidden) return; - memcpy(dst, &s->sel.cell, sizeof *dst); + memcpy(dst, &s->sel->cell, sizeof *dst); utf8_copy(&dst->data, &src->data); dst->attr = dst->attr & ~GRID_ATTR_CHARSET; @@ -400,14 +471,30 @@ screen_select_cell(struct screen *s, struct grid_cell *dst, static void screen_reflow(struct screen *s, u_int new_x) { - struct grid *old = s->grid; - u_int change; + u_int cx = s->cx, cy = s->grid->hsize + s->cy, wx, wy; + struct timeval start, tv; + + 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); + + grid_reflow(s->grid, new_x); - s->grid = grid_create(old->sx, old->sy, old->hlimit); + grid_unwrap_position(s->grid, &cx, &cy, wx, wy); + log_debug("%s: new cursor is %u,%u", __func__, cx, cy); - change = grid_reflow(s->grid, old, new_x); - if (change < s->cy) - s->cy -= change; - else + if (cy >= s->grid->hsize) { + s->cx = cx; + s->cy = cy - s->grid->hsize; + } else { + s->cx = 0; s->cy = 0; + } + + gettimeofday(&tv, NULL); + timersub(&tv, &start, &tv); + + log_debug("%s: reflow took %llu.%06u seconds", __func__, + (unsigned long long)tv.tv_sec, (u_int)tv.tv_usec); } diff --git a/server-client.c b/server-client.c index 7c4af0d6b3..e7e6f1e5ef 100644 --- a/server-client.c +++ b/server-client.c @@ -33,7 +33,7 @@ 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 key_code server_client_check_mouse(struct client *); +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 *); @@ -41,6 +41,8 @@ static void server_client_check_redraw(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_dispatch(struct imsg *, void *); static void server_client_dispatch_command(struct client *, struct imsg *); @@ -62,44 +64,56 @@ server_client_how_many(void) return (n); } -/* Identify mode callback. */ +/* Overlay timer callback. */ static void -server_client_callback_identify(__unused int fd, __unused short events, - void *data) +server_client_overlay_timer(__unused int fd, __unused short events, void *data) { - server_client_clear_identify(data, NULL); + server_client_clear_overlay(data); } -/* Set identify mode on client. */ +/* Set an overlay on client. */ void -server_client_set_identify(struct client *c, u_int delay) +server_client_set_overlay(struct client *c, u_int delay, overlay_draw_cb drawcb, + overlay_key_cb keycb, overlay_free_cb freecb, void *data) { struct timeval tv; + if (c->overlay_draw != NULL) + server_client_clear_overlay(c); + tv.tv_sec = delay / 1000; tv.tv_usec = (delay % 1000) * 1000L; - if (event_initialized(&c->identify_timer)) - evtimer_del(&c->identify_timer); - evtimer_set(&c->identify_timer, server_client_callback_identify, c); + if (event_initialized(&c->overlay_timer)) + evtimer_del(&c->overlay_timer); + evtimer_set(&c->overlay_timer, server_client_overlay_timer, c); if (delay != 0) - evtimer_add(&c->identify_timer, &tv); + evtimer_add(&c->overlay_timer, &tv); + + c->overlay_draw = drawcb; + c->overlay_key = keycb; + c->overlay_free = freecb; + c->overlay_data = data; - c->flags |= CLIENT_IDENTIFY; c->tty.flags |= (TTY_FREEZE|TTY_NOCURSOR); server_redraw_client(c); } -/* Clear identify mode on client. */ -void -server_client_clear_identify(struct client *c, struct window_pane *wp) +/* Clear overlay mode on client. */ +static void +server_client_clear_overlay(struct client *c) { - if (~c->flags & CLIENT_IDENTIFY) + if (c->overlay_draw == NULL) return; - c->flags &= ~CLIENT_IDENTIFY; - if (c->identify_callback != NULL) - c->identify_callback(c, wp); + if (event_initialized(&c->overlay_timer)) + evtimer_del(&c->overlay_timer); + + if (c->overlay_free != NULL) + c->overlay_free(c); + + c->overlay_draw = NULL; + c->overlay_key = NULL; c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); server_redraw_client(c); @@ -159,7 +173,7 @@ server_client_is_default_key_table(struct client *c, struct key_table *table) } /* Create a new client. */ -void +struct client * server_client_create(int fd) { struct client *c; @@ -182,18 +196,25 @@ server_client_create(int fd) TAILQ_INIT(&c->queue); c->stdin_data = evbuffer_new(); + if (c->stdin_data == NULL) + fatalx("out of memory"); c->stdout_data = evbuffer_new(); + if (c->stdout_data == NULL) + fatalx("out of memory"); c->stderr_data = evbuffer_new(); + if (c->stderr_data == NULL) + fatalx("out of memory"); c->tty.fd = -1; c->title = NULL; c->session = NULL; c->last_session = NULL; + c->tty.sx = 80; c->tty.sy = 24; - screen_init(&c->status, c->tty.sx, 1, 0); + status_init(c); c->message_string = NULL; TAILQ_INIT(&c->message_log); @@ -212,6 +233,7 @@ server_client_create(int fd) TAILQ_INSERT_TAIL(&clients, c, entry); log_debug("new client %p", c); + return (c); } /* Open client terminal if needed. */ @@ -225,9 +247,16 @@ server_client_open(struct client *c, char **cause) *cause = xstrdup("can't use /dev/tty"); return (-1); } + // Windows terminal issue not found in mintty + // https://cygwin.com/pipermail/cygwin/2020-May/244878.html + // In Windows terminal, can use script to allocate a pty if (!(c->flags & CLIENT_TERMINAL)) { - *cause = xstrdup("not a terminal"); + //*cause = xstrdup("not a terminal"); + // could show why it failed! + // In windows terminal, simply use script: + // /usr/bin/script -c '/usr/bin/tmux a' /dev/null + *cause = xstrdup(c->ttyname); return (-1); } @@ -245,7 +274,7 @@ server_client_lost(struct client *c) c->flags |= CLIENT_DEAD; - server_client_clear_identify(c, NULL); + server_client_clear_overlay(c); status_prompt_clear(c); status_message_clear(c); @@ -269,13 +298,7 @@ server_client_lost(struct client *c) if (c->stderr_data != c->stdout_data) evbuffer_free(c->stderr_data); - if (event_initialized(&c->status_timer)) - evtimer_del(&c->status_timer); - screen_free(&c->status); - if (c->old_status != NULL) { - screen_free(c->old_status); - free(c->old_status); - } + status_free(c); free(c->title); free((void *)c->cwd); @@ -285,9 +308,6 @@ server_client_lost(struct client *c) key_bindings_unref_table(c->keytable); - if (event_initialized(&c->identify_timer)) - evtimer_del(&c->identify_timer); - free(c->message_string); if (event_initialized(&c->message_timer)) evtimer_del(&c->message_timer); @@ -297,6 +317,7 @@ server_client_lost(struct client *c) free(msg); } + free(c->prompt_saved); free(c->prompt_string); free(c->prompt_buffer); @@ -400,24 +421,35 @@ server_client_exec(struct client *c, const char *cmd) /* Check for mouse keys. */ static key_code -server_client_check_mouse(struct client *c) +server_client_check_mouse(struct client *c, struct key_event *event) { + struct mouse_event *m = &event->m; struct session *s = c->session; - struct mouse_event *m = &c->tty.mouse; - struct window *w; + struct winlink *wl; struct window_pane *wp; - u_int x, y, b; + u_int x, y, b, sx, sy, px, py; int flag; key_code key; struct timeval tv; - enum { NOTYPE, MOVE, DOWN, UP, DRAG, WHEEL, DOUBLE, TRIPLE } type; - enum { NOWHERE, PANE, STATUS, BORDER } where; - - type = NOTYPE; - where = NOWHERE; - - log_debug("mouse %02x at %u,%u (last %u,%u) (%d)", m->b, m->x, m->y, - m->lx, m->ly, c->tty.mouse_drag_flag); + struct style_range *sr; + enum { NOTYPE, + MOVE, + DOWN, + UP, + DRAG, + WHEEL, + DOUBLE, + TRIPLE } type = NOTYPE; + enum { NOWHERE, + PANE, + STATUS, + STATUS_LEFT, + STATUS_RIGHT, + STATUS_DEFAULT, + BORDER } where = NOWHERE; + + log_debug("%s mouse %02x at %u,%u (last %u,%u) (%d)", c->name, m->b, + m->x, m->y, m->lx, m->ly, c->tty.mouse_drag_flag); /* What type of event is this? */ if ((m->sgr_type != ' ' && @@ -434,6 +466,8 @@ server_client_check_mouse(struct client *c) type = DRAG; if (c->tty.mouse_drag_flag) { x = m->x, y = m->y, b = m->b; + if (x == m->lx && y == m->ly) + return (KEYC_UNKNOWN); log_debug("drag update at %u,%u", x, y); } else { x = m->lx, y = m->ly, b = m->lb; @@ -490,48 +524,88 @@ server_client_check_mouse(struct client *c) if (type == NOTYPE) return (KEYC_UNKNOWN); - /* Always save the session. */ + /* Save the session. */ m->s = s->id; + m->w = -1; /* Is this on the status line? */ m->statusat = status_at_line(c); - if (m->statusat != -1 && y == (u_int)m->statusat) { - w = status_get_window_at(c, x); - if (w == NULL) - return (KEYC_UNKNOWN); - m->w = w->id; - where = STATUS; - } else - m->w = -1; + m->statuslines = status_line_size(c); + if (m->statusat != -1 && + y >= (u_int)m->statusat && + y < m->statusat + m->statuslines) { + sr = status_get_range(c, x, y - m->statusat); + if (sr == NULL) { + where = STATUS_DEFAULT; + } else { + switch (sr->type) { + case STYLE_RANGE_NONE: + return (KEYC_UNKNOWN); + case STYLE_RANGE_LEFT: + where = STATUS_LEFT; + break; + case STYLE_RANGE_RIGHT: + where = STATUS_RIGHT; + break; + case STYLE_RANGE_WINDOW: + wl = winlink_find_by_index(&s->windows, + sr->argument); + if (wl == NULL) + return (KEYC_UNKNOWN); + m->w = wl->window->id; + + where = STATUS; + break; + } + } + } /* Not on status line. Adjust position and check for border or pane. */ if (where == NOWHERE) { - if (m->statusat == 0 && y > 0) - y--; + px = x; + if (m->statusat == 0 && y >= m->statuslines) + py = y - m->statuslines; else if (m->statusat > 0 && y >= (u_int)m->statusat) - y = m->statusat - 1; - - TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { - if ((wp->xoff + wp->sx == x && - wp->yoff <= 1 + y && - wp->yoff + wp->sy >= y) || - (wp->yoff + wp->sy == y && - wp->xoff <= 1 + x && - wp->xoff + wp->sx >= x)) - break; + py = m->statusat - 1; + else + py = y; + + tty_window_offset(&c->tty, &m->ox, &m->oy, &sx, &sy); + log_debug("mouse window @%u at %u,%u (%ux%u)", + s->curw->window->id, m->ox, m->oy, sx, sy); + if (px > sx || py > sy) + return (KEYC_UNKNOWN); + px = px + m->ox; + py = py + m->oy; + + /* Try the pane borders if not zoomed. */ + if (~s->curw->window->flags & WINDOW_ZOOMED) { + TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { + if ((wp->xoff + wp->sx == px && + wp->yoff <= 1 + py && + wp->yoff + wp->sy >= py) || + (wp->yoff + wp->sy == py && + wp->xoff <= 1 + px && + wp->xoff + wp->sx >= px)) + break; + } + if (wp != NULL) + where = BORDER; } - if (wp != NULL) - where = BORDER; - else { - wp = window_get_active_at(s->curw->window, x, y); - if (wp != NULL) { + + /* Otherwise try inside the pane. */ + if (where == NOWHERE) { + wp = window_get_active_at(s->curw->window, px, py); + if (wp != NULL) where = PANE; - log_debug("mouse at %u,%u is on pane %%%u", - x, y, wp->id); - } } + 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) + log_debug("mouse on pane %%%u border", wp->id); m->wp = wp->id; m->w = wp->window->id; } else @@ -555,6 +629,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAGEND1_PANE; if (where == STATUS) key = KEYC_MOUSEDRAGEND1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAGEND1_BORDER; break; @@ -563,6 +643,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAGEND2_PANE; if (where == STATUS) key = KEYC_MOUSEDRAGEND2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAGEND2_BORDER; break; @@ -571,6 +657,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAGEND3_PANE; if (where == STATUS) key = KEYC_MOUSEDRAGEND3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAGEND3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAGEND3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAGEND3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAGEND3_BORDER; break; @@ -593,6 +685,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEMOVE_PANE; if (where == STATUS) key = KEYC_MOUSEMOVE_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEMOVE_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEMOVE_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEMOVE_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEMOVE_BORDER; break; @@ -606,6 +704,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAG1_PANE; if (where == STATUS) key = KEYC_MOUSEDRAG1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAG1_BORDER; break; @@ -614,6 +718,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAG2_PANE; if (where == STATUS) key = KEYC_MOUSEDRAG2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAG2_BORDER; break; @@ -622,6 +732,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDRAG3_PANE; if (where == STATUS) key = KEYC_MOUSEDRAG3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDRAG3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDRAG3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDRAG3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDRAG3_BORDER; break; @@ -640,6 +756,12 @@ server_client_check_mouse(struct client *c) key = KEYC_WHEELUP_PANE; if (where == STATUS) key = KEYC_WHEELUP_STATUS; + if (where == STATUS_LEFT) + key = KEYC_WHEELUP_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_WHEELUP_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_WHEELUP_STATUS_DEFAULT; if (where == BORDER) key = KEYC_WHEELUP_BORDER; } else { @@ -647,6 +769,12 @@ server_client_check_mouse(struct client *c) key = KEYC_WHEELDOWN_PANE; if (where == STATUS) key = KEYC_WHEELDOWN_STATUS; + if (where == STATUS_LEFT) + key = KEYC_WHEELDOWN_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_WHEELDOWN_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_WHEELDOWN_STATUS_DEFAULT; if (where == BORDER) key = KEYC_WHEELDOWN_BORDER; } @@ -658,6 +786,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEUP1_PANE; if (where == STATUS) key = KEYC_MOUSEUP1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEUP1_BORDER; break; @@ -666,6 +800,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEUP2_PANE; if (where == STATUS) key = KEYC_MOUSEUP2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEUP2_BORDER; break; @@ -674,6 +814,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEUP3_PANE; if (where == STATUS) key = KEYC_MOUSEUP3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEUP3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEUP3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEUP3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEUP3_BORDER; break; @@ -686,6 +832,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDOWN1_PANE; if (where == STATUS) key = KEYC_MOUSEDOWN1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDOWN1_BORDER; break; @@ -694,6 +846,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDOWN2_PANE; if (where == STATUS) key = KEYC_MOUSEDOWN2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDOWN2_BORDER; break; @@ -702,6 +860,12 @@ server_client_check_mouse(struct client *c) key = KEYC_MOUSEDOWN3_PANE; if (where == STATUS) key = KEYC_MOUSEDOWN3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_MOUSEDOWN3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_MOUSEDOWN3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_MOUSEDOWN3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_MOUSEDOWN3_BORDER; break; @@ -714,6 +878,12 @@ server_client_check_mouse(struct client *c) key = KEYC_DOUBLECLICK1_PANE; if (where == STATUS) key = KEYC_DOUBLECLICK1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_DOUBLECLICK1_BORDER; break; @@ -722,6 +892,12 @@ server_client_check_mouse(struct client *c) key = KEYC_DOUBLECLICK2_PANE; if (where == STATUS) key = KEYC_DOUBLECLICK2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_DOUBLECLICK2_BORDER; break; @@ -730,6 +906,12 @@ server_client_check_mouse(struct client *c) key = KEYC_DOUBLECLICK3_PANE; if (where == STATUS) key = KEYC_DOUBLECLICK3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_DOUBLECLICK3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_DOUBLECLICK3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_DOUBLECLICK3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_DOUBLECLICK3_BORDER; break; @@ -742,6 +924,12 @@ server_client_check_mouse(struct client *c) key = KEYC_TRIPLECLICK1_PANE; if (where == STATUS) key = KEYC_TRIPLECLICK1_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK1_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK1_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK1_STATUS_DEFAULT; if (where == BORDER) key = KEYC_TRIPLECLICK1_BORDER; break; @@ -750,6 +938,12 @@ server_client_check_mouse(struct client *c) key = KEYC_TRIPLECLICK2_PANE; if (where == STATUS) key = KEYC_TRIPLECLICK2_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK2_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK2_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK2_STATUS_DEFAULT; if (where == BORDER) key = KEYC_TRIPLECLICK2_BORDER; break; @@ -758,6 +952,12 @@ server_client_check_mouse(struct client *c) key = KEYC_TRIPLECLICK3_PANE; if (where == STATUS) key = KEYC_TRIPLECLICK3_STATUS; + if (where == STATUS_LEFT) + key = KEYC_TRIPLECLICK3_STATUS_LEFT; + if (where == STATUS_RIGHT) + key = KEYC_TRIPLECLICK3_STATUS_RIGHT; + if (where == STATUS_DEFAULT) + key = KEYC_TRIPLECLICK3_STATUS_DEFAULT; if (where == BORDER) key = KEYC_TRIPLECLICK3_BORDER; break; @@ -802,63 +1002,64 @@ server_client_assume_paste(struct session *s) return (0); } -/* Handle data key input from client. */ -void -server_client_handle_key(struct client *c, key_code key) +/* Has the latest client changed? */ +static void +server_client_update_latest(struct client *c) { - struct mouse_event *m = &c->tty.mouse; - struct session *s = c->session; - struct window *w; - struct window_pane *wp; - struct timeval tv; - struct key_table *table, *first; - struct key_binding bd_find, *bd; - int xtimeout, flags; - struct cmd_find_state fs; - key_code key0; + struct window *w; + + if (c->session == NULL) + return; + w = c->session->curw->window; + + if (w->latest == c) + return; + w->latest = c; + + if (options_get_number(w->options, "window-size") == WINDOW_SIZE_LATEST) + recalculate_size(w); +} + +/* + * Handle data key input from client. This owns and can modify the key event it + * is given and is responsible for freeing it. + */ +static enum cmd_retval +server_client_key_callback(struct cmdq_item *item, void *data) +{ + struct client *c = item->client; + struct key_event *event = data; + key_code key = event->key; + struct mouse_event *m = &event->m; + struct session *s = c->session; + struct winlink *wl; + struct window_pane *wp; + struct window_mode_entry *wme; + struct timeval tv; + struct key_table *table, *first; + struct key_binding *bd; + int xtimeout, flags; + struct cmd_find_state fs; + key_code key0; /* Check the client is good to accept input. */ if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) - return; - w = s->curw->window; + goto out; + wl = s->curw; /* Update the activity timer. */ if (gettimeofday(&c->activity_time, NULL) != 0) fatal("gettimeofday failed"); session_update_activity(s, &c->activity_time); - /* Number keys jump to pane in identify mode. */ - if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { - if (c->flags & CLIENT_READONLY) - return; - window_unzoom(w); - wp = window_pane_at_index(w, key - '0'); - if (wp != NULL && !window_pane_visible(wp)) - wp = NULL; - server_client_clear_identify(c, wp); - return; - } - - /* Handle status line. */ - if (!(c->flags & CLIENT_READONLY)) { - status_message_clear(c); - server_client_clear_identify(c, NULL); - } - if (c->prompt_string != NULL) { - if (c->flags & CLIENT_READONLY) - return; - if (status_prompt_key(c, key) == 0) - return; - } - /* Check for mouse keys. */ m->valid = 0; if (key == KEYC_MOUSE) { if (c->flags & CLIENT_READONLY) - return; - key = server_client_check_mouse(c); + goto out; + key = server_client_check_mouse(c, event); if (key == KEYC_UNKNOWN) - return; + goto out; m->valid = 1; m->key = key; @@ -869,10 +1070,9 @@ server_client_handle_key(struct client *c, key_code key) */ if (key == KEYC_DRAGGING) { c->tty.mouse_drag_update(c, m); - return; + goto out; } - } else - m->valid = 0; + } /* Find affected pane. */ if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) @@ -881,11 +1081,11 @@ server_client_handle_key(struct client *c, key_code key) /* Forward mouse keys if disabled. */ if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse")) - goto forward; + goto forward_key; /* Treat everything as a regular key when pasting is detected. */ if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) - goto forward; + goto forward_key; /* * Work out the current key table. If the pane is in a mode, use @@ -893,13 +1093,14 @@ server_client_handle_key(struct client *c, key_code key) */ if (server_client_is_default_key_table(c, c->keytable) && wp != NULL && - wp->mode != NULL && - wp->mode->key_table != NULL) - table = key_bindings_get_table(wp->mode->key_table(wp), 1); + (wme = TAILQ_FIRST(&wp->modes)) != NULL && + wme->mode->key_table != NULL) + table = key_bindings_get_table(wme->mode->key_table(wme), 1); else table = c->keytable; first = table; +table_changed: /* * The prefix always takes precedence and forces a switch to the prefix * table, unless we are already there. @@ -910,11 +1111,11 @@ server_client_handle_key(struct client *c, key_code key) strcmp(table->name, "prefix") != 0) { server_client_set_key_table(c, "prefix"); server_status_client(c); - return; + goto out; } flags = c->flags; -retry: +try_again: /* Log key table. */ if (wp == NULL) log_debug("key table %s (no pane)", table->name); @@ -924,8 +1125,7 @@ server_client_handle_key(struct client *c, key_code key) log_debug("currently repeating"); /* Try to see if there is a key binding in the current table. */ - bd_find.key = key0; - bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); + bd = key_bindings_get(table, key0); if (bd != NULL) { /* * Key was matched in this table. If currently repeating but a @@ -934,11 +1134,13 @@ server_client_handle_key(struct client *c, key_code key) */ if ((c->flags & CLIENT_REPEAT) && (~bd->flags & KEY_BINDING_REPEAT)) { + log_debug("found in key table %s (not repeating)", + table->name); server_client_set_key_table(c, NULL); + first = table = c->keytable; c->flags &= ~CLIENT_REPEAT; server_status_client(c); - table = c->keytable; - goto retry; + goto table_changed; } log_debug("found in key table %s", table->name); @@ -967,9 +1169,17 @@ server_client_handle_key(struct client *c, key_code key) server_status_client(c); /* Execute the key binding. */ - key_bindings_dispatch(bd, NULL, c, m, &fs); + key_bindings_dispatch(bd, item, c, m, &fs); key_bindings_unref_table(table); - return; + goto out; + } + + /* + * No match, try the ANY key. + */ + if (key0 != KEYC_ANY) { + key0 = KEYC_ANY; + goto try_again; } /* @@ -979,11 +1189,14 @@ server_client_handle_key(struct client *c, key_code key) log_debug("not found in key table %s", table->name); if (!server_client_is_default_key_table(c, table) || (c->flags & CLIENT_REPEAT)) { + log_debug("trying in root table"); server_client_set_key_table(c, NULL); + table = c->keytable; + if (c->flags & CLIENT_REPEAT) + first = table; c->flags &= ~CLIENT_REPEAT; server_status_client(c); - table = c->keytable; - goto retry; + goto table_changed; } /* @@ -993,14 +1206,63 @@ server_client_handle_key(struct client *c, key_code key) if (first != table && (~flags & CLIENT_REPEAT)) { server_client_set_key_table(c, NULL); server_status_client(c); - return; + goto out; } -forward: +forward_key: if (c->flags & CLIENT_READONLY) - return; + goto out; if (wp != NULL) - window_pane_key(wp, c, s, key, m); + window_pane_key(wp, c, s, wl, key, m); + +out: + if (s != NULL) + server_client_update_latest(c); + free(event); + return (CMD_RETURN_NORMAL); +} + +/* Handle a key event. */ +int +server_client_handle_key(struct client *c, struct key_event *event) +{ + struct session *s = c->session; + struct cmdq_item *item; + + /* Check the client is good to accept input. */ + if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) + return (0); + + /* + * Key presses in overlay mode and the command prompt are a special + * case. The queue might be blocked so they need to be processed + * 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) + return (0); + } + if (c->overlay_key != NULL) { + switch (c->overlay_key(c, event)) { + case 0: + return (0); + case 1: + server_client_clear_overlay(c); + return (0); + } + } + server_client_clear_overlay(c); + } + + /* + * Add the key to the queue so it happens after any commands queued by + * previous keys. + */ + item = cmdq_get_callback(server_client_key_callback, event); + cmdq_append(c, item); + return (1); } /* Client functions that need to happen every loop. */ @@ -1010,7 +1272,9 @@ server_client_loop(void) struct client *c; struct window *w; struct window_pane *wp; - int focus; + struct winlink *wl; + struct session *s; + int focus, attached, resize; TAILQ_FOREACH(c, &clients, entry) { server_client_check_exit(c); @@ -1023,14 +1287,34 @@ 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. */ 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); - server_client_check_resize(wp); + if (resize) + server_client_check_resize(wp); } wp->flags &= ~PANE_REDRAW; } @@ -1043,7 +1327,6 @@ static int server_client_resize_force(struct window_pane *wp) { struct timeval tv = { .tv_usec = 100000 }; - struct winsize ws; /* * If we are resizing to the same size as when we entered the loop @@ -1064,86 +1347,76 @@ server_client_resize_force(struct window_pane *wp) wp->sy <= 1) return (0); - memset(&ws, 0, sizeof ws); - ws.ws_col = wp->sx; - ws.ws_row = wp->sy - 1; - if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) -#ifdef __sun - if (errno != EINVAL && errno != ENXIO) -#endif - fatal("ioctl failed"); 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); +} + /* Resize timer event. */ static void server_client_resize_event(__unused int fd, __unused short events, void *data) { struct window_pane *wp = data; - struct winsize ws; evtimer_del(&wp->resize_timer); - if (!(wp->flags & PANE_RESIZE)) - return; - if (server_client_resize_force(wp)) + if (~wp->flags & PANE_RESIZE) return; - - memset(&ws, 0, sizeof ws); - ws.ws_col = wp->sx; - ws.ws_row = wp->sy; - if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) -#ifdef __sun - /* - * Some versions of Solaris apparently can return an error when - * resizing; don't know why this happens, can't reproduce on - * other platforms and ignoring it doesn't seem to cause any - * issues. - */ - if (errno != EINVAL && errno != ENXIO) -#endif - fatal("ioctl failed"); - log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); - - wp->flags &= ~PANE_RESIZE; - - wp->osx = wp->sx; - wp->osy = wp->sy; + 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) { - struct timeval tv = { .tv_usec = 250000 }; - - if (!(wp->flags & PANE_RESIZE)) + if (~wp->flags & PANE_RESIZE) return; - log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); if (!event_initialized(&wp->resize_timer)) evtimer_set(&wp->resize_timer, server_client_resize_event, wp); - /* - * The first resize should happen immediately, so if the timer is not - * running, do it now. - */ - if (!evtimer_pending(&wp->resize_timer, NULL)) - server_client_resize_event(-1, 0, wp); - - /* - * If the pane is in the alternate screen, let the timer expire and - * resize to give the application a chance to redraw. If not, keep - * pushing the timer back. - */ - if (wp->saved_grid != NULL && evtimer_pending(&wp->resize_timer, NULL)) - return; - evtimer_del(&wp->resize_timer); - evtimer_add(&wp->resize_timer, &tv); + 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. */ @@ -1157,10 +1430,6 @@ server_client_check_focus(struct window_pane *wp) push = wp->flags & PANE_FOCUSPUSH; wp->flags &= ~PANE_FOCUSPUSH; - /* If we don't care about focus, forget it. */ - if (!(wp->base.mode & MODE_FOCUSON)) - return; - /* If we're not the active pane in our window, we're not focused. */ if (wp->window->active != wp) goto not_focused; @@ -1176,7 +1445,7 @@ server_client_check_focus(struct window_pane *wp) TAILQ_FOREACH(c, &clients, entry) { if (c->session == NULL || !(c->flags & CLIENT_FOCUSED)) continue; - if (c->session->flags & SESSION_UNATTACHED) + if (c->session->attached == 0) continue; if (c->session->curw->window == wp->window) @@ -1184,14 +1453,21 @@ server_client_check_focus(struct window_pane *wp) } not_focused: - if (push || (wp->flags & PANE_FOCUSED)) - bufferevent_write(wp->event, "\033[O", 3); + 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; focused: - if (push || !(wp->flags & PANE_FOCUSED)) - bufferevent_write(wp->event, "\033[I", 3); + 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; } @@ -1211,27 +1487,39 @@ server_client_reset_state(struct client *c) struct window_pane *wp = w->active, *loop; struct screen *s = wp->screen; struct options *oo = c->session->options; - int status, mode, o; + int mode, cursor = 0; + 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); - status = options_get_number(oo, "status"); - if (!window_pane_visible(wp) || wp->yoff + s->cy >= c->tty.sy - status) - tty_cursor(&c->tty, 0, 0); - else { - o = status && options_get_number(oo, "status-position") == 0; - tty_cursor(&c->tty, wp->xoff + s->cx, o + wp->yoff + s->cy); + /* 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; + + cx = wp->xoff + s->cx - ox; + cy = wp->yoff + s->cy - oy; + + if (status_at_line(c) == 0) + cy += status_line_size(c); } + if (!cursor) + mode &= ~MODE_CURSOR; + tty_cursor(&c->tty, cx, cy); /* * Set mouse mode if requested. To support dragging, always use button * mode. */ - mode = s->mode; if (options_get_number(oo, "mouse")) { mode &= ~ALL_MOUSE_MODES; TAILQ_FOREACH(loop, &w->panes, entry) { @@ -1277,7 +1565,9 @@ server_client_click_timer(__unused int fd, __unused short events, void *data) static void server_client_check_exit(struct client *c) { - if (!(c->flags & CLIENT_EXIT)) + if (~c->flags & CLIENT_EXIT) + return; + if (c->flags & CLIENT_EXITED) return; if (EVBUFFER_LENGTH(c->stdin_data) != 0) @@ -1287,8 +1577,10 @@ server_client_check_exit(struct client *c) if (EVBUFFER_LENGTH(c->stderr_data) != 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_EXIT; + c->flags |= CLIENT_EXITED; } /* Redraw timer callback. */ @@ -1306,13 +1598,20 @@ server_client_check_redraw(struct client *c) struct session *s = c->session; struct tty *tty = &c->tty; struct window_pane *wp; - int needed, flags, masked; + int needed, flags; struct timeval tv = { .tv_usec = 1000 }; static struct event ev; size_t left; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) return; + if (c->flags & CLIENT_ALLREDRAWFLAGS) { + log_debug("%s: redraw%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" : ""); + } /* * If there is outstanding data, defer the redraw until it has been @@ -1320,7 +1619,7 @@ server_client_check_redraw(struct client *c) * end up back here. */ needed = 0; - if (c->flags & CLIENT_REDRAW) + if (c->flags & (CLIENT_ALLREDRAWFLAGS & ~CLIENT_REDRAWSTATUS)) needed = 1; else { TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { @@ -1343,25 +1642,19 @@ server_client_check_redraw(struct client *c) * 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_REDRAW; + c->flags |= CLIENT_ALLREDRAWFLAGS; return; } else if (needed) log_debug("%s: redraw needed", c->name); - if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) { - if (options_get_number(s->options, "set-titles")) - server_client_set_title(c); - screen_redraw_update(c); /* will adjust flags */ - } - flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE)) | TTY_NOCURSOR; - if (c->flags & CLIENT_REDRAW) { - tty_update_mode(tty, tty->mode, NULL); - screen_redraw_screen(c, 1, 1, 1); - c->flags &= ~(CLIENT_STATUS|CLIENT_BORDERS); - } else { + 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); @@ -1370,21 +1663,16 @@ server_client_check_redraw(struct client *c) } } - masked = c->flags & (CLIENT_BORDERS|CLIENT_STATUS); - if (masked != 0) - tty_update_mode(tty, tty->mode, NULL); - if (masked == CLIENT_BORDERS) - screen_redraw_screen(c, 0, 0, 1); - else if (masked == CLIENT_STATUS) - screen_redraw_screen(c, 0, 1, 0); - else if (masked != 0) - screen_redraw_screen(c, 0, 1, 1); + if (c->flags & CLIENT_ALLREDRAWFLAGS) { + if (options_get_number(s->options, "set-titles")) + server_client_set_title(c); + screen_redraw_screen(c); + } tty->flags = (tty->flags & ~(TTY_FREEZE|TTY_NOCURSOR)) | flags; tty_update_mode(tty, tty->mode, NULL); - c->flags &= ~(CLIENT_REDRAW|CLIENT_BORDERS|CLIENT_STATUS| - CLIENT_STATUSFORCE); + c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE); if (needed) { /* @@ -1411,7 +1699,7 @@ server_client_set_title(struct client *c) ft = format_create(c, NULL, FORMAT_NONE, 0); format_defaults(ft, c, NULL, NULL, NULL); - title = format_expand_time(ft, template, time(NULL)); + title = format_expand_time(ft, template); if (c->title == NULL || strcmp(title, c->title) != 0) { free(c->title); c->title = xstrdup(title); @@ -1470,8 +1758,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) evbuffer_add(c->stdin_data, stdindata.data, stdindata.size); } - c->stdin_callback(c, c->stdin_closed, - c->stdin_callback_data); + c->stdin_callback(c, c->stdin_closed, c->stdin_callback_data); break; case MSG_RESIZE: if (datalen != 0) @@ -1479,6 +1766,8 @@ 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(); server_redraw_client(c); @@ -1536,18 +1825,6 @@ server_client_command_done(struct cmdq_item *item, __unused void *data) return (CMD_RETURN_NORMAL); } -/* Show an error message. */ -static enum cmd_retval -server_client_command_error(struct cmdq_item *item, void *data) -{ - char *error = data; - - cmdq_error(item, "%s", error); - free(error); - - return (CMD_RETURN_NORMAL); -} - /* Handle command message. */ static void server_client_dispatch_command(struct client *c, struct imsg *imsg) @@ -1555,9 +1832,12 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) struct msg_command_data data; char *buf; size_t len; - struct cmd_list *cmdlist = NULL; int argc; char **argv, *cause; + struct cmd_parse_result *pr; + + if (c->flags & CLIENT_EXIT) + return; if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data) fatalx("bad MSG_COMMAND size"); @@ -1580,22 +1860,30 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) *argv = xstrdup("new-session"); } - if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) { - cmd_free_argv(argc, argv); + pr = cmd_parse_from_arguments(argc, argv, 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; } cmd_free_argv(argc, argv); - cmdq_append(c, cmdq_get_command(cmdlist, NULL, NULL, 0)); + cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL, NULL, 0)); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); - cmd_list_free(cmdlist); + + cmd_list_free(pr->cmdlist); return; error: - cmdq_append(c, cmdq_get_callback(server_client_command_error, cause)); + cmd_free_argv(argc, argv); - if (cmdlist != NULL) - cmd_list_free(cmdlist); + cmdq_append(c, cmdq_get_error(cause)); + free(cause); c->flags |= CLIENT_EXIT; } @@ -1680,7 +1968,7 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) c->name = name; log_debug("client %p name is %s", c, c->name); -#ifdef __CYGWIN__ +#if __MSYS__ || __CYGWIN__ || _WIN32 || _WIN64 c->fd = open(c->ttyname, O_RDWR|O_NOCTTY); #endif @@ -1698,26 +1986,29 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg) close(c->fd); c->fd = -1; - - return; - } - - if (c->fd == -1) - return; - if (tty_init(&c->tty, c, c->fd, c->term) != 0) { - close(c->fd); - c->fd = -1; - return; + } else if (c->fd != -1) { + if (tty_init(&c->tty, c, c->fd, c->term) != 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; + } } - 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); - if (!(c->flags & CLIENT_CONTROL)) - c->flags |= CLIENT_TERMINAL; + /* + * If this is the first client that has finished identifying, load + * configuration files. + */ + if ((~c->flags & CLIENT_EXIT) && + !cfg_finished && + c == TAILQ_FIRST(&clients) && + TAILQ_NEXT(c, entry) == NULL) + start_cfg(); } /* Handle shell message. */ @@ -1854,15 +2145,21 @@ server_client_add_message(struct client *c, const char *fmt, ...) /* Get client working directory. */ const char * -server_client_get_cwd(struct client *c) +server_client_get_cwd(struct client *c, struct session *s) { - struct session *s; + const char *home; + if (!cfg_finished && cfg_client != NULL) + return (cfg_client->cwd); if (c != NULL && c->session == NULL && c->cwd != NULL) return (c->cwd); + if (s != NULL && s->cwd != NULL) + return (s->cwd); if (c != NULL && (s = c->session) != NULL && s->cwd != NULL) return (s->cwd); - return ("."); + if ((home = find_home()) != NULL) + return (home); + return ("/"); } /* Resolve an absolute path or relative to client working directory. */ @@ -1874,7 +2171,7 @@ server_client_get_path(struct client *c, const char *file) if (*file == '/') path = xstrdup(file); else - xasprintf(&path, "%s/%s", server_client_get_cwd(c), file); + xasprintf(&path, "%s/%s", server_client_get_cwd(c, NULL), file); if (realpath(path, resolved) == NULL) return (path); free(path); diff --git a/server-fn.c b/server-fn.c index d13beec935..cfafef21bc 100644 --- a/server-fn.c +++ b/server-fn.c @@ -17,6 +17,7 @@ */ #include +#include #include #include @@ -32,13 +33,13 @@ static void server_destroy_session_group(struct session *); void server_redraw_client(struct client *c) { - c->flags |= CLIENT_REDRAW; + c->flags |= CLIENT_ALLREDRAWFLAGS; } void server_status_client(struct client *c) { - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; } void @@ -107,7 +108,7 @@ server_redraw_window_borders(struct window *w) TAILQ_FOREACH(c, &clients, entry) { if (c->session != NULL && c->session->curw->window == w) - c->flags |= CLIENT_BORDERS; + c->flags |= CLIENT_REDRAWBORDERS; } } @@ -174,6 +175,22 @@ server_lock_client(struct client *c) proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1); } +void +server_kill_pane(struct window_pane *wp) +{ + struct window *w = wp->window; + + if (window_count_panes(w) == 1) { + server_kill_window(w); + recalculate_sizes(); + } else { + server_unzoom_window(w); + layout_close_pane(wp); + window_remove_pane(w, wp); + server_redraw_window(w); + } +} + void server_kill_window(struct window *w) { @@ -276,37 +293,55 @@ void server_destroy_pane(struct window_pane *wp, int notify) { struct window *w = wp->window; - int old_fd; struct screen_write_ctx ctx; struct grid_cell gc; + time_t t; + char tim[26]; - old_fd = wp->fd; if (wp->fd != -1) { #ifdef HAVE_UTEMPTER utempter_remove_record(wp->fd); #endif bufferevent_free(wp->event); + wp->event = NULL; close(wp->fd); wp->fd = -1; } - if (options_get_number(w->options, "remain-on-exit")) { - if (old_fd == -1) + if (options_get_number(wp->options, "remain-on-exit")) { + if (~wp->flags & PANE_STATUSREADY) + return; + + if (wp->flags & PANE_STATUSDRAWN) return; + wp->flags |= PANE_STATUSDRAWN; if (notify) notify_pane("pane-died", wp); screen_write_start(&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); + screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1, 0); screen_write_linefeed(&ctx, 1, 8); memcpy(&gc, &grid_default_cell, sizeof gc); - gc.attr |= GRID_ATTR_BRIGHT; - screen_write_puts(&ctx, &gc, "Pane is dead"); + + time(&t); + ctime_r(&t, tim); + + if (WIFEXITED(wp->status)) { + screen_write_nputs(&ctx, -1, &gc, + "Pane is dead (status %d, %s)", + WEXITSTATUS(wp->status), + tim); + } else if (WIFSIGNALED(wp->status)) { + screen_write_nputs(&ctx, -1, &gc, + "Pane is dead (signal %d, %s)", + WTERMSIG(wp->status), + tim); + } + screen_write_stop(&ctx); wp->flags |= PANE_REDRAW; - return; } @@ -334,7 +369,7 @@ server_destroy_session_group(struct session *s) else { TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { server_destroy_session(s); - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } } } @@ -376,6 +411,7 @@ server_destroy_session(struct session *s) 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); @@ -397,14 +433,13 @@ server_check_unattached(void) * set, collect them. */ RB_FOREACH(s, sessions, &sessions) { - if (!(s->flags & SESSION_UNATTACHED)) + if (s->attached != 0) continue; if (options_get_number (s->options, "destroy-unattached")) - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } } -/* Set stdin callback. */ int server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, void *), void *cb_data, char **cause) @@ -418,7 +453,7 @@ server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, return (-1); } if (c->stdin_callback != NULL) { - *cause = xstrdup("stdin in use"); + *cause = xstrdup("stdin is in use"); return (-1); } @@ -438,8 +473,6 @@ server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, void server_unzoom_window(struct window *w) { - if (window_unzoom(w) == 0) { + if (window_unzoom(w) == 0) server_redraw_window(w); - server_status_window(w); - } } diff --git a/server.c b/server.c index dad28aa518..a5d5e37f6d 100644 --- a/server.c +++ b/server.c @@ -43,13 +43,12 @@ struct clients clients; struct tmuxproc *server_proc; -static int server_fd; +static int server_fd = -1; static int server_exit; static struct event server_ev_accept; struct cmd_find_state marked_pane; -static int server_create_socket(void); static int server_loop(void); static void server_send_exit(void); static void server_accept(int, short, void *); @@ -98,39 +97,50 @@ server_check_marked(void) /* Create server socket. */ static int -server_create_socket(void) +server_create_socket(char **cause) { struct sockaddr_un sa; size_t size; mode_t mask; - int fd; + int fd, saved_errno; memset(&sa, 0, sizeof sa); sa.sun_family = AF_UNIX; size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path); if (size >= sizeof sa.sun_path) { errno = ENAMETOOLONG; - return (-1); + goto fail; } unlink(sa.sun_path); if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) - return (-1); + goto fail; mask = umask(S_IXUSR|S_IXGRP|S_IRWXO); - if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == -1) { + if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) { + saved_errno = errno; close(fd); - return (-1); + errno = saved_errno; + goto fail; } umask(mask); if (listen(fd, 128) == -1) { + saved_errno = errno; close(fd); - return (-1); + errno = saved_errno; + goto fail; } setblocking(fd, 0); return (fd); + +fail: + if (cause != NULL) { + xasprintf(cause, "error creating %s (%s)", socket_path, + strerror(errno)); + } + return (-1); } /* Fork new server. */ @@ -139,8 +149,9 @@ server_start(struct tmuxproc *client, struct event_base *base, int lockfd, char *lockfile) { int pair[2]; - struct job *job; sigset_t set, oldset; + struct client *c; + char *cause = NULL; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) fatal("socketpair failed"); @@ -177,16 +188,14 @@ server_start(struct tmuxproc *client, struct event_base *base, int lockfd, RB_INIT(&all_window_panes); TAILQ_INIT(&clients); RB_INIT(&sessions); - RB_INIT(&session_groups); key_bindings_init(); gettimeofday(&start_time, NULL); - server_fd = server_create_socket(); - if (server_fd == -1) - fatal("couldn't create socket"); - server_update_socket(); - server_client_create(pair[1]); + server_fd = server_create_socket(&cause); + if (server_fd != -1) + server_update_socket(); + c = server_client_create(pair[1]); if (lockfd >= 0) { unlink(lockfile); @@ -194,18 +203,18 @@ server_start(struct tmuxproc *client, struct event_base *base, int lockfd, close(lockfd); } - start_cfg(); + if (cause != NULL) { + cmdq_append(c, cmdq_get_error(cause)); + free(cause); + c->flags |= CLIENT_EXIT; + } server_add_accept(0); - proc_loop(server_proc, server_loop); - LIST_FOREACH(job, &all_jobs, entry) { - if (job->pid != -1) - kill(job->pid, SIGTERM); - } - + job_kill_all(); status_prompt_save_history(); + exit(0); } @@ -226,6 +235,9 @@ server_loop(void) server_client_loop(); + if (!options_get_number(global_options, "exit-empty") && !server_exit) + return (0); + if (!options_get_number(global_options, "exit-unattached")) { if (!RB_EMPTY(&sessions)) return (0); @@ -244,6 +256,9 @@ server_loop(void) if (!TAILQ_EMPTY(&clients)) return (0); + if (job_still_running()) + return (0); + return (1); } @@ -259,13 +274,16 @@ server_send_exit(void) TAILQ_FOREACH_SAFE(c, &clients, entry, c1) { if (c->flags & CLIENT_SUSPENDED) server_client_lost(c); - else + else { + if (c->flags & CLIENT_ATTACHED) + notify_client("client-detached", c); proc_send(c->peer, MSG_SHUTDOWN, -1, NULL, 0); + } c->session = NULL; } RB_FOREACH_SAFE(s, sessions, &sessions, s1) - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } /* Update socket execute permissions based on whether sessions are attached. */ @@ -279,7 +297,7 @@ server_update_socket(void) n = 0; RB_FOREACH(s, sessions, &sessions) { - if (!(s->flags & SESSION_UNATTACHED)) { + if (s->attached != 0) { n++; break; } @@ -343,6 +361,9 @@ server_add_accept(int timeout) { struct timeval tv = { timeout, 0 }; + if (server_fd == -1) + return; + if (event_initialized(&server_ev_accept)) event_del(&server_ev_accept); @@ -374,7 +395,7 @@ server_signal(int sig) break; case SIGUSR1: event_del(&server_ev_accept); - fd = server_create_socket(); + fd = server_create_socket(NULL); if (fd != -1) { close(server_fd); server_fd = fd; @@ -417,12 +438,12 @@ server_child_exited(pid_t pid, int status) { struct window *w, *w1; struct window_pane *wp; - struct job *job; RB_FOREACH_SAFE(w, windows, &windows, w1) { TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->pid == pid) { wp->status = status; + wp->flags |= PANE_STATUSREADY; log_debug("%%%u exited", wp->id); wp->flags |= PANE_EXITED; @@ -433,13 +454,7 @@ server_child_exited(pid_t pid, int status) } } } - - LIST_FOREACH(job, &all_jobs, entry) { - if (pid == job->pid) { - job_died(job, status); /* might free job */ - break; - } - } + job_check_died(pid, status); } /* Handle stopped children. */ diff --git a/session.c b/session.c index 6143807c9a..b361bde31a 100644 --- a/session.c +++ b/session.c @@ -28,7 +28,7 @@ struct sessions sessions; static u_int next_session_id; -struct session_groups session_groups; +struct session_groups session_groups = RB_INITIALIZER(&session_groups); static void session_free(int, short, void *); @@ -38,27 +38,21 @@ static struct winlink *session_next_alert(struct winlink *); static struct winlink *session_previous_alert(struct winlink *); static void session_group_remove(struct session *); -static u_int session_group_count(struct session_group *); static void session_group_synchronize1(struct session *, struct session *); -static u_int session_group_count(struct session_group *); -static void session_group_synchronize1(struct session *, struct session *); - -RB_GENERATE(sessions, session, entry, session_cmp); - int session_cmp(struct session *s1, struct session *s2) { return (strcmp(s1->name, s2->name)); } +RB_GENERATE(sessions, session, entry, session_cmp); -RB_GENERATE(session_groups, session_group, entry, session_group_cmp); - -int +static int session_group_cmp(struct session_group *s1, struct session_group *s2) { return (strcmp(s1->name, s2->name)); } +RB_GENERATE_STATIC(session_groups, session_group, entry, session_group_cmp); /* * Find if session is still alive. This is true if it is still on the global @@ -117,12 +111,10 @@ session_find_by_id(u_int id) /* Create a new session. */ struct session * -session_create(const char *prefix, const char *name, int argc, char **argv, - const char *path, const char *cwd, struct environ *env, struct termios *tio, - int idx, u_int sx, u_int sy, char **cause) +session_create(const char *prefix, const char *name, const char *cwd, + struct environ *env, struct options *oo, struct termios *tio) { struct session *s; - struct winlink *wl; s = xcalloc(1, sizeof *s); s->references = 1; @@ -134,14 +126,10 @@ session_create(const char *prefix, const char *name, int argc, char **argv, TAILQ_INIT(&s->lastw); RB_INIT(&s->windows); - s->environ = environ_create(); - if (env != NULL) - environ_copy(env, s->environ); - - s->options = options_create(global_s_options); - s->hooks = hooks_create(global_hooks); + s->environ = env; + s->options = oo; - status_update_saved(s); + status_update_cache(s); s->tio = NULL; if (tio != NULL) { @@ -149,9 +137,6 @@ session_create(const char *prefix, const char *name, int argc, char **argv, memcpy(s->tio, tio, sizeof *s->tio); } - s->sx = sx; - s->sy = sy; - if (name != NULL) { s->name = xstrdup(name); s->id = next_session_id++; @@ -174,17 +159,6 @@ session_create(const char *prefix, const char *name, int argc, char **argv, fatal("gettimeofday failed"); session_update_activity(s, &s->creation_time); - if (argc >= 0) { - wl = session_new(s, NULL, argc, argv, path, cwd, idx, cause); - if (wl == NULL) { - session_destroy(s, __func__); - return (NULL); - } - session_select(s, RB_ROOT(&s->windows)->idx); - } - - log_debug("session %s created", s->name); - return (s); } @@ -217,9 +191,7 @@ session_free(__unused int fd, __unused short events, void *arg) if (s->references == 0) { environ_free(s->environ); - options_free(s->options); - hooks_free(s->hooks); free(s->name); free(s); @@ -228,7 +200,7 @@ session_free(__unused int fd, __unused short events, void *arg) /* Destroy a session. */ void -session_destroy(struct session *s, const char *from) +session_destroy(struct session *s, int notify, const char *from) { struct winlink *wl; @@ -236,7 +208,8 @@ session_destroy(struct session *s, const char *from) s->curw = NULL; RB_REMOVE(sessions, &sessions, s); - notify_session("session-closed", s); + if (notify) + notify_session("session-closed", s); free(s->tio); @@ -271,7 +244,7 @@ session_lock_timer(__unused int fd, __unused short events, void *arg) { struct session *s = arg; - if (s->flags & SESSION_UNATTACHED) + if (s->attached == 0) return; log_debug("session %s locked, activity time %lld", s->name, @@ -294,16 +267,17 @@ session_update_activity(struct session *s, struct timeval *from) else memcpy(&s->activity_time, from, sizeof s->activity_time); - log_debug("session %s activity %lld.%06d (last %lld.%06d)", s->name, - (long long)s->activity_time.tv_sec, (int)s->activity_time.tv_usec, - (long long)last->tv_sec, (int)last->tv_usec); + log_debug("session $%u %s activity %lld.%06d (last %lld.%06d)", s->id, + s->name, (long long)s->activity_time.tv_sec, + (int)s->activity_time.tv_usec, (long long)last->tv_sec, + (int)last->tv_usec); if (evtimer_initialized(&s->lock_timer)) evtimer_del(&s->lock_timer); else evtimer_set(&s->lock_timer, session_lock_timer, s); - if (~s->flags & SESSION_UNATTACHED) { + if (s->attached != 0) { timerclear(&tv); tv.tv_sec = options_get_number(s->options, "lock-after-time"); if (tv.tv_sec != 0) @@ -345,44 +319,6 @@ session_previous_session(struct session *s) return (s2); } -/* Create a new window on a session. */ -struct winlink * -session_new(struct session *s, const char *name, int argc, char **argv, - const char *path, const char *cwd, int idx, char **cause) -{ - struct window *w; - struct winlink *wl; - struct environ *env; - const char *shell; - u_int hlimit; - - if ((wl = winlink_add(&s->windows, idx)) == NULL) { - xasprintf(cause, "index in use: %d", idx); - return (NULL); - } - wl->session = s; - - shell = options_get_string(s->options, "default-shell"); - if (*shell == '\0' || areshell(shell)) - shell = _PATH_BSHELL; - - hlimit = options_get_number(s->options, "history-limit"); - env = environ_for_session(s, 0); - w = window_create_spawn(name, argc, argv, path, shell, cwd, env, s->tio, - s->sx, s->sy, hlimit, cause); - if (w == NULL) { - winlink_remove(&s->windows, wl); - environ_free(env); - return (NULL); - } - winlink_set_window(wl, w); - environ_free(env); - notify_session_window("window-linked", s, w); - - session_group_synchronize_from(s); - return (wl); -} - /* Attach a window to a session. */ struct winlink * session_attach(struct session *s, struct window *w, int idx, char **cause) @@ -418,7 +354,7 @@ session_detach(struct session *s, struct winlink *wl) session_group_synchronize_from(s); if (RB_EMPTY(&s->windows)) { - session_destroy(s, __func__); + session_destroy(s, 1, __func__); return (1); } return (0); @@ -552,6 +488,7 @@ session_set_current(struct session *s, struct winlink *wl) s->curw = wl; winlink_clear_flags(wl); window_update_activity(wl->window); + tty_update_window_offset(wl->window); notify_session("session-window-changed", s); return (0); } @@ -623,7 +560,7 @@ session_group_remove(struct session *s) } /* Count number of sessions in session group. */ -static u_int +u_int session_group_count(struct session_group *sg) { struct session *s; diff --git a/sixel.c b/sixel.c new file mode 100644 index 0000000000..37479e5b5e --- /dev/null +++ b/sixel.c @@ -0,0 +1,1812 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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 "tmux.h" + +#define SIXEL_COLOUR_REGISTERS 1024 +#define SIXEL_WIDTH_LIMIT 2016 +#define SIXEL_HEIGHT_LIMIT 2016 + +#define SIXEL_FLAG_HSL 0x02000000 +#define SIXEL_FLAG_RGB 0x04000000 + +#define CN 3 +#define DIST_THRESHOLD 0.00001f + +typedef __int128 int128_t; +typedef unsigned __int128 uint128_t; + +#define GLYPH_COUNT 35 +static const wchar_t kRunes[] = { + u' ', /* 0020 empty block */ + u'█', /* 2588 full block */ + u'▄', /* 2584 lower half block */ + u'▀', /* 2580 upper half block */ + u'▐', /* 2590 right half block */ + u'▌', /* 258C left half block */ + u'▝', /* 259D quadrant upper right */ + u'▙', /* 2599 quadrant upper left and lower left and lower right */ + u'▗', /* 2597 quadrant lower right */ + u'▛', /* 259B quadrant upper left and upper right and lower left */ + u'▖', /* 2596 quadrant lower left */ + u'▜', /* 259C quadrant upper left and upper right and lower right */ + u'▘', /* 2598 quadrant upper left */ + u'▟', /* 259F quadrant upper right and lower left and lower right */ + u'▞', /* 259E quadrant upper right and lower left */ + u'▚', /* 259A quadrant upper left and lower right */ + u'▔', /* 2594 upper one eighth block */ + u'▁', /* 2581 lower one eighth block */ + u'▂', /* 2582 lower one quarter block */ + u'▃', /* 2583 lower three eighths block */ + u'▅', /* 2585 lower five eighths block */ + u'▆', /* 2586 lower three quarters block */ + u'▇', /* 2587 lower seven eighths block */ + u'▕', /* 2595 right one eight block */ + u'▏', /* 258F left one eight block */ + u'▎', /* 258E left one quarter block */ + u'▍', /* 258D left three eigths block */ + u'▋', /* 258B left five eigths block */ + u'▊', /* 258A left three quarters block */ + u'━', /* 2501 box drawings heavy horizontal */ + u'┉', /* 2509 box drawings heavy quadruple dash horizontal */ + u'┃', /* 2503 box drawings heavy vertical */ + u'╋', /* 254B box drawings heavy vertical & horiz. */ + u'╹', /* 2579 box drawings heavy up */ + u'╺', /* 257A box drawings heavy right */ + u'╻', /* 257B box drawings heavy down */ + u'╸', /* 2578 box drawings heavy left */ + u'┏', /* 250F box drawings heavy down and right */ + u'┛', /* 251B box drawings heavy up and left */ + u'┓', /* 2513 box drawings heavy down and left */ + u'┗', /* 2517 box drawings heavy up and right */ + u'◢', /* 25E2 black lower right triangle */ + u'◣', /* 25E3 black lower left triangle */ + u'◥', /* 25E4 black upper right triangle */ + u'◤', /* 25E5 black upper left triangle */ + // FIXME: add diagonals lines ,‘` /\ and less angleed: ╱╲ cf /╱╲\ + u'═', /* 2550 box drawings double horizontal */ + u'⎻', /* 23BB horizontal scan line 3 */ + u'⎼', /* 23BD horizontal scan line 9 */ +}; + + +#define RP4(B) ((((B) & 0xf0) >> 4 ) | ((B & 0x0f) << 4)) +#define RP2(B) ((((B) & 0xcc) >> 2 ) | ((B & 0x33) << 2)) +#define RP1(B) ((((B) & 0xaa) >> 1 ) | ((B & 0x55) << 1)) +#define R(B) RP1(RP2(RP4(B))) + +#define RRBB128( \ + A,B,C,D,E,F,G,H \ + , \ + I,J,K,L,M,N,O,P \ + ) ( \ + ((uint128_t)R(P)<<120) \ ++ ((uint128_t)R(O)<<112) \ ++ ((uint128_t)R(N)<<104) \ ++ ((uint128_t)R(M)<<96) \ ++ ((uint128_t)R(L)<<88) \ ++ ((uint128_t)R(K)<<80) \ ++ ((uint128_t)R(J)<<72) \ ++ ((uint128_t)R(I)<<64) \ ++ (( uint64_t)R(H)<<56) \ ++ (( uint64_t)R(G)<<48) \ ++ (( uint64_t)R(F)<<40) \ ++ (( uint64_t)R(E)<<32) \ ++ (( uint64_t)R(D)<<24) \ ++ (( uint64_t)R(C)<<16) \ ++ (( uint64_t)R(B)<<8) \ ++ ( uint64_t)R(A)) + + + +static const uint128_t kGlyphs128[] = /* clang-format off */ { + /* U+0020 ' ' empty block [ascii;200cp437;20] */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* U+2588 '█' full block [cp437] */ +RRBB128(0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2584 '▄' lower half block [cp437,dc] */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2580 '▀' upper half block [cp437] */ +RRBB128(0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + // Mode B + /* U+2590 '▐' right half block [cp437,de] */ +RRBB128(0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111), + /* U+258C '▌' left half block [cp437] */ +RRBB128(0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000), + /* U+259D '▝' quadrant upper right */ +RRBB128(0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* U+2599 '▙' quadrant upper left and lower left and lower right */ +RRBB128(0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2597 '▗' quadrant lower right */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111), + /* U+259B '▛' quadrant upper left and upper right and lower left */ +RRBB128(0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000), + /* U+2596 '▖' quadrant lower left */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000), + /* U+259C '▜' quadrant upper left and upper right and lower right */ +RRBB128(0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111), + /* U+2598 '▘' quadrant upper left */ +RRBB128(0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* U+259F '▟' quadrant upper right and lower left and lower right */ +RRBB128(0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+259E '▞' quadrant upper right and lower left */ +RRBB128(0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000), + /* U+259A '▚' quadrant upper left and lower right */ +RRBB128(0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001100, + 0b00001100), + // Mode C + /* U+2594 '▔' upper one eighth block */ +RRBB128(0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* U+2581 '▁' lower one eighth block */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111), + /* U+2582 '▂' lower one quarter block */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2583 '▃' lower three eighths block */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2585 '▃' lower five eighths block */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2586 '▆' lower three quarters block */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2587 '▇' lower seven eighths block */ +RRBB128(0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111), + /* U+2595 '▕' right one eight block */ +RRBB128(0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001, + 0b00000001), + /* U+258F '▏' left one eight block */ +RRBB128(0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000, + 0b10000000), + /* U+258E '▎' left one quarter block */ +RRBB128(0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000, + 0b11000000), + /* U+258D '▍' left three eigths block */ +RRBB128(0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000, + 0b11100000), + /* U+258B '▋' left five eigths block */ +RRBB128(0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000, + 0b11111000), + /* U+258A '▊' left three quarter block */ +RRBB128(0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100), + /* U+2589 '▉' left seven eights block */ +RRBB128(0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110, + 0b11111110), + /* ▁ *\ + 2501▕━▎box drawings heavy horizontal + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 25019▕┉▎box drawings heavy quadruple dash horizontal + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b01010101, + 0b01010101, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 2503▕┃▎box drawings heavy vertical + \* ▔ */ + // FIXME, X=9 would work better for that +RRBB128(0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000), + /* ▁ *\ + 254b▕╋▎box drawings heavy vertical and horizontal + \* ▔ */ +RRBB128(0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000), + /* ▁ *\ + 2579▕╹▎box drawings heavy up + \* ▔ */ +RRBB128(0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 257a▕╺▎box drawings heavy right + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00001111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 257b▕╻▎box drawings heavy down + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000), + /* ▁ *\ + 2578▕╸▎box drawings heavy left + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b11110000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 250f▕┏▎box drawings heavy down and right + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00111111, + 0b00111111, + 0b00111111, + 0b00111111, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000), + /* ▁ *\ + 251b▕┛▎box drawings heavy up and left + \* ▔ */ +RRBB128(0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 2513▕┓▎box drawings heavy down and left + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111100, + 0b11111100, + 0b11111100, + 0b11111100, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000), + /* ▁ *\ + 2517▕┗▎box drawings heavy up and right + \* ▔ */ +RRBB128(0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00011100, + 0b00111000, + 0b00111111, + 0b00111111, + 0b00111111, + 0b00111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 25E2▕◢▎black lower right triangle + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000001, + 0b00000011, + 0b00001111, + 0b00111111, + 0b01111111, + 0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 25E3▕◣▎black lower left triangle + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b10000000, + 0b11000000, + 0b11110000, + 0b11111100, + 0b11111110, + 0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 25E4▕◥▎black upper right triangle + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b01111111, + 0b00111111, + 0b00001111, + 0b00000011, + 0b00000001, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 25E5▕◤▎black upper left triangle + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b11111110, + 0b11111100, + 0b11110000, + 0b11000000, + 0b10000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + 2500▕═▎box drawings double horizontal + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11111111, + 0b00000000, + 0b00000000, + 0b11111111, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + TODO▕⎻▎horizontal scan line 3 + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11100000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000), + /* ▁ *\ + TODO▕⎼▎horizontal scan line 9 + \* ▔ */ +RRBB128(0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b11100000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000) +} /* clang-format on */; + + +static unsigned bsr(unsigned x) { +#if -__STRICT_ANSI__ + !!(__GNUC__ + 0) && (__i386__ + __x86_64__ + 0) + asm("bsr\t%1,%0" : "=r"(x) : "r"(x) : "cc"); +#else + static const unsigned char kDebruijn[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x = kDebruijn[(x * 0x07c4acddu) >> 27]; +#endif + return x; +} + +static unsigned long tpenc(wchar_t x) { + if (0x00 <= x && x <= 0x7f) { + return x; + } else { + static const struct ThomPike { + unsigned char len, mark; + } kThomPike[32 - 7] = { + {1, 0xc0}, {1, 0xc0}, {1, 0xc0}, {1, 0xc0}, {2, 0xe0}, + {2, 0xe0}, {2, 0xe0}, {2, 0xe0}, {2, 0xe0}, {3, 0xf0}, + {3, 0xf0}, {3, 0xf0}, {3, 0xf0}, {3, 0xf0}, {4, 0xf8}, + {4, 0xf8}, {4, 0xf8}, {4, 0xf8}, {4, 0xf8}, {5, 0xfc}, + {5, 0xfc}, {5, 0xfc}, {5, 0xfc}, {5, 0xfc}, {5, 0xfc}, + }; + wchar_t wc; + unsigned long ec; + struct ThomPike op; + ec = 0; + wc = x; + op = kThomPike[bsr(wc) - 7]; + do { + ec |= 0x3f & wc; + ec |= 0x80; + ec <<= 010; + wc >>= 006; + } while (--op.len); + return ec | wc | op.mark; + } +} + + +static char *tptoa(char *p, wchar_t x) { + unsigned long w; + for (w = tpenc(x); w; w >>= 010) *p++ = w & 0xff; + return p; +} + + +struct sixel_line { + u_int x; + uint16_t *data; +}; + +struct sixel_image { + u_int x; + u_int y; + u_int xpixel; + u_int ypixel; + + u_int *colours; + u_int ncolours; + + u_int dx; + u_int dy; + u_int dc; + + struct sixel_line *lines; +}; + +static int +sixel_parse_expand_lines(struct sixel_image *si, u_int y) +{ + if (y <= si->y) + return (0); + if (y > SIXEL_HEIGHT_LIMIT) + return (1); + si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines); + si->y = y; + return (0); +} + +static int +sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) +{ + if (x <= sl->x) + return (0); + if (x > SIXEL_WIDTH_LIMIT) + return (1); + if (x > si->x) + si->x = x; + sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data); + sl->x = si->x; + return (0); +} + +static u_int +sixel_get_pixel(struct sixel_image *si, u_int x, u_int y) +{ + struct sixel_line *sl; + + if (y >= si->y) + return (0); + sl = &si->lines[y]; + if (x >= sl->x) + return (0); + return (sl->data[x]); +} + +static int +sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c) +{ + struct sixel_line *sl; + + if (sixel_parse_expand_lines(si, y + 1) != 0) + return (1); + sl = &si->lines[y]; + if (sixel_parse_expand_line(si, sl, x + 1) != 0) + return (1); + sl->data[x] = c; + return (0); +} + +static int +sixel_parse_write(struct sixel_image *si, u_int ch) +{ + struct sixel_line *sl; + u_int i; + + if (sixel_parse_expand_lines(si, si->dy + 6) != 0) + return (1); + sl = &si->lines[si->dy]; + + for (i = 0; i < 6; i++) { + if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0) + return (1); + if (ch & (1 << i)) + sl->data[si->dx] = si->dc; + sl++; + } + return (0); +} + +static const char * +sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char *endptr; + u_int d, x, y; + + last = cp; + while (last != end) { + if (*last != ';' && (*last < '0' || *last > '9')) + break; + last++; + } + d = strtoul(cp, &endptr, 10); + if (endptr == last || *endptr != ';') + return (last); + d = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') + return (NULL); + + x = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') + return (NULL); + if (x > SIXEL_WIDTH_LIMIT) + return (NULL); + y = strtoul(endptr + 1, &endptr, 10); + if (endptr != last) + return (NULL); + if (y > SIXEL_HEIGHT_LIMIT) + return (NULL); + + si->x = x; + sixel_parse_expand_lines(si, y); + + return (last); +} + +static const char * +sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char *endptr; + u_int c, type, r, g, b; + + last = cp; + while (last != end) { + if (*last != ';' && (*last < '0' || *last > '9')) + break; + last++; + } + + c = strtoul(cp, &endptr, 10); + if (c > SIXEL_COLOUR_REGISTERS) + return (NULL); + si->dc = c + 1; + if (endptr == last || *endptr != ';') + return (last); + + type = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') + return (NULL); + r = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') + return (NULL); + g = strtoul(endptr + 1, &endptr, 10); + if (endptr == last || *endptr != ';') + return (NULL); + b = strtoul(endptr + 1, &endptr, 10); + if (endptr != last) + return (NULL); + + if (type != 1 && type != 2) + return (NULL); + if (c + 1 > si->ncolours) { + si->colours = xrecallocarray(si->colours, si->ncolours, c + 1, + sizeof *si->colours); + si->ncolours = c + 1; + } + + /* In HLS, the hue is in [0;360[ so it is stored on 9 bits */ + si->colours[c] = (type << 25) | (r << 16) | (g << 8) | b; + return (last); +} + +static const char * +sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) +{ + const char *last; + char tmp[32], ch; + u_int n = 0, i; + const char *errstr = NULL; + + last = cp; + while (last != end) { + if (*last < '0' || *last > '9') + break; + tmp[n++] = *last++; + if (n == (sizeof tmp) - 1) + return (NULL); + } + if (n == 0 || last == end) + return (NULL); + tmp[n] = '\0'; + + n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr); + if (n == 0 || errstr != NULL) + return (NULL); + + ch = (*last++) - 0x3f; + for (i = 0; i < n; i++) { + if (sixel_parse_write(si, ch) != 0) + return (NULL); + si->dx++; + } + return (last); +} + +struct sixel_image * +sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel) +{ + struct sixel_image *si; + const char *cp = buf, *end = buf + len; + char ch; + + if (len == 0 || len == 1 || *cp++ != 'q') + return (NULL); + + si = xcalloc (1, sizeof *si); + si->xpixel = xpixel; + si->ypixel = ypixel; + + while (cp != end) { + ch = *cp++; + switch (ch) { + case '"': + cp = sixel_parse_attributes(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '#': + cp = sixel_parse_colour(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '!': + cp = sixel_parse_repeat(si, cp, end); + if (cp == NULL) + goto bad; + break; + case '-': + si->dx = 0; + si->dy += 6; + break; + case '$': + si->dx = 0; + break; + default: + if (ch < 0x20) + break; + if (ch < 0x3f || ch > 0x7e) + goto bad; + if (sixel_parse_write(si, ch - 0x3f) != 0) + goto bad; + si->dx++; + break; + } + } + + if (si->x == 0 || si->y == 0) + goto bad; + return (si); + +bad: + free(si); + return (NULL); +} + +void +sixel_free(struct sixel_image *si) +{ + u_int y; + + for (y = 0; y < si->y; y++) + free(si->lines[y].data); + free(si->lines); + + free(si->colours); + free(si); +} + +void +sixel_log(struct sixel_image *si) +{ + struct sixel_line *sl; + char s[SIXEL_WIDTH_LIMIT + 1]; + u_int i, x, y, cx, cy; + + sixel_size_in_cells(si, &cx, &cy); + log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy); + for (i = 0; i < si->ncolours; i++) + log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]); + for (y = 0; y < si->y; y++) { + sl = &si->lines[y]; + for (x = 0; x < si->x; x++) { + if (x >= sl->x) + s[x] = '_'; + else if (sl->data[x] != 0) + s[x] = '0' + (sl->data[x] - 1) % 10; + else + s[x] = '.'; + } + s[x] = '\0'; + log_debug("%s: %4u: %s", __func__, y, s); + } +} + +void +sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) +{ + if ((si->x % si->xpixel) == 0) + *x = (si->x / si->xpixel); + else + *x = 1 + (si->x / si->xpixel); + if ((si->y % si->ypixel) == 0) + *y = (si->y / si->ypixel); + else + *y = 1 + (si->y / si->ypixel); +} + +struct sixel_image * +sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, + u_int oy, u_int sx, u_int sy) +{ + struct sixel_image *new; + u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; + u_int x, y; + + /* + * We want to get the section of the image at ox,oy in image cells and + * map it onto the same size in terminal cells, remembering that we + * can only draw vertical sections of six pixels. + */ + + sixel_size_in_cells(si, &cx, &cy); + if (ox >= cx) + return (NULL); + if (oy >= cy) + return (NULL); + if (ox + sx >= cx) + sx = cx - ox; + if (oy + sy >= cy) + sy = cy - oy; + + pox = ox * si->xpixel; + poy = oy * si->ypixel; + psx = sx * si->xpixel; + psy = sy * si->ypixel; + + tsx = sx * xpixel; + tsy = ((sy * ypixel) / 6) * 6; + + new = xcalloc (1, sizeof *si); + new->xpixel = xpixel; + new->ypixel = ypixel; + + for (y = 0; y < tsy; y++) { + py = poy + ((double)y * psy / tsy); + for (x = 0; x < tsx; x++) { + px = pox + ((double)x * psx / tsx); + sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py)); + } + } + return (new); +} + +static void +sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, + size_t slen) +{ + if (*used + slen >= *len + 1) { + (*len) *= 2; + *buf = xrealloc(*buf, *len); + } + memcpy(*buf + *used, s, slen); + (*used) += slen; +} + +static void +sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) +{ + char tmp[16]; + size_t tmplen; + + if (count == 1) + sixel_print_add(buf, len, used, &ch, 1); + else if (count == 2) { + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + } else if (count == 3) { + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + sixel_print_add(buf, len, used, &ch, 1); + } else if (count != 0) { + tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch); + sixel_print_add(buf, len, used, tmp, tmplen); + } +} + +char * +sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) +{ + char *buf, tmp[64], *contains, data, last; + size_t len, used = 0, tmplen; + u_int *colours, ncolours, i, c, x, y, count; + struct sixel_line *sl; + + if (map != NULL) { + colours = map->colours; + ncolours = map->ncolours; + } else { + colours = si->colours; + ncolours = si->ncolours; + } + contains = xcalloc(1, ncolours); + + len = 8192; + buf = xmalloc(len); + + sixel_print_add(&buf, &len, &used, "\033Pq", 3); + + tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + + for (i = 0; i < ncolours; i++) { + c = colours[i]; + tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", + i, c >> 25, (c >> 16) & 0x1ff, (c >> 8) & 0xff, c & 0xff); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + } + + for (y = 0; y < si->y; y += 6) { + memset(contains, 0, ncolours); + for (x = 0; x < si->x; x++) { + for (i = 0; i < 6; i++) { + if (y + i >= si->y) + break; + sl = &si->lines[y + i]; + if (x < sl->x && sl->data[x] != 0) + contains[sl->data[x] - 1] = 1; + } + } + + for (c = 0; c < ncolours; c++) { + if (!contains[c]) + continue; + tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); + sixel_print_add(&buf, &len, &used, tmp, tmplen); + + count = 0; + for (x = 0; x < si->x; x++) { + data = 0; + for (i = 0; i < 6; i++) { + if (y + i >= si->y) + break; + sl = &si->lines[y + i]; + if (x < sl->x && sl->data[x] == c + 1) + data |= (1 << i); + } + data += 0x3f; + if (data != last) { + sixel_print_repeat(&buf, &len, &used, + count, last); + last = data; + count = 1; + } else + count++; + } + sixel_print_repeat(&buf, &len, &used, count, data); + sixel_print_add(&buf, &len, &used, "$", 1); + } + + if (buf[used - 1] == '$') + used--; + if (buf[used - 1] != '-') + sixel_print_add(&buf, &len, &used, "-", 1); + } + if (buf[used - 1] == '$' || buf[used - 1] == '-') + used--; + + sixel_print_add(&buf, &len, &used, "\033\\", 2); + + buf[used] = '\0'; + if (size != NULL) + *size = used; + + free(contains); + return (buf); +} + +#define FLOAT_C(X) X##f + +static float pow24(float x) { + float x2, x3, x4; + x2 = x * x; + x3 = x * x * x; + x4 = x * x * x * x; + return FLOAT_C(0.0985766365536824) + FLOAT_C(0.839474952656502) * x2 + + FLOAT_C(0.363287814061725) * x3 - + FLOAT_C(0.0125559718896615) / + (FLOAT_C(0.12758338921578) + FLOAT_C(0.290283465468235) * x) - + FLOAT_C(0.231757513261358) * x - FLOAT_C(0.0395365717969074) * x4; +} + +static float frgb2linl(float x) +{ + float r1, r2; + r1 = x / FLOAT_C(12.92); + r2 = pow24((x + FLOAT_C(0.055)) / (FLOAT_C(1.0) + FLOAT_C(0.055))); + return x < FLOAT_C(0.04045) ? r1 : r2; +} + +static void rgb2lin(float *f, const unsigned char *u, u_int size) +{ + unsigned i; + for (i = 0; i < size; ++i) f[i] = u[i]; + for (i = 0; i < size; ++i) f[i] /= FLOAT_C(255.0); + for (i = 0; i < size; ++i) f[i] = frgb2linl(f[i]); +} + +static float adjudicate(u_int glyph_idx, float bg[CN], float fg[CN], const float *lb, u_int gs, float bestSoFar) { + uint128_t glyph_data = kGlyphs128[glyph_idx]; + float dist_sq = 0.0f; + + for (u_int c = 0; c < CN; c++) { + float fg_chan = fg[c]; + float bg_chan = bg[c]; + float *this_chan = lb + c * gs; + + for (u_int i = 0; i < gs; i++) { + uint128_t one = 1; + uint128_t is_fg = (glyph_data & (one << i)) ? 1 : 0; + float diff = (is_fg ? fg_chan : bg_chan) - this_chan[i]; + dist_sq += diff*diff; + } + if (dist_sq >= bestSoFar) + return dist_sq; + } + return dist_sq; +} + +u_int sixel_derasterize_block(unsigned char *rgb_block, u_int gs, unsigned char *picked_fg, unsigned char *picked_bg) { + float best, lin_block[CN * gs]; + u_int picked_glyph; + u_int temp_b[CN], temp_f[CN]; + unsigned char b[CN], f[CN]; + float bf[CN], ff[CN]; + + best = -1u; + rgb2lin(lin_block, rgb_block, CN * gs); + + for (u_int glyph_idx = 0; glyph_idx < GLYPH_COUNT; glyph_idx++) { + memset(temp_b, 0, sizeof(temp_b)); + memset(temp_f, 0, sizeof(temp_f)); + memset(b, 0, sizeof(b)); + memset(f, 0, sizeof(f)); + + + /* Compute the best background/foreground color to use with the currently tried glyph */ + int ones = 0; + for (u_int i = 0; i < gs; i++) { + uint128_t one = 1; + if (kGlyphs128[glyph_idx] & (one << i)) { + for (u_int c = 0; c < CN; c++) { + temp_f[c] += rgb_block[c * gs + i]; + } + ones++; + } else { + for (u_int c = 0; c < CN; c++) { + temp_b[c] += rgb_block[c * gs + i]; + } + } + } + + if (ones) { + for (u_int c = 0; c < CN; c++) { + f[c] = temp_f[c] / ones; + } + } + + if (ones < gs) { + for (u_int c = 0; c < CN; c++) { + b[c] = temp_b[c] / (gs - ones); + } + } + + rgb2lin(bf, b, CN); + rgb2lin(ff, f, CN); + + float r = adjudicate(glyph_idx, bf, ff, lin_block, gs, best); + if (r < best) { + best = r; + picked_glyph = glyph_idx; + memcpy(picked_fg, f, CN); + memcpy(picked_bg, b, CN); +// if (best < DIST_THRESHOLD) return picked_glyph; + } + } + return picked_glyph; +} + +struct screen * +sixel_to_screen(struct sixel_image *si) +{ + u_int gx = 8, gy = 16; /* TODO */ + struct screen *s; + struct screen_write_ctx ctx; + u_int cell_, cell_y, sx, sy; + unsigned char *rgbdata = NULL; + u_int rgbdata_size; + + sixel_size_in_cells(si, &sx, &sy); + + s = xmalloc(sizeof *s); + screen_init(s, sx, sy, 0); + + + screen_write_start(&ctx, NULL, s); + + rgbdata_size = CN * sx * sy * gx * gy; + rgbdata = xmalloc(rgbdata_size); + memset(rgbdata, 0, rgbdata_size); + + u_int *colours = xmalloc(si->ncolours * sizeof(u_int)); + /* Convert HSL to RGB if necessary */ + for (u_int k = 0; k < si->ncolours; k++) { + u_int R = 0; + u_int G = 0; + u_int B = 0; + u_int flag = SIXEL_FLAG_RGB; + if (si->colours[k] & SIXEL_FLAG_HSL) { + + /* sixel HSL format is in fact 0xHHLLSS and H=0 is blue */ + float H = ((si->colours[k] >> 16) & 0x1FF); + H -= 120.0; + if (H < 0.0) + H += 360.0; + float L = ((si->colours[k] >> 8) & 0xFF) / 100.0; + float S = (si->colours[k] & 0xFF) / 100.0; + + float C = (1.0 - fabs(2*L - 1.0)) * S; /* range [0;10000] */ + float X = C * (1.0 - fabs(fmodf((H / 60.0), 2.0) - 1)); + float m = L - C/2; + + float _R, _G, _B; + if (H < 60) { + _R = C; + _G = X; + _B = 0; + } else if (H < 120) { + _R = X; + _G = C; + _B = 0; + } else if (H < 180) { + _R = 0; + _G = C; + _B = X; + } else if (H < 240) { + _R = 0; + _G = X; + _B = C; + } else if (H < 300) { + _R = X; + _G = 0; + _B = C; + } else { /* H < 360 */ + _R = C; + _G = 0; + _B = X; + } + + R = (_R + m) * 255; + G = (_G + m) * 255; + B = (_B + m) * 255; + flag = SIXEL_FLAG_HSL; + } else if (si->colours[k] & SIXEL_FLAG_RGB) { + flag = SIXEL_FLAG_RGB; + R = (((si->colours[k] >> 16) & 0xFF) * 255) / 100; + G = (((si->colours[k] >> 8) & 0xFF) * 255) / 100; + B = ((si->colours[k] & 0xFF) * 255) / 100; + } + colours[k] = flag | (R << 16) | (G << 8) | B; + } + + /* resample sixel rgbdata (nearest-neighbor) */ + for (u_int c = 0; c < CN; c++) { + for (u_int x = 0; x < sx * gx; x++) { + for (u_int y = 0; y < sy * gy; y++) { + u_int source_x = (x * si->xpixel) / gx; + u_int source_y = (y * si->ypixel) / gy; + + if ((source_x >= si->x) || (source_y >= si->y)) continue; + u_int source_colour = si->lines[source_y].data[source_x]; + + if ((source_colour <= 0) || (source_colour > si->ncolours)) continue; + + rgbdata[c * sx * sy * gx * gy + y * sx * gx + x] = (colours[source_colour - 1] >> (c * 8)) & 0xFF; + } + } + } + + free(colours); + + /* derasterize */ + unsigned char *rgb_block = xmalloc(CN * gx * gy); + for (u_int cell_x = 0; cell_x < sx; cell_x++) { + for (u_int cell_y = 0; cell_y < sy; cell_y++) { + + /* extract gx x gy block from rgbdata */ + for (u_int c = 0; c < CN; c++) { + for (u_int y = 0; y < gy; y++) { + memcpy(rgb_block + c * gx * gy + y * gx, rgbdata + c * sx * sy * gx * gy + (cell_y * gy + y)* sx * gx + cell_x * gx, gx); + } + } + + /* pick glyph and bg/fg color closest to rgb block */ + unsigned char fg[CN]; + unsigned char bg[CN]; + u_int glyph_idx = sixel_derasterize_block(rgb_block, gx * gy, fg, bg); + + /* build grid cell corresponding to glyph and colors */ + struct grid_cell gc; + memcpy(&gc, &grid_default_cell, sizeof gc); + int utf8_size; + char utf8[8]; /* even though utf8 max size is 4, tptoa documentation requires a 8 bytes buffer */ + char *end = tptoa(utf8, kRunes[glyph_idx]); + for (char *ptr = utf8; ptr != end; ptr++) { + if (ptr == utf8) { + if (ptr + 1 == end) { + utf8_set(&gc.data, *ptr); + } else { + utf8_open(&gc.data, *ptr); + } + } else { + utf8_append(&gc.data, *ptr); + } + } + gc.fg = COLOUR_FLAG_RGB; + gc.bg = COLOUR_FLAG_RGB; + for (u_int c = 0; c < CN; c++) { + gc.fg |= fg[c] << (c * 8); + gc.bg |= bg[c] << (c * 8); + } + grid_view_set_cell(s->grid, cell_x, cell_y, &gc); + } + } + + screen_write_stop(&ctx); + + free(rgb_block); + free(rgbdata); + + return (s); +} diff --git a/spawn.c b/spawn.c new file mode 100644 index 0000000000..5402181779 --- /dev/null +++ b/spawn.c @@ -0,0 +1,453 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 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" + +/* + * Set up the environment and create a new window and pane or a new pane. + * + * We need to set up the following items: + * + * - history limit, comes from the session; + * + * - base index, comes from the session; + * + * - current working directory, may be specified - if it isn't it comes from + * either the client or the session; + * + * - PATH variable, comes from the client if any, otherwise from the session + * environment; + * + * - shell, comes from default-shell; + * + * - termios, comes from the session; + * + * - remaining environment, comes from the session. + */ + +static void +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; + char tmp[128]; + const char *name; + + log_debug("%s: %s, flags=%#x", from, sc->item->name, sc->flags); + + if (wl != NULL && wp0 != NULL) + xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id); + else if (wl != NULL) + xsnprintf(tmp, sizeof tmp, "wl=%d wp0=none", wl->idx); + else if (wp0 != NULL) + xsnprintf(tmp, sizeof tmp, "wl=none wp0=%%%u", wp0->id); + 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); +} + +struct winlink * +spawn_window(struct spawn_context *sc, char **cause) +{ + struct session *s = sc->s; + struct window *w; + struct window_pane *wp; + struct winlink *wl; + int idx = sc->idx; + u_int sx, sy, xpixel, ypixel; + + spawn_log(__func__, sc); + + /* + * If the window already exists, we are respawning, so destroy all the + * panes except one. + */ + if (sc->flags & SPAWN_RESPAWN) { + w = sc->wl->window; + if (~sc->flags & SPAWN_KILL) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->fd != -1) + break; + } + if (wp != NULL) { + xasprintf(cause, "window %s:%d still active", + s->name, sc->wl->idx); + return (NULL); + } + } + + sc->wp0 = TAILQ_FIRST(&w->panes); + TAILQ_REMOVE(&w->panes, sc->wp0, entry); + + layout_free(w); + window_destroy_panes(w); + + TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry); + window_pane_resize(sc->wp0, w->sx, w->sy); + + layout_init(w, sc->wp0); + window_set_active_pane(w, sc->wp0, 0); + } + + /* + * Otherwise we have no window so we will need to create one. First + * check if the given index already exists and destroy it if so. + */ + if ((~sc->flags & SPAWN_RESPAWN) && idx != -1) { + wl = winlink_find_by_index(&s->windows, idx); + if (wl != NULL && (~sc->flags & SPAWN_KILL)) { + xasprintf(cause, "index %d in use", idx); + return (NULL); + } + if (wl != NULL) { + /* + * Can't use session_detach as it will destroy session + * if this makes it empty. + */ + wl->flags &= ~WINLINK_ALERTFLAGS; + notify_session_window("window-unlinked", s, wl->window); + winlink_stack_remove(&s->lastw, wl); + winlink_remove(&s->windows, wl); + + if (s->curw == wl) { + s->curw = NULL; + sc->flags &= ~SPAWN_DETACHED; + } + } + } + + /* Then create a window if needed. */ + if (~sc->flags & SPAWN_RESPAWN) { + if (idx == -1) + idx = -1 - options_get_number(s->options, "base-index"); + if ((sc->wl = winlink_add(&s->windows, idx)) == NULL) { + xasprintf(cause, "couldn't add window %d", idx); + return (NULL); + } + default_window_size(sc->c, s, NULL, &sx, &sy, &xpixel, &ypixel, + -1); + if ((w = window_create(sx, sy, xpixel, ypixel)) == NULL) { + winlink_remove(&s->windows, sc->wl); + xasprintf(cause, "couldn't create window %d", idx); + return (NULL); + } + if (s->curw == NULL) + s->curw = sc->wl; + sc->wl->session = s; + w->latest = sc->c; + winlink_set_window(sc->wl, w); + } else + w = NULL; + sc->flags |= SPAWN_NONOTIFY; + + /* Spawn the pane. */ + wp = spawn_pane(sc, cause); + if (wp == NULL) { + if (~sc->flags & SPAWN_RESPAWN) + winlink_remove(&s->windows, sc->wl); + return (NULL); + } + + /* Set the name of the new window. */ + if (~sc->flags & SPAWN_RESPAWN) { + if (sc->name != NULL) { + w->name = xstrdup(sc->name); + options_set_number(w->options, "automatic-rename", 0); + } else + w->name = xstrdup(default_window_name(w)); + } + + /* Switch to the new window if required. */ + if (~sc->flags & SPAWN_DETACHED) + session_select(s, sc->wl->idx); + + /* Fire notification if new window. */ + if (~sc->flags & SPAWN_RESPAWN) + notify_session_window("window-linked", s, w); + + session_group_synchronize_from(s); + return (sc->wl); +} + +struct window_pane * +spawn_pane(struct spawn_context *sc, char **cause) +{ + struct cmdq_item *item = sc->item; + struct client *c = item->client; + struct session *s = sc->s; + struct window *w = sc->wl->window; + struct window_pane *new_wp; + struct environ *child; + struct environ_entry *ee; + char **argv, *cp, **argvp, *argv0, *cwd; + const char *cmd, *tmp; + int argc; + u_int idx; + struct termios now; + u_int hlimit; + struct winsize ws; + sigset_t set, oldset; + key_code key; + + spawn_log(__func__, sc); + + /* + * If we are respawning then get rid of the old process. Otherwise + * either create a new cell or assign to the one we are given. + */ + hlimit = options_get_number(s->options, "history-limit"); + if (sc->flags & SPAWN_RESPAWN) { + if (sc->wp0->fd != -1 && (~sc->flags & SPAWN_KILL)) { + window_pane_index(sc->wp0, &idx); + xasprintf(cause, "pane %s:%d.%u still active", + s->name, sc->wl->idx, idx); + return (NULL); + } + if (sc->wp0->fd != -1) { + bufferevent_free(sc->wp0->event); + close(sc->wp0->fd); + } + window_pane_reset_mode_all(sc->wp0); + screen_reinit(&sc->wp0->base); + input_init(sc->wp0); + new_wp = sc->wp0; + new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); + } else if (sc->lc == NULL) { + new_wp = window_add_pane(w, NULL, hlimit, sc->flags); + layout_init(w, new_wp); + } else { + new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags); + layout_assign_pane(sc->lc, new_wp); + } + + /* + * Now we have a pane with nothing running in it ready for the new + * process. Work out the command and arguments. + */ + if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) { + cmd = options_get_string(s->options, "default-command"); + if (cmd != NULL && *cmd != '\0') { + argc = 1; + argv = (char **)&cmd; + } else { + argc = 0; + argv = NULL; + } + } else { + argc = sc->argc; + argv = sc->argv; + } + + /* + * Replace the stored arguments if there are new ones. If not, the + * existing ones will be used (they will only exist for respawn). + */ + if (argc > 0) { + cmd_free_argv(new_wp->argc, new_wp->argv); + new_wp->argc = argc; + new_wp->argv = cmd_copy_argv(argc, argv); + } + + /* + * Work out the current working directory. If respawning, use + * the pane's stored one unless specified. + */ + if (sc->cwd != NULL) + cwd = format_single(item, sc->cwd, c, s, NULL, NULL); + else if (~sc->flags & SPAWN_RESPAWN) + cwd = xstrdup(server_client_get_cwd(c, s)); + else + cwd = NULL; + if (cwd != NULL) { + free(new_wp->cwd); + new_wp->cwd = cwd; + } + + /* Create an environment for this pane. */ + child = environ_for_session(s, 0); + if (sc->environ != NULL) + environ_copy(sc->environ, child); + environ_set(child, "TMUX_PANE", "%%%u", new_wp->id); + + /* + * Then the PATH environment variable. The session one is replaced from + * the client if there is one because otherwise running "tmux new + * myprogram" wouldn't work if myprogram isn't in the session's path. + */ + 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); + } + if (environ_find(child, "PATH") == NULL) + environ_set(child, "%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)) + tmp = _PATH_BSHELL; + free(new_wp->shell); + new_wp->shell = xstrdup(tmp); + } + environ_set(child, "SHELL", "%s", new_wp->shell); + + /* Log the arguments we are going to use. */ + log_debug("%s: shell=%s", __func__, new_wp->shell); + if (new_wp->argc != 0) { + cp = cmd_stringify_argv(new_wp->argc, new_wp->argv); + log_debug("%s: cmd=%s", __func__, cp); + free(cp); + } + if (cwd != NULL) + log_debug("%s: cwd=%s", __func__, cwd); + cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__); + environ_log(child, "%s: environment ", __func__); + + /* Initialize the window size. */ + memset(&ws, 0, sizeof ws); + ws.ws_col = screen_size_x(&new_wp->base); + ws.ws_row = screen_size_y(&new_wp->base); + ws.ws_xpixel = w->xpixel * ws.ws_col; + ws.ws_ypixel = w->ypixel * ws.ws_row; + + /* Block signals until fork has completed. */ + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, &oldset); + + /* If the command is empty, don't fork a child process. */ + if (sc->flags & SPAWN_EMPTY) { + new_wp->flags |= PANE_EMPTY; + new_wp->base.mode &= ~MODE_CURSOR; + new_wp->base.mode |= MODE_CRLF; + goto complete; + } + + /* Fork the new process. */ + new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws); + if (new_wp->pid == -1) { + xasprintf(cause, "fork failed: %s", strerror(errno)); + new_wp->fd = -1; + if (~sc->flags & SPAWN_RESPAWN) { + layout_close_pane(new_wp); + window_remove_pane(w, new_wp); + } + sigprocmask(SIG_SETMASK, &oldset, NULL); + return (NULL); + } + + /* In the parent process, everything is done now. */ + if (new_wp->pid != 0) + goto complete; + + /* + * 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("/"); + } + + /* + * Update terminal escape characters from the session if available and + * force VERASE to tmux's backspace. + */ + if (tcgetattr(STDIN_FILENO, &now) != 0) + _exit(1); + if (s->tio != NULL) + memcpy(now.c_cc, s->tio->c_cc, sizeof now.c_cc); + key = options_get_number(global_options, "backspace"); + if (key >= 0x7f) + now.c_cc[VERASE] = '\177'; + else + now.c_cc[VERASE] = key; + if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0) + _exit(1); + + /* Clean up file descriptors and signals and update the environment. */ + closefrom(STDERR_FILENO + 1); + proc_clear_signals(server_proc, 1); + sigprocmask(SIG_SETMASK, &oldset, NULL); + log_close(); + environ_push(child); + + /* + * If given multiple arguments, use execvp(). Copy the arguments to + * ensure they end in a NULL. + */ + if (new_wp->argc != 0 && new_wp->argc != 1) { + argvp = cmd_copy_argv(new_wp->argc, new_wp->argv); + execvp(argvp[0], argvp); + _exit(1); + } + + /* + * If one argument, pass it to $SHELL -c. Otherwise create a login + * shell. + */ + cp = strrchr(new_wp->shell, '/'); + if (new_wp->argc == 1) { + tmp = new_wp->argv[0]; + if (cp != NULL && cp[1] != '\0') + xasprintf(&argv0, "%s", cp + 1); + else + xasprintf(&argv0, "%s", new_wp->shell); + execl(new_wp->shell, argv0, "-c", tmp, (char *)NULL); + _exit(1); + } + if (cp != NULL && cp[1] != '\0') + xasprintf(&argv0, "-%s", cp + 1); + else + xasprintf(&argv0, "-%s", new_wp->shell); + execl(new_wp->shell, argv0, (char *)NULL); + _exit(1); + +complete: + new_wp->pipe_off = 0; + new_wp->flags &= ~PANE_EXITED; + + sigprocmask(SIG_SETMASK, &oldset, NULL); + window_pane_set_event(new_wp); + + if (sc->flags & SPAWN_RESPAWN) + return (new_wp); + if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { + if (sc->flags & SPAWN_NONOTIFY) + window_set_active_pane(w, new_wp, 0); + else + window_set_active_pane(w, new_wp, 1); + } + if (~sc->flags & SPAWN_NONOTIFY) + notify_window("window-layout-changed", w); + return (new_wp); +} diff --git a/status.c b/status.c index b7d2471504..0f96f0d393 100644 --- a/status.c +++ b/status.c @@ -29,14 +29,6 @@ #include "tmux.h" -static char *status_redraw_get_left(struct client *, time_t, - struct grid_cell *, size_t *); -static char *status_redraw_get_right(struct client *, time_t, - struct grid_cell *, size_t *); -static char *status_print(struct client *, struct winlink *, time_t, - struct grid_cell *); -static char *status_replace(struct client *, struct winlink *, const char *, - time_t); static void status_message_callback(int, short, void *); static void status_timer_callback(int, short, void *); @@ -45,8 +37,8 @@ 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_complete_list(u_int *, const char *); -static char *status_prompt_complete_prefix(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 *); /* Status prompt history. */ @@ -151,19 +143,19 @@ status_timer_callback(__unused int fd, __unused short events, void *arg) struct session *s = c->session; struct timeval tv; - evtimer_del(&c->status_timer); + evtimer_del(&c->status.timer); if (s == NULL) return; if (c->message_string == NULL && c->prompt_string == NULL) - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; timerclear(&tv); tv.tv_sec = options_get_number(s->options, "status-interval"); if (tv.tv_sec != 0) - evtimer_add(&c->status_timer, &tv); + evtimer_add(&c->status.timer, &tv); log_debug("client %p, status interval %d", c, (int)tv.tv_sec); } @@ -173,10 +165,10 @@ status_timer_start(struct client *c) { struct session *s = c->session; - if (event_initialized(&c->status_timer)) - evtimer_del(&c->status_timer); + if (event_initialized(&c->status.timer)) + evtimer_del(&c->status.timer); else - evtimer_set(&c->status_timer, status_timer_callback, c); + evtimer_set(&c->status.timer, status_timer_callback, c); if (s != NULL && options_get_number(s->options, "status")) status_timer_callback(-1, 0, c); @@ -194,9 +186,10 @@ status_timer_start_all(void) /* Update status cache. */ void -status_update_saved(struct session *s) +status_update_cache(struct session *s) { - if (!options_get_number(s->options, "status")) + s->statuslines = options_get_number(s->options, "status"); + if (s->statuslines == 0) s->statusat = -1; else if (options_get_number(s->options, "status-position") == 0) s->statusat = 0; @@ -210,359 +203,208 @@ status_at_line(struct client *c) { struct session *s = c->session; + if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) + return (-1); if (s->statusat != 1) return (s->statusat); - return (c->tty.sy - 1); + return (c->tty.sy - status_line_size(c)); } -/* Retrieve options for left string. */ -static char * -status_redraw_get_left(struct client *c, time_t t, struct grid_cell *gc, - size_t *size) +/* Get size of status line for client's session. 0 means off. */ +u_int +status_line_size(struct client *c) { struct session *s = c->session; - const char *template; - char *left; - size_t leftlen; - - style_apply_update(gc, s->options, "status-left-style"); - - template = options_get_string(s->options, "status-left"); - left = status_replace(c, NULL, template, t); - *size = options_get_number(s->options, "status-left-length"); - leftlen = screen_write_cstrlen("%s", left); - if (leftlen < *size) - *size = leftlen; - return (left); + if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) + return (0); + return (s->statuslines); } -/* Retrieve options for right string. */ -static char * -status_redraw_get_right(struct client *c, time_t t, struct grid_cell *gc, - size_t *size) +/* Get window at window list position. */ +struct style_range * +status_get_range(struct client *c, u_int x, u_int y) { - struct session *s = c->session; - const char *template; - char *right; - size_t rightlen; - - style_apply_update(gc, s->options, "status-right-style"); - - template = options_get_string(s->options, "status-right"); - right = status_replace(c, NULL, template, t); + struct status_line *sl = &c->status; + struct style_range *sr; - *size = options_get_number(s->options, "status-right-length"); - rightlen = screen_write_cstrlen("%s", right); - if (rightlen < *size) - *size = rightlen; - return (right); + if (y >= nitems(sl->entries)) + return (NULL); + TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { + if (x >= sr->start && x < sr->end) + return (sr); + } + return (NULL); } -/* Get window at window list position. */ -struct window * -status_get_window_at(struct client *c, u_int x) +/* Free all ranges. */ +static void +status_free_ranges(struct style_ranges *srs) { - struct session *s = c->session; - struct winlink *wl; - struct options *oo; - const char *sep; - size_t seplen; - - x += c->wlmouse; - RB_FOREACH(wl, winlinks, &s->windows) { - oo = wl->window->options; + struct style_range *sr, *sr1; - sep = options_get_string(oo, "window-status-separator"); - seplen = screen_write_cstrlen("%s", sep); - - if (x < wl->status_width) - return (wl->window); - x -= wl->status_width + seplen; + TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { + TAILQ_REMOVE(srs, sr, entry); + free(sr); } - return (NULL); } -/* Draw status for client on the last lines of given context. */ -int -status_redraw(struct client *c) +/* Save old status line. */ +static void +status_push_screen(struct client *c) { - struct screen_write_ctx ctx; - struct session *s = c->session; - struct winlink *wl; - struct screen old_status, window_list; - struct grid_cell stdgc, lgc, rgc, gc; - struct options *oo; - time_t t; - char *left, *right; - const char *sep; - u_int offset, needed; - u_int wlstart, wlwidth, wlavailable, wloffset, wlsize; - size_t llen, rlen, seplen; - int larrow, rarrow; - - /* Delete the saved status line, if any. */ - if (c->old_status != NULL) { - screen_free(c->old_status); - free(c->old_status); - c->old_status = NULL; + struct status_line *sl = &c->status; + + if (sl->active == &sl->screen) { + sl->active = xmalloc(sizeof *sl->active); + screen_init(sl->active, c->tty.sx, status_line_size(c), 0); } + sl->references++; +} - /* No status line? */ - if (c->tty.sy == 0 || !options_get_number(s->options, "status")) - return (1); - left = right = NULL; - larrow = rarrow = 0; +/* Restore old status line. */ +static void +status_pop_screen(struct client *c) +{ + struct status_line *sl = &c->status; - /* Store current time. */ - t = time(NULL); + if (--sl->references == 0) { + screen_free(sl->active); + free(sl->active); + sl->active = &sl->screen; + } +} - /* Set up default colour. */ - style_apply(&stdgc, s->options, "status-style"); +/* Initialize status line. */ +void +status_init(struct client *c) +{ + struct status_line *sl = &c->status; + u_int i; - /* Create the target screen. */ - memcpy(&old_status, &c->status, sizeof old_status); - screen_init(&c->status, c->tty.sx, 1, 0); - screen_write_start(&ctx, NULL, &c->status); - for (offset = 0; offset < c->tty.sx; offset++) - screen_write_putc(&ctx, &stdgc, ' '); - screen_write_stop(&ctx); + for (i = 0; i < nitems(sl->entries); i++) + TAILQ_INIT(&sl->entries[i].ranges); - /* If the height is one line, blank status line. */ - if (c->tty.sy <= 1) - goto out; + screen_init(&sl->screen, c->tty.sx, 1, 0); + sl->active = &sl->screen; +} - /* Work out left and right strings. */ - memcpy(&lgc, &stdgc, sizeof lgc); - left = status_redraw_get_left(c, t, &lgc, &llen); - memcpy(&rgc, &stdgc, sizeof rgc); - right = status_redraw_get_right(c, t, &rgc, &rlen); +/* Free status line. */ +void +status_free(struct client *c) +{ + struct status_line *sl = &c->status; + u_int i; - /* - * Figure out how much space we have for the window list. If there - * isn't enough space, just show a blank status line. - */ - needed = 0; - if (llen != 0) - needed += llen; - if (rlen != 0) - needed += rlen; - if (c->tty.sx == 0 || c->tty.sx <= needed) - goto out; - wlavailable = c->tty.sx - needed; - - /* Calculate the total size needed for the window list. */ - wlstart = wloffset = wlwidth = 0; - RB_FOREACH(wl, winlinks, &s->windows) { - free(wl->status_text); - memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); - wl->status_text = status_print(c, wl, t, &wl->status_cell); - wl->status_width = screen_write_cstrlen("%s", wl->status_text); - - if (wl == s->curw) - wloffset = wlwidth; - - oo = wl->window->options; - sep = options_get_string(oo, "window-status-separator"); - seplen = screen_write_cstrlen("%s", sep); - wlwidth += wl->status_width + seplen; + for (i = 0; i < nitems(sl->entries); i++) { + status_free_ranges(&sl->entries[i].ranges); + free((void *)sl->entries[i].expanded); } - /* Create a new screen for the window list. */ - screen_init(&window_list, wlwidth, 1, 0); - - /* And draw the window list into it. */ - screen_write_start(&ctx, NULL, &window_list); - RB_FOREACH(wl, winlinks, &s->windows) { - screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s", - wl->status_text); + if (event_initialized(&sl->timer)) + evtimer_del(&sl->timer); - oo = wl->window->options; - sep = options_get_string(oo, "window-status-separator"); - screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep); + if (sl->active != &sl->screen) { + screen_free(sl->active); + free(sl->active); } - screen_write_stop(&ctx); - - /* If there is enough space for the total width, skip to draw now. */ - if (wlwidth <= wlavailable) - goto draw; + screen_free(&sl->screen); +} - /* Find size of current window text. */ - wlsize = s->curw->status_width; +/* Draw status line for client. */ +int +status_redraw(struct client *c) +{ + struct status_line *sl = &c->status; + struct status_line_entry *sle; + struct session *s = c->session; + struct screen_write_ctx ctx; + struct grid_cell gc; + u_int lines, i, n, width = c->tty.sx; + int flags, force = 0, changed = 0; + struct options_entry *o; + union options_value *ov; + struct format_tree *ft; + char *expanded; + + log_debug("%s enter", __func__); + + /* Shouldn't get here if not the active screen. */ + if (sl->active != &sl->screen) + fatalx("not the active screen"); - /* - * If the current window is already on screen, good to draw from the - * start and just leave off the end. - */ - if (wloffset + wlsize < wlavailable) { - if (wlavailable > 0) { - rarrow = 1; - wlavailable--; - } - wlwidth = wlavailable; - } else { - /* - * Work out how many characters we need to omit from the - * start. There are wlavailable characters to fill, and - * wloffset + wlsize must be the last. So, the start character - * is wloffset + wlsize - wlavailable. - */ - if (wlavailable > 0) { - larrow = 1; - wlavailable--; - } + /* No status line? */ + lines = status_line_size(c); + if (c->tty.sy == 0 || lines == 0) + return (1); - wlstart = wloffset + wlsize - wlavailable; - if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { - rarrow = 1; - wlstart++; - wlavailable--; - } - wlwidth = wlavailable; + /* Set up default colour. */ + style_apply(&gc, s->options, "status-style"); + if (!grid_cells_equal(&gc, &sl->style)) { + force = 1; + memcpy(&sl->style, &gc, sizeof sl->style); } - /* Bail if anything is now too small too. */ - if (wlwidth == 0 || wlavailable == 0) { - screen_free(&window_list); - goto out; + /* Resize the target screen. */ + if (screen_size_x(&sl->screen) != width || + screen_size_y(&sl->screen) != lines) { + screen_resize(&sl->screen, width, lines, 0); + changed = force = 1; } + screen_write_start(&ctx, NULL, &sl->screen); - /* - * Now the start position is known, work out the state of the left and - * right arrows. - */ - offset = 0; - RB_FOREACH(wl, winlinks, &s->windows) { - if (wl->flags & WINLINK_ALERTFLAGS && - larrow == 1 && offset < wlstart) - larrow = -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); - offset += wl->status_width; + /* Write the status lines. */ + o = options_get(s->options, "status-format"); + if (o == NULL) { + for (n = 0; n < width * lines; n++) + screen_write_putc(&ctx, &gc, ' '); + } else { + for (i = 0; i < lines; i++) { + screen_write_cursormove(&ctx, 0, i, 0); - if (wl->flags & WINLINK_ALERTFLAGS && - rarrow == 1 && offset > wlstart + wlwidth) - rarrow = -1; - } + ov = options_array_get(o, i); + if (ov == NULL) { + for (n = 0; n < width; n++) + screen_write_putc(&ctx, &gc, ' '); + continue; + } + sle = &sl->entries[i]; -draw: - /* Begin drawing. */ - screen_write_start(&ctx, NULL, &c->status); - - /* Draw the left string and arrow. */ - screen_write_cursormove(&ctx, 0, 0); - if (llen != 0) - screen_write_cnputs(&ctx, llen, &lgc, "%s", left); - if (larrow != 0) { - memcpy(&gc, &stdgc, sizeof gc); - if (larrow == -1) - gc.attr ^= GRID_ATTR_REVERSE; - screen_write_putc(&ctx, &gc, '<'); - } + expanded = format_expand_time(ft, ov->string); + if (!force && + sle->expanded != NULL && + strcmp(expanded, sle->expanded) == 0) { + free(expanded); + continue; + } + changed = 1; - /* Draw the right string and arrow. */ - if (rarrow != 0) { - screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); - memcpy(&gc, &stdgc, sizeof gc); - if (rarrow == -1) - gc.attr ^= GRID_ATTR_REVERSE; - screen_write_putc(&ctx, &gc, '>'); - } else - screen_write_cursormove(&ctx, c->tty.sx - rlen, 0); - if (rlen != 0) - screen_write_cnputs(&ctx, rlen, &rgc, "%s", right); + for (n = 0; n < width; n++) + screen_write_putc(&ctx, &gc, ' '); + screen_write_cursormove(&ctx, 0, i, 0); - /* Figure out the offset for the window list. */ - if (llen != 0) - wloffset = llen; - else - wloffset = 0; - if (wlwidth < wlavailable) { - switch (options_get_number(s->options, "status-justify")) { - case 1: /* centred */ - wloffset += (wlavailable - wlwidth) / 2; - break; - case 2: /* right */ - wloffset += (wlavailable - wlwidth); - break; + status_free_ranges(&sle->ranges); + format_draw(&ctx, &gc, width, expanded, &sle->ranges); + + free(sle->expanded); + sle->expanded = expanded; } } - if (larrow != 0) - wloffset++; - - /* Copy the window list. */ - c->wlmouse = -wloffset + wlstart; - screen_write_cursormove(&ctx, wloffset, 0); - screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1, NULL, - NULL); - screen_free(&window_list); - screen_write_stop(&ctx); -out: - free(left); - free(right); - - if (grid_compare(c->status.grid, old_status.grid) == 0) { - screen_free(&old_status); - return (0); - } - screen_free(&old_status); - return (1); -} - -/* Replace special sequences in fmt. */ -static char * -status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t) -{ - struct format_tree *ft; - char *expanded; - u_int tag; - - if (fmt == NULL) - return (xstrdup("")); - - if (wl != NULL) - tag = FORMAT_WINDOW|wl->window->id; - else - tag = FORMAT_NONE; - if (c->flags & CLIENT_STATUSFORCE) - ft = format_create(c, NULL, tag, FORMAT_STATUS|FORMAT_FORCE); - else - ft = format_create(c, NULL, tag, FORMAT_STATUS); - format_defaults(ft, c, NULL, wl, NULL); - - expanded = format_expand_time(ft, fmt, t); - + /* Free the format tree. */ format_free(ft); - return (expanded); -} -/* Return winlink status line entry and adjust gc as necessary. */ -static char * -status_print(struct client *c, struct winlink *wl, time_t t, - struct grid_cell *gc) -{ - struct options *oo = wl->window->options; - struct session *s = c->session; - const char *fmt; - char *text; - - style_apply_update(gc, oo, "window-status-style"); - fmt = options_get_string(oo, "window-status-format"); - if (wl == s->curw) { - style_apply_update(gc, oo, "window-status-current-style"); - fmt = options_get_string(oo, "window-status-current-format"); - } - if (wl == TAILQ_FIRST(&s->lastw)) - style_apply_update(gc, oo, "window-status-last-style"); - - if (wl->flags & WINLINK_BELL) - style_apply_update(gc, oo, "window-status-bell-style"); - else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) - style_apply_update(gc, oo, "window-status-activity-style"); - - text = status_replace(c, wl, fmt, t); - return (text); + /* Return if the status line has changed. */ + log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); + return (force || changed); } /* Set a status line message. */ @@ -574,12 +416,7 @@ status_message_set(struct client *c, const char *fmt, ...) int delay; status_message_clear(c); - - if (c->old_status == NULL) { - c->old_status = xmalloc(sizeof *c->old_status); - memcpy(c->old_status, &c->status, sizeof *c->old_status); - screen_init(&c->status, c->tty.sx, 1, 0); - } + status_push_screen(c); va_start(ap, fmt); xvasprintf(&c->message_string, fmt, ap); @@ -599,7 +436,7 @@ status_message_set(struct client *c, const char *fmt, ...) } c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; } /* Clear status line message. */ @@ -614,9 +451,9 @@ status_message_clear(struct client *c) if (c->prompt_string == NULL) c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); - c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ + c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ - screen_reinit(&c->status); + status_pop_screen(c); } /* Clear status line message after timer expires. */ @@ -632,16 +469,22 @@ status_message_callback(__unused int fd, __unused short event, void *data) int status_message_redraw(struct client *c) { - struct screen_write_ctx ctx; - struct session *s = c->session; - struct screen old_status; - size_t len; - struct grid_cell gc; + struct status_line *sl = &c->status; + struct screen_write_ctx ctx; + struct session *s = c->session; + struct screen old_screen; + size_t len; + u_int lines, offset; + struct grid_cell gc; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); - memcpy(&old_status, &c->status, sizeof old_status); - screen_init(&c->status, c->tty.sx, 1, 0); + memcpy(&old_screen, sl->active, sizeof old_screen); + + lines = status_line_size(c); + if (lines <= 1) + lines = 1; + screen_init(sl->active, c->tty.sx, lines, 0); len = screen_write_strlen("%s", c->message_string); if (len > c->tty.sx) @@ -649,20 +492,20 @@ status_message_redraw(struct client *c) style_apply(&gc, s->options, "message-style"); - screen_write_start(&ctx, NULL, &c->status); - - screen_write_cursormove(&ctx, 0, 0); - screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); - for (; len < c->tty.sx; len++) + screen_write_start(&ctx, NULL, 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); screen_write_stop(&ctx); - if (grid_compare(c->status.grid, old_status.grid) == 0) { - screen_free(&old_status); + if (grid_compare(sl->active->grid, old_screen.grid) == 0) { + screen_free(&old_screen); return (0); } - screen_free(&old_status); + screen_free(&old_screen); return (1); } @@ -672,30 +515,23 @@ status_prompt_set(struct client *c, const char *msg, const char *input, prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) { struct format_tree *ft; - time_t t; char *tmp, *cp; ft = format_create(c, NULL, FORMAT_NONE, 0); format_defaults(ft, c, NULL, NULL, NULL); - t = time(NULL); if (input == NULL) input = ""; if (flags & PROMPT_NOFORMAT) tmp = xstrdup(input); else - tmp = format_expand_time(ft, input, t); + tmp = format_expand_time(ft, input); status_message_clear(c); status_prompt_clear(c); + status_push_screen(c); - if (c->old_status == NULL) { - c->old_status = xmalloc(sizeof *c->old_status); - memcpy(c->old_status, &c->status, sizeof *c->old_status); - screen_init(&c->status, c->tty.sx, 1, 0); - } - - c->prompt_string = format_expand_time(ft, msg, t); + c->prompt_string = format_expand_time(ft, msg); c->prompt_buffer = utf8_fromcstr(tmp); c->prompt_index = utf8_strlen(c->prompt_buffer); @@ -711,7 +547,7 @@ status_prompt_set(struct client *c, const char *msg, const char *input, if (~flags & PROMPT_INCREMENTAL) c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { xasprintf(&cp, "=%s", tmp); @@ -739,10 +575,13 @@ status_prompt_clear(struct client *c) free(c->prompt_buffer); c->prompt_buffer = NULL; + free(c->prompt_saved); + c->prompt_saved = NULL; + c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); - c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ + c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ - screen_reinit(&c->status); + status_pop_screen(c); } /* Update status line prompt with a new prompt string. */ @@ -750,17 +589,15 @@ void status_prompt_update(struct client *c, const char *msg, const char *input) { struct format_tree *ft; - time_t t; char *tmp; ft = format_create(c, NULL, FORMAT_NONE, 0); format_defaults(ft, c, NULL, NULL, NULL); - t = time(NULL); - tmp = format_expand_time(ft, input, t); + tmp = format_expand_time(ft, input); free(c->prompt_string); - c->prompt_string = format_expand_time(ft, msg, t); + c->prompt_string = format_expand_time(ft, msg); free(c->prompt_buffer); c->prompt_buffer = utf8_fromcstr(tmp); @@ -768,7 +605,7 @@ status_prompt_update(struct client *c, const char *msg, const char *input) c->prompt_hindex = 0; - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; free(tmp); format_free(ft); @@ -778,16 +615,22 @@ status_prompt_update(struct client *c, const char *msg, const char *input) int status_prompt_redraw(struct client *c) { + struct status_line *sl = &c->status; struct screen_write_ctx ctx; struct session *s = c->session; - struct screen old_status; - u_int i, offset, left, start, pcursor, pwidth, width; + struct screen old_screen; + u_int i, lines, offset, left, start, width; + u_int pcursor, pwidth; struct grid_cell gc, cursorgc; if (c->tty.sx == 0 || c->tty.sy == 0) return (0); - memcpy(&old_status, &c->status, sizeof old_status); - screen_init(&c->status, c->tty.sx, 1, 0); + memcpy(&old_screen, sl->active, sizeof old_screen); + + lines = status_line_size(c); + if (lines <= 1) + lines = 1; + screen_init(sl->active, c->tty.sx, lines, 0); if (c->prompt_mode == PROMPT_COMMAND) style_apply(&gc, s->options, "message-command-style"); @@ -801,12 +644,14 @@ status_prompt_redraw(struct client *c) if (start > c->tty.sx) start = c->tty.sx; - screen_write_start(&ctx, NULL, &c->status); - screen_write_cursormove(&ctx, 0, 0); - screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); - while (c->status.cx < screen_size_x(&c->status)) + screen_write_start(&ctx, NULL, 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, start, 0); + screen_write_cursormove(&ctx, 0, lines - 1, 0); + screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); + screen_write_cursormove(&ctx, start, lines - 1, 0); left = c->tty.sx - start; if (left == 0) @@ -846,17 +691,17 @@ status_prompt_redraw(struct client *c) screen_write_cell(&ctx, &cursorgc); } } - if (c->status.cx < screen_size_x(&c->status) && c->prompt_index >= i) + if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) screen_write_putc(&ctx, &cursorgc, ' '); finished: screen_write_stop(&ctx); - if (grid_compare(c->status.grid, old_status.grid) == 0) { - screen_free(&old_status); + if (grid_compare(sl->active->grid, old_screen.grid) == 0) { + screen_free(&old_screen); return (0); } - screen_free(&old_status); + screen_free(&old_screen); return (1); } @@ -906,7 +751,7 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) return (1); case '\033': /* Escape */ c->prompt_mode = PROMPT_COMMAND; - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; return (0); } *new_key = key; @@ -920,17 +765,17 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) case 's': case 'a': c->prompt_mode = PROMPT_ENTRY; - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; break; /* switch mode and... */ case 'S': c->prompt_mode = PROMPT_ENTRY; - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; *new_key = '\025'; /* C-u */ return (1); case 'i': case '\033': /* Escape */ c->prompt_mode = PROMPT_ENTRY; - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; return (0); } @@ -999,16 +844,79 @@ status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) return (0); } +/* Paste into prompt. */ +static int +status_prompt_paste(struct client *c) +{ + struct paste_buffer *pb; + const char *bufdata; + size_t size, n, bufsize; + u_int i; + struct utf8_data *ud, *udp; + enum utf8_state more; + + size = utf8_strlen(c->prompt_buffer); + if (c->prompt_saved != NULL) { + ud = c->prompt_saved; + n = utf8_strlen(c->prompt_saved); + } else { + if ((pb = paste_get_top(NULL)) == NULL) + return (0); + bufdata = paste_buffer_data(pb, &bufsize); + ud = xreallocarray(NULL, bufsize + 1, sizeof *ud); + udp = ud; + for (i = 0; i != bufsize; /* nothing */) { + more = utf8_open(udp, bufdata[i]); + if (more == UTF8_MORE) { + while (++i != bufsize && more == UTF8_MORE) + more = utf8_append(udp, bufdata[i]); + if (more == UTF8_DONE) { + udp++; + continue; + } + i -= udp->have; + } + if (bufdata[i] <= 31 || bufdata[i] >= 127) + break; + utf8_set(udp, bufdata[i]); + udp++; + i++; + } + udp->size = 0; + n = udp - ud; + } + if (n == 0) + return (0); + + c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, + sizeof *c->prompt_buffer); + if (c->prompt_index == size) { + memcpy(c->prompt_buffer + c->prompt_index, ud, + n * sizeof *c->prompt_buffer); + c->prompt_index += n; + c->prompt_buffer[c->prompt_index].size = 0; + } else { + memmove(c->prompt_buffer + c->prompt_index + n, + c->prompt_buffer + c->prompt_index, + (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer); + memcpy(c->prompt_buffer + c->prompt_index, ud, + n * sizeof *c->prompt_buffer); + c->prompt_index += n; + } + + if (ud != c->prompt_saved) + free(ud); + return (1); +} + /* Handle keys in prompt. */ int status_prompt_key(struct client *c, key_code key) { struct options *oo = c->session->options; - struct paste_buffer *pb; char *s, *cp, word[64], prefix = '='; - const char *histstr, *bufdata, *ws = NULL; - u_char ch; - size_t size, n, off, idx, bufsize, used; + const char *histstr, *ws = NULL; + size_t size, n, off, idx, used; struct utf8_data tmp, *first, *last, *ud; int keys; @@ -1023,6 +931,7 @@ status_prompt_key(struct client *c, key_code key) free(s); return (1); } + key &= ~KEYC_XTERM; keys = options_get_number(c->session->options, "status-keys"); if (keys == MODEKEY_VI) { @@ -1180,6 +1089,12 @@ status_prompt_key(struct client *c, key_code key) } } + free(c->prompt_saved); + c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, + (c->prompt_index - idx) + 1); + memcpy(c->prompt_saved, c->prompt_buffer + idx, + (c->prompt_index - idx) * sizeof *c->prompt_buffer); + memmove(c->prompt_buffer + idx, c->prompt_buffer + c->prompt_index, (size + 1 - c->prompt_index) * @@ -1190,6 +1105,7 @@ status_prompt_key(struct client *c, key_code key) goto changed; case 'f'|KEYC_ESCAPE: + case KEYC_RIGHT|KEYC_CTRL: ws = options_get_string(oo, "word-separators"); /* Find a word. */ @@ -1213,6 +1129,7 @@ status_prompt_key(struct client *c, key_code key) goto changed; case 'b'|KEYC_ESCAPE: + case KEYC_LEFT|KEYC_CTRL: ws = options_get_string(oo, "word-separators"); /* Find a non-separator. */ @@ -1251,36 +1168,9 @@ status_prompt_key(struct client *c, key_code key) c->prompt_index = utf8_strlen(c->prompt_buffer); goto changed; case '\031': /* C-y */ - if ((pb = paste_get_top(NULL)) == NULL) - break; - bufdata = paste_buffer_data(pb, &bufsize); - for (n = 0; n < bufsize; n++) { - ch = (u_char)bufdata[n]; - if (ch < 32 || ch >= 127) - break; - } - - c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, - sizeof *c->prompt_buffer); - if (c->prompt_index == size) { - for (idx = 0; idx < n; idx++) { - ud = &c->prompt_buffer[c->prompt_index + idx]; - utf8_set(ud, bufdata[idx]); - } - c->prompt_index += n; - c->prompt_buffer[c->prompt_index].size = 0; - } else { - memmove(c->prompt_buffer + c->prompt_index + n, - c->prompt_buffer + c->prompt_index, - (size + 1 - c->prompt_index) * - sizeof *c->prompt_buffer); - for (idx = 0; idx < n; idx++) { - ud = &c->prompt_buffer[c->prompt_index + idx]; - utf8_set(ud, bufdata[idx]); - } - c->prompt_index += n; - } - goto changed; + if (status_prompt_paste(c)) + goto changed; + break; case '\024': /* C-t */ idx = c->prompt_index; if (idx < size) @@ -1305,6 +1195,7 @@ status_prompt_key(struct client *c, key_code key) break; case '\033': /* Escape */ case '\003': /* C-c */ + case '\007': /* C-g */ if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) status_prompt_clear(c); break; @@ -1324,7 +1215,7 @@ status_prompt_key(struct client *c, key_code key) goto append_key; } - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; return (0); append_key: @@ -1359,7 +1250,7 @@ status_prompt_key(struct client *c, key_code key) } changed: - c->flags |= CLIENT_STATUS; + c->flags |= CLIENT_REDRAWSTATUS; if (c->prompt_flags & PROMPT_INCREMENTAL) { s = utf8_tocstr(c->prompt_buffer); xasprintf(&cp, "%c%s", prefix, s); @@ -1423,12 +1314,17 @@ status_prompt_add_history(const char *line) } /* Build completion list. */ -static const char ** +char ** status_prompt_complete_list(u_int *size, const char *s) { - const char **list = NULL, **layout; + 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; const char *layouts[] = { "even-horizontal", "even-vertical", "main-horizontal", "main-vertical", "tiled", NULL @@ -1436,29 +1332,49 @@ status_prompt_complete_list(u_int *size, const char *s) *size = 0; for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { - if (strncmp((*cmdent)->name, s, strlen(s)) == 0) { + if (strncmp((*cmdent)->name, s, slen) == 0) { list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = (*cmdent)->name; + list[(*size)++] = xstrdup((*cmdent)->name); } } for (oe = options_table; oe->name != NULL; oe++) { - if (strncmp(oe->name, s, strlen(s)) == 0) { + if (strncmp(oe->name, s, slen) == 0) { list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = oe->name; + list[(*size)++] = xstrdup(oe->name); } } for (layout = layouts; *layout != NULL; layout++) { - if (strncmp(*layout, s, strlen(s)) == 0) { + if (strncmp(*layout, s, slen) == 0) { list = xreallocarray(list, (*size) + 1, sizeof *list); - list[(*size)++] = *layout; + list[(*size)++] = xstrdup(*layout); + } + } + o = options_get_only(global_options, "command-alias"); + if (o != NULL) { + a = options_array_first(o); + while (a != NULL) { + value = options_array_item_value(a)->string; + if ((cp = strchr(value, '=')) == NULL) + goto next; + valuelen = cp - value; + if (slen > valuelen || strncmp(value, s, slen) != 0) + goto next; + + list = xreallocarray(list, (*size) + 1, sizeof *list); + list[(*size)++] = xstrndup(value, valuelen); + + next: + a = options_array_next(a); } } + for (idx = 0; idx < (*size); idx++) + log_debug("complete %u: %s", idx, list[idx]); return (list); } /* Find longest prefix. */ static char * -status_prompt_complete_prefix(const char **list, u_int size) +status_prompt_complete_prefix(char **list, u_int size) { char *out; u_int i; @@ -1481,7 +1397,8 @@ status_prompt_complete_prefix(const char **list, u_int size) static char * status_prompt_complete(struct session *session, const char *s) { - const char **list = NULL, *colon; + char **list = NULL; + const char *colon; u_int size = 0, i; struct session *s_loop; struct winlink *wl; @@ -1500,6 +1417,8 @@ status_prompt_complete(struct session *session, const char *s) 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); } diff --git a/style.c b/style.c index 6e78b3a9b6..6ba4c524ce 100644 --- a/style.c +++ b/style.c @@ -19,32 +19,52 @@ #include +#include +#include #include #include "tmux.h" -/* Parse an embedded style of the form "fg=colour,bg=colour,bright,...". */ +/* Mask for bits not included in style. */ +#define STYLE_ATTR_MASK (~GRID_ATTR_CHARSET) + +/* Default style. */ +static struct style style_default = { + { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + + 8, + STYLE_ALIGN_DEFAULT, + STYLE_LIST_OFF, + + STYLE_RANGE_NONE, 0, + + STYLE_DEFAULT_BASE +}; + +/* + * Parse an embedded style of the form "fg=colour,bg=colour,bright,...". Note + * that this adds onto the given style, so it must have been initialized + * already. + */ int -style_parse(const struct grid_cell *defgc, struct grid_cell *gc, - const char *in) +style_parse(struct style *sy, const struct grid_cell *base, const char *in) { - struct grid_cell savedgc; - const char delimiters[] = " ,"; - char tmp[32]; - int val, fg, bg, attr, flags; - size_t end; + struct style saved; + const char delimiters[] = " ,", *cp; + char tmp[256], *found; + int value; + size_t end; if (*in == '\0') return (0); - if (strchr(delimiters, in[strlen(in) - 1]) != NULL) - return (-1); - memcpy(&savedgc, gc, sizeof savedgc); - - fg = gc->fg; - bg = gc->bg; - attr = gc->attr; - flags = gc->flags; + style_copy(&saved, sy); + do { + while (*in != '\0' && strchr(delimiters, *in) != NULL) + in++; + if (*in == '\0') + break; + end = strcspn(in, delimiters); if (end > (sizeof tmp) - 1) goto error; @@ -52,74 +72,185 @@ style_parse(const struct grid_cell *defgc, struct grid_cell *gc, tmp[end] = '\0'; if (strcasecmp(tmp, "default") == 0) { - fg = defgc->fg; - bg = defgc->bg; - attr = defgc->attr; - flags = defgc->flags; + 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) + sy->default_type = STYLE_DEFAULT_PUSH; + else if (strcasecmp(tmp, "pop-default") == 0) + sy->default_type = STYLE_DEFAULT_POP; + else if (strcasecmp(tmp, "nolist") == 0) + sy->list = STYLE_LIST_OFF; + else if (strncasecmp(tmp, "list=", 5) == 0) { + if (strcasecmp(tmp + 5, "on") == 0) + sy->list = STYLE_LIST_ON; + else if (strcasecmp(tmp + 5, "focus") == 0) + sy->list = STYLE_LIST_FOCUS; + else if (strcasecmp(tmp + 5, "left-marker") == 0) + sy->list = STYLE_LIST_LEFT_MARKER; + else if (strcasecmp(tmp + 5, "right-marker") == 0) + sy->list = STYLE_LIST_RIGHT_MARKER; + else + goto error; + } else if (strcasecmp(tmp, "norange") == 0) { + sy->range_type = style_default.range_type; + sy->range_argument = style_default.range_type; + } else if (end > 6 && strncasecmp(tmp, "range=", 6) == 0) { + found = strchr(tmp + 6, '|'); + if (found != NULL) { + *found++ = '\0'; + if (*found == '\0') + goto error; + for (cp = found; *cp != '\0'; cp++) { + if (!isdigit((u_char)*cp)) + goto error; + } + } + if (strcasecmp(tmp + 6, "left") == 0) { + if (found != NULL) + goto error; + sy->range_type = STYLE_RANGE_LEFT; + sy->range_argument = 0; + } else if (strcasecmp(tmp + 6, "right") == 0) { + if (found != NULL) + goto error; + sy->range_type = STYLE_RANGE_RIGHT; + sy->range_argument = 0; + } else if (strcasecmp(tmp + 6, "window") == 0) { + if (found == NULL) + goto error; + sy->range_type = STYLE_RANGE_WINDOW; + sy->range_argument = atoi(found); + } + } else if (strcasecmp(tmp, "noalign") == 0) + sy->align = style_default.align; + else if (end > 6 && strncasecmp(tmp, "align=", 6) == 0) { + if (strcasecmp(tmp + 6, "left") == 0) + sy->align = STYLE_ALIGN_LEFT; + else if (strcasecmp(tmp + 6, "centre") == 0) + sy->align = STYLE_ALIGN_CENTRE; + else if (strcasecmp(tmp + 6, "right") == 0) + sy->align = STYLE_ALIGN_RIGHT; + else + goto error; + } else if (end > 5 && strncasecmp(tmp, "fill=", 5) == 0) { + if ((value = colour_fromstring(tmp + 5)) == -1) + goto error; + sy->fill = value; } else if (end > 3 && strncasecmp(tmp + 1, "g=", 2) == 0) { - if ((val = colour_fromstring(tmp + 3)) == -1) + if ((value = colour_fromstring(tmp + 3)) == -1) goto error; if (*in == 'f' || *in == 'F') { - if (val != 8) - fg = val; + if (value != 8) + sy->gc.fg = value; else - fg = defgc->fg; + sy->gc.fg = base->fg; } else if (*in == 'b' || *in == 'B') { - if (val != 8) - bg = val; + if (value != 8) + sy->gc.bg = value; else - bg = defgc->bg; + sy->gc.bg = base->bg; } else goto error; } else if (strcasecmp(tmp, "none") == 0) - attr = 0; + sy->gc.attr = 0; else if (end > 2 && strncasecmp(tmp, "no", 2) == 0) { - if ((val = attributes_fromstring(tmp + 2)) == -1) + if ((value = attributes_fromstring(tmp + 2)) == -1) goto error; - attr &= ~val; + sy->gc.attr &= ~value; } else { - if ((val = attributes_fromstring(tmp)) == -1) + if ((value = attributes_fromstring(tmp)) == -1) goto error; - attr |= val; + sy->gc.attr |= value; } in += end + strspn(in + end, delimiters); } while (*in != '\0'); - gc->fg = fg; - gc->bg = bg; - gc->attr = attr; - gc->flags = flags; return (0); error: - memcpy(gc, &savedgc, sizeof *gc); + style_copy(sy, &saved); return (-1); } /* Convert style to a string. */ const char * -style_tostring(struct grid_cell *gc) +style_tostring(struct style *sy) { - int off = 0, comma = 0; - static char s[256]; + struct grid_cell *gc = &sy->gc; + int off = 0; + const char *comma = "", *tmp = ""; + static char s[256]; + char b[16]; *s = '\0'; + if (sy->list != STYLE_LIST_OFF) { + if (sy->list == STYLE_LIST_ON) + tmp = "on"; + else if (sy->list == STYLE_LIST_FOCUS) + tmp = "focus"; + else if (sy->list == STYLE_LIST_LEFT_MARKER) + tmp = "left-marker"; + else if (sy->list == STYLE_LIST_RIGHT_MARKER) + tmp = "right-marker"; + off += xsnprintf(s + off, sizeof s - off, "%slist=%s", comma, + tmp); + comma = ","; + } + if (sy->range_type != STYLE_RANGE_NONE) { + if (sy->range_type == STYLE_RANGE_LEFT) + tmp = "left"; + else if (sy->range_type == STYLE_RANGE_RIGHT) + tmp = "right"; + else if (sy->range_type == STYLE_RANGE_WINDOW) { + snprintf(b, sizeof b, "window|%u", sy->range_argument); + tmp = b; + } + off += xsnprintf(s + off, sizeof s - off, "%srange=%s", comma, + tmp); + comma = ","; + } + if (sy->align != STYLE_ALIGN_DEFAULT) { + if (sy->align == STYLE_ALIGN_LEFT) + tmp = "left"; + else if (sy->align == STYLE_ALIGN_CENTRE) + tmp = "centre"; + else if (sy->align == STYLE_ALIGN_RIGHT) + tmp = "right"; + off += xsnprintf(s + off, sizeof s - off, "%salign=%s", comma, + tmp); + comma = ","; + } + if (sy->default_type != STYLE_DEFAULT_BASE) { + if (sy->default_type == STYLE_DEFAULT_PUSH) + tmp = "push-default"; + else if (sy->default_type == STYLE_DEFAULT_POP) + tmp = "pop-default"; + off += xsnprintf(s + off, sizeof s - off, "%s%s", comma, tmp); + comma = ","; + } + if (sy->fill != 8) { + off += xsnprintf(s + off, sizeof s - off, "%sfill=%s", comma, + colour_tostring(sy->fill)); + comma = ","; + } if (gc->fg != 8) { - off += xsnprintf(s, sizeof s, "fg=%s", colour_tostring(gc->fg)); - comma = 1; + off += xsnprintf(s + off, sizeof s - off, "%sfg=%s", comma, + colour_tostring(gc->fg)); + comma = ","; } - if (gc->bg != 8) { - off += xsnprintf(s + off, sizeof s - off, "%sbg=%s", - comma ? "," : "", colour_tostring(gc->bg)); - comma = 1; + off += xsnprintf(s + off, sizeof s - off, "%sbg=%s", comma, + colour_tostring(gc->bg)); + comma = ","; } - if (gc->attr != 0 && gc->attr != GRID_ATTR_CHARSET) { - xsnprintf(s + off, sizeof s - off, "%s%s", - comma ? "," : "", attributes_tostring(gc->attr)); + xsnprintf(s + off, sizeof s - off, "%s%s", comma, + attributes_tostring(gc->attr)); + comma = ","; } if (*s == '\0') @@ -131,38 +262,53 @@ style_tostring(struct grid_cell *gc) void style_apply(struct grid_cell *gc, struct options *oo, const char *name) { - const struct grid_cell *gcp; + struct style *sy; memcpy(gc, &grid_default_cell, sizeof *gc); - gcp = options_get_style(oo, name); - gc->fg = gcp->fg; - gc->bg = gcp->bg; - gc->attr |= gcp->attr; + sy = options_get_style(oo, name); + gc->fg = sy->gc.fg; + gc->bg = sy->gc.bg; + gc->attr |= sy->gc.attr; } -/* Apply a style, updating if default. */ +/* Initialize style from cell. */ void -style_apply_update(struct grid_cell *gc, struct options *oo, const char *name) +style_set(struct style *sy, const struct grid_cell *gc) { - const struct grid_cell *gcp; - - gcp = options_get_style(oo, name); - if (gcp->fg != 8) - gc->fg = gcp->fg; - if (gcp->bg != 8) - gc->bg = gcp->bg; - if (gcp->attr != 0) - gc->attr |= gcp->attr; + memcpy(sy, &style_default, sizeof *sy); + memcpy(&sy->gc, gc, sizeof sy->gc); +} + +/* Copy style. */ +void +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); } -/* Check if two styles are the same. */ +/* Is this style default? */ int -style_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) +style_is_default(struct style *sy) { - return (gc1->fg == gc2->fg && - gc1->bg == gc2->bg && - (gc1->flags & ~GRID_FLAG_PADDING) == - (gc2->flags & ~GRID_FLAG_PADDING) && - (gc1->attr & ~GRID_ATTR_CHARSET) == - (gc2->attr & ~GRID_ATTR_CHARSET)); + return (style_equal(sy, &style_default)); } diff --git a/tmux.1 b/tmux.1 index 49a5350a68..9513bd7942 100644 --- a/tmux.1 +++ b/tmux.1 @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: March 25 2013 $ +.Dd $Mdocdate$ .Dt TMUX 1 .Os .Sh NAME @@ -176,27 +176,16 @@ is specified, the default socket directory is not used and any .Fl L flag is ignored. .It Fl u -When starting, -.Nm -looks for the +Write UTF-8 output to the terminal even if the first environment +variable of .Ev LC_ALL , -.Ev LC_CTYPE -and +.Ev LC_CTYPE , +or .Ev LANG -environment variables: if the first found contains -.Ql UTF-8 , -then the terminal is assumed to support UTF-8. -This is not always correct: the -.Fl u -flag explicitly informs -.Nm -that UTF-8 is supported. -.Pp -Note that -.Nm -itself always accepts UTF-8; this controls whether it will send UTF-8 -characters to the terminal it is running (if not, they are replaced by -.Ql _ ) . +that is set does not contain +.Qq UTF-8 +or +.Qq UTF8 . .It Fl v Request verbose logging. Log messages will be saved into @@ -234,7 +223,7 @@ If no commands are specified, the .Ic new-session command is assumed. .El -.Sh KEY BINDINGS +.Sh DEFAULT KEY BINDINGS .Nm may be controlled from an attached client by using a key combination of a prefix key, @@ -255,6 +244,7 @@ client. .It ! Break the current pane out of the window. .It \&" +.\" " Split the current pane into two, top and bottom. .It # List all paste buffers. @@ -306,6 +296,12 @@ Prompt to search for text in open windows. Display some information about the current window. .It l Move to the previously selected window. +.It m +Mark the current pane (see +.Ic select-pane +.Fl m ) . +.It M +Clear the marked pane. .It n Change to the next window. .It o @@ -316,12 +312,6 @@ Change to the previous window. Briefly display pane indexes. .It r Force redraw of the attached client. -.It m -Mark the current pane (see -.Ic select-pane -.Fl m ) . -.It M -Clear the marked pane. .It s Select a new session for the attached client interactively. .It t @@ -370,8 +360,253 @@ Key bindings may be changed with the and .Ic unbind-key commands. +.Sh COMMAND PARSING AND EXECUTION +.Nm +supports a large number of commands which can be used to control its +behaviour. +Each command is named and can accept zero or more flags and arguments. +They may be bound to a key with the +.Ic bind-key +command or run from the shell prompt, a shell script, a configuration file or +the command prompt. +For example, the same +.Ic set-option +command run from the shell prompt, from +.Pa ~/.tmux.conf +and bound to a key may look like: +.Bd -literal -offset indent +$ tmux set-option -g status-style bg=cyan + +set-option -g status-style bg=cyan + +bind-key C set-option -g status-style bg=cyan +.Ed +.Pp +Here, the command name is +.Ql set-option , +.Ql Fl g +is a flag and +.Ql status-style +and +.Ql bg=cyan +are arguments. +.Pp +.Nm +distinguishes between command parsing and execution. +In order to execute a command, +.Nm +needs it to be split up into its name and arguments. +This is command parsing. +If a command is run from the shell, the shell parses it; from inside +.Nm +or from a configuration file, +.Nm +does. +Examples of when +.Nm +parses commands are: +.Bl -dash -offset indent +.It +in a configuration file; +.It +typed at the command prompt (see +.Ic command-prompt ) ; +.It +given to +.Ic bind-key ; +.It +passed as arguments to +.Ic if-shell +or +.Ic confirm-before . +.El +.Pp +To execute commands, each client has a +.Ql command queue . +A global command queue not attached to any client is used on startup +for configuration files like +.Pa ~/.tmux.conf . +Parsed commands added to the queue are executed in order. +Some commands, like +.Ic if-shell +and +.Ic confirm-before , +parse their argument to create a new command which is inserted immediately +after themselves. +This means that arguments can be parsed twice or more - once when the parent command (such as +.Ic if-shell ) +is parsed and again when it parses and executes its command. +Commands like +.Ic if-shell , +.Ic run-shell +and +.Ic display-panes +stop execution of subsequent commands on the queue until something happens - +.Ic if-shell +and +.Ic run-shell +until a shell command finishes and +.Ic display-panes +until a key is pressed. +For example, the following commands: +.Bd -literal -offset indent +new-session; new-window +if-shell "true" "split-window" +kill-session +.Ed +.Pp +Will execute +.Ic new-session , +.Ic new-window , +.Ic if-shell , +the shell command +.Xr true 1 , +.Ic split-window +and +.Ic kill-session +in that order. +.Pp +The +.Sx COMMANDS +section lists the +.Nm +commands and their arguments. +.Sh PARSING SYNTAX +This section describes the syntax of commands parsed by +.Nm , +for example in a configuration file or at the command prompt. +Note that when commands are entered into the shell, they are parsed by the shell +- see for example +.Xr ksh 1 +or +.Xr csh 1 . +.Pp +Each command is terminated by a newline or a semicolon (;). +Commands separated by semicolons together form a +.Ql command sequence +- if a command in the sequence encounters an error, no subsequent commands are +executed. +.Pp +Comments are marked by the unquoted # character - any remaining text after a +comment is ignored until the end of the line. +.Pp +If the last character of a line is \e, the line is joined with the following +line (the \e and the newline are completely removed). +This is called line continuation and applies both inside and outside quoted +strings and in comments, but not inside braces. +.Pp +Command arguments may be specified as strings surrounded by single (') quotes, +double quotes (") or braces ({}). +.\" " +This is required when the argument contains any special character. +Single and double quoted strings cannot span multiple lines except with line +continuation. +Braces can span multiple lines. +.Pp +Outside of quotes and inside double quotes, these replacements are performed: +.Bl -dash -offset indent +.It +Environment variables preceded by $ are replaced with their value from the +global environment (see the +.Sx GLOBAL AND SESSION ENVIRONMENT +section). +.It +A leading ~ or ~user is expanded to the home directory of the current or +specified user. +.It +\euXXXX or \euXXXXXXXX is replaced by the Unicode codepoint corresponding to +the given four or eight digit hexadecimal number. +.It +When preceded (escaped) by a \e, the following characters are replaced: \ee by +the escape character; \er by a carriage return; \en by a newline; and \et by a +tab. +.It +\eooo is replaced by a character of the octal value ooo. +Three octal digits are required, for example \e001. +The largest valid character is \e377. +.It +Any other characters preceded by \e are replaced by themselves (that is, the \e +is removed) and are not treated as having any special meaning - so for example +\e; will not mark a command sequence and \e$ will not expand an environment +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 +.Nm +or shell commands as an argument (for example to +.Ic if-shell +or +.Ic pipe-pane ) . +These two examples produce an identical command - note that no escaping is +needed when using {}: +.Bd -literal -offset indent +if-shell true { + display -p 'brace-dollar-foo: }$foo' +} + +if-shell true "\en display -p 'brace-dollar-foo: }\e$foo'\en" +.Ed +.Pp +Braces may be enclosed inside braces, for example: +.Bd -literal -offset indent +bind x if-shell "true" { + if-shell "true" { + display "true!" + } +} +.Ed +.Pp +Environment variables may be set by using the syntax +.Ql name=value , +for example +.Ql HOME=/home/user . +Variables set during parsing are added to the global environment. +.Pp +Commands may be parsed conditionally by surrounding them with +.Ql %if , +.Ql %elif , +.Ql %else +and +.Ql %endif . +The argument to +.Ql %if +and +.Ql %elif +is expanded as a format (see +.Sx FORMATS ) +and if it evaluates to false (zero or empty), subsequent text is ignored until +the closing +.Ql %elif , +.Ql %else +or +.Ql %endif . +For example: +.Bd -literal -offset indent +%if "#{==:#{host},myhost}" +set -g status-style bg=red +%elif "#{==:#{host},myotherhost}" +set -g status-style bg=green +%else +set -g status-style bg=blue +%endif +.Ed +.Pp +Will change the status line to red if running on +.Ql myhost , +green if running on +.Ql myotherhost , +or blue if running on another host. +Conditionals may be given on one line, for example: +.Bd -literal -offset indent +%if #{==:#{host},myhost} set -g status-style bg=red %endif +.Ed .Sh COMMANDS -This section contains a list of the commands supported by +This section describes the commands supported by .Nm . Most commands accept the optional .Fl t @@ -379,7 +614,7 @@ Most commands accept the optional .Fl s ) argument with one of .Ar target-client , -.Ar target-session +.Ar target-session , .Ar target-window , or .Ar target-pane . @@ -546,7 +781,7 @@ may consist entirely of the token .Ql {mouse} (alternative form .Ql = ) -to specify the most recent mouse event +to specify the session, window or pane where the most recent mouse event occurred (see the .Sx MOUSE SUPPORT section) @@ -626,27 +861,18 @@ directly without invoking the shell. .Op Ar arguments refers to a .Nm -command, passed with the command and arguments separately, for example: +command, either passed with the command and arguments separately, for example: .Bd -literal -offset indent -bind-key F1 set-window-option force-width 81 +bind-key F1 set-option status off .Ed .Pp -Or if using -.Xr sh 1 : +Or passed as a single string argument in +.Pa .tmux.conf , +for example: .Bd -literal -offset indent -$ tmux bind-key F1 set-window-option force-width 81 +bind-key F1 { set-option status off } .Ed .Pp -Multiple commands may be specified together as part of a -.Em command sequence . -Each command should be separated by spaces and a semicolon; -commands are executed sequentially from left to right and -lines ending with a backslash continue on to the next line, -except when escaped by another backslash. -A literal semicolon may be included by escaping it with a backslash (for -example, when specifying a command sequence to -.Ic bind-key ) . -.Pp Example .Nm commands include: @@ -655,7 +881,7 @@ refresh-client -t/dev/ttyp2 rename-session -tfirst newname -set-window-option -t:0 monitor-activity on +set-option -wt:0 monitor-activity on new-window ; split-window -d @@ -697,7 +923,7 @@ section. The following commands are available to manage clients and sessions: .Bl -tag -width Ds .It Xo Ic attach-session -.Op Fl dEr +.Op Fl dErx .Op Fl c Ar working-directory .Op Fl t Ar target-session .Xc @@ -710,6 +936,10 @@ If used from inside, switch the current client. If .Fl d is specified, any other clients attached to the session are detached. +If +.Fl x +is given, send SIGHUP to the parent process of the client as well as +detaching the client, typically causing it to exit. .Fl r signifies the client is read-only (only keys bound to the .Ic detach-client @@ -827,7 +1057,7 @@ command. Lock all clients attached to .Ar target-session . .It Xo Ic new-session -.Op Fl AdDEP +.Op Fl AdDEPX .Op Fl c Ar start-directory .Op Fl F Ar format .Op Fl n Ar window-name @@ -850,11 +1080,22 @@ and are the name of and shell command to execute in the initial window. With .Fl d , -the initial size is 80 x 24; +the initial size comes from the global +.Ic default-size +option; .Fl x and .Fl y can be used to specify a different size. +.Ql - +uses the size of the current client if any. +If +.Fl x +or +.Fl y +is given, the +.Ic default-size +option is set for the session. .Pp If run from a terminal, any .Xr termios 4 @@ -873,6 +1114,12 @@ already exists; in this case, behaves like .Fl d to +.Ic attach-session , +and +.Fl X +behaves like +.Fl x +to .Ic attach-session . .Pp If @@ -909,7 +1156,7 @@ The .Fl P option prints information about the new session after it has been created. By default, it uses the format -.Ql #{session_name}: +.Ql #{session_name}:\& but a different format may be specified with .Fl F . .Pp @@ -919,9 +1166,11 @@ is used, the .Ic update-environment option will not be applied. .It Xo Ic refresh-client -.Op Fl C Ar width,height -.Op Fl S +.Op Fl cDlLRSU +.Op Fl C Ar XxY +.Op Fl F Ar flags .Op Fl t Ar target-client +.Op Ar adjustment .Xc .D1 (alias: Ic refresh ) Refresh the current client if bound to a key, or a single client if one is given @@ -931,8 +1180,63 @@ If .Fl S is specified, only update the client's status line. .Pp +The +.Fl U , +.Fl D , +.Fl L +.Fl R , +and +.Fl c +flags allow the visible portion of a window which is larger than the client +to be changed. +.Fl U +moves the visible part up by +.Ar adjustment +rows and +.Fl D +down, +.Fl L +left by +.Ar adjustment +columns and +.Fl R +right. +.Fl c +returns to tracking the cursor automatically. +If +.Ar adjustment +is omitted, 1 is used. +Note that the visible position is a property of the client not of the +window, changing the current window in the attached session will reset +it. +.Pp .Fl C -sets the width and height of a control client. +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. +.Pp +.Fl l +requests the clipboard from the client using the +.Xr xterm 1 +escape sequence and stores it in a new paste buffer. +.Pp +.Fl L , +.Fl R , +.Fl U +and +.Fl D +move the visible portion of the window left, right, up or down +by +.Ar adjustment , +if the window is larger than the client. +.Fl c +resets so that the position follows the cursor. +See the +.Ic window-size +option. .It Xo Ic rename-session .Op Fl t Ar target-session .Ar new-name @@ -959,43 +1263,26 @@ and .Fl T show debugging information about jobs and terminals. .It Xo Ic source-file -.Op Fl q +.Op Fl nqv .Ar path +.Ar ... .Xc .D1 (alias: Ic source ) -Execute commands from +Execute commands from one or more files specified by .Ar path -(which may be a -.Xr glob 3 -pattern). +(which may be +.Xr glob 7 +patterns). If .Fl q is given, no error will be returned if .Ar path does not exist. -.Pp -Within a configuration file, commands may be made conditional by surrounding -them with -.Em %if -and -.Em %endif -lines. -The argument to -.Em %if -is expanded as a format and if it evaluates to false -(zero or empty), subsequent lines are ignored until -.Em %endif . -For example: -.Bd -literal -offset indent -%if #{==:#{host},myhost} -set -g status-style bg=red -%endif -.Ed -.Pp -Will change the status line to red if running on -.Ql myhost . -.Em %if -may not be nested. +With +.Fl n , +the file is parsed but no commands are executed. +.Fl v +shows the parsed commands and line numbers if possible. .It Ic start-server .D1 (alias: Ic start ) Start the @@ -1009,7 +1296,7 @@ Suspend a client by sending .Dv SIGTSTP (tty stop). .It Xo Ic switch-client -.Op Fl Elnpr +.Op Fl ElnprZ .Op Fl c Ar target-client .Op Fl t Ar target-session .Op Fl T Ar key-table @@ -1019,6 +1306,17 @@ Switch the current session for client .Ar target-client to .Ar target-session . +As a special case, +.Fl t +may refer to a pane (a target that contains +.Ql \&: , +.Ql \&. +or +.Ql % ) , +to change session, window and pane. +In that case, +.Fl Z +keeps the window zoomed if it was zoomed. If .Fl l , .Fl n @@ -1054,11 +1352,41 @@ bind-key -Troot a switch-client -Ttable1 .Ed .El .Sh WINDOWS AND PANES -A +Each window displayed by +.Nm +may be split into one or more +.Em panes ; +each pane takes up a certain area of the display and is a separate terminal. +A window may be split into panes using the +.Ic split-window +command. +Windows may be split horizontally (with the +.Fl h +flag) or vertically. +Panes may be resized with the +.Ic resize-pane +command (bound to +.Ql C-Up , +.Ql C-Down +.Ql C-Left +and +.Ql C-Right +by default), the current pane may be changed with the +.Ic select-pane +command and the +.Ic rotate-window +and +.Ic swap-pane +commands may be used to swap panes without changing their position. +Panes are numbered beginning from zero in the order they are created. +.Pp +By default, a .Nm -window may be in one of two modes. -The default permits direct access to the terminal attached to the window. -The other is copy mode, which permits a section of a window or its +pane permits direct access to the terminal contained in the pane. +A pane may also be put into one of several modes: +.Bl -dash -offset indent +.It +Copy mode, which permits a section of a window or its history to be copied to a .Em paste buffer for later insertion into another window. @@ -1067,9 +1395,24 @@ This mode is entered with the command, bound to .Ql \&[ by default. -It is also entered when a command that produces output, such as +.It +View mode, which is like copy mode but is entered when a command that produces +output, such as .Ic list-keys , is executed from a key binding. +.It +Choose mode, which allows an item to be chosen from a list. +This may be a client, a session or window or pane, or a buffer. +This mode is entered with the +.Ic choose-buffer , +.Ic choose-client +and +.Ic choose-tree +commands. +.El +.Pp +In copy mode an indicator is displayed in the top-right corner of the pane with +the current position and the number of lines in the history. .Pp Commands are sent to copy mode using the .Fl X @@ -1089,7 +1432,7 @@ Key tables may be viewed with the command. .Pp The following commands are supported in copy mode: -.Bl -column "CommandXXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXXXXXXXX" "emacs" -offset indent +.Bl -column "CommandXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXXXXXXXX" "emacs" -offset indent .It Sy "Command" Ta Sy "vi" Ta Sy "emacs" .It Li "append-selection" Ta "" Ta "" .It Li "append-selection-and-cancel" Ta "A" Ta "" @@ -1098,22 +1441,26 @@ 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-line" Ta "" Ta "" -.It Li "copy-pipe " Ta "" Ta "" -.It Li "copy-pipe-and-cancel " Ta "" Ta "" -.It Li "copy-selection" Ta "" Ta "" -.It Li "copy-selection-and-cancel" Ta "Enter" Ta "M-w" +.It Li "copy-end-of-line []" 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-selection []" Ta "" Ta "" +.It Li "copy-selection-no-clear []" Ta "" Ta "" +.It Li "copy-selection-and-cancel []" Ta "Enter" Ta "M-w" .It Li "cursor-down" Ta "j" Ta "Down" +.It Li "cursor-down-and-cancel" Ta "" Ta "" .It Li "cursor-left" Ta "h" Ta "Left" .It Li "cursor-right" Ta "l" Ta "Right" .It Li "cursor-up" Ta "k" Ta "Up" .It Li "end-of-line" Ta "$" Ta "C-e" .It Li "goto-line " Ta ":" Ta "g" .It Li "halfpage-down" Ta "C-d" Ta "M-Down" +.It Li "halfpage-down-and-cancel" Ta "" Ta "" .It Li "halfpage-up" Ta "C-u" Ta "M-Up" -.It Li "history-bottom" Ta "G" Ta "M-<" -.It Li "history-top" Ta "g" Ta "M->" +.It Li "history-bottom" Ta "G" Ta "M->" +.It Li "history-top" Ta "g" Ta "M-<" .It Li "jump-again" Ta ";" Ta ";" .It Li "jump-backward " Ta "F" Ta "F" .It Li "jump-forward " Ta "f" Ta "f" @@ -1121,6 +1468,7 @@ The following commands are supported in copy mode: .It Li "jump-to-backward " Ta "T" Ta "" .It Li "jump-to-forward " Ta "t" Ta "" .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-}" .It Li "next-space" Ta "W" Ta "" .It Li "next-space-end" Ta "E" Ta "" @@ -1128,12 +1476,15 @@ The following commands are supported in copy mode: .It Li "next-word-end" Ta "e" Ta "M-f" .It Li "other-end" Ta "o" Ta "" .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 "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-toggle" Ta "v" 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 "" @@ -1142,11 +1493,28 @@ The following commands are supported in copy mode: .It Li "search-forward-incremental " Ta "" Ta "C-s" .It Li "search-reverse" Ta "N" Ta "N" .It Li "select-line" Ta "V" Ta "" +.It Li "select-word" Ta "" Ta "" .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 +Copy commands may take an optional buffer prefix argument which is used +to generate the buffer name (the default is +.Ql buffer +so buffers are named +.Ql buffer0 , +.Ql buffer1 +and so on). +Pipe commands take a command argument which is the command to which the +copied text is piped. +The +.Ql -and-cancel +variants of some commands exit copy mode after they have completed (for copy +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 _ @@ -1204,37 +1572,7 @@ bind PageUp copy-mode -eu .Ed .El .Pp -Each window displayed by -.Nm -may be split into one or more -.Em panes ; -each pane takes up a certain area of the display and is a separate terminal. -A window may be split into panes using the -.Ic split-window -command. -Windows may be split horizontally (with the -.Fl h -flag) or vertically. -Panes may be resized with the -.Ic resize-pane -command (bound to -.Ql C-Up , -.Ql C-Down -.Ql C-Left -and -.Ql C-Right -by default), the current pane may be changed with the -.Ic select-pane -command and the -.Ic rotate-window -and -.Ic swap-pane -commands may be used to swap panes without changing their position. -Panes are numbered beginning from zero in the order they are created. -.Pp -A number of preset -.Em layouts -are available. +A number of preset arrangements of panes are available, these are called layouts. These may be selected with the .Ic select-layout command or cycled with @@ -1313,7 +1651,7 @@ By default, it uses the format but a different format may be specified with .Fl F . .It Xo Ic capture-pane -.Op Fl aepPqCJ +.Op Fl aepPqCJN .Op Fl b Ar buffer-name .Op Fl E Ar end-line .Op Fl S Ar start-line @@ -1338,8 +1676,10 @@ is given, the output includes escape sequences for text and background attributes. .Fl C also escapes non-printable characters as octal \exxx. +.Fl N +preserves trailing spaces at each line's end and .Fl J -joins wrapped lines and preserves trailing spaces at each line's end. +preserves trailing spaces and joins any wrapped lines. .Fl P captures only any output that the pane has received that is the beginning of an as-yet incomplete escape sequence. @@ -1358,7 +1698,7 @@ the end of the visible pane. The default is to capture only the visible contents of the pane. .It Xo .Ic choose-client -.Op Fl N +.Op Fl NrZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl O Ar sort-order @@ -1367,6 +1707,8 @@ The default is to capture only the visible contents of the pane. .Xc Put a pane into client mode, allowing a client to be selected interactively from a list. +.Fl Z +zooms the pane. The following keys may be used in client mode: .Bl -column "Key" "Function" -offset indent .It Sy "Key" Ta Sy "Function" @@ -1385,7 +1727,8 @@ The following keys may be used in client mode: .It Li "z" Ta "Suspend selected client" .It Li "Z" Ta "Suspend tagged clients" .It Li "f" Ta "Enter a format to filter items" -.It Li "O" Ta "Change sort order" +.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 @@ -1400,12 +1743,14 @@ If is not given, "detach-client -t '%%'" is used. .Pp .Fl O -specifies the initial sort order: one of +specifies the initial sort field: one of .Ql name , .Ql size , .Ql creation , or .Ql activity . +.Fl r +reverses the sort order. .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. @@ -1417,7 +1762,7 @@ starts without the preview. This command works only if at least one client is attached. .It Xo .Ic choose-tree -.Op Fl Nsw +.Op Fl GNrswZ .Op Fl F Ar format .Op Fl f Ar filter .Op Fl O Ar sort-order @@ -1430,12 +1775,16 @@ interactively from a list. starts with sessions collapsed and .Fl w with windows collapsed. +.Fl Z +zooms the pane. The following keys may be used in tree mode: .Bl -column "Key" "Function" -offset indent .It Sy "Key" Ta Sy "Function" .It Li "Enter" Ta "Choose selected item" .It Li "Up" Ta "Select previous item" .It Li "Down" Ta "Select next item" +.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" @@ -1445,7 +1794,8 @@ The following keys may be used in tree mode: .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 "O" Ta "Change sort order" +.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 @@ -1460,11 +1810,13 @@ If is not given, "switch-client -t '%%'" is used. .Pp .Fl O -specifies the initial sort order: one of +specifies the initial sort field: one of .Ql index , .Ql name , or .Ql time . +.Fl r +reverses the sort order. .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. @@ -1473,9 +1825,13 @@ If a filter would lead to an empty list, it is ignored. specifies the format for each item in the tree. .Fl N starts without the preview. +.Fl G +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 display-panes +.Op Fl b .Op Fl d Ar duration .Op Fl t Ar target-client .Op Ar template @@ -1509,15 +1865,20 @@ substituted by the pane ID. The default .Ar template is "select-pane -t '%%'". +With +.Fl b , +other commands are not blocked from running until the indicator is closed. .It Xo Ic find-window -.Op Fl CNT +.Op Fl rCNTZ .Op Fl t Ar target-pane .Ar match-string .Xc .D1 (alias: Ic findw ) -Search for the +Search for a .Xr fnmatch 3 -pattern +pattern or, with +.Fl r , +regular expression .Ar match-string in window names, titles, and visible content (but not history). The flags control matching behavior: @@ -1529,13 +1890,13 @@ matches only the window name and matches only the window title. The default is .Fl CNT . +.Fl Z +zooms the pane. .Pp This command works only if at least one client is attached. .It Xo Ic join-pane -.Op Fl bdhv -.Oo Fl l -.Ar size | -.Fl p Ar percentage Oc +.Op Fl bdfhv +.Op Fl l Ar size .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc @@ -1586,11 +1947,13 @@ The option kills all but the window given with .Fl t . .It Xo Ic last-pane -.Op Fl de +.Op Fl deZ .Op Fl t Ar target-window .Xc .D1 (alias: Ic lastp ) Select the last (previously selected) pane. +.Fl Z +keeps the window zoomed if it was zoomed. .Fl e enables or .Fl d @@ -1670,9 +2033,7 @@ flag, see the section. .It Xo Ic move-pane .Op Fl bdhv -.Oo Fl l -.Ar size | -.Fl p Ar percentage Oc +.Op Fl l Ar size .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc @@ -1705,6 +2066,7 @@ option. .It Xo Ic new-window .Op Fl adkP .Op Fl c Ar start-directory +.Op Fl e Ar environment .Op Fl F Ar format .Op Fl n Ar window-name .Op Fl t Ar target-window @@ -1744,6 +2106,12 @@ See the .Ic remain-on-exit option to change this behaviour. .Pp +.Fl e +takes the form +.Ql VARIABLE=value +and sets an environment variable for the newly created window; it may be +specified multiple times. +.Pp The .Ev TERM environment variable must be set to @@ -1756,7 +2124,9 @@ for all programs running New windows will automatically have .Ql TERM=screen added to their environment, but care must be taken not to reset this in shell -start-up files. +start-up files or by the +.Fl e +option. .Pp The .Fl P @@ -1778,15 +2148,15 @@ If .Fl a is used, move to the next window with an alert. .It Xo Ic pipe-pane -.Op Fl o +.Op Fl IOo .Op Fl t Ar target-pane .Op Ar shell-command .Xc .D1 (alias: Ic pipep ) -Pipe any output sent by the program in +Pipe output sent by the program in .Ar target-pane -to a shell command. -A pane may only be piped to one command at a time, any existing pipe is +to a shell command or vice versa. +A pane may only be connected to one command at a time, any existing pipe is closed before .Ar shell-command is executed. @@ -1799,6 +2169,25 @@ If no .Ar shell-command is given, the current pipe (if any) is closed. .Pp +.Fl I +and +.Fl O +specify which of the +.Ar shell-command +output streams are connected to the pane: +with +.Fl I +stdout is connected (so anything +.Ar shell-command +prints is written to the pane as if it were typed); +with +.Fl O +stdin is connected (so any output in the pane is piped to +.Ar shell-command ) . +Both may be used together and if neither are specified, +.Fl O +is used. +.Pp The .Fl o option only opens a new pipe if no previous pipe exists, allowing a pipe to @@ -1853,9 +2242,15 @@ or .Fl y . The .Ar adjustment -is given in lines or cells (the default is 1). -.Pp -With +is given in lines or columns (the default is 1); +.Fl x +and +.Fl y +may be a given as a number of lines or columns or followed by +.Ql % +for a percentage of the window size (for example +.Ql -x 10% ) . +With .Fl Z , the active pane is toggled between zoomed (occupying the whole of the window) and unzoomed (its normal position in the layout). @@ -1863,9 +2258,42 @@ 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 ) . +.It Xo Ic resize-window +.Op Fl aADLRU +.Op Fl t Ar target-window +.Op Fl x Ar width +.Op Fl y Ar height +.Op Ar adjustment +.Xc +.D1 (alias: Ic resizew ) +Resize a window, up, down, left or right by +.Ar adjustment +with +.Fl U , +.Fl D , +.Fl L +or +.Fl R , +or +to an absolute size +with +.Fl x +or +.Fl y . +The +.Ar adjustment +is given in lines or cells (the default is 1). +.Fl A +sets the size of the largest session containing the window; +.Fl a +the size of the smallest. +This command will automatically set +.Ic window-size +to manual in the window options. .It Xo Ic respawn-pane -.Op Fl c Ar start-directory .Op Fl k +.Op Fl c Ar start-directory +.Op Fl e Ar environment .Op Fl t Ar target-pane .Op Ar shell-command .Xc @@ -1881,9 +2309,15 @@ The pane must be already inactive, unless is given, in which case any existing command is killed. .Fl c specifies a new working directory for the pane. +The +.Fl e +option has the same meaning as for the +.Ic new-window +command. .It Xo Ic respawn-window -.Op Fl c Ar start-directory .Op Fl k +.Op Fl c Ar start-directory +.Op Fl e Ar environment .Op Fl t Ar target-window .Op Ar shell-command .Xc @@ -1899,8 +2333,13 @@ The window must be already inactive, unless is given, in which case any existing command is killed. .Fl c specifies a new working directory for the window. +The +.Fl e +option has the same meaning as for the +.Ic new-window +command. .It Xo Ic rotate-window -.Op Fl DU +.Op Fl DUZ .Op Fl t Ar target-window .Xc .D1 (alias: Ic rotatew ) @@ -1908,9 +2347,11 @@ Rotate the positions of the panes within a window, either upward (numerically lower) with .Fl U or downward (numerically higher). +.Fl Z +keeps the window zoomed if it was zoomed. .It Xo Ic select-layout -.Op Fl nop -.Op Fl t Ar target-window +.Op Fl Enop +.Op Fl t Ar target-pane .Op Ar layout-name .Xc .D1 (alias: Ic selectl ) @@ -1928,9 +2369,10 @@ and commands. .Fl o applies the last set layout if possible (undoes the most recent layout change). +.Fl E +spreads the current pane and any panes next to it out evenly. .It Xo Ic select-pane -.Op Fl DdegLlMmRU -.Op Fl P Ar style +.Op Fl DdeLlMmRUZ .Op Fl T Ar title .Op Fl t Ar target-pane .Xc @@ -1938,9 +2380,7 @@ applies the last set layout if possible (undoes the most recent layout change). Make pane .Ar target-pane the active pane in window -.Ar target-window , -or set its style (with -.Fl P ) . +.Ar target-window . If one of .Fl D , .Fl L , @@ -1949,6 +2389,8 @@ or .Fl U is used, respectively the pane below, to the left, to the right, or above the target pane is used. +.Fl Z +keeps the window zoomed if it was zoomed. .Fl l is the same as using the .Ic last-pane @@ -1957,6 +2399,8 @@ command. enables or .Fl d disables input to the pane. +.Fl T +sets the pane title. .Pp .Fl m and @@ -1971,25 +2415,6 @@ to .Ic swap-pane and .Ic swap-window . -.Pp -Each pane has a style: by default the -.Ic window-style -and -.Ic window-active-style -options are used, -.Ic select-pane -.Fl P -sets the style for a single pane. -For example, to set the pane 1 background to red: -.Bd -literal -offset indent -select-pane -t:.1 -P 'bg=red' -.Ed -.Pp -.Fl g -shows the current pane style. -.Pp -.Fl T -sets the pane title. .It Xo Ic select-window .Op Fl lnpT .Op Fl t Ar target-window @@ -2013,11 +2438,10 @@ 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 bdfhvP +.Op Fl bdfhIvP .Op Fl c Ar start-directory -.Oo Fl l -.Ar size | -.Fl p Ar percentage Oc +.Op Fl e Ar environment +.Op Fl l Ar size .Op Fl t Ar target-pane .Op Ar shell-command .Op Fl F Ar format @@ -2033,10 +2457,12 @@ a vertical split; if neither is specified, is assumed. The .Fl l -and -.Fl p -options specify the size of the new pane in lines (for vertical split) or in -cells (for horizontal split), or as a percentage, respectively. +option specifies the size of the new pane in lines (for vertical split) or in +columns (for horizontal split); +.Ar size +may be followed by +.Ql % +to specify a percentage of the available space. The .Fl b option causes the new pane to be created to the left of or above @@ -2048,11 +2474,29 @@ option creates a new pane spanning the full window height (with or full window width (with .Fl v ) , instead of splitting the active pane. +.Pp +An empty +.Ar shell-command +('') will create a pane with no command running in it. +Output can be sent to such a pane with the +.Ic display-message +command. +The +.Fl I +flag (if +.Ar shell-command +is not specified or empty) +will create an empty pane and forward any output from stdin to it. +For example: +.Bd -literal -offset indent +$ make 2>&1|tmux splitw -dI & +.Ed +.Pp All other options have the same meaning as for the .Ic new-window command. .It Xo Ic swap-pane -.Op Fl dDU +.Op Fl dDUZ .Op Fl s Ar src-pane .Op Fl t Ar dst-pane .Xc @@ -2069,7 +2513,9 @@ swaps with the next pane (after it numerically). .Fl d instructs .Nm -not to change the active pane. +not to change the active pane and +.Fl Z +keeps the window zoomed if it was zoomed. .Pp If .Fl s @@ -2159,6 +2605,10 @@ bind-key '"' split-window bind-key "'" new-window .Ed .Pp +A command bound to the +.Em Any +key will execute for all keys which do not have a more specific binding. +.Pp Commands related to key bindings are as follows: .Bl -tag -width Ds .It Xo Ic bind-key @@ -2227,7 +2677,7 @@ With only .Ar key-table . .It Xo Ic send-keys -.Op Fl lMRX +.Op Fl FHlMRX .Op Fl N Ar repeat-count .Op Fl t Ar target-pane .Ar key Ar ... @@ -2242,10 +2692,16 @@ or .Ql NPage ) 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. +.Pp The .Fl l -flag disables key name lookup and sends the keys literally. -All arguments are sent sequentially from first to last. +flag disables key name lookup and processes the keys as literal UTF-8 +characters. +The +.Fl H +flag expects each key to be a hexadecimal number for an ASCII character. +.Pp The .Fl R flag causes the terminal state to be reset. @@ -2260,7 +2716,9 @@ the .Sx WINDOWS AND PANES section. .Fl N -specifies a repeat count. +specifies a repeat count and +.Fl F +expands formats in arguments where appropriate. .It Xo Ic send-prefix .Op Fl 2 .Op Fl t Ar target-pane @@ -2289,16 +2747,17 @@ is present, all key bindings are removed. The appearance and behaviour of .Nm may be modified by changing the value of various options. -There are three types of option: +There are four types of option: .Em server options , .Em session options +.Em window options and -.Em window options . +.Em pane options . .Pp The .Nm -server has a set of global options which do not apply to any particular -window or session. +server has a set of global server options which do not apply to any particular +window or session or pane. These are altered with the .Ic set-option .Fl s @@ -2320,16 +2779,29 @@ The available server and session options are listed under the .Ic set-option command. .Pp -Similarly, a set of window options is attached to each window, and there is -a set of global window options from which any unset options are inherited. -Window options are altered with the -.Ic set-window-option -command and can be listed with the -.Ic show-window-options -command. -All window options are documented with the -.Ic set-window-option -command. +Similarly, a set of window options is attached to each window and a set of pane +options to each pane. +Pane options inherit from window options. +This means any pane option may be set as a window option to apply the option to +all panes in the window without the option set, for example these commands will +set the background colour to red for all panes except pane 0: +.Bd -literal -offset indent +set -w window-style bg=red +set -pt:.0 window-style bg=blue +.Ed +.Pp +There is also a set of global window options from which any unset window or +pane options are inherited. +Window and pane options are altered with +.Ic set-option +.Fl w +and +.Fl p +commands and displayed with +.Ic show-option +.Fl w +and +.Fl p . .Pp .Nm also supports user options which are prefixed with a @@ -2347,22 +2819,31 @@ abc123 Commands which set options are as follows: .Bl -tag -width Ds .It Xo Ic set-option -.Op Fl aFgoqsuw -.Op Fl t Ar target-session | Ar target-window +.Op Fl aFgopqsuw +.Op Fl t Ar target-pane .Ar option Ar value .Xc .D1 (alias: Ic set ) -Set a window option with -.Fl w -(equivalent to the -.Ic set-window-option -command), +Set a pane option with +.Fl p , +a window option with +.Fl w , a server option with .Fl s , otherwise a session option. +If the option is not a user option, +.Fl w +or +.Fl s +may be unnecessary - +.Nm +will infer the type from the option name, assuming +.Fl w +for pane options. If .Fl g is given, the global session or window option is set. +.Pp .Fl F expands formats in the option value. The @@ -2403,16 +2884,56 @@ blue foreground. Without .Fl a , the result would be the default background and a blue foreground. -.Pp -Available window options are listed under -.Ic set-window-option . -.Pp +.It Xo Ic show-options +.Op Fl AgHpqsvw +.Op Fl t Ar target-pane +.Op Ar option +.Xc +.D1 (alias: Ic show ) +Show the pane options (or a single option if +.Ar option +is provided) with +.Fl p , +the window options with +.Fl w , +the server options with +.Fl s , +otherwise the session options. +If the option is not a user option, +.Fl w +or +.Fl s +may be unnecessary - +.Nm +will infer the type from the option name, assuming +.Fl w +for pane options. +Global session or window options are listed if +.Fl g +is used. +.Fl v +shows only the option value, not the name. +If +.Fl q +is set, no error will be returned if +.Ar option +is unset. +.Fl H +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: .Bl -tag -width Ds +.It Ic backspace Ar key +Set the key sent by +.Nm +for backspace. .It Ic buffer-limit Ar number Set the number of buffers; as new buffers are added to the top of the stack, old ones are removed from the bottom if necessary to maintain this maximum @@ -2460,6 +2981,11 @@ Set the time in milliseconds for which 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 Xo Ic exit-empty +.Op Ic on | off +.Xc +If enabled (the default), the server will exit when there are no active +sessions. .It Xo Ic exit-unattached .Op Ic on | off .Xc @@ -2545,6 +3071,18 @@ for all terminal types matching The terminal entry value is passed through .Xr strunvis 3 before interpretation. +.It Ic user-keys[] Ar key +Set list of user-defined key escape sequences. +Each item is associated with a key named +.Ql User0 , +.Ql User1 , +and so on. +.Pp +For example: +.Bd -literal -offset indent +set -s user-keys[0] "\ee[5;30012~" +bind User0 resize-pane -L 3 +.Ed .El .Pp Available session options are: @@ -2615,6 +3153,16 @@ or This option should be configured when .Nm is used as a login shell. +.It Ic default-size Ar XxY +Set the default size of new windows when the +.Ic window-size +option is set to manual or when a session is created with +.Ic new-session +.Fl d . +The value is the width and height separated by an +.Ql x +character. +The default is 80x24. .It Xo Ic destroy-unattached .Op Ic on | off .Xc @@ -2668,74 +3216,19 @@ The default is to run with .Fl np . .It Ic message-command-style Ar style -Set status line message command style, where -.Ar style -is a comma-separated list of characteristics to be specified. -.Pp -These may be -.Ql bg=colour -to set the background colour, -.Ql fg=colour -to set the foreground colour, and a list of attributes as specified below. -.Pp -The colour is one of: -.Ic black , -.Ic red , -.Ic green , -.Ic yellow , -.Ic blue , -.Ic magenta , -.Ic cyan , -.Ic white , -aixterm bright variants (if supported: -.Ic brightred , -.Ic brightgreen , -and so on), -.Ic colour0 -to -.Ic colour255 -from the 256-colour set, -.Ic default , -or a hexadecimal RGB string such as -.Ql #ffffff , -which chooses the closest match from the default 256-colour set. -.Pp -The attributes is either -.Ic none -or a comma-delimited list of one or more of: -.Ic bright -(or -.Ic bold ) , -.Ic dim , -.Ic underscore , -.Ic blink , -.Ic reverse , -.Ic hidden , -.Ic italics , -or -.Ic strikethrough -to turn an attribute on, or an attribute prefixed with -.Ql no -to turn one off. -.Pp -Examples are: -.Bd -literal -offset indent -fg=yellow,bold,underscore,blink -bg=black,fg=default,noreverse -.Ed -.Pp -With the -.Fl a -flag to the -.Ic set-option -command the new style is added otherwise the existing style is replaced. +Set status line message command style. +For how to specify +.Ar style , +see the +.Sx STYLES +section. .It Ic message-style Ar style Set status line message style. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .It Xo Ic mouse .Op Ic on | off .Xc @@ -2796,7 +3289,7 @@ the terminal appears to be .Xr xterm 1 . This option is off by default. .It Ic set-titles-string Ar string -String used to set the window title if +String used to set the client terminal title if .Ic set-titles is on. Formats are expanded, see the @@ -2811,9 +3304,22 @@ is on. The values are the same as those for .Ic activity-action . .It Xo Ic status -.Op Ic on | off +.Op Ic off | on | 2 | 3 | 4 | 5 .Xc -Show or hide the status line. +Show or hide the status line or specify its size. +Using +.Ic on +gives a status line one row in height; +.Ic 2 , +.Ic 3 , +.Ic 4 +or +.Ic 5 +more rows. +.It Ic status-format[] Ar format +Specify the format to be used for each line of the status line. +The default builds the top status line from the various individual status +options below. .It Ic status-interval Ar interval Update the status line every .Ar interval @@ -2842,17 +3348,12 @@ Display (by default the session name) to the left of the status line. .Ar string will be passed through -.Xr strftime 3 -and formats (see -.Sx FORMATS ) -will be expanded. -It may also contain the special character sequence #[] to change the colour -or attributes, for example -.Ql #[fg=red,bright] -to set a bright red foreground. -See the -.Ic message-command-style -option for a description of colours and attributes. +.Xr strftime 3 . +Also see the +.Sx FORMATS +and +.Sx STYLES +sections. .Pp For details on how the names and titles can be set see the .Sx "NAMES AND TITLES" @@ -2876,8 +3377,8 @@ Set the style of the left part of the status line. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .It Xo Ic status-position .Op Ic top | bottom .Xc @@ -2904,15 +3405,15 @@ Set the style of the right part of the status line. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .It Ic status-style Ar style Set status line style. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .It Ic update-environment[] Ar variable Set list of environment variables to be copied into the session environment when a new session is created or an existing session is attached. @@ -2922,18 +3423,6 @@ removed from the session environment (as if was given to the .Ic set-environment command). -.It Ic user-keys[] Ar key -Set list of user-defined key escape sequences. -Each item is associated with a key named -.Ql User0 , -.Ql User1 , -and so on. -.Pp -For example: -.Bd -literal -offset indent -set -s user-keys[0] "\ee[5;30012~" -bind User0 resize-pane -L 3 -.Ed .It Xo Ic visual-activity .Op Ic on | off | both .Xc @@ -2968,26 +3457,8 @@ copy mode. The default is .Ql \ -_@ . .El -.It Xo Ic set-window-option -.Op Fl aFgoqu -.Op Fl t Ar target-window -.Ar option Ar value -.Xc -.D1 (alias: Ic setw ) -Set a window option. -The -.Fl a , -.Fl F , -.Fl g , -.Fl o , -.Fl q -and -.Fl u -flags work similarly to the -.Ic set-option -command. .Pp -Supported window options are: +Available window options are: .Pp .Bl -tag -width Ds -compact .It Xo Ic aggressive-resize @@ -2996,36 +3467,16 @@ Supported window options are: Aggressively resize the chosen window. This means that .Nm -will resize the window to the size of the smallest session for which it is the -current window, rather than the smallest session to which it is attached. -The window may resize when the current window is changed on another sessions; -this option is good for full-screen programs which support +will resize the window to the size of the smallest or largest session +(see the +.Ic window-size +option) for which it is the current window, rather than the session to +which it is attached. +The window may resize when the current window is changed on another +session; this option is good for full-screen programs which support .Dv SIGWINCH and poor for interactive programs such as shells. .Pp -.It Xo Ic allow-rename -.Op Ic on | off -.Xc -Allow programs to change the window name using a terminal escape -sequence (\eek...\ee\e\e). -The default is on. -.Pp -.It Xo Ic alternate-screen -.Op Ic on | off -.Xc -This option configures whether programs running inside -.Nm -may use the terminal alternate screen feature, which allows the -.Em smcup -and -.Em rmcup -.Xr terminfo 5 -capabilities. -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. -The default is on. -.Pp .It Xo Ic automatic-rename .Op Ic on | off .Xc @@ -3044,7 +3495,7 @@ or later with or with a terminal escape sequence. It may be switched off globally with: .Bd -literal -offset indent -set-window-option -g automatic-rename off +set-option -wg automatic-rename off .Ed .Pp .It Ic automatic-rename-format Ar format @@ -3062,16 +3513,6 @@ Set clock colour. .Xc Set clock hour format. .Pp -.It Ic force-height Ar height -.It Ic force-width Ar width -Prevent -.Nm -from resizing a window to greater than -.Ar width -or -.Ar height . -A value of zero restores the default unlimited setting. -.Pp .It Ic main-pane-height Ar height .It Ic main-pane-width Ar width Set the width or height of the main (left or top) pane in the @@ -3096,8 +3537,8 @@ Set window modes style. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp .It Xo Ic monitor-activity .Op Ic on | off @@ -3145,8 +3586,8 @@ Set the pane border style for the currently active pane. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. Attributes are ignored. .Pp .It Ic pane-base-index Ar index @@ -3167,48 +3608,31 @@ Set the pane border style for panes aside from the active pane. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. Attributes are ignored. .Pp -.It Xo Ic remain-on-exit -.Op Ic on | off -.Xc -A window with this flag set is not destroyed when the program running in it -exits. -The window may be reactivated with the -.Ic respawn-window -command. -.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-active-style Ar style -Set the style for the window's active pane. -For how to specify -.Ar style , -see the -.Ic message-command-style -option. -.Pp .It Ic window-status-activity-style Ar style Set status line style for windows with an activity alert. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp .It Ic window-status-bell-style Ar style Set status line style for windows with a bell alert. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp .It Ic window-status-current-format Ar string Like @@ -3220,24 +3644,24 @@ Set status line style for the currently active window. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp .It Ic window-status-format Ar string Set the format in which the window is displayed in the status line window list. See the -.Ar status-left -option for details of special character sequences available. -The default is -.Ql #I:#W#F . +.Sx FORMATS +and +.Sx STYLES +sections. .Pp .It Ic window-status-last-style Ar style Set status line style for the last active window. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp .It Ic window-status-separator Ar string Sets the separator drawn between windows in the status line. @@ -3248,15 +3672,33 @@ Set status line style for a single window. For how to specify .Ar style , see the -.Ic message-command-style -option. +.Sx STYLES +section. .Pp -.It Ic window-style Ar style -Set the default window style. -For how to specify -.Ar style , -see the -.Ic message-command-style +.It Xo Ic window-size +.Ar largest | Ar smallest | Ar manual | Ar latest +.Xc +Configure how +.Nm +determines the window size. +If set to +.Ar largest , +the size of the largest attached session is used; if +.Ar smallest , +the size of the smallest. +If +.Ar manual , +the size of a new window is set from the +.Ic default-size +option and windows are resized automatically. +With +.Ar latest , +.Nm +uses the size of the client that had the most recent activity. +See also the +.Ic resize-window +command and the +.Ic aggressive-resize option. .Pp .It Xo Ic wrap-search @@ -3275,43 +3717,54 @@ will generate function key sequences; these have a number included to indicate modifiers such as Shift, Alt or Ctrl. .El -.It Xo Ic show-options -.Op Fl gqsvw -.Op Fl t Ar target-session | Ar target-window -.Op Ar option +.Pp +Available pane options are: +.Pp +.Bl -tag -width Ds -compact +.It Xo Ic allow-rename +.Op Ic on | off .Xc -.D1 (alias: Ic show ) -Show the window options (or a single window option if given) with -.Fl w -(equivalent to -.Ic show-window-options ) , -the server options with -.Fl s , -otherwise the session options for -.Ar target session . -Global session or window options are listed if -.Fl g -is used. -.Fl v -shows only the option value, not the name. -If -.Fl q -is set, no error will be returned if -.Ar option -is unset. -.It Xo Ic show-window-options -.Op Fl gv -.Op Fl t Ar target-window -.Op Ar option +Allow programs in the pane to change the window name using a terminal escape +sequence (\eek...\ee\e\e). +.Pp +.It Xo Ic alternate-screen +.Op Ic on | off .Xc -.D1 (alias: Ic showw ) -List the window options or a single option for -.Ar target-window , -or the global window options if -.Fl g -is used. -.Fl v -shows only the option value, not the name. +This option configures whether programs running inside the pane may use the +terminal alternate screen feature, which allows the +.Em smcup +and +.Em rmcup +.Xr terminfo 5 +capabilities. +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 Xo Ic remain-on-exit +.Op Ic on | off +.Xc +A pane with this flag set is not destroyed when the program running in it +exits. +The pane may be reactivated with the +.Ic respawn-pane +command. +.Pp +.It Ic window-active-style Ar style +Set the pane style when it is the active pane. +For how to specify +.Ar style , +see the +.Sx STYLES +section. +.Pp +.It Ic window-style Ar style +Set the pane style. +For how to specify +.Ar style , +see the +.Sx STYLES +section. .El .Sh HOOKS .Nm @@ -3323,6 +3776,26 @@ commands have an .Em after 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. +Hooks may be configured with the +.Ic set-hook +or +.Ic set-option +commands and displayed with +.Ic show-hooks +or +.Ic show-options +.Fl H . +The following two commands are equivalent: +.Bd -literal -offset indent. +set-hook -g pane-mode-changed[42] 'set -g status-left-style bg=red' +set-option -g pane-mode-changed[42] 'set -g status-left-style bg=red' +.Ed +.Pp +Setting a hook without specifying an array index clears the hook and sets the +first member of the array. +.Pp A command's after hook is run after it completes, except when the command is run as part of a hook itself. @@ -3333,10 +3806,14 @@ For example, the following command adds a hook to select the even-vertical layout after every .Ic split-window : .Bd -literal -offset indent -set-hook after-split-window "selectl even-vertical" +set-hook -g after-split-window "selectl even-vertical" .Ed .Pp -In addition, the following hooks are available: +All the notifications listed in the +.Sx CONTROL MODE +section are hooks (without any arguments), except +.Ic %exit . +The following additional hooks are available: .Bl -tag -width "XXXXXXXXXXXXXXXXXXXXXX" .It alert-activity Run when a window has activity. @@ -3364,6 +3841,14 @@ Run when the program running in a pane exits, but is on so the pane has not closed. .It pane-exited Run when the program running in a pane exits. +.It pane-focus-in +Run when the focus enters a pane, if the +.Ic focus-events +option is on. +.It pane-focus-out +Run when the focus exits a pane, if the +.Ic focus-events +option is on. .It pane-set-clipboard Run when the terminal clipboard is set using the .Xr xterm 1 @@ -3385,12 +3870,14 @@ 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 gu +.Op Fl agRu .Op Fl t Ar target-session .Ar hook-name .Ar command .Xc -Sets (or with +Without +.Fl R , +sets (or with .Fl u unsets) hook .Ar hook-name @@ -3405,7 +3892,15 @@ hooks (for .Ar target-session with .Fl t ) . +.Fl a +appends to a hook. Like options, session hooks inherit from the global ones. +.Pp +With +.Fl R , +run +.Ar hook-name +immediately. .It Xo Ic show-hooks .Op Fl g .Op Fl t Ar target-session @@ -3422,21 +3917,24 @@ option is on (the default is off), allows mouse events to be bound as keys. The name of each key is made up of a mouse event (such as .Ql MouseUp1 ) -and a location suffix (one of -.Ql Pane -for the contents of a pane, -.Ql Border -for a pane border or -.Ql Status -for the status line). +and a location suffix, one of the following: +.Bl -column "XXXXXXXXXXXXX" -offset indent +.It Li "Pane" Ta "the contents of a pane" +.It Li "Border" Ta "a pane border" +.It Li "Status" Ta "the status line window list" +.It Li "StatusLeft" Ta "the left part of the status line" +.It Li "StatusRight" Ta "the right part of the status line" +.It Li "StatusDefault" Ta "any other part of the status line" +.El +.Pp The following mouse events are available: .Bl -column "MouseDown1" "MouseDrag1" "WheelDown" -offset indent .It Li "WheelUp" Ta "WheelDown" Ta "" .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 "DoubleClick1" Ta "DoubleClick2" Ta "DoubleClick3" Ta "WheelUp" -.It Li "TripleClick1" Ta "TripleClick2" Ta "TripleClick3" Ta "WheelDown" +.It Li "DoubleClick1" Ta "DoubleClick2" Ta "DoubleClick3" +.It Li "TripleClick1" Ta "TripleClick2" Ta "TripleClick3" .El .Pp Each should be suffixed with a location, for example @@ -3475,7 +3973,7 @@ flag with a .Ar format argument. This is a string which controls the output format of the command. -Replacement variables are enclosed in +Format variables are enclosed in .Ql #{ and .Ql } , @@ -3485,11 +3983,17 @@ The possible variables are listed in the table below, or the name of a .Nm option may be used for an option's value. Some variables have a shorter alias such as -.Ql #S , -and +.Ql #S ; .Ql ## is replaced by a single -.Ql # . +.Ql # , +.Ql #, +by a +.Ql \&, +and +.Ql #} +by a +.Ql } . .Pp Conditionals are available by prefixing with .Ql \&? @@ -3511,12 +4015,32 @@ if is enabled, or .Ql no if not. +Conditionals can be nested arbitrarily. +Inside a conditional, +.Ql \&, +and +.Ql } +must be escaped as +.Ql #, +and +.Ql #} , +unless they are part of a +.Ql #{...} +replacement. +For example: +.Bd -literal -offset indent +#{?pane_in_mode,#[fg=white#,bg=red],#[fg=red#,bg=white]}#W . +.Ed .Pp -Comparisons may be expressed by prefixing two comma-separated +String comparisons may be expressed by prefixing two comma-separated alternatives by -.Ql == +.Ql == , +.Ql != , +.Ql < , +.Ql > , +.Ql <= or -.Ql != +.Ql >= and a colon. For example .Ql #{==:#{host},myhost} @@ -3526,25 +4050,45 @@ if running on .Ql myhost , otherwise by .Ql 0 . -An -.Ql m -specifies an -.Xr fnmatch 3 -comparison where the first argument is the pattern and the second the string to -compare, for example -.Ql #{m:*foo*,#{host}} . .Ql || and .Ql && evaluate to true if either or both of two comma-separated alternatives are true, for example -.Ql #{||,#{pane_in_mode},#{alternate_on}} . +.Ql #{||:#{pane_in_mode},#{alternate_on}} . +.Pp +An +.Ql m +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: +.Ql r +means the pattern is a regular expression instead of the default +.Xr fnmatch 3 +pattern, and +.Ql i +means to ignore case. +For example: +.Ql #{m:*foo*,#{host}} +or +.Ql #{m/ri:^A,MYVAR} . A .Ql C performs a search for an .Xr fnmatch 3 -pattern in the pane content and evaluates to zero if not found, or a line -number if found. +pattern or regular expression in the pane content and evaluates to zero if not +found, or a line number if found. +Like +.Ql m , +an +.Ql r +flag means search for a regular expression and +.Ql i +ignores case. +For example: +.Ql #{C/r:^Start} .Pp A limit may be placed on the length of the resultant string by prefixing it by an @@ -3553,11 +4097,24 @@ a number and a colon. Positive numbers count from the start of the string and negative from the end, so .Ql #{=5:pane_title} -will include at most the first 5 characters of the pane title, or +will include at most the first five characters of the pane title, or .Ql #{=-5:pane_title} -the last 5 characters. +the last five characters. +A suffix or prefix may be given as a second argument - if provided then it is +appended or prepended to the string if the length has been trimmed, for example +.Ql #{=/5/...:pane_title} +will append +.Ql ... +if the pane title is more than five characters. +Similarly, +.Ql p +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. +.Pp Prefixing a time variable with -.Ql t: +.Ql t:\& will convert it to a string, so if .Ql #{window_activity} gives @@ -3566,23 +4123,60 @@ gives gives .Ql Sun Oct 25 09:25:02 2015 . The -.Ql b: +.Ql b:\& and -.Ql d: +.Ql d:\& prefixes are .Xr basename 3 and .Xr dirname 3 of the variable respectively. +.Ql q:\& +will escape +.Xr sh 1 +special characters. +.Ql E:\& +will expand the format twice, for example +.Ql #{E:status-left} +is the result of expanding the content of the +.Ic status-left +option rather than the option itself. +.Ql T:\& +is like +.Ql E:\& +but also expands +.Xr strftime 3 +specifiers. +.Ql S:\& , +.Ql W:\& +or +.Ql P:\& +will loop over each session, window or pane and insert the format once +for each. +For windows and panes, two comma-separated formats may be given: +the second is used for the current window or active pane. +For example, to get a list of windows formatted like the status line: +.Bd -literal -offset indent +#{W:#{E:window-status-format} ,#{E:window-status-current-format} } +.Ed +.Pp A prefix of the form -.Ql s/foo/bar/: +.Ql s/foo/bar/:\& will substitute .Ql foo with .Ql bar throughout. -.Pp -In addition, the first line of a shell command's output may be inserted using +The first argument may be an extended regular expression and a final argument may be +.Ql i +to ignore case, for example +.Ql s/a(.)/\e1x/i:\& +would change +.Ql abABab +into +.Ql bxBxbx . +.Pp +In addition, the last line of a shell command's output may be inserted using .Ql #() . For example, .Ql #(uptime) @@ -3598,13 +4192,21 @@ line will not be updated more than once a second. Commands are executed with the .Nm global environment set (see the -.Sx ENVIRONMENT +.Sx GLOBAL AND SESSION ENVIRONMENT section). .Pp +An +.Ql l +specifies that a string should be interpreted literally and not expanded. +For example +.Ql #{l:#{?pane_in_mode,yes,no}} +will be replaced by +.Ql #{?pane_in_mode,yes,no} . +.Pp The following variables are available, where appropriate: .Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX" .It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with" -.It Li "alternate_on" Ta "" Ta "If pane is in alternate screen" +.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" .It Li "buffer_created" Ta "" Ta "Time buffer created" @@ -3612,8 +4214,10 @@ The following variables are available, where appropriate: .It Li "buffer_sample" Ta "" Ta "Sample of start of buffer" .It Li "buffer_size" Ta "" Ta "Size of the specified buffer in bytes" .It Li "client_activity" Ta "" Ta "Time client last had activity" -.It Li "client_created" Ta "" Ta "Time client created" +.It Li "client_cell_height" Ta "" Ta "Height of each client cell in pixels" +.It Li "client_cell_width" Ta "" Ta "Width of each client cell in pixels" .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_height" Ta "" Ta "Height of client" .It Li "client_key_table" Ta "" Ta "Current key table" @@ -3626,19 +4230,24 @@ The following variables are available, where appropriate: .It Li "client_termname" Ta "" Ta "Terminal name of client" .It Li "client_termtype" Ta "" Ta "Terminal type of client" .It Li "client_tty" Ta "" Ta "Pseudo terminal of client" -.It Li "client_utf8" Ta "" Ta "1 if client supports utf8" +.It Li "client_utf8" Ta "" Ta "1 if client supports UTF-8" .It Li "client_width" Ta "" Ta "Width of client" .It Li "client_written" Ta "" Ta "Bytes written to client" .It Li "command" Ta "" Ta "Name of command in use, if any" -.It Li "command_list_name" Ta "" Ta "Command name if listing commands" .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 "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 "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" .It Li "cursor_y" Ta "" Ta "Cursor Y position in pane" .It Li "history_bytes" Ta "" Ta "Number of bytes in window history" .It Li "history_limit" Ta "" Ta "Maximum window history lines" -.It Li "history_size" Ta "" Ta "Size of history in bytes" +.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_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" @@ -3651,10 +4260,17 @@ The following variables are available, where appropriate: .It Li "keypad_cursor_flag" Ta "" Ta "Pane keypad cursor flag" .It Li "keypad_flag" Ta "" Ta "Pane keypad flag" .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" .It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag" +.It Li "mouse_line" Ta "" Ta "Line under mouse, if any" +.It Li "mouse_sgr_flag" Ta "" Ta "Pane mouse SGR flag" .It Li "mouse_standard_flag" Ta "" Ta "Pane mouse standard flag" -.It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" +.It Li "mouse_utf8_flag" Ta "" Ta "Pane mouse UTF-8 flag" +.It Li "mouse_word" Ta "" Ta "Word under mouse, if any" +.It Li "mouse_x" Ta "" Ta "Mouse X position, if any" +.It Li "mouse_y" Ta "" Ta "Mouse Y position, if any" +.It Li "origin_flag" Ta "" Ta "Pane origin flag" .It Li "pane_active" Ta "" Ta "1 if active pane" .It Li "pane_at_bottom" Ta "" Ta "1 if pane is at the bottom of window" .It Li "pane_at_left" Ta "" Ta "1 if pane is at the left of window" @@ -3665,69 +4281,224 @@ The following variables are available, where appropriate: .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_format" Ta "" Ta "1 if format is for a pane (not assuming the current)" +.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 "If pane is in a mode" -.It Li "pane_input_off" Ta "" Ta "If input to pane is disabled" +.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_left" Ta "" Ta "Left of pane" -.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any." +.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_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" .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode" .It Li "pane_start_command" Ta "" Ta "Command pane started with" -.It Li "pane_synchronized" Ta "" Ta "If pane is synchronized" +.It Li "pane_synchronized" Ta "" Ta "1 if pane is synchronized" .It Li "pane_tabs" Ta "" Ta "Pane tab positions" -.It Li "pane_title" Ta "#T" Ta "Title of pane" +.It Li "pane_title" Ta "#T" Ta "Title of pane (can be set by application)" .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 "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 "scroll_position" Ta "" Ta "Scroll position 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" .It Li "selection_present" Ta "" Ta "1 if selection started in copy mode" +.It Li "selection_start_x" Ta "" Ta "X position of the start of the selection" +.It Li "selection_start_y" Ta "" Ta "Y position of the start of the selection" +.It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_alerts" Ta "" Ta "List of window indexes with alerts" .It Li "session_attached" Ta "" Ta "Number of clients session is attached to" -.It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_created" Ta "" Ta "Time session created" -.It Li "session_format" Ta "" Ta "1 if format is for a session (not assuming the current)" -.It Li "session_last_attached" Ta "" Ta "Time session last attached" +.It Li "session_format" Ta "" Ta "1 if format is for a session" .It Li "session_group" Ta "" Ta "Name of session group" +.It Li "session_group_list" Ta "" Ta "List of sessions in group" +.It Li "session_group_size" Ta "" Ta "Size of session group" .It Li "session_grouped" Ta "" Ta "1 if session in a group" -.It Li "session_height" Ta "" Ta "Height of session" .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_name" Ta "#S" Ta "Name of session" .It Li "session_stack" Ta "" Ta "Window indexes in most recent order" -.It Li "session_width" Ta "" Ta "Width of session" .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "start_time" Ta "" Ta "Server start time" .It Li "version" Ta "" Ta "Server version" +.It Li "window_active" Ta "" Ta "1 if window active" .It Li "window_activity" Ta "" Ta "Time of window last activity" .It Li "window_activity_flag" Ta "" Ta "1 if window has activity" -.It Li "window_active" Ta "" Ta "1 if window active" .It Li "window_bell_flag" Ta "" Ta "1 if window has bell" +.It Li "window_bigger" Ta "" Ta "1 if window is larger than client" +.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_format" Ta "" Ta "1 if format is for a window (not assuming the current)" +.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" .It Li "window_index" Ta "#I" Ta "Index of window" .It Li "window_last_flag" Ta "" Ta "1 if window is the last used" .It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes" .It Li "window_linked" Ta "" Ta "1 if window is linked across sessions" +.It Li "window_marked_flag" Ta "" Ta "1 if window contains the marked pane" .It Li "window_name" Ta "#W" Ta "Name of window" +.It Li "window_offset_x" Ta "" Ta "X offset into window if larger than client" +.It Li "window_offset_y" Ta "" Ta "Y offset into window if larger than client" .It Li "window_panes" Ta "" Ta "Number of panes in window" .It Li "window_silence_flag" Ta "" Ta "1 if window has silence alert" .It Li "window_stack_index" Ta "" Ta "Index in session most recent stack" +.It Li "window_start_flag" Ta "" Ta "1 if window has the lowest index" .It Li "window_visible_layout" Ta "" Ta "Window layout description, respecting zoomed window panes" .It Li "window_width" Ta "" Ta "Width of window" .It Li "window_zoomed_flag" Ta "" Ta "1 if window is zoomed" .It Li "wrap_flag" Ta "" Ta "Pane wrap flag" .El +.Sh STYLES +.Nm +offers various options to specify the colour and attributes of aspects of the +interface, for example +.Ic status-style +for the status line. +In addition, embedded styles may be specified in format options, such as +.Ic status-left-format , +by enclosing them in +.Ql #[ +and +.Ql \&] . +.Pp +A style may be the single term +.Ql default +to specify the default style (which may come from an option, for example +.Ic status-style +in the status line) or a space +or comma separated list of the following: +.Bl -tag -width Ds +.It Ic fg=colour +Set the foreground colour. +The colour is one of: +.Ic black , +.Ic red , +.Ic green , +.Ic yellow , +.Ic blue , +.Ic magenta , +.Ic cyan , +.Ic white ; +if supported the bright variants +.Ic brightred , +.Ic brightgreen , +.Ic brightyellow ; +.Ic colour0 +to +.Ic colour255 +from the 256-colour set; +.Ic default +for the default colour; +.Ic terminal +for the terminal default colour; or a hexadecimal RGB string such as +.Ql #ffffff . +.It Ic bg=colour +Set the background colour. +.It Ic none +Set no attributes (turn off any active attributes). +.It Xo Ic bright +(or +.Ic bold ) , +.Ic dim , +.Ic underscore , +.Ic blink , +.Ic reverse , +.Ic hidden , +.Ic italics , +.Ic overline , +.Ic strikethrough , +.Ic double-underscore , +.Ic curly-underscore , +.Ic dotted-underscore , +.Ic dashed-underscore +.Xc +Set an attribute. +Any of the attributes may be prefixed with +.Ql no +to unset. +.It Xo Ic align=left +(or +.Ic noalign ) , +.Ic align=centre , +.Ic align=right +.Xc +Align text to the left, centre or right of the available space if appropriate. +.It Ic fill=colour +Fill the available space with a background colour if appropriate. +.It Xo Ic list=on , +.Ic list=focus , +.Ic list=left-marker , +.Ic list=right-marker , +.Ic nolist +.Xc +Mark the position of the various window list components in the +.Ic status-format +option: +.Ic list=on +marks the start of the list; +.Ic list=focus +is the part of the list that should be kept in focus if the entire list won't fit +in the available space (typically the current window); +.Ic list=left-marker +and +.Ic list=right-marker +mark the text to be used to mark that text has been trimmed from the left or +right of the list if there is not enough space. +.It Xo Ic push-default , +.Ic pop-default +.Xc +Store the current colours and attributes as the default or reset to the previous +default. +A +.Ic push-default +affects any subsequent use of the +.Ic default +term until a +.Ic pop-default . +Only one default may be pushed (each +.Ic push-default +replaces the previous saved default). +.It Xo Ic range=left , +.Ic range=right , +.Ic range=window|X , +.Ic norange +.Xc +Mark a range in the +.Ic status-format +option. +.Ic range=left +and +.Ic range=right +are the text used for the +.Ql StatusLeft +and +.Ql StatusRight +mouse keys. +.Ic range=window|X +is the range for a window passed to the +.Ql Status +mouse key, where +.Ql X +is a window index. +.El +.Pp +Examples are: +.Bd -literal -offset indent +fg=yellow bold underscore blink +bg=black,fg=default,noreverse +.Ed .Sh NAMES AND TITLES .Nm distinguishes between names and titles. @@ -3764,7 +4535,9 @@ for or .Ic new-session ) . .It -An escape sequence: +An escape sequence (if the +.Ic allow-rename +option is turned on): .Bd -literal -offset indent $ printf '\e033kWINDOW_NAME\e033\e\e' .Ed @@ -3777,7 +4550,7 @@ option. .El .Pp When a pane is first created, its title is the hostname. -A pane's title can be set via the OSC title setting sequence, for example: +A pane's title can be set via the title setting escape sequence, for example: .Bd -literal -offset indent $ printf '\e033]2;My Title\e033\e\e' .Ed @@ -3786,7 +4559,7 @@ It can also be modified with the .Ic select-pane .Fl T command. -.Sh ENVIRONMENT +.Sh GLOBAL AND SESSION ENVIRONMENT When the server is started, .Nm copies the environment into the @@ -3853,15 +4626,20 @@ is used, the output is formatted as a set of Bourne shell commands. .Nm includes an optional status line which is displayed in the bottom line of each terminal. -By default, the status line is enabled (it may be disabled with the +.Pp +By default, the status line is enabled and one line in height (it may be +disabled or made multiple lines with the .Ic status session option) and contains, from left-to-right: the name of the current session in square brackets; the window list; the title of the active pane in double quotes; and the time and date. .Pp -The status line is made of three parts: configurable left and right sections -(which may contain dynamic content such as the time or output from a shell -command, see the +Each line of the status line is configured with the +.Ic status-format +option. +The default is made of three parts: configurable left and right sections (which +may contain dynamic content such as the time or output from a shell command, +see the .Ic status-left , .Ic status-left-length , .Ic status-right , @@ -3908,7 +4686,7 @@ session option. Commands related to the status line are as follows: .Bl -tag -width Ds .It Xo Ic command-prompt -.Op Fl 1i +.Op Fl 1Ni .Op Fl I Ar inputs .Op Fl p Ar prompts .Op Fl t Ar target-client @@ -3958,6 +4736,8 @@ but any quotation marks are escaped. .Fl 1 makes the prompt only accept one key press, in this case the resulting input is a single character. +.Fl N +makes the prompt only accept numeric key presses. .Fl i executes the command every time the prompt input changes instead of when the user exits the command prompt. @@ -3969,7 +4749,7 @@ 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 "Delete current word" Ta "" Ta "C-w" +.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" .It Li "Execute command" Ta "Enter" Ta "Enter" @@ -4005,8 +4785,69 @@ option. .Pp This command works only from inside .Nm . +.It Xo Ic display-menu +.Op Fl c Ar target-client +.Op Fl t Ar target-pane +.Op Fl T Ar title +.Op Fl x Ar position +.Op Fl y Ar position +.Ar name +.Ar key +.Ar command +.Ar ... +.Xc +.D1 (alias: Ic menu ) +Display a menu on +.Ar target-client . +.Ar target-pane +gives the target for any commands run from the menu. +.Pp +A menu is passed as a series of arguments: first the menu item name, +second the key shortcut (or empty for none) and third the command +to run when the menu item is chosen. +The name and command are formats, see the +.Sx FORMATS +and +.Sx STYLES +sections. +If the name begins with a hyphen (-), then the item is disabled (shown dim) and +may not be chosen. +The name may be empty for a separator line, in which case both the key and +command should be omitted. +.Pp +.Fl T +is a format for the menu title (see +.Sx FORMATS ) . +.Pp +.Fl x +and +.Fl y +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 "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 "S" Ta Fl y Ta "The line above or below the status line" +.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. +The following keys are also available: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Choose selected item" +.It Li "Up" Ta "Select previous item" +.It Li "Down" Ta "Select next item" +.It Li "q" Ta "Exit menu" +.El .It Xo Ic display-message -.Op Fl p +.Op Fl aIpv .Op Fl c Ar target-client .Op Fl t Ar target-pane .Op Ar message @@ -4026,8 +4867,16 @@ section; information is taken from .Ar target-pane if .Fl t -is given, otherwise the active pane for the session attached to -.Ar target-client . +is given, otherwise the active pane. +.Pp +.Fl v +prints verbose logging as the format is parsed and +.Fl a +lists the format variables and their values. +.Pp +.Fl I +forwards any input read from stdin to the empty pane given by +.Ar target-pane . .El .Sh BUFFERS .Nm @@ -4077,7 +4926,7 @@ The buffer commands are as follows: .Bl -tag -width Ds .It Xo .Ic choose-buffer -.Op Fl N +.Op Fl NZr .Op Fl F Ar format .Op Fl f Ar filter .Op Fl O Ar sort-order @@ -4086,10 +4935,12 @@ The buffer commands are as follows: .Xc Put a pane into buffer mode, where a buffer may be chosen interactively from a list. +.Fl Z +zooms the pane. The following keys may be used in buffer mode: .Bl -column "Key" "Function" -offset indent .It Sy "Key" Ta Sy "Function" -.It Li "Enter" Ta "Choose selected buffer" +.It Li "Enter" Ta "Paste selected buffer" .It Li "Up" Ta "Select previous buffer" .It Li "Down" Ta "Select next buffer" .It Li "C-s" Ta "Search by name or content" @@ -4097,10 +4948,13 @@ The following keys may be used in buffer mode: .It Li "t" Ta "Toggle if buffer is tagged" .It Li "T" Ta "Tag no buffers" .It Li "C-t" Ta "Tag all buffers" +.It Li "p" Ta "Paste selected buffer" +.It Li "P" Ta "Paste tagged buffers" .It Li "d" Ta "Delete selected buffer" .It Li "D" Ta "Delete tagged buffers" .It Li "f" Ta "Enter a format to filter items" -.It Li "O" Ta "Change sort order" +.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 @@ -4115,11 +4969,13 @@ If is not given, "paste-buffer -b '%%'" is used. .Pp .Fl O -specifies the initial sort order: one of +specifies the initial sort field: one of .Ql time , .Ql name or .Ql size . +.Fl r +reverses the sort order. .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. @@ -4288,8 +5144,6 @@ is used, the channel is locked and any clients that try to lock the same channel are made to wait until the channel is unlocked with .Ic wait-for .Fl U . -This command only works from outside -.Nm . .El .Sh TERMINFO EXTENSIONS .Nm @@ -4306,6 +5160,35 @@ to change the cursor colour from inside .Bd -literal -offset indent $ printf '\e033]12;red\e033\e\e' .Ed +.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. +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 @@ -4322,7 +5205,7 @@ Indicate that the terminal supports the .Ql direct colour RGB escape sequence (for example, \ee[38;2;255;255;255m). .Pp -If supported, this is used for the OSC initialize colour escape sequence (which +If supported, this is used for the initialize colour escape sequence (which may be enabled by adding the .Ql initc and @@ -4452,6 +5335,91 @@ The window with ID was renamed to .Ar name . .El +.Sh ENVIRONMENT +When +.Nm +is started, it inspects the following environment variables: +.Bl -tag -width LC_CTYPE +.It Ev EDITOR +If the command specified in this variable contains the string +.Ql vi +and +.Ev VISUAL +is unset, use vi-style key bindings. +Overridden by the +.Ic mode-keys +and +.Ic status-keys +options. +.It Ev HOME +The user's login directory. +If unset, the +.Xr passwd 5 +database is consulted. +.It Ev LC_CTYPE +The character encoding +.Xr locale 1 . +It is used for two separate purposes. +For output to the terminal, UTF-8 is used if the +.Fl u +option is given or if +.Ev LC_CTYPE +contains +.Qq UTF-8 +or +.Qq UTF8 . +Otherwise, only ASCII characters are written and non-ASCII characters +are replaced with underscores +.Pq Ql _ . +For input, +.Nm +always runs with a UTF-8 locale. +If en_US.UTF-8 is provided by the operating system it is used and +.Ev LC_CTYPE +is ignored for input. +Otherwise, +.Ev LC_CTYPE +tells +.Nm +what the UTF-8 locale is called on the current system. +If the locale specified by +.Ev LC_CTYPE +is not available or is not a UTF-8 locale, +.Nm +exits with an error message. +.It Ev LC_TIME +The date and time format +.Xr locale 1 . +It is used for locale-dependent +.Xr strftime 3 +format specifiers. +.It Ev PWD +The current working directory to be set in the global environment. +This may be useful if it contains symbolic links. +If the value of the variable does not match the current working +directory, the variable is ignored and the result of +.Xr getcwd 3 +is used instead. +.It Ev SHELL +The absolute path to the default shell for new windows. +See the +.Ic default-shell +option for details. +.It Ev TMUX_TMPDIR +The parent directory of the directory containing the server sockets. +See the +.Fl L +option for details. +.It Ev VISUAL +If the command specified in this variable contains the string +.Ql vi , +use vi-style key bindings. +Overridden by the +.Ic mode-keys +and +.Ic status-keys +options. +.El .Sh FILES .Bl -tag -width "@SYSCONFDIR@/tmux.confXXX" -compact .It Pa ~/.tmux.conf diff --git a/tmux.c b/tmux.c index 78cb499bb3..244e2d35e8 100644 --- a/tmux.c +++ b/tmux.c @@ -36,7 +36,6 @@ struct options *global_options; /* server options */ struct options *global_s_options; /* session options */ struct options *global_w_options; /* window options */ struct environ *global_environ; -struct hooks *global_hooks; struct timeval start_time; const char *socket_path; @@ -44,7 +43,7 @@ int ptm_fd = -1; const char *shell_command; static __dead void usage(void); -static char *make_label(const char *); +static char *make_label(const char *, char **); static const char *getshell(void); static int checkshell(const char *); @@ -106,12 +105,13 @@ areshell(const char *shell) } static char * -make_label(const char *label) +make_label(const char *label, char **cause) { char *base, resolved[PATH_MAX], *path, *s; struct stat sb; uid_t uid; - int saved_errno; + + *cause = NULL; if (label == NULL) label = "default"; @@ -121,32 +121,36 @@ make_label(const char *label) xasprintf(&base, "%s/tmux-%ld", s, (long)uid); else xasprintf(&base, "%s/tmux-%ld", _PATH_TMP, (long)uid); - - if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) + if (realpath(base, resolved) == NULL && + strlcpy(resolved, base, sizeof resolved) >= sizeof resolved) { + errno = ERANGE; + free(base); goto fail; + } + free(base); - if (lstat(base, &sb) != 0) + if (mkdir(resolved, S_IRWXU) != 0 && errno != EEXIST) + goto fail; + if (lstat(resolved, &sb) != 0) goto fail; if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; goto fail; } - if (sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0) { +// msys2 https://github.com/msys2/MSYS2-packages/issues/ +#if __MSYS__ || __CYGWIN__ || _WIN32 || _WIN64 + if (sb.st_uid != uid) { +#else + if (sb.st_uid != uid || (sb.st_mode & PERMS) != 0) { +#endif errno = EACCES; goto fail; } - - if (realpath(base, resolved) == NULL) - strlcpy(resolved, base, sizeof resolved); xasprintf(&path, "%s/%s", resolved, label); - - free(base); return (path); fail: - saved_errno = errno; - free(base); - errno = saved_errno; + xasprintf(cause, "error creating %s (%s)", resolved, strerror(errno)); return (NULL); } @@ -164,6 +168,31 @@ setblocking(int fd, int state) } } +const char * +find_cwd(void) +{ + char resolved1[PATH_MAX], resolved2[PATH_MAX]; + static char cwd[PATH_MAX]; + const char *pwd; + + if (getcwd(cwd, sizeof cwd) == NULL) + return (NULL); + if ((pwd = getenv("PWD")) == NULL || *pwd == '\0') + return (cwd); + + /* + * We want to use PWD so that symbolic links are maintained, + * but only if it matches the actual working directory. + */ + if (realpath(pwd, resolved1) == NULL) + return (cwd); + if (realpath(cwd, resolved2) == NULL) + return (cwd); + if (strcmp(resolved1, resolved2) != 0) + return (cwd); + return (pwd); +} + const char * find_home(void) { @@ -188,9 +217,8 @@ find_home(void) int main(int argc, char **argv) { - char *path, *label, **var; - char tmp[PATH_MAX]; - const char *s, *shell; + char *path, *label, *cause, **var; + const char *s, *shell, *cwd; int opt, flags, keys; const struct options_table_entry *oe; @@ -289,23 +317,21 @@ main(int argc, char **argv) flags |= CLIENT_UTF8; } - global_hooks = hooks_create(NULL); - global_environ = environ_create(); for (var = environ; *var != NULL; var++) environ_put(global_environ, *var); - if (getcwd(tmp, sizeof tmp) != NULL) - environ_set(global_environ, "PWD", "%s", tmp); + 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); for (oe = options_table; oe->name != NULL; oe++) { - if (oe->scope == OPTIONS_TABLE_SERVER) + if (oe->scope & OPTIONS_TABLE_SERVER) options_default(global_options, oe); - if (oe->scope == OPTIONS_TABLE_SESSION) + if (oe->scope & OPTIONS_TABLE_SESSION) options_default(global_s_options, oe); - if (oe->scope == OPTIONS_TABLE_WINDOW) + if (oe->scope & OPTIONS_TABLE_WINDOW) options_default(global_w_options, oe); } @@ -340,8 +366,11 @@ main(int argc, char **argv) path[strcspn(path, ",")] = '\0'; } } - if (path == NULL && (path = make_label(label)) == NULL) { - fprintf(stderr, "can't create socket: %s\n", strerror(errno)); + if (path == NULL && (path = make_label(label, &cause)) == NULL) { + if (cause != NULL) { + fprintf(stderr, "%s\n", cause); + free(cause); + } exit(1); } socket_path = path; diff --git a/tmux.exe b/tmux.exe new file mode 100644 index 0000000000..4ee18902aa Binary files /dev/null and b/tmux.exe differ diff --git a/tmux.h b/tmux.h index adc020d5c8..ae1ccbbd6e 100644 --- a/tmux.h +++ b/tmux.h @@ -19,8 +19,6 @@ #ifndef TMUX_H #define TMUX_H -#define PROTOCOL_VERSION 8 - #include #include @@ -41,31 +39,41 @@ extern char **environ; struct args; +struct args_value; struct client; struct cmd_find_state; struct cmdq_item; struct cmdq_list; struct environ; struct format_job_tree; +struct format_tree; struct input_ctx; +struct job; struct mode_tree_data; struct mouse_event; struct options; struct options_entry; +struct options_array_item; struct session; +struct sixel_image; struct tmuxpeer; struct tmuxproc; +struct winlink; + +/* Client-server protocol version. */ +#define PROTOCOL_VERSION 8 /* Default global configuration file. */ #ifndef TMUX_CONF #define TMUX_CONF "/etc/tmux.conf" #endif -/* - * Minimum layout cell size, NOT including separator line. The scroll region - * cannot be one line in height so this must be at least two. - */ -#define PANE_MINIMUM 2 +/* Minimum layout cell size, NOT including border lines. */ +#define PANE_MINIMUM 1 + +/* Minimum and maximum window size. */ +#define WINDOW_MINIMUM PANE_MINIMUM +#define WINDOW_MAXIMUM 10000 /* Automatic name refresh interval, in microseconds. Must be < 1 second. */ #define NAME_INTERVAL 500000 @@ -73,6 +81,10 @@ struct tmuxproc; /* 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 + /* Attribute to make GCC check printf-like arguments. */ #define printflike(a, b) __attribute__ ((format (printf, a, b))) @@ -106,9 +118,10 @@ struct tmuxproc; #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) +#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? */ @@ -119,13 +132,19 @@ struct tmuxproc; #define KEYC_CLICK_TIMEOUT 300 /* Mouse key codes. */ -#define KEYC_MOUSE_KEY(name) \ - KEYC_ ## name ## _PANE, \ - KEYC_ ## name ## _STATUS, \ +#define KEYC_MOUSE_KEY(name) \ + KEYC_ ## name ## _PANE, \ + KEYC_ ## name ## _STATUS, \ + KEYC_ ## name ## _STATUS_LEFT, \ + KEYC_ ## name ## _STATUS_RIGHT, \ + KEYC_ ## name ## _STATUS_DEFAULT, \ KEYC_ ## name ## _BORDER -#define KEYC_MOUSE_STRING(name, s) \ - { #s "Pane", KEYC_ ## name ## _PANE }, \ - { #s "Status", KEYC_ ## name ## _STATUS }, \ +#define KEYC_MOUSE_STRING(name, s) \ + { #s "Pane", KEYC_ ## name ## _PANE }, \ + { #s "Status", KEYC_ ## name ## _STATUS }, \ + { #s "StatusLeft", KEYC_ ## name ## _STATUS_LEFT }, \ + { #s "StatusRight", KEYC_ ## name ## _STATUS_RIGHT }, \ + { #s "StatusDefault", KEYC_ ## name ## _STATUS_DEFAULT }, \ { #s "Border", KEYC_ ## name ## _BORDER } /* @@ -140,6 +159,9 @@ enum { KEYC_FOCUS_IN = KEYC_BASE, KEYC_FOCUS_OUT, + /* "Any" key, used if not found in key table. */ + KEYC_ANY, + /* Paste brackets. */ KEYC_PASTE_START, KEYC_PASTE_END, @@ -220,8 +242,8 @@ enum { /* Termcap codes. */ enum tty_code_code { - TTYC_AX = 0, TTYC_ACSC, + TTYC_AX, TTYC_BCE, TTYC_BEL, TTYC_BLINK, @@ -403,7 +425,9 @@ enum tty_code_code { TTYC_MS, TTYC_OP, TTYC_REV, + TTYC_RGB, TTYC_RI, + TTYC_RIN, TTYC_RMACS, TTYC_RMCUP, TTYC_RMKX, @@ -412,14 +436,18 @@ enum tty_code_code { TTYC_SETAF, TTYC_SETRGBB, TTYC_SETRGBF, + TTYC_SETULC, TTYC_SGR0, TTYC_SITM, TTYC_SMACS, TTYC_SMCUP, + TTYC_SMOL, TTYC_SMKX, TTYC_SMSO, + TTYC_SMULX, TTYC_SMUL, TTYC_SMXX, + TTYC_SXL, TTYC_SS, TTYC_TC, TTYC_TSL, @@ -505,15 +533,18 @@ struct msg_stderr_data { #define MODE_BRACKETPASTE 0x400 #define MODE_FOCUSON 0x800 #define MODE_MOUSE_ALL 0x1000 +#define MODE_ORIGIN 0x2000 +#define MODE_CRLF 0x4000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) /* - * A single UTF-8 character. UTF8_SIZE must be big enough to hold at least one - * combining character as well. + * 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 9 +#define UTF8_SIZE 18 struct utf8_data { u_char data[UTF8_SIZE]; @@ -532,6 +563,9 @@ enum utf8_state { #define COLOUR_FLAG_256 0x01000000 #define COLOUR_FLAG_RGB 0x02000000 +/* Special colours. */ +#define COLOUR_DEFAULT(c) ((c) == 8 || (c) == 9) + /* Grid attributes. Anything above 0xff is stored in an extended cell. */ #define GRID_ATTR_BRIGHT 0x1 #define GRID_ATTR_DIM 0x2 @@ -542,6 +576,19 @@ enum utf8_state { #define GRID_ATTR_ITALICS 0x40 #define GRID_ATTR_CHARSET 0x80 /* alternative character set */ #define GRID_ATTR_STRIKETHROUGH 0x100 +#define GRID_ATTR_UNDERSCORE_2 0x200 +#define GRID_ATTR_UNDERSCORE_3 0x400 +#define GRID_ATTR_UNDERSCORE_4 0x800 +#define GRID_ATTR_UNDERSCORE_5 0x1000 +#define GRID_ATTR_OVERLINE 0x2000 + +/* All underscore attributes. */ +#define GRID_ATTR_ALL_UNDERSCORE \ + (GRID_ATTR_UNDERSCORE| \ + GRID_ATTR_UNDERSCORE_2| \ + GRID_ATTR_UNDERSCORE_3| \ + GRID_ATTR_UNDERSCORE_4| \ + GRID_ATTR_UNDERSCORE_5) /* Grid flags. */ #define GRID_FLAG_FG256 0x1 @@ -550,19 +597,22 @@ enum utf8_state { #define GRID_FLAG_EXTENDED 0x8 #define GRID_FLAG_SELECTED 0x10 #define GRID_FLAG_NOPALETTE 0x20 +#define GRID_FLAG_CLEARED 0x40 /* Grid line flags. */ #define GRID_LINE_WRAPPED 0x1 #define GRID_LINE_EXTENDED 0x2 +#define GRID_LINE_DEAD 0x4 /* Grid cell data. */ struct grid_cell { - u_char flags; + struct utf8_data data; /* 21 bytes */ u_short attr; + u_char flags; int fg; int bg; - struct utf8_data data; -}; + int us; +} __packed; struct grid_cell_entry { u_char flags; union { @@ -603,69 +653,69 @@ struct grid { struct grid_line *linedata; }; -/* Hook data structures. */ -struct hook { - const char *name; - - struct cmd_list *cmdlist; - - RB_ENTRY(hook) entry; +/* Style alignment. */ +enum style_align { + STYLE_ALIGN_DEFAULT, + STYLE_ALIGN_LEFT, + STYLE_ALIGN_CENTRE, + STYLE_ALIGN_RIGHT }; -/* Scheduled job. */ -struct job; -typedef void (*job_update_cb) (struct job *); -typedef void (*job_complete_cb) (struct job *); -typedef void (*job_free_cb) (void *); -struct job { - enum { - JOB_RUNNING, - JOB_DEAD, - JOB_CLOSED - } state; - - char *cmd; - pid_t pid; - int status; +/* Style list. */ +enum style_list { + STYLE_LIST_OFF, + STYLE_LIST_ON, + STYLE_LIST_FOCUS, + STYLE_LIST_LEFT_MARKER, + STYLE_LIST_RIGHT_MARKER, +}; - int fd; - struct bufferevent *event; +/* Style range. */ +enum style_range_type { + STYLE_RANGE_NONE, + STYLE_RANGE_LEFT, + STYLE_RANGE_RIGHT, + STYLE_RANGE_WINDOW +}; +struct style_range { + enum style_range_type type; + u_int argument; - job_update_cb updatecb; - job_complete_cb completecb; - job_free_cb freecb; - void *data; + u_int start; + u_int end; /* not included */ - LIST_ENTRY(job) entry; + TAILQ_ENTRY(style_range) entry; }; -LIST_HEAD(joblist, job); - -/* Screen selection. */ -struct screen_sel { - int flag; - int hidden; +TAILQ_HEAD(style_ranges, style_range); - int rectflag; - enum { - LINE_SEL_NONE, - LINE_SEL_LEFT_RIGHT, - LINE_SEL_RIGHT_LEFT, - } lineflag; +/* Style default. */ +enum style_default_type { + STYLE_DEFAULT_BASE, + STYLE_DEFAULT_PUSH, + STYLE_DEFAULT_POP +}; - int modekeys; +/* Style option. */ +struct style { + struct grid_cell gc; - u_int sx; - u_int sy; + int fill; + enum style_align align; + enum style_list list; - u_int ex; - u_int ey; + enum style_range_type range_type; + u_int range_argument; - struct grid_cell cell; + enum style_default_type default_type; }; /* Virtual screen. */ +struct screen_sel; +struct screen_titles; struct screen { char *title; + char *path; + struct screen_titles *titles; struct grid *grid; /* grid data */ @@ -682,7 +732,7 @@ struct screen { bitstr_t *tabs; - struct screen_sel sel; + struct screen_sel *sel; }; /* Screen write context. */ @@ -702,53 +752,79 @@ struct screen_write_ctx { u_int skipped; }; +/* Screen redraw context. */ +struct screen_redraw_ctx { + struct client *c; + + u_int statuslines; + int statustop; + + int pane_status; + + u_int sx; + u_int sy; + u_int ox; + u_int oy; +}; + /* Screen size. */ #define screen_size_x(s) ((s)->grid->sx) #define screen_size_y(s) ((s)->grid->sy) #define screen_hsize(s) ((s)->grid->hsize) #define screen_hlimit(s) ((s)->grid->hlimit) +/* Menu. */ +struct menu_item { + const char *name; + key_code key; + const char *command; +}; +struct menu { + const char *title; + struct menu_item *items; + u_int count; + 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 * right function to handle input and output. */ +struct window_mode_entry; struct window_mode { const char *name; + const char *default_format; + + struct screen *(*init)(struct window_mode_entry *, + struct cmd_find_state *, struct args *); + void (*free)(struct window_mode_entry *); + void (*resize)(struct window_mode_entry *, u_int, u_int); + void (*key)(struct window_mode_entry *, struct client *, + struct session *, struct winlink *, key_code, + struct mouse_event *); - struct screen *(*init)(struct window_pane *, struct cmd_find_state *, - struct args *); - void (*free)(struct window_pane *); - void (*resize)(struct window_pane *, u_int, u_int); - void (*key)(struct window_pane *, struct client *, - struct session *, key_code, struct mouse_event *); - - const char *(*key_table)(struct window_pane *); - void (*command)(struct window_pane *, struct client *, - struct session *, struct args *, + const char *(*key_table)(struct window_mode_entry *); + void (*command)(struct window_mode_entry *, struct client *, + struct session *, struct winlink *, struct args *, struct mouse_event *); + void (*formats)(struct window_mode_entry *, + struct format_tree *); }; #define WINDOW_MODE_TIMEOUT 180 -/* Structures for choose mode. */ -struct window_choose_data { - struct client *start_client; - struct session *start_session; +/* Active window mode. */ +struct window_mode_entry { + struct window_pane *wp; - u_int idx; - int type; -#define TREE_OTHER 0x0 -#define TREE_WINDOW 0x1 -#define TREE_SESSION 0x2 + const struct window_mode *mode; + void *data; - struct session *tree_session; /* session of items in tree */ - - struct winlink *wl; - int pane_id; + struct screen *screen; + u_int prefix; - char *ft_template; - struct format_tree *ft; - - char *command; + TAILQ_ENTRY (window_mode_entry) entry; }; /* Child window structure. */ @@ -757,6 +833,7 @@ struct window_pane { u_int active_point; struct window *window; + struct options *options; struct layout_cell *layout_cell; struct layout_cell *saved_layout_cell; @@ -780,11 +857,16 @@ struct window_pane { #define PANE_INPUTOFF 0x40 #define PANE_CHANGED 0x80 #define PANE_EXITED 0x100 +#define PANE_STATUSREADY 0x200 +#define PANE_STATUSDRAWN 0x400 +#define PANE_EMPTY 0x800 +#define PANE_STYLECHANGED 0x1000 +#define PANE_RESIZED 0x2000 int argc; char **argv; char *shell; - const char *cwd; + char *cwd; pid_t pid; char tty[TTY_NAME_MAX]; @@ -792,13 +874,14 @@ struct window_pane { int fd; struct bufferevent *event; + u_int disabled; struct event resize_timer; struct input_ctx *ictx; - struct grid_cell colgc; - + struct style cached_style; + struct style cached_active_style; int *palette; int pipe_fd; @@ -817,11 +900,9 @@ struct window_pane { struct grid *saved_grid; struct grid_cell saved_cell; - const struct window_mode *mode; - void *modedata; + TAILQ_HEAD (, window_mode_entry) modes; struct event modetimer; time_t modelast; - u_int modeprefix; char *searchstr; TAILQ_ENTRY(window_pane) entry; @@ -833,12 +914,14 @@ RB_HEAD(window_pane_tree, window_pane); /* Window structure. */ struct window { u_int id; + void *latest; char *name; struct event name_event; struct timeval name_time; struct event alerts_timer; + struct event offset_timer; struct timeval activity_time; @@ -853,16 +936,15 @@ struct window { u_int sx; u_int sy; + u_int xpixel; + u_int ypixel; int flags; #define WINDOW_BELL 0x1 #define WINDOW_ACTIVITY 0x2 -/* 0x4 unused */ -#define WINDOW_SILENCE 0x8 -#define WINDOW_ZOOMED 0x1000 -#define WINDOW_FORCEWIDTH 0x2000 -#define WINDOW_FORCEHEIGHT 0x4000 -#define WINDOW_STYLECHANGED 0x8000 +#define WINDOW_SILENCE 0x4 +#define WINDOW_ZOOMED 0x8 +#define WINDOW_WASZOOMED 0x10 #define WINDOW_ALERTFLAGS (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_SILENCE) int alerts_queued; @@ -870,9 +952,6 @@ struct window { struct options *options; - struct grid_cell style; - struct grid_cell active_style; - u_int references; TAILQ_HEAD(, winlink) winlinks; @@ -886,10 +965,6 @@ struct winlink { struct session *session; struct window *window; - size_t status_width; - struct grid_cell status_cell; - char *status_text; - int flags; #define WINLINK_BELL 0x1 #define WINLINK_ACTIVITY 0x2 @@ -903,6 +978,17 @@ struct winlink { RB_HEAD(winlinks, winlink); TAILQ_HEAD(winlink_stack, winlink); +/* Window size option. */ +#define WINDOW_SIZE_LARGEST 0 +#define WINDOW_SIZE_SMALLEST 1 +#define WINDOW_SIZE_MANUAL 2 +#define WINDOW_SIZE_LATEST 3 + +/* Pane border status option. */ +#define PANE_STATUS_OFF 0 +#define PANE_STATUS_TOP 1 +#define PANE_STATUS_BOTTOM 2 + /* Layout direction. */ enum layout_type { LAYOUT_LEFTRIGHT, @@ -961,21 +1047,17 @@ struct session { struct event lock_timer; - u_int sx; - u_int sy; - struct winlink *curw; struct winlink_stack lastw; struct winlinks windows; int statusat; + u_int statuslines; - struct hooks *hooks; struct options *options; -#define SESSION_UNATTACHED 0x1 /* not attached to any clients */ -#define SESSION_PASTING 0x2 -#define SESSION_ALERTED 0x4 +#define SESSION_PASTING 0x1 +#define SESSION_ALERTED 0x2 int flags; u_int attached; @@ -1001,7 +1083,7 @@ RB_HEAD(sessions, session); /* Mouse wheel states. */ #define MOUSE_WHEEL_UP 0 -#define MOUSE_WHEEL_DOWN 64 +#define MOUSE_WHEEL_DOWN 1 /* Mouse helpers. */ #define MOUSE_BUTTONS(b) ((b) & MOUSE_MASK_BUTTONS) @@ -1014,7 +1096,9 @@ struct mouse_event { int valid; key_code key; + int statusat; + u_int statuslines; u_int x; u_int y; @@ -1024,6 +1108,9 @@ struct mouse_event { u_int ly; u_int lb; + u_int ox; + u_int oy; + int s; int w; int wp; @@ -1032,6 +1119,12 @@ struct mouse_event { u_int sgr_b; }; +/* Key event. */ +struct key_event { + key_code key; + struct mouse_event m; +}; + /* TTY information. */ struct tty_key { char ch; @@ -1054,6 +1147,7 @@ struct tty_term { #define TERM_256COLOURS 0x1 #define TERM_EARLYWRAP 0x2 +#define TERM_SIXEL 0x4 int flags; LIST_ENTRY(tty_term) entry; @@ -1065,12 +1159,20 @@ struct tty { u_int sx; u_int sy; + u_int xpixel; + u_int ypixel; u_int cx; u_int cy; u_int cstyle; char *ccolour; + int oflag; + u_int oox; + u_int ooy; + u_int osx; + u_int osy; + int mode; u_int rlower; @@ -1102,6 +1204,7 @@ struct tty { #define TTY_OPENED 0x20 #define TTY_FOCUS 0x40 #define TTY_BLOCK 0x80 +#define TTY_NOBLOCK 0x100 int flags; struct tty_term *term; @@ -1114,10 +1217,13 @@ struct tty { TTY_VT220, TTY_VT320, TTY_VT420, + TTY_VT520, TTY_UNKNOWN } term_type; - struct mouse_event mouse; + u_int mouse_last_x; + u_int mouse_last_y; + u_int mouse_last_b; int mouse_drag_flag; void (*mouse_drag_update)(struct client *, struct mouse_event *); @@ -1128,7 +1234,14 @@ struct tty { struct tty_key *key_tree; }; #define TTY_TYPES \ - { "VT100", "VT101", "VT102", "VT220", "VT320", "VT420", "Unknown" } + { "VT100", \ + "VT101", \ + "VT102", \ + "VT220", \ + "VT320", \ + "VT420", \ + "VT520", \ + "Unknown" } /* TTY command context. */ struct tty_ctx { @@ -1137,8 +1250,8 @@ struct tty_ctx { const struct grid_cell *cell; int wrapped; - u_int num; - void *ptr; + u_int num; + void *ptr; /* * Cursor and region position before the screen was updated - this is @@ -1151,11 +1264,19 @@ struct tty_ctx { u_int orupper; u_int orlower; + /* Pane offset. */ u_int xoff; u_int yoff; /* The background colour used for clearing (erasing). */ u_int bg; + + /* Window offset and size. */ + int bigger; + u_int ox; + u_int oy; + u_int sx; + u_int sy; }; /* Saved message entry. */ @@ -1203,21 +1324,25 @@ struct cmd_find_state { /* Command and list of commands. */ struct cmd { - const struct cmd_entry *entry; - struct args *args; + const struct cmd_entry *entry; + struct args *args; + u_int group; - char *file; - u_int line; + char *file; + u_int line; -#define CMD_CONTROL 0x1 - int flags; + char *alias; + int argc; + char **argv; - TAILQ_ENTRY(cmd) qentry; + TAILQ_ENTRY(cmd) qentry; }; +TAILQ_HEAD(cmds, cmd); struct cmd_list { - int references; - TAILQ_HEAD(, cmd) list; + int references; + u_int group; + struct cmds list; }; /* Command return values. */ @@ -1228,6 +1353,32 @@ enum cmd_retval { CMD_RETURN_STOP }; +/* Command parse result. */ +enum cmd_parse_status { + CMD_PARSE_EMPTY, + CMD_PARSE_ERROR, + CMD_PARSE_SUCCESS +}; +struct cmd_parse_result { + enum cmd_parse_status status; + struct cmd_list *cmdlist; + char *error; +}; +struct cmd_parse_input { + int flags; +#define CMD_PARSE_QUIET 0x1 +#define CMD_PARSE_PARSEONLY 0x2 +#define CMD_PARSE_NOALIAS 0x4 +#define CMD_PARSE_VERBOSE 0x8 + + const char *file; + u_int line; + + struct cmdq_item *item; + struct client *c; + struct cmd_find_state fs; +}; + /* Command queue item type. */ enum cmdq_type { CMDQ_COMMAND, @@ -1240,6 +1391,7 @@ struct cmdq_shared { int flags; #define CMDQ_SHARED_REPEAT 0x1 +#define CMDQ_SHARED_CONTROL 0x2 struct format_tree *formats; @@ -1250,7 +1402,7 @@ struct cmdq_shared { /* Command queue item. */ typedef enum cmd_retval (*cmdq_cb) (struct cmdq_item *, void *); struct cmdq_item { - const char *name; + char *name; struct cmdq_list *queue; struct cmdq_item *next; @@ -1311,9 +1463,29 @@ struct cmd_entry { enum cmd_retval (*exec)(struct cmd *, struct cmdq_item *); }; +/* Status line. */ +#define STATUS_LINES_LIMIT 5 +struct status_line_entry { + char *expanded; + struct style_ranges ranges; +}; +struct status_line { + struct event timer; + + struct screen screen; + struct screen *active; + int references; + + struct grid_cell style; + struct status_line_entry entries[STATUS_LINES_LIMIT]; +}; + /* 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 *); struct client { const char *name; struct tmuxpeer *peer; @@ -1353,22 +1525,19 @@ struct client { struct event click_timer; u_int click_button; - struct event status_timer; - struct screen status; - - struct screen *old_status; + struct status_line status; #define CLIENT_TERMINAL 0x1 #define CLIENT_LOGIN 0x2 #define CLIENT_EXIT 0x4 -#define CLIENT_REDRAW 0x8 -#define CLIENT_STATUS 0x10 +#define CLIENT_REDRAWWINDOW 0x8 +#define CLIENT_REDRAWSTATUS 0x10 #define CLIENT_REPEAT 0x20 #define CLIENT_SUSPENDED 0x40 #define CLIENT_ATTACHED 0x80 -#define CLIENT_IDENTIFY 0x100 +#define CLIENT_EXITED 0x100 #define CLIENT_DEAD 0x200 -#define CLIENT_BORDERS 0x400 +#define CLIENT_REDRAWBORDERS 0x400 #define CLIENT_READONLY 0x800 #define CLIENT_DETACHING 0x1000 #define CLIENT_CONTROL 0x2000 @@ -1381,14 +1550,23 @@ struct client { #define CLIENT_DOUBLECLICK 0x100000 #define CLIENT_TRIPLECLICK 0x200000 #define CLIENT_SIZECHANGED 0x400000 +#define CLIENT_STATUSOFF 0x800000 +#define CLIENT_REDRAWSTATUSALWAYS 0x1000000 +#define CLIENT_REDRAWOVERLAY 0x2000000 +#define CLIENT_CONTROL_NOOUTPUT 0x4000000 +#define CLIENT_ALLREDRAWFLAGS \ + (CLIENT_REDRAWWINDOW| \ + CLIENT_REDRAWSTATUS| \ + CLIENT_REDRAWSTATUSALWAYS| \ + CLIENT_REDRAWBORDERS| \ + CLIENT_REDRAWOVERLAY) +#define CLIENT_NOSIZEFLAGS \ + (CLIENT_DEAD| \ + CLIENT_SUSPENDED| \ + CLIENT_DETACHING) int flags; struct key_table *keytable; - struct event identify_timer; - void (*identify_callback)(struct client *, - struct window_pane *); - void *identify_callback_data; - char *message_string; struct event message_timer; u_int message_next; @@ -1402,6 +1580,7 @@ struct client { void *prompt_data; u_int prompt_hindex; enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode; + struct utf8_data *prompt_saved; #define PROMPT_SINGLE 0x1 #define PROMPT_NUMERIC 0x2 @@ -1412,10 +1591,18 @@ struct client { struct session *session; struct session *last_session; - int wlmouse; - int references; + void *pan_window; + u_int pan_ox; + u_int pan_oy; + + overlay_draw_cb overlay_draw; + overlay_key_cb overlay_key; + overlay_free_cb overlay_free; + void *overlay_data; + struct event overlay_timer; + TAILQ_ENTRY(client) entry; }; TAILQ_HEAD(clients, client); @@ -1433,7 +1620,7 @@ struct key_binding { RB_HEAD(key_bindings, key_binding); struct key_table { - const char *name; + const char *name; struct key_bindings key_bindings; u_int references; @@ -1442,29 +1629,42 @@ struct key_table { }; 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; +}; + /* Option table entries. */ enum options_table_type { OPTIONS_TABLE_STRING, OPTIONS_TABLE_NUMBER, OPTIONS_TABLE_KEY, OPTIONS_TABLE_COLOUR, - OPTIONS_TABLE_ATTRIBUTES, OPTIONS_TABLE_FLAG, OPTIONS_TABLE_CHOICE, OPTIONS_TABLE_STYLE, - OPTIONS_TABLE_ARRAY, -}; -enum options_table_scope { - OPTIONS_TABLE_NONE, - OPTIONS_TABLE_SERVER, - OPTIONS_TABLE_SESSION, - OPTIONS_TABLE_WINDOW, + OPTIONS_TABLE_COMMAND }; +#define OPTIONS_TABLE_NONE 0 +#define OPTIONS_TABLE_SERVER 0x1 +#define OPTIONS_TABLE_SESSION 0x2 +#define OPTIONS_TABLE_WINDOW 0x4 +#define OPTIONS_TABLE_PANE 0x8 + +#define OPTIONS_TABLE_IS_ARRAY 0x1 +#define OPTIONS_TABLE_IS_HOOK 0x2 + struct options_table_entry { const char *name; enum options_table_type type; - enum options_table_scope scope; + int scope; + int flags; u_int minimum; u_int maximum; @@ -1472,9 +1672,10 @@ struct options_table_entry { const char *default_str; long long default_num; + const char **default_arr; const char *separator; - const char *style; + const char *pattern; }; /* Common command usages. */ @@ -1488,8 +1689,42 @@ struct options_table_entry { #define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]" #define CMD_BUFFER_USAGE "[-b buffer-name]" +/* Spawn common context. */ +struct spawn_context { + struct cmdq_item *item; + + struct session *s; + struct winlink *wl; + struct client *c; + + struct window_pane *wp0; + struct layout_cell *lc; + + const char *name; + char **argv; + int argc; + struct environ *environ; + + int idx; + const char *cwd; + + int flags; +#define SPAWN_KILL 0x1 +#define SPAWN_DETACHED 0x2 +#define SPAWN_RESPAWN 0x4 +#define SPAWN_BEFORE 0x8 +#define SPAWN_NONOTIFY 0x10 +#define SPAWN_FULLSIZE 0x20 +#define SPAWN_EMPTY 0x40 +}; + +/* Mode tree sort order. */ +struct mode_tree_sort_criteria { + u_int field; + int reversed; +}; + /* tmux.c */ -extern struct hooks *global_hooks; extern struct options *global_options; extern struct options *global_s_options; extern struct options *global_w_options; @@ -1501,6 +1736,7 @@ extern int ptm_fd; extern const char *shell_command; int areshell(const char *); void setblocking(int, int); +const char *find_cwd(void); const char *find_home(void); /* proc.c */ @@ -1519,8 +1755,10 @@ void proc_toggle_log(struct tmuxproc *); /* cfg.c */ extern int cfg_finished; +extern struct client *cfg_client; void start_cfg(void); -int load_cfg(const char *, struct client *, struct cmdq_item *, int); +int load_cfg(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 *); @@ -1536,7 +1774,7 @@ struct paste_buffer *paste_walk(struct paste_buffer *); struct paste_buffer *paste_get_top(const char **); struct paste_buffer *paste_get_name(const char *); void paste_free(struct paste_buffer *); -void paste_add(char *, size_t); +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 **); char *paste_make_sample(struct paste_buffer *); @@ -1545,17 +1783,21 @@ char *paste_make_sample(struct paste_buffer *); #define FORMAT_STATUS 0x1 #define FORMAT_FORCE 0x2 #define FORMAT_NOJOBS 0x4 +#define FORMAT_VERBOSE 0x8 #define FORMAT_NONE 0 #define FORMAT_PANE 0x80000000U #define FORMAT_WINDOW 0x40000000U struct format_tree; +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 printflike(3, 4) format_add(struct format_tree *, const char *, const char *, ...); -char *format_expand_time(struct format_tree *, const char *, time_t); +void format_each(struct format_tree *, void (*)(const char *, + const char *, void *), void *); +char *format_expand_time(struct format_tree *, const char *); char *format_expand(struct format_tree *, const char *); char *format_single(struct cmdq_item *, const char *, struct client *, struct session *, struct winlink *, @@ -1568,25 +1810,20 @@ void format_defaults_pane(struct format_tree *, void format_defaults_paste_buffer(struct format_tree *, struct paste_buffer *); void format_lost_client(struct client *); +char *format_grid_word(struct grid *, u_int, u_int); +char *format_grid_line(struct grid *, u_int); -/* hooks.c */ -struct hook; -struct hooks *hooks_get(struct session *); -struct hooks *hooks_create(struct hooks *); -void hooks_free(struct hooks *); -struct hook *hooks_first(struct hooks *); -struct hook *hooks_next(struct hook *); -void hooks_add(struct hooks *, const char *, struct cmd_list *); -void hooks_copy(struct hooks *, struct hooks *); -void hooks_remove(struct hooks *, const char *); -struct hook *hooks_find(struct hooks *, const char *); -void printflike(4, 5) hooks_run(struct hooks *, struct client *, - struct cmd_find_state *, const char *, ...); -void printflike(4, 5) hooks_insert(struct hooks *, struct cmdq_item *, - struct cmd_find_state *, const char *, ...); +/* format-draw.c */ +void format_draw(struct screen_write_ctx *, + const struct grid_cell *, u_int, const char *, + struct style_ranges *); +u_int format_width(const char *); +char *format_trim_left(const char *, u_int); +char *format_trim_right(const char *, u_int); /* notify.c */ -void notify_input(struct window_pane *, struct evbuffer *); +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 *); @@ -1597,6 +1834,7 @@ void notify_pane(const char *, struct window_pane *); /* options.c */ struct options *options_create(struct options *); void options_free(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 *); struct options_entry *options_empty(struct options *, @@ -1609,13 +1847,18 @@ 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 *); -const char *options_array_get(struct options_entry *, u_int); +union options_value *options_array_get(struct options_entry *, u_int); int options_array_set(struct options_entry *, u_int, const char *, - int); -int options_array_size(struct options_entry *, u_int *); -void options_array_assign(struct options_entry *, const char *); + int, char **); +int options_array_assign(struct options_entry *, const char *, + char **); +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 *); -const char *options_tostring(struct options_entry *, int, int); +char *options_tostring(struct options_entry *, int, int); char *options_parse(const char *, int *); struct options_entry *options_parse_get(struct options *, const char *, int *, int); @@ -1624,29 +1867,37 @@ 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 *); -const struct grid_cell *options_get_style(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 *); -enum options_table_scope options_scope_from_flags(struct args *, int, +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 **); -void options_style_update_new(struct options *, - struct options_entry *); -void options_style_update_old(struct options *, - struct options_entry *); /* options-table.c */ extern const struct options_table_entry options_table[]; /* job.c */ -extern struct joblist all_jobs; +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 *); + job_update_cb, job_complete_cb, job_free_cb, void *, int); void job_free(struct job *); -void job_died(struct job *, int); +void job_check_died(pid_t, int); +int job_get_status(struct job *); +void *job_get_data(struct job *); +struct bufferevent *job_get_event(struct job *); +void job_kill_all(void); +int job_still_running(void); +void job_print_summary(struct cmdq_item *, int); /* environ.c */ struct environ *environ_create(void); @@ -1667,9 +1918,13 @@ struct environ *environ_for_session(struct session *, int); /* tty.c */ void tty_create_log(void); +int tty_window_bigger(struct tty *); +int tty_window_offset(struct tty *, u_int *, u_int *, u_int *, u_int *); +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 *, - const struct window_pane *); + struct window_pane *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); @@ -1686,19 +1941,17 @@ 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_resize(struct tty *); -void tty_set_size(struct tty *, u_int, u_int); +void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); void tty_start_tty(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_pane(struct tty *, const struct window_pane *, u_int, u_int, - u_int); -void tty_draw_line(struct tty *, const struct window_pane *, struct screen *, - u_int, u_int, u_int); +void tty_draw_line(struct tty *, struct window_pane *, struct screen *, + u_int, u_int, u_int, u_int, u_int); int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); -void tty_set_type(struct tty *, int); +void tty_set_type(struct tty *, int, int); void tty_write(void (*)(struct tty *, const struct tty_ctx *), struct tty_ctx *); void tty_cmd_alignmenttest(struct tty *, const struct tty_ctx *); @@ -1718,9 +1971,11 @@ void tty_cmd_insertcharacter(struct tty *, const struct tty_ctx *); void tty_cmd_insertline(struct tty *, const struct tty_ctx *); void tty_cmd_linefeed(struct tty *, const struct tty_ctx *); void tty_cmd_scrollup(struct tty *, const struct tty_ctx *); +void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); 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 *); /* tty-term.c */ extern struct tty_terms tty_terms; @@ -1749,28 +2004,31 @@ const char *tty_acs_get(struct tty *, u_char); /* tty-keys.c */ void tty_keys_build(struct tty *); void tty_keys_free(struct tty *); -key_code tty_keys_next(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_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 **); long long args_strtonum(struct args *, u_char, long long, long long, char **); /* cmd-find.c */ int cmd_find_target(struct cmd_find_state *, struct cmdq_item *, const char *, enum cmd_find_type, int); +struct client *cmd_find_best_client(struct session *); struct client *cmd_find_client(struct cmdq_item *, const char *, int); void cmd_find_clear_state(struct cmd_find_state *, int); int cmd_find_empty_state(struct cmd_find_state *); int cmd_find_valid_state(struct cmd_find_state *); void cmd_find_copy_state(struct cmd_find_state *, struct cmd_find_state *); -void cmd_find_log_state(const char *, struct cmd_find_state *); void cmd_find_from_session(struct cmd_find_state *, struct session *, int); void cmd_find_from_winlink(struct cmd_find_state *, @@ -1790,12 +2048,17 @@ int cmd_find_from_mouse(struct cmd_find_state *, int cmd_find_from_nothing(struct cmd_find_state *, int); /* cmd.c */ +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 *); 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 **); +void cmd_free(struct cmd *); char *cmd_print(struct cmd *); int cmd_mouse_at(struct window_pane *, struct mouse_event *, u_int *, u_int *, int); @@ -1807,20 +2070,34 @@ extern const struct cmd_entry *cmd_table[]; /* cmd-attach-session.c */ enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int, - const char *, int); + int, const char *, int); + +/* cmd-parse.c */ +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 *); +struct cmd_parse_result *cmd_parse_from_arguments(int, char **, + struct cmd_parse_input *); /* cmd-list.c */ -struct cmd_list *cmd_list_parse(int, char **, const char *, u_int, char **); +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 *); +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); #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 *); void cmdq_insert_after(struct cmdq_item *, struct cmdq_item *); void cmdq_append(struct client *, struct cmdq_item *); +void 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 *); @@ -1828,10 +2105,6 @@ 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 *, ...); -/* cmd-string.c */ -int cmd_string_split(const char *, int *, char ***); -struct cmd_list *cmd_string_parse(const char *, const char *, u_int, char **); - /* cmd-wait-for.c */ void cmd_wait_for_flush(void); @@ -1839,19 +2112,20 @@ void cmd_wait_for_flush(void); int client_main(struct event_base *, int, char **, int); /* key-bindings.c */ -RB_PROTOTYPE(key_bindings, key_binding, entry, key_bindings_cmp); -RB_PROTOTYPE(key_tables, key_table, entry, key_table_cmp); -extern struct key_tables key_tables; -int key_table_cmp(struct key_table *, struct key_table *); -int key_bindings_cmp(struct key_binding *, struct key_binding *); struct key_table *key_bindings_get_table(const char *, int); +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_first(struct key_table *); +struct key_binding *key_bindings_next(struct key_table *, struct key_binding *); void key_bindings_add(const char *, key_code, int, struct cmd_list *); void key_bindings_remove(const char *, key_code); void key_bindings_remove_table(const char *); void key_bindings_init(void); -void key_bindings_dispatch(struct key_binding *, struct cmdq_item *, - struct client *, struct mouse_event *, struct cmd_find_state *); +struct cmdq_item *key_bindings_dispatch(struct key_binding *, + struct cmdq_item *, struct client *, struct mouse_event *, + struct cmd_find_state *); /* key-string.c */ key_code key_string_lookup_string(const char *); @@ -1878,13 +2152,13 @@ void server_add_accept(int); /* server-client.c */ u_int server_client_how_many(void); -void server_client_set_identify(struct client *, u_int); -void server_client_clear_identify(struct client *, struct window_pane *); +void server_client_set_overlay(struct client *, u_int, overlay_draw_cb, + overlay_key_cb, overlay_free_cb, void *); 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 *); -void server_client_handle_key(struct client *, key_code); -void server_client_create(int); +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_lost(struct client *); @@ -1897,7 +2171,7 @@ void server_client_push_stderr(struct client *); void printflike(2, 3) server_client_add_message(struct client *, const char *, ...); char *server_client_get_path(struct client *, const char *); -const char *server_client_get_cwd(struct client *); +const char *server_client_get_cwd(struct client *, struct session *); /* server-fn.c */ void server_redraw_client(struct client *); @@ -1912,6 +2186,7 @@ void server_status_window(struct window *); 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 *); int server_link_window(struct session *, struct winlink *, struct session *, int, int, int, char **); @@ -1926,9 +2201,12 @@ void server_unzoom_window(struct window *); /* status.c */ void status_timer_start(struct client *); void status_timer_start_all(void); -void status_update_saved(struct session *s); +void status_update_cache(struct session *); int status_at_line(struct client *); -struct window *status_get_window_at(struct client *, u_int); +u_int status_line_size(struct client *); +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 status_message_clear(struct client *); @@ -1943,6 +2221,10 @@ void status_prompt_load_history(void); void status_prompt_save_history(void); /* 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_sizes(void); /* input.c */ @@ -1951,6 +2233,7 @@ 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 *); +void input_parse_buffer(struct window_pane *, u_char *, size_t); /* input-key.c */ void input_key(struct window_pane *, key_code, struct mouse_event *); @@ -1965,7 +2248,8 @@ int colour_join_rgb(u_char, u_char, u_char); void colour_split_rgb(int, u_char *, u_char *, u_char *); const char *colour_tostring(int); int colour_fromstring(const char *s); -u_char colour_256to16(u_char); +int colour_256toRGB(int); +int colour_256to16(int); /* attributes.c */ const char *attributes_tostring(int); @@ -1986,6 +2270,8 @@ 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_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); +void grid_adjust_lines(struct grid *, u_int); void grid_clear(struct grid *, u_int, u_int, u_int, u_int, u_int); void grid_clear_lines(struct grid *, u_int, u_int, u_int); void grid_move_lines(struct grid *, u_int, u_int, u_int, u_int); @@ -1994,7 +2280,10 @@ char *grid_string_cells(struct grid *, u_int, u_int, u_int, struct grid_cell **, int, int, int); void grid_duplicate_lines(struct grid *, u_int, struct grid *, u_int, u_int); -u_int grid_reflow(struct grid *, struct grid *, u_int); +void grid_reflow(struct grid *, u_int); +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-view.c */ void grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *); @@ -2008,10 +2297,10 @@ void grid_view_scroll_region_up(struct grid *, u_int, u_int, u_int); void grid_view_scroll_region_down(struct grid *, u_int, u_int, u_int); void grid_view_insert_lines(struct grid *, u_int, u_int, u_int); void grid_view_insert_lines_region(struct grid *, u_int, u_int, u_int, - u_int); + u_int); void grid_view_delete_lines(struct grid *, u_int, u_int, u_int); void grid_view_delete_lines_region(struct grid *, u_int, u_int, u_int, - u_int); + u_int); void grid_view_insert_cells(struct grid *, u_int, u_int, u_int, u_int); 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); @@ -2021,9 +2310,6 @@ void screen_write_start(struct screen_write_ctx *, struct window_pane *, struct screen *); void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); -size_t printflike(1, 2) screen_write_cstrlen(const char *, ...); -void printflike(4, 5) screen_write_cnputs(struct screen_write_ctx *, - ssize_t, const struct grid_cell *, const char *, ...); size_t printflike(1, 2) screen_write_strlen(const char *, ...); void printflike(3, 4) screen_write_puts(struct screen_write_ctx *, const struct grid_cell *, const char *, ...); @@ -2035,8 +2321,11 @@ 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_box(struct screen_write_ctx *, u_int, u_int); void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, u_int); @@ -2056,11 +2345,12 @@ void screen_write_deleteline(struct screen_write_ctx *, u_int, u_int); void screen_write_clearline(struct screen_write_ctx *, u_int); void screen_write_clearendofline(struct screen_write_ctx *, u_int); void screen_write_clearstartofline(struct screen_write_ctx *, u_int); -void screen_write_cursormove(struct screen_write_ctx *, u_int, u_int); +void screen_write_cursormove(struct screen_write_ctx *, int, int, int); void screen_write_reverseindex(struct screen_write_ctx *, u_int); void screen_write_scrollregion(struct screen_write_ctx *, u_int, u_int); void screen_write_linefeed(struct screen_write_ctx *, int, u_int); void screen_write_scrollup(struct screen_write_ctx *, u_int, u_int); +void screen_write_scrolldown(struct screen_write_ctx *, u_int, u_int); void screen_write_carriagereturn(struct screen_write_ctx *); void screen_write_clearendofscreen(struct screen_write_ctx *, u_int); void screen_write_clearstartofscreen(struct screen_write_ctx *, u_int); @@ -2072,10 +2362,10 @@ void screen_write_collect_add(struct screen_write_ctx *, void screen_write_cell(struct screen_write_ctx *, const struct grid_cell *); 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 *); /* screen-redraw.c */ -void screen_redraw_update(struct client *); -void screen_redraw_screen(struct client *, int, int, int); +void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *); /* screen.c */ @@ -2086,9 +2376,12 @@ void screen_reset_tabs(struct screen *); void screen_set_cursor_style(struct screen *, u_int); void screen_set_cursor_colour(struct screen *, const char *); void screen_set_title(struct screen *, const char *); +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_set_selection(struct screen *, - u_int, u_int, u_int, u_int, u_int, struct grid_cell *); +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 *); void screen_hide_selection(struct screen *); int screen_check_selection(struct screen *, u_int, u_int); @@ -2098,6 +2391,7 @@ void screen_select_cell(struct screen *, struct grid_cell *, /* 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 *); @@ -2122,21 +2416,23 @@ void winlink_stack_remove(struct winlink_stack *, struct winlink *); struct window *window_find_by_id_str(const char *); struct window *window_find_by_id(u_int); void window_update_activity(struct window *); -struct window *window_create(u_int, u_int); -struct window *window_create_spawn(const char *, int, char **, const char *, - const char *, const char *, struct environ *, - struct termios *, u_int, u_int, u_int, char **); +struct window *window_create(u_int, u_int, u_int, u_int); +void window_pane_set_event(struct window_pane *); struct window_pane *window_get_active_at(struct window *, u_int, u_int); 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 window_set_active_pane(struct window *, struct window_pane *, + int); void window_redraw_active_switch(struct window *, struct window_pane *); struct window_pane *window_add_pane(struct window *, struct window_pane *, - int, u_int); -void window_resize(struct window *, u_int, u_int); + u_int, int); +void window_resize(struct window *, u_int, u_int, int, int); +void window_pane_send_resize(struct window_pane *, int); int window_zoom(struct window_pane *); int window_unzoom(struct window *); +int window_push_zoom(struct window *, 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 *); struct window_pane *window_pane_at_index(struct window *, u_int); @@ -2150,9 +2446,6 @@ void window_destroy_panes(struct window *); 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 *); -int window_pane_spawn(struct window_pane *, int, char **, - const char *, const char *, const char *, struct environ *, - struct termios *, char **); void window_pane_resize(struct window_pane *, u_int, u_int); void window_pane_alternate_on(struct window_pane *, struct grid_cell *, int); @@ -2161,15 +2454,18 @@ void window_pane_alternate_off(struct window_pane *, 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(const struct window_pane *, int); +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 *); void window_pane_reset_mode(struct window_pane *); +void window_pane_reset_mode_all(struct window_pane *); void window_pane_key(struct window_pane *, struct client *, - struct session *, key_code, struct mouse_event *); + struct session *, struct winlink *, key_code, + struct mouse_event *); int window_pane_visible(struct window_pane *); -u_int window_pane_search(struct window_pane *, const char *); +u_int window_pane_search(struct window_pane *, const char *, int, + int); const char *window_printable_flags(struct winlink *); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); @@ -2180,6 +2476,8 @@ 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 window_pane_start_input(struct window_pane *, + struct cmdq_item *, char **); /* layout.c */ u_int layout_count_cells(struct layout_cell *); @@ -2188,12 +2486,15 @@ void layout_free_cell(struct layout_cell *); void layout_print_cell(struct layout_cell *, const char *, u_int); void layout_destroy_cell(struct window *, struct layout_cell *, struct layout_cell **); +void layout_resize_layout(struct window *, struct layout_cell *, + enum layout_type, int, int); +struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int); void layout_set_size(struct layout_cell *, u_int, 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 layout_cell *); -void layout_fix_panes(struct window *, u_int, u_int); +void layout_fix_offsets(struct window *); +void layout_fix_panes(struct window *); void layout_resize_adjust(struct window *, struct layout_cell *, enum layout_type, int); void layout_init(struct window *, struct window_pane *); @@ -2205,8 +2506,10 @@ void layout_resize_pane_to(struct window_pane *, enum layout_type, u_int); void layout_assign_pane(struct layout_cell *, struct window_pane *); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, - int, int, int); + int, int); void layout_close_pane(struct window_pane *); +int layout_spread_cell(struct window *, struct layout_cell *); +void layout_spread_out(struct window_pane *); /* layout-custom.c */ char *layout_dump(struct layout_cell *); @@ -2219,17 +2522,25 @@ u_int layout_set_next(struct window *); u_int layout_set_previous(struct window *); /* mode-tree.c */ +typedef void (*mode_tree_build_cb)(void *, struct mode_tree_sort_criteria *, + uint64_t *, const char *); +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 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 *); -void mode_tree_each_tagged(struct mode_tree_data *, void (*)(void *, void *, - key_code), key_code, int); -void mode_tree_up(struct mode_tree_data *, int); +void mode_tree_expand_current(struct mode_tree_data *); +void 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_down(struct mode_tree_data *, int); struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *, - void (*)(void *, u_int, uint64_t *, const char *), - struct screen *(*)(void *, void *, u_int, u_int), - int (*)(void *, void *, const char *), void *, const char **, + 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 **); +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 *); void mode_tree_resize(struct mode_tree_data *, u_int, u_int); @@ -2239,7 +2550,7 @@ struct mode_tree_item *mode_tree_add(struct mode_tree_data *, 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 *, - struct mouse_event *); + struct mouse_event *, u_int *, u_int *); void mode_tree_run_command(struct client *, struct cmd_find_state *, const char *, const char *); @@ -2258,14 +2569,11 @@ extern const struct window_mode window_client_mode; /* window-copy.c */ extern const struct window_mode window_copy_mode; -void window_copy_init_from_pane(struct window_pane *, int); -void window_copy_init_for_output(struct window_pane *); +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 window_copy_pageup(struct window_pane *, int); void window_copy_start_drag(struct client *, struct mouse_event *); -void window_copy_add_formats(struct window_pane *, - struct format_tree *); /* names.c */ void check_window_name(struct window *); @@ -2279,7 +2587,7 @@ void control_write_buffer(struct client *, struct evbuffer *); /* control-notify.c */ void control_notify_input(struct client *, struct window_pane *, - struct evbuffer *); + const u_char *, size_t); void control_notify_pane_mode_changed(int); void control_notify_window_layout_changed(struct window *); void control_notify_window_pane_changed(struct window *); @@ -2294,19 +2602,15 @@ void control_notify_session_window_changed(struct session *); /* session.c */ extern struct sessions sessions; -extern struct session_groups session_groups; int session_cmp(struct session *, struct session *); RB_PROTOTYPE(sessions, session, entry, session_cmp); -int session_group_cmp(struct session_group *, struct session_group *); -RB_PROTOTYPE(session_groups, session_group, entry, session_group_cmp); int session_alive(struct session *); struct session *session_find(const char *); 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 *, int, char **, - const char *, const char *, struct environ *, - struct termios *, int, u_int, u_int, char **); -void session_destroy(struct session *, const char *); +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_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); int session_check_name(const char *); @@ -2331,6 +2635,7 @@ struct session_group *session_group_new(const char *); void session_group_add(struct session_group *, struct session *); void session_group_synchronize_to(struct session *); void session_group_synchronize_from(struct session *); +u_int session_group_count(struct session_group *); void session_renumber_windows(struct session *); /* utf8.c */ @@ -2349,9 +2654,9 @@ u_int utf8_strwidth(const struct utf8_data *, ssize_t); struct utf8_data *utf8_fromcstr(const char *); char *utf8_tocstr(struct utf8_data *); u_int utf8_cstrwidth(const char *); -char *utf8_rtrimcstr(const char *, u_int); -char *utf8_trimcstr(const char *, u_int); char *utf8_padcstr(const char *, u_int); +char *utf8_rpadcstr(const char *, u_int); +int utf8_cstrhas(const char *, const struct utf8_data *); /* osdep-*.c */ char *osdep_get_name(int, char *); @@ -2368,15 +2673,47 @@ void printflike(1, 2) log_debug(const char *, ...); __dead void printflike(1, 2) fatal(const char *, ...); __dead void printflike(1, 2) fatalx(const char *, ...); +/* menu.c */ +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 *, + struct cmdq_item *, struct client *, + struct cmd_find_state *); + +void menu_free(struct menu *); +int menu_display(struct menu *, int, struct cmdq_item *, u_int, + u_int, struct client *, struct cmd_find_state *, + menu_choice_cb, void *); + /* style.c */ -int style_parse(const struct grid_cell *, - struct grid_cell *, const char *); -const char *style_tostring(struct grid_cell *); -void style_apply(struct grid_cell *, struct options *, +int style_parse(struct style *,const struct grid_cell *, const char *); -void style_apply_update(struct grid_cell *, struct options *, +const char *style_tostring(struct style *); +void style_apply(struct grid_cell *, struct options *, const char *); -int style_equal(const struct grid_cell *, - const struct grid_cell *); +int style_equal(struct style *, struct style *); +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 **); +struct window_pane *spawn_pane(struct spawn_context *, char **); + +/* regsub.c */ +char *regsub(const char *, const char *, const char *, int); + +/* sixel.c */ +struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int); +void sixel_free(struct sixel_image *); +void sixel_log(struct sixel_image *); +void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *); +struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int, + u_int, u_int, u_int); +char *sixel_print(struct sixel_image *, struct sixel_image *, + size_t *); +struct screen *sixel_to_screen(struct sixel_image *); #endif /* TMUX_H */ diff --git a/tools/24-bit-color.sh b/tools/24-bit-color.sh index 81d7cd4121..3e91da20bb 100644 --- a/tools/24-bit-color.sh +++ b/tools/24-bit-color.sh @@ -14,13 +14,27 @@ # https://github.com/gnachman/iTerm2/blob/master/LICENSE # -if which gseq >/dev/null -then - SEQ=gseq +SEQ1= +if which gseq >/dev/null 2>&1; then + SEQ1=gseq +elif seq --version|grep -q GNU; then + SEQ1=seq +fi +if [ -n "$SEQ1" ]; then + # GNU seq requires a -ve increment if going backwards + seq1() + { + if [ $1 -gt $2 ]; then + $SEQ1 $1 -1 $2 + else + $SEQ1 $1 $2 + fi + } + SEQ=seq1 else SEQ=seq fi -SEPARATOR=';' +SEPARATOR=':' setBackgroundColor() { diff --git a/tty-acs.c b/tty-acs.c index 1f7a2b11b2..14634120a8 100644 --- a/tty-acs.c +++ b/tty-acs.c @@ -37,10 +37,14 @@ static const struct tty_acs_entry tty_acs_table[] = { { '0', "\342\226\256" }, /* solid square block */ { '`', "\342\227\206" }, /* diamond */ { 'a', "\342\226\222" }, /* checker board (stipple) */ + { 'b', "\342\220\211" }, + { 'c', "\342\220\214" }, + { 'd', "\342\220\215" }, + { 'e', "\342\220\212" }, { 'f', "\302\260" }, /* degree symbol */ { 'g', "\302\261" }, /* plus/minus */ - { 'h', "\342\226\222" }, /* board of squares */ - { 'i', "\342\230\203" }, /* lantern symbol */ + { 'h', "\342\220\244" }, + { 'i', "\342\220\213" }, { 'j', "\342\224\230" }, /* lower right corner */ { 'k', "\342\224\220" }, /* upper right corner */ { 'l', "\342\224\214" }, /* upper left corner */ diff --git a/tty-keys.c b/tty-keys.c index 7c0455f020..79f42d473e 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -19,7 +19,10 @@ #include #include +#include + #include +#include #include #include #include @@ -43,7 +46,10 @@ 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_mouse(struct tty *, const char *, size_t, size_t *); +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 *); @@ -171,7 +177,12 @@ static const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[201~", KEYC_PASTE_END }, }; -/* Default terminfo(5) keys. */ +/* + * 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; @@ -191,61 +202,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 }, - { 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 }, - { 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 }, - { 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 }, - { TTYC_KF50, KEYC_F2|KEYC_ESCAPE }, - { TTYC_KF51, KEYC_F3|KEYC_ESCAPE }, - { TTYC_KF52, KEYC_F4|KEYC_ESCAPE }, - { TTYC_KF53, KEYC_F5|KEYC_ESCAPE }, - { TTYC_KF54, KEYC_F6|KEYC_ESCAPE }, - { TTYC_KF55, KEYC_F7|KEYC_ESCAPE }, - { TTYC_KF56, KEYC_F8|KEYC_ESCAPE }, - { TTYC_KF57, KEYC_F9|KEYC_ESCAPE }, - { TTYC_KF58, KEYC_F10|KEYC_ESCAPE }, - { TTYC_KF59, KEYC_F11|KEYC_ESCAPE }, - { TTYC_KF60, KEYC_F12|KEYC_ESCAPE }, - - { TTYC_KF61, KEYC_F1|KEYC_ESCAPE|KEYC_SHIFT }, - { TTYC_KF62, KEYC_F2|KEYC_ESCAPE|KEYC_SHIFT }, - { TTYC_KF63, KEYC_F3|KEYC_ESCAPE|KEYC_SHIFT }, + { 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_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_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_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_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_KICH1, KEYC_IC }, { TTYC_KDCH1, KEYC_DC }, @@ -261,17 +272,14 @@ static const struct tty_default_key_code tty_default_code_keys[] = { { TTYC_KCUB1, KEYC_LEFT }, { TTYC_KCUF1, KEYC_RIGHT }, - /* - * Key and modifier capabilities. We set the xterm flag to mark that - * any leading escape means an escape key press and not the modifier. - */ + /* 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_UP|KEYC_SHIFT|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 }, @@ -391,9 +399,11 @@ tty_keys_build(struct tty *tty) { const struct tty_default_key_raw *tdkr; const struct tty_default_key_code *tdkc; - u_int i, size; - const char *s, *value; + u_int i; + const char *s; struct options_entry *o; + struct options_array_item *a; + union options_value *ov; if (tty->key_tree != NULL) tty_keys_free(tty); @@ -416,11 +426,13 @@ tty_keys_build(struct tty *tty) } o = options_get(global_options, "user-keys"); - if (o != NULL && options_array_size(o, &size) != -1) { - for (i = 0; i < size; i++) { - value = options_array_get(o, i); - if (value != NULL) - tty_keys_add(tty, value, KEYC_USER + i); + if (o != NULL) { + a = options_array_first(o); + while (a != NULL) { + i = options_array_item_index(a); + ov = options_array_item_value(a); + tty_keys_add(tty, ov->string, KEYC_USER + i); + a = options_array_next(a); } } } @@ -457,6 +469,10 @@ tty_keys_find(struct tty *tty, const char *buf, size_t len, size_t *size) static struct tty_key * tty_keys_find1(struct tty_key *tk, const char *buf, size_t len, size_t *size) { + /* If no data, no match. */ + if (len == 0) + return (NULL); + /* If the node is NULL, this is the end of the tree. No match. */ if (tk == NULL) return (NULL); @@ -546,29 +562,40 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key, return (-1); } -/* - * Process at least one key in the buffer and invoke tty->key_callback. Return - * 0 if there are no further keys, or 1 if there could be more in the buffer. - */ -key_code +/* Process at least one key in the buffer. Return 0 if no keys present. */ +int tty_keys_next(struct tty *tty) { - struct client *c = tty->client; - struct timeval tv; - const char *buf; - size_t len, size; - cc_t bspace; - int delay, expired = 0, n; - key_code key; + struct client *c = tty->client; + struct timeval tv; + const char *buf; + size_t len, size; + cc_t bspace; + int delay, expired = 0, n; + key_code key; + 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); - if (len == 0) return (0); log_debug("%s: keys are %zu (%.*s)", c->name, len, (int)len, buf); + /* Is this a clipboard response? */ + switch (tty_keys_clipboard(tty, buf, len, &size)) { + case 0: /* yes */ + key = KEYC_UNKNOWN; + goto complete_key; + case -1: /* no, or not valid */ + break; + case 1: /* partial */ + goto partial_key; + } + /* Is this a device attributes response? */ switch (tty_keys_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ @@ -581,7 +608,7 @@ tty_keys_next(struct tty *tty) } /* Is this a mouse key press? */ - switch (tty_keys_mouse(tty, buf, len, &size)) { + switch (tty_keys_mouse(tty, buf, len, &size, &m)) { case 0: /* yes */ key = KEYC_MOUSE; goto complete_key; @@ -606,7 +633,7 @@ tty_keys_next(struct tty *tty) * If not a complete key, look for key with an escape prefix (meta * modifier). */ - if (*buf == '\033') { + if (*buf == '\033' && len > 1) { /* Look for a key without the escape. */ n = tty_keys_next1(tty, buf + 1, len - 1, &key, &size, expired); if (n == 0) { /* found */ @@ -700,8 +727,13 @@ tty_keys_next(struct tty *tty) } /* Fire the key. */ - if (key != KEYC_UNKNOWN) - server_client_handle_key(tty->client, key); + if (key != KEYC_UNKNOWN) { + event = xmalloc(sizeof *event); + event->key = key; + memcpy(&event->m, &m, sizeof event->m); + if (!server_client_handle_key(c, event)) + free(event); + } return (1); @@ -731,12 +763,12 @@ tty_keys_callback(__unused int fd, __unused short events, void *data) * (probably a mouse sequence but need more data). */ static int -tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) +tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size, + struct mouse_event *m) { - struct client *c = tty->client; - struct mouse_event *m = &tty->mouse; - u_int i, x, y, b, sgr_b; - u_char sgr_type, ch; + struct client *c = tty->client; + u_int i, x, y, b, sgr_b; + u_char sgr_type, ch; /* * Standard mouse sequences are \033[M followed by three characters @@ -857,15 +889,107 @@ tty_keys_mouse(struct tty *tty, const char *buf, size_t len, size_t *size) return (-1); /* Fill mouse event. */ - m->lx = m->x; + m->lx = tty->mouse_last_x; m->x = x; - m->ly = m->y; + m->ly = tty->mouse_last_y; m->y = y; - m->lb = m->b; + m->lb = tty->mouse_last_b; m->b = b; m->sgr_type = sgr_type; m->sgr_b = sgr_b; + /* Update last mouse state. */ + tty->mouse_last_x = x; + tty->mouse_last_y = y; + tty->mouse_last_b = b; + + return (0); +} + +/* + * Handle OSC 52 clipboard input. Returns 0 for success, -1 for failure, 1 for + * partial. + */ +static int +tty_keys_clipboard(__unused struct tty *tty, const char *buf, size_t len, + size_t *size) +{ + size_t end, terminator, needed; + char *copy, *out; + int outlen; + + *size = 0; + + /* First three bytes are always \033]52;. */ + if (buf[0] != '\033') + return (-1); + if (len == 1) + return (1); + if (buf[1] != ']') + return (-1); + if (len == 2) + return (1); + if (buf[2] != '5') + return (-1); + if (len == 3) + return (1); + if (buf[3] != '2') + return (-1); + if (len == 4) + return (1); + if (buf[4] != ';') + return (-1); + if (len == 5) + return (1); + + /* Find the terminator if any. */ + for (end = 5; end < len; end++) { + if (buf[end] == '\007') { + terminator = 1; + break; + } + if (end > 5 && buf[end - 1] == '\033' && buf[end] == '\\') { + terminator = 2; + break; + } + } + if (end == len) + return (1); + *size = end + terminator; + + /* Skip the initial part. */ + buf += 5; + end -= 5; + + /* Get the second argument. */ + while (end != 0 && *buf != ';') { + buf++; + end--; + } + if (end == 0 || end == 1) + return (0); + buf++; + end--; + + /* It has to be a string so copy it. */ + copy = xmalloc(end + 1); + memcpy(copy, buf, end); + copy[end] = '\0'; + + /* Convert from base64. */ + needed = (end / 4) * 3; + out = xmalloc(needed); + if ((outlen = b64_pton(copy, out, len)) == -1) { + free(out); + free(copy); + return (0); + } + free(copy); + + /* Create a new paste buffer. */ + log_debug("%s: %.*s", __func__, outlen, out); + paste_add(NULL, out, outlen); + return (0); } @@ -878,10 +1002,10 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; - u_int i, a, b; - char tmp[64], *endptr; + u_int i, n = 0; + char tmp[64], *endptr, p[32] = { 0 }, *cp, *next; static const char *types[] = TTY_TYPES; - int type; + int type, flags = 0; *size = 0; @@ -911,21 +1035,21 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, *size = 4 + i; /* Convert version numbers. */ - a = strtoul(tmp, &endptr, 10); - if (*endptr == ';') { - b = strtoul(endptr + 1, &endptr, 10); + cp = tmp; + while ((next = strsep(&cp, ";")) != NULL) { + p[n] = strtoul(next, &endptr, 10); if (*endptr != '\0' && *endptr != ';') - b = 0; - } else - a = b = 0; + p[n] = 0; + n++; + } /* Store terminal type. */ type = TTY_UNKNOWN; - switch (a) { + switch (p[0]) { case 1: - if (b == 2) + if (p[1] == 2) type = TTY_VT100; - else if (b == 0) + else if (p[1] == 0) type = TTY_VT101; break; case 6: @@ -940,8 +1064,15 @@ tty_keys_device_attributes(struct tty *tty, const char *buf, size_t len, case 64: type = TTY_VT420; break; + case 65: + type = TTY_VT520; + break; } - tty_set_type(tty, type); + for (i = 2; i < n; i++) + log_debug("%s: DA feature: %d", c->name, p[i]); + if (p[i] == 4) + flags |= TERM_SIXEL; + tty_set_type(tty, type, flags); log_debug("%s: received DA %.*s (%s)", c->name, (int)*size, buf, types[type]); diff --git a/tty-term.c b/tty-term.c index 9d026fa3cb..3ac9bc6c07 100644 --- a/tty-term.c +++ b/tty-term.c @@ -240,7 +240,9 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_MS] = { TTYCODE_STRING, "Ms" }, [TTYC_OP] = { TTYCODE_STRING, "op" }, [TTYC_REV] = { TTYCODE_STRING, "rev" }, + [TTYC_RGB] = { TTYCODE_FLAG, "RGB" }, [TTYC_RI] = { TTYCODE_STRING, "ri" }, + [TTYC_RIN] = { TTYCODE_STRING, "rin" }, [TTYC_RMACS] = { TTYCODE_STRING, "rmacs" }, [TTYC_RMCUP] = { TTYCODE_STRING, "rmcup" }, [TTYC_RMKX] = { TTYCODE_STRING, "rmkx" }, @@ -248,13 +250,16 @@ static const struct tty_term_code_entry tty_term_codes[] = { [TTYC_SETAF] = { TTYCODE_STRING, "setaf" }, [TTYC_SETRGBB] = { TTYCODE_STRING, "setrgbb" }, [TTYC_SETRGBF] = { TTYCODE_STRING, "setrgbf" }, + [TTYC_SETULC] = { TTYCODE_STRING, "Setulc" }, [TTYC_SE] = { TTYCODE_STRING, "Se" }, [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" }, [TTYC_SITM] = { TTYCODE_STRING, "sitm" }, [TTYC_SMACS] = { TTYCODE_STRING, "smacs" }, [TTYC_SMCUP] = { TTYCODE_STRING, "smcup" }, [TTYC_SMKX] = { TTYCODE_STRING, "smkx" }, + [TTYC_SMOL] = { TTYCODE_STRING, "Smol" }, [TTYC_SMSO] = { TTYCODE_STRING, "smso" }, + [TTYC_SMULX] = { TTYCODE_STRING, "Smulx" }, [TTYC_SMUL] = { TTYCODE_STRING, "smul" }, [TTYC_SMXX] = { TTYCODE_STRING, "smxx" }, [TTYC_SS] = { TTYCODE_STRING, "Ss" }, @@ -276,7 +281,7 @@ static char * tty_term_strip(const char *s) { const char *ptr; - static char buf[BUFSIZ]; + static char buf[8192]; size_t len; /* Ignore strings with no padding. */ @@ -301,25 +306,53 @@ tty_term_strip(const char *s) return (xstrdup(buf)); } +static char * +tty_term_override_next(const char *s, size_t *offset) +{ + static char value[8192]; + size_t n = 0, at = *offset; + + if (s[at] == '\0') + return (NULL); + + while (s[at] != '\0') { + if (s[at] == ':') { + if (s[at + 1] == ':') { + value[n++] = ':'; + at += 2; + } else + break; + } else { + value[n++] = s[at]; + at++; + } + if (n == (sizeof value) - 1) + return (NULL); + } + if (s[at] != '\0') + *offset = at + 1; + else + *offset = at; + value[n] = '\0'; + return (value); +} + static void tty_term_override(struct tty_term *term, const char *override) { const struct tty_term_code_entry *ent; struct tty_code *code; - char *next, *s, *copy, *cp, *value; + size_t offset = 0; + char *cp, *value, *s; const char *errstr; u_int i; int n, remove; - copy = next = xstrdup(override); - - s = strsep(&next, ":"); - if (s == NULL || next == NULL || fnmatch(s, term->name, 0) != 0) { - free(copy); + s = tty_term_override_next(override, &offset); + if (s == NULL || fnmatch(s, term->name, 0) != 0) return; - } - while ((s = strsep(&next, ":")) != NULL) { + while ((s = tty_term_override_next(override, &offset)) != NULL) { if (*s == '\0') continue; value = NULL; @@ -340,6 +373,8 @@ tty_term_override(struct tty_term *term, const char *override) 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); @@ -378,7 +413,6 @@ tty_term_override(struct tty_term *term, const char *override) free(value); } - free(s); } struct tty_term * @@ -388,7 +422,9 @@ tty_term_find(char *name, int fd, char **cause) const struct tty_term_code_entry *ent; struct tty_code *code; struct options_entry *o; - u_int size, i; + struct options_array_item *a; + union options_value *ov; + u_int i; int n, error; const char *s, *acs; @@ -463,12 +499,11 @@ tty_term_find(char *name, int fd, char **cause) /* Apply terminal overrides. */ o = options_get_only(global_options, "terminal-overrides"); - if (options_array_size(o, &size) != -1) { - for (i = 0; i < size; i++) { - s = options_array_get(o, i); - if (s != NULL) - tty_term_override(term, s); - } + a = options_array_first(o); + while (a != NULL) { + ov = options_array_item_value(a); + tty_term_override(term, ov->string); + a = options_array_next(a); } /* Delete curses data. */ @@ -493,8 +528,9 @@ tty_term_find(char *name, int fd, char **cause) goto error; } - /* Figure out if we have 256. */ - if (tty_term_number(term, TTYC_COLORS) == 256) + /* Figure out if we have 256 colours (or more). */ + if (tty_term_number(term, TTYC_COLORS) >= 256 || + tty_term_has(term, TTYC_RGB)) term->flags |= TERM_256COLOURS; /* @@ -531,8 +567,11 @@ tty_term_find(char *name, int fd, char **cause) code->type = TTYCODE_STRING; } - /* On terminals with RGB colour (TC), fill in setrgbf and setrgbb. */ - if (tty_term_flag(term, TTYC_TC) && + /* + * On terminals with RGB colour (Tc or RGB), fill in setrgbf and + * setrgbb if they are missing. + */ + if ((tty_term_flag(term, TTYC_TC) || tty_term_flag(term, TTYC_RGB)) && !tty_term_has(term, TTYC_SETRGBF) && !tty_term_has(term, TTYC_SETRGBB)) { code = &term->codes[TTYC_SETRGBF]; @@ -603,7 +642,8 @@ tty_term_string2(struct tty_term *term, enum tty_code_code code, int a, int b) } const char * -tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, int c) +tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, + int c) { return (tparm((char *) tty_term_string(term, code), a, b, c, 0, 0, 0, 0, 0, 0)); } @@ -611,14 +651,14 @@ tty_term_string3(struct tty_term *term, enum tty_code_code code, int a, int b, i const char * tty_term_ptr1(struct tty_term *term, enum tty_code_code code, const void *a) { - return (tparm((char *) tty_term_string(term, code), a, 0, 0, 0, 0, 0, 0, 0, 0)); + return (tparm((char *) tty_term_string(term, code), (long)a, 0, 0, 0, 0, 0, 0, 0, 0)); } const char * tty_term_ptr2(struct tty_term *term, enum tty_code_code code, const void *a, const void *b) { - return (tparm((char *) tty_term_string(term, code), a, b, 0, 0, 0, 0, 0, 0, 0)); + return (tparm((char *) tty_term_string(term, code), (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0)); } int @@ -654,7 +694,7 @@ tty_term_describe(struct tty_term *term, enum tty_code_code code) break; case TTYCODE_STRING: strnvis(out, term->codes[code].value.string, sizeof out, - VIS_OCTAL|VIS_TAB|VIS_NL); + VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); xsnprintf(s, sizeof s, "%4u: %s: (string) %s", code, tty_term_codes[code].name, out); diff --git a/tty.c b/tty.c index 0d1aa368f0..21a8c501df 100644 --- a/tty.c +++ b/tty.c @@ -45,12 +45,15 @@ 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 *, const struct window_pane *, +static void tty_check_fg(struct tty *, struct window_pane *, struct grid_cell *); -static void tty_check_bg(struct tty *, const struct window_pane *, +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_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 *); static void tty_region_pane(struct tty *, const struct tty_ctx *, u_int, u_int); @@ -58,17 +61,16 @@ 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 *, const struct window_pane *, - u_int); +static int tty_fake_bce(const struct tty *, struct window_pane *, 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 *, - const struct window_pane *); -static void tty_default_colours(struct grid_cell *, - const struct window_pane *); -static void tty_default_attributes(struct tty *, const struct window_pane *, + 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); #define tty_use_margin(tty) \ @@ -125,29 +127,40 @@ tty_resize(struct tty *tty) { struct client *c = tty->client; struct winsize ws; - u_int sx, sy; + u_int sx, sy, xpixel, ypixel; if (ioctl(tty->fd, TIOCGWINSZ, &ws) != -1) { sx = ws.ws_col; - if (sx == 0) + if (sx == 0) { sx = 80; + xpixel = 0; + } else + xpixel = ws.ws_xpixel / sx; sy = ws.ws_row; - if (sy == 0) + if (sy == 0) { sy = 24; + ypixel = 0; + } else + ypixel = ws.ws_ypixel / sy; } else { sx = 80; sy = 24; + xpixel = 0; + ypixel = 0; } - log_debug("%s: %s now %ux%u", __func__, c->name, sx, sy); - tty_set_size(tty, sx, sy); + log_debug("%s: %s now %ux%u (%ux%u)", __func__, c->name, sx, sy, + xpixel, ypixel); + tty_set_size(tty, sx, sy, xpixel, ypixel); tty_invalidate(tty); } void -tty_set_size(struct tty *tty, u_int sx, u_int sy) +tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel) { tty->sx = sx; tty->sy = sy; + tty->xpixel = xpixel; + tty->ypixel = ypixel; } static void @@ -179,7 +192,7 @@ tty_timer_callback(__unused int fd, __unused short events, void *data) log_debug("%s: %zu discarded", c->name, tty->discarded); - c->flags |= CLIENT_REDRAW; + c->flags |= CLIENT_ALLREDRAWFLAGS; c->discarded += tty->discarded; if (tty->discarded < TTY_BLOCK_STOP(tty)) { @@ -257,9 +270,13 @@ tty_open(struct tty *tty, char **cause) event_set(&tty->event_in, tty->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); tty->out = evbuffer_new(); + if (tty->out == NULL) + fatal("out of memory"); evtimer_set(&tty->timer, tty_timer_callback, tty); @@ -419,9 +436,10 @@ tty_free(struct tty *tty) } void -tty_set_type(struct tty *tty, int type) +tty_set_type(struct tty *tty, int type, int flags) { tty->term_type = type; + tty->term_flags |= flags; if (tty_use_margin(tty)) tty_puts(tty, "\033[?69h"); /* DECLRMM */ @@ -524,6 +542,12 @@ tty_putc(struct tty *tty, u_char ch) { const char *acs; + if ((tty->term->flags & TERM_EARLYWRAP) && + ch >= 0x20 && ch != 0x7f && + tty->cy == tty->sy - 1 && + tty->cx + 1 >= tty->sx) + return; + if (tty->cell.attr & GRID_ATTR_CHARSET) { acs = tty_acs_get(tty, ch); if (acs != NULL) @@ -554,6 +578,11 @@ 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_EARLYWRAP) && + tty->cy == tty->sy - 1 && + tty->cx + len >= tty->sx) + len = tty->sx - tty->cx - 1; + tty_add(tty, buf, len); if (tty->cx + width > tty->sx) { tty->cx = (tty->cx + width) - tty->sx; @@ -698,6 +727,132 @@ tty_repeat_space(struct tty *tty, u_int n) tty_putn(tty, s, n, n); } +/* Is this window bigger than the terminal? */ +int +tty_window_bigger(struct tty *tty) +{ + struct client *c = tty->client; + struct window *w = c->session->curw->window; + + return (tty->sx < w->sx || tty->sy - status_line_size(c) < w->sy); +} + +/* What offset should this window be drawn at? */ +int +tty_window_offset(struct tty *tty, u_int *ox, u_int *oy, u_int *sx, u_int *sy) +{ + *ox = tty->oox; + *oy = tty->ooy; + *sx = tty->osx; + *sy = tty->osy; + + return (tty->oflag); +} + +/* What offset should this window be drawn at? */ +static int +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; + u_int cx, cy, lines; + + lines = status_line_size(c); + + if (tty->sx >= w->sx && tty->sy - lines >= w->sy) { + *ox = 0; + *oy = 0; + *sx = w->sx; + *sy = w->sy; + + c->pan_window = NULL; + return (0); + } + + *sx = tty->sx; + *sy = tty->sy - lines; + + if (c->pan_window == w) { + if (*sx >= w->sx) + c->pan_ox = 0; + else if (c->pan_ox + *sx > w->sx) + c->pan_ox = w->sx - *sx; + *ox = c->pan_ox; + if (*sy >= w->sy) + c->pan_oy = 0; + else if (c->pan_oy + *sy > w->sy) + c->pan_oy = w->sy - *sy; + *oy = c->pan_oy; + return (1); + } + + if (~wp->screen->mode & MODE_CURSOR) { + *ox = 0; + *oy = 0; + } else { + cx = wp->xoff + wp->screen->cx; + cy = wp->yoff + wp->screen->cy; + + if (cx < *sx) + *ox = 0; + else if (cx > w->sx - *sx) + *ox = w->sx - *sx; + else + *ox = cx - *sx / 2; + + if (cy < *sy) + *oy = 0; + else if (cy > w->sy - *sy) + *oy = w->sy - *sy; + else + *oy = cy - *sy / 2; + } + + c->pan_window = NULL; + return (1); +} + +/* Update stored offsets for a window and redraw if necessary. */ +void +tty_update_window_offset(struct window *w) +{ + struct client *c; + + TAILQ_FOREACH(c, &clients, entry) { + if (c->session != NULL && c->session->curw->window == w) + tty_update_client_offset(c); + } +} + +/* Update stored offsets for a client and redraw if necessary. */ +void +tty_update_client_offset(struct client *c) +{ + u_int ox, oy, sx, sy; + + if (~c->flags & CLIENT_TERMINAL) + return; + + c->tty.oflag = tty_window_offset1(&c->tty, &ox, &oy, &sx, &sy); + if (ox == c->tty.oox && + oy == c->tty.ooy && + sx == c->tty.osx && + sy == c->tty.osy) + return; + + log_debug ("%s: %s offset has changed (%u,%u %ux%u -> %u,%u %ux%u)", + __func__, c->name, c->tty.oox, c->tty.ooy, c->tty.osx, c->tty.osy, + ox, oy, sx, sy); + + c->tty.oox = ox; + c->tty.ooy = oy; + c->tty.osx = sx; + c->tty.osy = sy; + + c->flags |= (CLIENT_REDRAWWINDOW|CLIENT_REDRAWSTATUS); +} + /* * Is the region large enough to be worth redrawing once later rather than * probably several times now? Currently yes if it is more than 50% of the @@ -716,7 +871,7 @@ tty_large_region(__unused struct tty *tty, const struct tty_ctx *ctx) * emulated. */ static int -tty_fake_bce(const struct tty *tty, const struct window_pane *wp, u_int bg) +tty_fake_bce(const struct tty *tty, struct window_pane *wp, u_int bg) { struct grid_cell gc; @@ -727,7 +882,7 @@ tty_fake_bce(const struct tty *tty, const struct window_pane *wp, u_int bg) if (wp != NULL) tty_default_colours(&gc, wp); - if (bg != 8 || gc.bg != 8) + if (!COLOUR_DEFAULT(bg) || !COLOUR_DEFAULT(gc.bg)) return (1); return (0); } @@ -755,18 +910,81 @@ tty_redraw_region(struct tty *tty, const struct tty_ctx *ctx) if (ctx->ocy < ctx->orupper || ctx->ocy > ctx->orlower) { for (i = ctx->ocy; i < screen_size_y(s); i++) - tty_draw_pane(tty, wp, i, ctx->xoff, ctx->yoff); + tty_draw_pane(tty, ctx, i); } else { for (i = ctx->orupper; i <= ctx->orlower; i++) - tty_draw_pane(tty, wp, i, ctx->xoff, ctx->yoff); + 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) +{ + u_int xoff = ctx->xoff + px, yoff = ctx->yoff + py, lines; + + 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) + return (0); + return (1); +} + +/* Clamp line position to visible part of pane. */ +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; + + if (!tty_is_visible(tty, ctx, px, py, nx, 1)) + return (0); + *ry = ctx->yoff + py - ctx->oy; + + if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + /* All visible. */ + *i = 0; + *x = ctx->xoff + px - ctx->ox; + *rx = nx; + } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + /* Both left and right not visible. */ + *i = ctx->ox; + *x = 0; + *rx = ctx->sx; + } else if (xoff < ctx->ox) { + /* Left not visible. */ + *i = ctx->ox - (ctx->xoff + px); + *x = 0; + *rx = nx - *i; + } else { + /* Right not visible. */ + *i = 0; + *x = (ctx->xoff + px) - ctx->ox; + *rx = ctx->sx - *x; } + if (*rx > nx) + fatalx("%s: x too big, %u > %u", __func__, *rx, nx); + + return (1); } +/* Clear a line. */ static void -tty_clear_line(struct tty *tty, const struct window_pane *wp, u_int py, - u_int px, u_int nx, u_int bg) +tty_clear_line(struct tty *tty, struct window_pane *wp, u_int py, u_int px, + u_int nx, u_int bg) { - log_debug("%s: %u at %u,%u", __func__, nx, px, py); + struct client *c = tty->client; + + log_debug("%s: %s, %u at %u,%u", __func__, c->name, nx, px, py); /* Nothing to clear. */ if (nx == 0) @@ -801,14 +1019,93 @@ tty_clear_line(struct tty *tty, const struct window_pane *wp, u_int py, tty_repeat_space(tty, nx); } +/* Clear a line, adjusting to visible part of pane. */ static void -tty_clear_area(struct tty *tty, const struct window_pane *wp, u_int py, - u_int ny, u_int px, u_int nx, u_int bg) +tty_clear_pane_line(struct tty *tty, const struct tty_ctx *ctx, u_int py, + u_int px, u_int nx, u_int bg) +{ + struct client *c = tty->client; + u_int i, x, rx, ry; + + 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); +} + +/* Clamp area position to visible part of pane. */ +static int +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; + + if (!tty_is_visible(tty, ctx, px, py, nx, ny)) + return (0); + + if (xoff >= ctx->ox && xoff + nx <= ctx->ox + ctx->sx) { + /* All visible. */ + *i = 0; + *x = ctx->xoff + px - ctx->ox; + *rx = nx; + } else if (xoff < ctx->ox && xoff + nx > ctx->ox + ctx->sx) { + /* Both left and right not visible. */ + *i = ctx->ox; + *x = 0; + *rx = ctx->sx; + } else if (xoff < ctx->ox) { + /* Left not visible. */ + *i = ctx->ox - (ctx->xoff + px); + *x = 0; + *rx = nx - *i; + } else { + /* Right not visible. */ + *i = 0; + *x = (ctx->xoff + px) - ctx->ox; + *rx = ctx->sx - *x; + } + if (*rx > nx) + fatalx("%s: x too big, %u > %u", __func__, *rx, nx); + + if (yoff >= ctx->oy && yoff + ny <= ctx->oy + ctx->sy) { + /* All visible. */ + *j = 0; + *y = ctx->yoff + py - ctx->oy; + *ry = ny; + } else if (yoff < ctx->oy && yoff + ny > ctx->oy + ctx->sy) { + /* Both top and bottom not visible. */ + *j = ctx->oy; + *y = 0; + *ry = ctx->sy; + } else if (yoff < ctx->oy) { + /* Top not visible. */ + *j = ctx->oy - (ctx->yoff + py); + *y = 0; + *ry = ny - *j; + } else { + /* Bottom not visible. */ + *j = 0; + *y = (ctx->yoff + py) - ctx->oy; + *ry = ctx->sy - *y; + } + if (*ry > ny) + fatalx("%s: y too big, %u > %u", __func__, *ry, ny); + + return (1); +} + +/* 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) { - u_int yy; - char tmp[64]; + struct client *c = tty->client; + u_int yy; + char tmp[64]; - log_debug("%s: %u,%u at %u,%u", __func__, nx, ny, px, py); + log_debug("%s: %s, %u,%u at %u,%u", __func__, c->name, nx, ny, px, py); /* Nothing to clear. */ if (nx == 0 || ny == 0) @@ -831,7 +1128,7 @@ tty_clear_area(struct tty *tty, const struct window_pane *wp, u_int py, * background colour isn't default (because it doesn't work * after SGR 0). */ - if (tty->term_type == TTY_VT420 && bg != 8) { + if (tty->term_type == TTY_VT420 && !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); @@ -871,22 +1168,80 @@ tty_clear_area(struct tty *tty, const struct window_pane *wp, u_int py, tty_clear_line(tty, wp, yy, px, nx, bg); } -void -tty_draw_pane(struct tty *tty, const struct window_pane *wp, u_int py, u_int ox, - u_int oy) +/* Clear an area in a pane. */ +static void +tty_clear_pane_area(struct tty *tty, const struct tty_ctx *ctx, u_int py, + u_int ny, u_int px, u_int nx, u_int bg) { - tty_draw_line(tty, wp, wp->screen, py, ox, oy); + 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); +} + +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; + + 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); + 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); +} + +static const struct grid_cell * +tty_check_codeset(struct tty *tty, const struct grid_cell *gc) +{ + static struct grid_cell new; + u_int n; + + /* 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) + return (gc); + + /* 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); + return (&new); } void -tty_draw_line(struct tty *tty, const struct window_pane *wp, - struct screen *s, u_int py, u_int ox, u_int oy) +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) { + struct grid *gd = s->grid; struct grid_cell gc, last; - u_int i, j, ux, sx, nx, width; - int flags, cleared = 0; + const struct grid_cell *gcp; + struct grid_line *gl; + u_int i, j, ux, sx, width; + int flags, cleared = 0, wrapped = 0; char buf[512]; - size_t len, old_len; + size_t len; + u_int cellsize; + + log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, + px, py, nx, atx, aty); + + /* + * py is the line in the screen to draw. + * px is the start x and nx is the width to draw. + * atx,aty is the line on the terminal to draw it. + */ flags = (tty->flags & TTY_NOCURSOR); tty->flags |= TTY_NOCURSOR; @@ -900,103 +1255,118 @@ tty_draw_line(struct tty *tty, const struct window_pane *wp, * there may be empty background cells after it (from BCE). */ sx = screen_size_x(s); - if (sx > s->grid->linedata[s->grid->hsize + py].cellsize) - sx = s->grid->linedata[s->grid->hsize + py].cellsize; + if (nx > sx) + nx = sx; + cellsize = grid_get_line(gd, gd->hsize + py)->cellsize; + if (sx > cellsize) + sx = cellsize; if (sx > tty->sx) sx = tty->sx; + if (sx > nx) + sx = nx; ux = 0; + if (py == 0) + gl = NULL; + else + gl = grid_get_line(gd, gd->hsize + py - 1); if (wp == NULL || - py == 0 || - (~s->grid->linedata[s->grid->hsize + py - 1].flags & GRID_LINE_WRAPPED) || - ox != 0 || + gl == NULL || + (~gl->flags & GRID_LINE_WRAPPED) || + atx != 0 || tty->cx < tty->sx || - screen_size_x(s) < tty->sx) { - if (screen_size_x(s) < tty->sx && - ox == 0 && - sx != screen_size_x(s) && + nx < tty->sx) { + if (nx < tty->sx && + 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_cursor(tty, screen_size_x(s) - 1, oy + py); + tty_cursor(tty, nx - 1, aty); tty_putcode(tty, TTYC_EL1); cleared = 1; } - if (sx != 0) - tty_cursor(tty, ox, oy + py); - } else - log_debug("%s: wrapped line %u", __func__, oy + py); + } else { + log_debug("%s: wrapped line %u", __func__, aty); + wrapped = 1; + } memcpy(&last, &grid_default_cell, sizeof last); len = 0; width = 0; for (i = 0; i < sx; i++) { - grid_view_get_cell(s->grid, i, py, &gc); + grid_view_get_cell(gd, px + i, py, &gc); + gcp = tty_check_codeset(tty, &gc); if (len != 0 && - (((~tty->flags & TTY_UTF8) && - (gc.data.size != 1 || - *gc.data.data >= 0x7f || - gc.data.width != 1)) || - (gc.attr & GRID_ATTR_CHARSET) || - gc.flags != last.flags || - gc.attr != last.attr || - gc.fg != last.fg || - gc.bg != last.bg || - (sizeof buf) - len < gc.data.size)) { + ((gcp->attr & GRID_ATTR_CHARSET) || + gcp->flags != last.flags || + gcp->attr != last.attr || + gcp->fg != last.fg || + gcp->bg != last.bg || + gcp->us != last.us || + ux + width + gcp->data.width > nx || + (sizeof buf) - len < gcp->data.size)) { tty_attributes(tty, &last, wp); - tty_putn(tty, buf, len, width); + if (last.flags & GRID_FLAG_CLEARED) { + log_debug("%s: %zu cleared", __func__, len); + tty_clear_line(tty, wp, aty, atx + ux, width, + last.bg); + } else { + if (!wrapped || atx != 0 || ux != 0) + tty_cursor(tty, atx + ux, aty); + tty_putn(tty, buf, len, width); + } ux += width; len = 0; width = 0; + wrapped = 0; } - if (gc.flags & GRID_FLAG_SELECTED) - screen_select_cell(s, &last, &gc); + if (gcp->flags & GRID_FLAG_SELECTED) + screen_select_cell(s, &last, gcp); else - memcpy(&last, &gc, sizeof last); - if (((~tty->flags & TTY_UTF8) && - (gc.data.size != 1 || - *gc.data.data >= 0x7f || - gc.data.width != 1)) || - (gc.attr & GRID_ATTR_CHARSET)) { + memcpy(&last, gcp, sizeof last); + if (ux + gcp->data.width > nx) { tty_attributes(tty, &last, wp); - if (~tty->flags & TTY_UTF8) { - for (j = 0; j < gc.data.width; j++) - tty_putc(tty, '_'); - } else { - for (j = 0; j < gc.data.size; j++) - tty_putc(tty, gc.data.data[j]); + tty_cursor(tty, atx + ux, aty); + for (j = 0; j < gcp->data.width; j++) { + if (ux + j > nx) + break; + tty_putc(tty, ' '); + ux++; } + } else if (gcp->attr & GRID_ATTR_CHARSET) { + tty_attributes(tty, &last, wp); + 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 { - memcpy(buf + len, gc.data.data, gc.data.size); - len += gc.data.size; - width += gc.data.width; + memcpy(buf + len, gcp->data.data, gcp->data.size); + len += gcp->data.size; + width += gcp->data.width; } } - if (len != 0) { - if (grid_cells_equal(&last, &grid_default_cell)) { - old_len = len; - while (len > 0 && buf[len - 1] == ' ') { - len--; - width--; - } - log_debug("%s: trimmed %zu spaces", __func__, - old_len - len); - } - if (len != 0) { - tty_attributes(tty, &last, wp); + if (len != 0 && ((~last.flags & GRID_FLAG_CLEARED) || last.bg != 8)) { + tty_attributes(tty, &last, wp); + 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); + } else { + if (!wrapped || atx != 0 || ux != 0) + tty_cursor(tty, atx + ux, aty); tty_putn(tty, buf, len, width); - ux += width; } + ux += width; } - nx = screen_size_x(s) - ux; - if (!cleared && ux < tty->sx && nx != 0) { + 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, oy + py, ox + ux, nx, 8); + tty_clear_line(tty, wp, aty, atx + ux, nx - ux, 8); } tty->flags = (tty->flags & ~TTY_NOCURSOR) | flags; @@ -1008,12 +1378,14 @@ tty_client_ready(struct client *c, struct window_pane *wp) { if (c->session == NULL || c->tty.term == NULL) return (0); - if (c->flags & (CLIENT_REDRAW|CLIENT_SUSPENDED)) + if (c->flags & (CLIENT_REDRAWWINDOW|CLIENT_SUSPENDED)) 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); } @@ -1024,21 +1396,23 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), struct window_pane *wp = ctx->wp; struct client *c; - /* wp can be NULL if updating the screen but not the terminal. */ if (wp == NULL) return; - - if ((wp->flags & (PANE_REDRAW|PANE_DROP)) || !window_pane_visible(wp)) + if (wp->flags & (PANE_REDRAW|PANE_DROP)) return; TAILQ_FOREACH(c, &clients, entry) { if (!tty_client_ready(c, wp)) 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++; + ctx->yoff += status_line_size(c); cmdfn(&c->tty, ctx); } @@ -1049,11 +1423,12 @@ tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - if (!tty_pane_full_width(tty, ctx) || + if (ctx->bigger || + !tty_pane_full_width(tty, ctx) || tty_fake_bce(tty, wp, ctx->bg) || (!tty_term_has(tty->term, TTYC_ICH) && !tty_term_has(tty->term, TTYC_ICH1))) { - tty_draw_pane(tty, wp, ctx->ocy, ctx->xoff, ctx->yoff); + tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1069,11 +1444,12 @@ tty_cmd_deletecharacter(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - if (!tty_pane_full_width(tty, ctx) || + if (ctx->bigger || + !tty_pane_full_width(tty, ctx) || tty_fake_bce(tty, wp, ctx->bg) || (!tty_term_has(tty->term, TTYC_DCH) && !tty_term_has(tty->term, TTYC_DCH1))) { - tty_draw_pane(tty, wp, ctx->ocy, ctx->xoff, ctx->yoff); + tty_draw_pane(tty, ctx, ctx->ocy); return; } @@ -1087,6 +1463,11 @@ 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->wp, ctx->bg); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); @@ -1101,10 +1482,13 @@ tty_cmd_clearcharacter(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) { - if (!tty_pane_full_width(tty, ctx) || + if (ctx->bigger || + !tty_pane_full_width(tty, ctx) || tty_fake_bce(tty, ctx->wp, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || - !tty_term_has(tty->term, TTYC_IL1)) { + !tty_term_has(tty->term, TTYC_IL1) || + ctx->wp->sx == 1 || + ctx->wp->sy == 1) { tty_redraw_region(tty, ctx); return; } @@ -1122,10 +1506,13 @@ tty_cmd_insertline(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_deleteline(struct tty *tty, const struct tty_ctx *ctx) { - if (!tty_pane_full_width(tty, ctx) || + if (ctx->bigger || + !tty_pane_full_width(tty, ctx) || tty_fake_bce(tty, ctx->wp, ctx->bg) || !tty_term_has(tty->term, TTYC_CSR) || - !tty_term_has(tty->term, TTYC_DL1)) { + !tty_term_has(tty->term, TTYC_DL1) || + ctx->wp->sx == 1 || + ctx->wp->sy == 1) { tty_redraw_region(tty, ctx); return; } @@ -1144,35 +1531,34 @@ void tty_cmd_clearline(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - u_int nx, py = ctx->yoff + ctx->ocy; + u_int nx; tty_default_attributes(tty, wp, ctx->bg); nx = screen_size_x(wp->screen); - tty_clear_line(tty, wp, py, ctx->xoff, nx, ctx->bg); + tty_clear_pane_line(tty, ctx, ctx->ocy, 0, nx, ctx->bg); } void tty_cmd_clearendofline(struct tty *tty, const struct tty_ctx *ctx) { struct window_pane *wp = ctx->wp; - u_int nx, py = ctx->yoff + ctx->ocy; + u_int nx; tty_default_attributes(tty, wp, ctx->bg); nx = screen_size_x(wp->screen) - ctx->ocx; - tty_clear_line(tty, wp, py, ctx->xoff + ctx->ocx, nx, ctx->bg); + 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; - u_int py = ctx->yoff + ctx->ocy; tty_default_attributes(tty, wp, ctx->bg); - tty_clear_line(tty, wp, py, ctx->xoff, ctx->ocx + 1, ctx->bg); + tty_clear_pane_line(tty, ctx, ctx->ocy, 0, ctx->ocx + 1, ctx->bg); } void @@ -1183,10 +1569,14 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) if (ctx->ocy != ctx->orupper) return; - if (!tty_pane_full_width(tty, ctx) || + if (ctx->bigger || + (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || !tty_term_has(tty->term, TTYC_CSR) || - !tty_term_has(tty->term, TTYC_RI)) { + (!tty_term_has(tty->term, TTYC_RI) && + !tty_term_has(tty->term, TTYC_RIN)) || + ctx->wp->sx == 1 || + ctx->wp->sy == 1) { tty_redraw_region(tty, ctx); return; } @@ -1194,10 +1584,13 @@ tty_cmd_reverseindex(struct tty *tty, const struct tty_ctx *ctx) tty_default_attributes(tty, wp, ctx->bg); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); - tty_margin_off(tty); + tty_margin_pane(tty, ctx); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); - tty_putcode(tty, TTYC_RI); + if (tty_term_has(tty->term, TTYC_RI)) + tty_putcode(tty, TTYC_RI); + else + tty_putcode1(tty, TTYC_RIN, 1); } void @@ -1208,9 +1601,12 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) if (ctx->ocy != ctx->orlower) return; - if ((!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + if (ctx->bigger || + (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || - !tty_term_has(tty->term, TTYC_CSR)) { + !tty_term_has(tty->term, TTYC_CSR) || + wp->sx == 1 || + wp->sy == 1) { tty_redraw_region(tty, ctx); return; } @@ -1221,13 +1617,18 @@ tty_cmd_linefeed(struct tty *tty, const struct tty_ctx *ctx) tty_margin_pane(tty, ctx); /* - * If we want to wrap a pane, the cursor needs to be exactly on the - * right of the region. But if the pane isn't on the right, it may be - * off the edge - if so, move the cursor back to the right. + * If we want to wrap a pane while using margins, the cursor needs to + * be exactly on the right of the region. If the cursor is entirely off + * the edge - move it back to the right. Some terminals are funny about + * this and insert extra spaces, so only use the right if margins are + * enabled. */ - if (ctx->xoff + ctx->ocx > tty->rright) - tty_cursor(tty, tty->rright, ctx->yoff + ctx->ocy); - else + if (ctx->xoff + ctx->ocx > tty->rright) { + if (!tty_use_margin(tty)) + tty_cursor(tty, 0, ctx->yoff + ctx->ocy); + else + tty_cursor(tty, tty->rright, ctx->yoff + ctx->ocy); + } else tty_cursor_pane(tty, ctx, ctx->ocx, ctx->ocy); tty_putc(tty, '\n'); @@ -1239,9 +1640,12 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) struct window_pane *wp = ctx->wp; u_int i; - if ((!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + if (ctx->bigger || + (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || tty_fake_bce(tty, wp, 8) || - !tty_term_has(tty->term, TTYC_CSR)) { + !tty_term_has(tty->term, TTYC_CSR) || + wp->sx == 1 || + wp->sy == 1) { tty_redraw_region(tty, ctx); return; } @@ -1252,11 +1656,48 @@ tty_cmd_scrollup(struct tty *tty, const struct tty_ctx *ctx) tty_margin_pane(tty, ctx); if (ctx->num == 1 || !tty_term_has(tty->term, TTYC_INDN)) { - tty_cursor(tty, tty->rright, tty->rlower); + if (!tty_use_margin(tty)) + tty_cursor(tty, 0, tty->rlower); + else + tty_cursor(tty, tty->rright, tty->rlower); for (i = 0; i < ctx->num; i++) tty_putc(tty, '\n'); - } else + } else { + tty_cursor(tty, 0, tty->cy); tty_putcode1(tty, TTYC_INDN, ctx->num); + } +} + +void +tty_cmd_scrolldown(struct tty *tty, const struct tty_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + u_int i; + + if (ctx->bigger || + (!tty_pane_full_width(tty, ctx) && !tty_use_margin(tty)) || + tty_fake_bce(tty, wp, 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) { + tty_redraw_region(tty, ctx); + return; + } + + tty_default_attributes(tty, wp, ctx->bg); + + tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); + tty_margin_pane(tty, ctx); + tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); + + if (tty_term_has(tty->term, TTYC_RIN)) + tty_putcode1(tty, TTYC_RIN, ctx->num); + else { + for (i = 0; i < ctx->num; i++) + tty_putcode(tty, TTYC_RI); + } } void @@ -1270,18 +1711,18 @@ tty_cmd_clearendofscreen(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); tty_margin_off(tty); - px = ctx->xoff; + px = 0; nx = screen_size_x(wp->screen); - py = ctx->yoff + ctx->ocy + 1; + py = ctx->ocy + 1; ny = screen_size_y(wp->screen) - ctx->ocy - 1; - tty_clear_area(tty, wp, py, ny, px, nx, ctx->bg); + tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); - px = ctx->xoff + ctx->ocx; + px = ctx->ocx; nx = screen_size_x(wp->screen) - ctx->ocx; - py = ctx->yoff + ctx->ocy; + py = ctx->ocy; - tty_clear_line(tty, wp, py, px, nx, ctx->bg); + tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg); } void @@ -1295,18 +1736,18 @@ tty_cmd_clearstartofscreen(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); tty_margin_off(tty); - px = ctx->xoff; + px = 0; nx = screen_size_x(wp->screen); - py = ctx->yoff; - ny = ctx->ocy - 1; + py = 0; + ny = ctx->ocy; - tty_clear_area(tty, wp, py, ny, px, nx, ctx->bg); + tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); - px = ctx->xoff; + px = 0; nx = ctx->ocx + 1; - py = ctx->yoff + ctx->ocy; + py = ctx->ocy; - tty_clear_line(tty, wp, py, px, nx, ctx->bg); + tty_clear_pane_line(tty, ctx, py, px, nx, ctx->bg); } void @@ -1320,12 +1761,12 @@ tty_cmd_clearscreen(struct tty *tty, const struct tty_ctx *ctx) tty_region_pane(tty, ctx, 0, screen_size_y(wp->screen) - 1); tty_margin_off(tty); - px = ctx->xoff; + px = 0; nx = screen_size_x(wp->screen); - py = ctx->yoff; + py = 0; ny = screen_size_y(wp->screen); - tty_clear_area(tty, wp, py, ny, px, nx, ctx->bg); + tty_clear_pane_area(tty, ctx, py, ny, px, nx, ctx->bg); } void @@ -1335,6 +1776,11 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) struct screen *s = wp->screen; u_int i, j; + if (ctx->bigger) { + wp->flags |= PANE_REDRAW; + return; + } + tty_attributes(tty, &grid_default_cell, wp); tty_region_pane(tty, ctx, 0, screen_size_y(s) - 1); @@ -1350,13 +1796,15 @@ tty_cmd_alignmenttest(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) { - if (ctx->xoff + ctx->ocx > tty->sx - 1 && ctx->ocy == ctx->orlower) { - if (tty_pane_full_width(tty, ctx)) - tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); - else - tty_margin_off(tty); - } + if (!tty_is_visible(tty, ctx, ctx->ocx, ctx->ocy, 1, 1)) + return; + + if (ctx->xoff + ctx->ocx - ctx->ox > tty->sx - 1 && + ctx->ocy == ctx->orlower && + tty_pane_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); @@ -1365,6 +1813,28 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx) void tty_cmd_cells(struct tty *tty, const struct tty_ctx *ctx) { + struct window_pane *wp = ctx->wp; + + 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)) { + if (!ctx->wrapped || + !tty_pane_full_width(tty, ctx) || + (tty->term->flags & TERM_EARLYWRAP) || + 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; + return; + } + + tty_margin_off(tty); tty_cursor_pane_unless_wrap(tty, ctx, ctx->ocx, ctx->ocy); tty_attributes(tty, ctx->cell, ctx->wp); @@ -1396,11 +1866,58 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); } +void +tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) +{ + struct window_pane *wp = ctx->wp; + struct screen *s = wp->screen; + struct sixel_image *si = ctx->ptr; + struct sixel_image *new; + int flags = (tty->term->flags|tty->term_flags); + char *data; + size_t size; + u_int cx = s->cx, cy = s->cy, sx, sy; + u_int i, j, x, y, rx, ry; + + if ((~flags & TERM_SIXEL) && !tty_term_has(tty->term, TTYC_SXL)) { + wp->flags |= PANE_REDRAW; + return; + } + if (tty->xpixel == 0 || tty->ypixel == 0) + return; + + sixel_size_in_cells(si, &sx, &sy); + if (sx > wp->sx - cx) + sx = wp->sx - cx; + if (sy > wp->sy - cy) + sy = wp->sy - cy; + log_debug("%s: image is %ux%u", __func__, sx, sy); + if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry)) + return; + log_debug("%s: clamping to section %u,%u-%u,%u", __func__, i, j, rx, ry); + + new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry); + if (new == NULL) + return; + + data = sixel_print(new, si, &size); + if (data != NULL) { + log_debug("%s: %zu bytes: %s", __func__, size, data); + tty_region_off(tty); + tty_margin_off(tty); + tty_cursor(tty, x, y); + tty->flags |= TTY_NOBLOCK; + tty_add(tty, data, size); + tty_invalidate(tty); + free(data); + } + sixel_free(new); +} + static void -tty_cell(struct tty *tty, const struct grid_cell *gc, - const struct window_pane *wp) +tty_cell(struct tty *tty, const struct grid_cell *gc, struct window_pane *wp) { - u_int i; + const struct grid_cell *gcp; /* Skip last character if terminal is stupid. */ if ((tty->term->flags & TERM_EARLYWRAP) && @@ -1416,22 +1933,16 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, tty_attributes(tty, gc, wp); /* Get the cell and if ASCII write with putc to do ACS translation. */ - if (gc->data.size == 1) { - if (*gc->data.data < 0x20 || *gc->data.data == 0x7f) + gcp = tty_check_codeset(tty, gc); + if (gcp->data.size == 1) { + if (*gcp->data.data < 0x20 || *gcp->data.data == 0x7f) return; - tty_putc(tty, *gc->data.data); - return; - } - - /* If not UTF-8, write _. */ - if (!(tty->flags & TTY_UTF8)) { - for (i = 0; i < gc->data.width; i++) - tty_putc(tty, '_'); + tty_putc(tty, *gcp->data.data); return; } /* Write the data. */ - tty_putn(tty, gc->data.data, gc->data.size, gc->data.width); + tty_putn(tty, gcp->data.data, gcp->data.size, gcp->data.width); } void @@ -1464,6 +1975,8 @@ tty_invalidate(struct tty *tty) 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_SGR0); tty->mode = ALL_MODES; @@ -1488,7 +2001,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->yoff + rlower); + tty_region(tty, ctx->yoff + rupper - ctx->oy, + ctx->yoff + rlower - ctx->oy); } /* Set region at absolute position. */ @@ -1527,7 +2041,8 @@ 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->xoff + ctx->wp->sx - 1); + tty_margin(tty, ctx->xoff - ctx->ox, + ctx->xoff + ctx->wp->sx - 1 - ctx->ox); } /* Set margin at absolute position. */ @@ -1578,7 +2093,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->yoff + cy); + tty_cursor(tty, ctx->xoff + cx - ctx->ox, ctx->yoff + cy - ctx->oy); } /* Move cursor to absolute position. */ @@ -1651,7 +2166,9 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) if ((u_int) abs(change) > cx && tty_term_has(term, TTYC_HPA)) { tty_putcode1(tty, TTYC_HPA, cx); goto out; - } else if (change > 0 && tty_term_has(term, TTYC_CUB)) { + } else if (change > 0 && + tty_term_has(term, TTYC_CUB) && + !tty_use_margin(tty)) { if (change == 2 && tty_term_has(term, TTYC_CUB1)) { tty_putcode(tty, TTYC_CUB1); tty_putcode(tty, TTYC_CUB1); @@ -1659,7 +2176,9 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) } tty_putcode1(tty, TTYC_CUB, change); goto out; - } else if (change < 0 && tty_term_has(term, TTYC_CUF)) { + } else if (change < 0 && + tty_term_has(term, TTYC_CUF) && + !tty_use_margin(tty)) { tty_putcode1(tty, TTYC_CUF, -change); goto out; } @@ -1716,7 +2235,7 @@ tty_cursor(struct tty *tty, u_int cx, u_int cy) void tty_attributes(struct tty *tty, const struct grid_cell *gc, - const struct window_pane *wp) + struct window_pane *wp) { struct grid_cell *tc = &tty->cell, gc2; int changed; @@ -1724,10 +2243,11 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, /* Ignore cell if it is the same as the last one. */ if (wp != NULL && (int)wp->id == tty->last_wp && - ~(wp->window->flags & WINDOW_STYLECHANGED) && + ~(wp->flags & PANE_STYLECHANGED) && gc->attr == tty->last_cell.attr && gc->fg == tty->last_cell.fg && - gc->bg == tty->last_cell.bg) + 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); @@ -1744,10 +2264,10 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, */ if (!tty_term_has(tty->term, TTYC_SETAB)) { if (gc2.attr & GRID_ATTR_REVERSE) { - if (gc2.fg != 7 && gc2.fg != 8) + if (gc2.fg != 7 && !COLOUR_DEFAULT(gc2.fg)) gc2.attr &= ~GRID_ATTR_REVERSE; } else { - if (gc2.bg != 0 && gc2.bg != 8) + if (gc2.bg != 0 && !COLOUR_DEFAULT(gc2.bg)) gc2.attr |= GRID_ATTR_REVERSE; } } @@ -1755,14 +2275,18 @@ 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); - /* If any bits are being cleared, reset everything. */ - if (tc->attr & ~gc2.attr) + /* + * If any bits are being cleared or the underline colour is now default, + * reset everything. + */ + if ((tc->attr & ~gc2.attr) || (tc->us != gc2.us && gc2.us == 0)) tty_reset(tty); /* * Set the colours. This may call tty_reset() (so it comes next) and - * may add to (NOT remove) the desired attributes by changing new_attr. + * may add to (NOT remove) the desired attributes. */ tty_colours(tty, &gc2); @@ -1777,8 +2301,19 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_putcode(tty, TTYC_DIM); if (changed & GRID_ATTR_ITALICS) tty_set_italics(tty); - if (changed & GRID_ATTR_UNDERSCORE) - tty_putcode(tty, TTYC_SMUL); + if (changed & GRID_ATTR_ALL_UNDERSCORE) { + if ((changed & GRID_ATTR_UNDERSCORE) || + !tty_term_has(tty->term, TTYC_SMULX)) + tty_putcode(tty, TTYC_SMUL); + else if (changed & GRID_ATTR_UNDERSCORE_2) + tty_putcode1(tty, TTYC_SMULX, 2); + else if (changed & GRID_ATTR_UNDERSCORE_3) + tty_putcode1(tty, TTYC_SMULX, 3); + else if (changed & GRID_ATTR_UNDERSCORE_4) + tty_putcode1(tty, TTYC_SMULX, 4); + else if (changed & GRID_ATTR_UNDERSCORE_5) + tty_putcode1(tty, TTYC_SMULX, 5); + } if (changed & GRID_ATTR_BLINK) tty_putcode(tty, TTYC_BLINK); if (changed & GRID_ATTR_REVERSE) { @@ -1791,6 +2326,8 @@ tty_attributes(struct tty *tty, const struct grid_cell *gc, tty_putcode(tty, TTYC_INVIS); if (changed & GRID_ATTR_STRIKETHROUGH) tty_putcode(tty, TTYC_SMXX); + if (changed & GRID_ATTR_OVERLINE) + tty_putcode(tty, TTYC_SMOL); if ((changed & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_SMACS); } @@ -1802,7 +2339,7 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) int have_ax; /* No changes? Nothing is necessary. */ - if (gc->fg == tc->fg && gc->bg == tc->bg) + if (gc->fg == tc->fg && gc->bg == tc->bg && gc->us == tc->us) return; /* @@ -1811,7 +2348,7 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) * case if only one is default need to fall onward to set the other * colour. */ - if (gc->fg == 8 || gc->bg == 8) { + if (COLOUR_DEFAULT(gc->fg) || COLOUR_DEFAULT(gc->bg)) { /* * If don't have AX but do have op, send sgr0 (op can't * actually be used because it is sometimes the same as sgr0 @@ -1823,38 +2360,41 @@ tty_colours(struct tty *tty, const struct grid_cell *gc) if (!have_ax && tty_term_has(tty->term, TTYC_OP)) tty_reset(tty); else { - if (gc->fg == 8 && tc->fg != 8) { + if (COLOUR_DEFAULT(gc->fg) && !COLOUR_DEFAULT(tc->fg)) { if (have_ax) tty_puts(tty, "\033[39m"); else if (tc->fg != 7) tty_putcode1(tty, TTYC_SETAF, 7); - tc->fg = 8; + tc->fg = gc->fg; } - if (gc->bg == 8 && tc->bg != 8) { + if (COLOUR_DEFAULT(gc->bg) && !COLOUR_DEFAULT(tc->bg)) { if (have_ax) tty_puts(tty, "\033[49m"); else if (tc->bg != 0) tty_putcode1(tty, TTYC_SETAB, 0); - tc->bg = 8; + tc->bg = gc->bg; } } } /* Set the foreground colour. */ - if (gc->fg != 8 && gc->fg != tc->fg) + if (!COLOUR_DEFAULT(gc->fg) && gc->fg != tc->fg) tty_colours_fg(tty, gc); /* * Set the background colour. This must come after the foreground as * tty_colour_fg() can call tty_reset(). */ - if (gc->bg != 8 && gc->bg != tc->bg) + if (!COLOUR_DEFAULT(gc->bg) && gc->bg != tc->bg) tty_colours_bg(tty, gc); + + /* Set the underscore color. */ + if (gc->us != tc->us) + tty_colours_us(tty, gc); } static void -tty_check_fg(struct tty *tty, const struct window_pane *wp, - struct grid_cell *gc) +tty_check_fg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -1898,10 +2438,7 @@ tty_check_fg(struct tty *tty, const struct window_pane *wp, gc->fg &= 7; if (colours >= 16) gc->fg += 90; - else - gc->attr |= GRID_ATTR_BRIGHT; - } else - gc->attr &= ~GRID_ATTR_BRIGHT; + } } return; } @@ -1914,8 +2451,7 @@ tty_check_fg(struct tty *tty, const struct window_pane *wp, } static void -tty_check_bg(struct tty *tty, const struct window_pane *wp, - struct grid_cell *gc) +tty_check_bg(struct tty *tty, struct window_pane *wp, struct grid_cell *gc) { u_char r, g, b; u_int colours; @@ -1955,7 +2491,7 @@ tty_check_bg(struct tty *tty, const struct window_pane *wp, if (gc->bg & 8) { gc->bg &= 7; if (colours >= 16) - gc->fg += 90; + gc->bg += 90; } } return; @@ -1966,6 +2502,23 @@ tty_check_bg(struct tty *tty, const struct window_pane *wp, gc->bg -= 90; } +static void +tty_check_us(__unused struct tty *tty, struct window_pane *wp, + 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) + gc->us = c; + } + + /* Underscore colour is set as RGB so convert a 256 colour to RGB. */ + if (gc->us & COLOUR_FLAG_256) + gc->us = colour_256toRGB (gc->us); +} + static void tty_colours_fg(struct tty *tty, const struct grid_cell *gc) { @@ -1973,8 +2526,7 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc) char s[32]; /* Is this a 24-bit or 256-colour colour? */ - if (gc->fg & COLOUR_FLAG_RGB || - gc->fg & COLOUR_FLAG_256) { + if (gc->fg & COLOUR_FLAG_RGB || gc->fg & COLOUR_FLAG_256) { if (tty_try_colour(tty, gc->fg, "38") == 0) goto save_fg; /* Should not get here, already converted in tty_check_fg. */ @@ -1983,8 +2535,11 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc) /* Is this an aixterm bright colour? */ if (gc->fg >= 90 && gc->fg <= 97) { - xsnprintf(s, sizeof s, "\033[%dm", gc->fg); - tty_puts(tty, s); + 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; } @@ -2003,8 +2558,7 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc) char s[32]; /* Is this a 24-bit or 256-colour colour? */ - if (gc->bg & COLOUR_FLAG_RGB || - gc->bg & COLOUR_FLAG_256) { + if (gc->bg & COLOUR_FLAG_RGB || gc->bg & COLOUR_FLAG_256) { if (tty_try_colour(tty, gc->bg, "48") == 0) goto save_bg; /* Should not get here, already converted in tty_check_bg. */ @@ -2013,8 +2567,11 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc) /* Is this an aixterm bright colour? */ if (gc->bg >= 90 && gc->bg <= 97) { - xsnprintf(s, sizeof s, "\033[%dm", gc->bg + 10); - tty_puts(tty, s); + 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; } @@ -2026,6 +2583,31 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc) tc->bg = gc->bg; } +static void +tty_colours_us(struct tty *tty, const struct grid_cell *gc) +{ + struct grid_cell *tc = &tty->cell; + u_int c; + u_char r, g, b; + + /* 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" + * 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); + + /* Save the new values in the terminal current cell. */ + tc->us = gc->us; +} + static int tty_try_colour(struct tty *tty, int colour, const char *type) { @@ -2034,11 +2616,15 @@ tty_try_colour(struct tty *tty, int colour, const char *type) if (colour & COLOUR_FLAG_256) { /* - * If the user has specified -2 to the client, setaf and setab - * may not work (or they may not want to use them), so send the - * usual sequence. + * 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) + if ((tty->term_flags & TERM_256COLOURS) || + tty_term_has(tty->term, TTYC_RGB)) goto fallback_256; /* @@ -2079,59 +2665,60 @@ tty_try_colour(struct tty *tty, int colour, const char *type) 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); } static void -tty_default_colours(struct grid_cell *gc, const struct window_pane *wp) -{ - struct window *w = wp->window; - struct options *oo = w->options; - const struct grid_cell *agc, *pgc, *wgc; - int c; - - if (w->flags & WINDOW_STYLECHANGED) { - w->flags &= ~WINDOW_STYLECHANGED; - agc = options_get_style(oo, "window-active-style"); - memcpy(&w->active_style, agc, sizeof w->active_style); - wgc = options_get_style(oo, "window-style"); - memcpy(&w->style, wgc, sizeof w->style); +tty_default_colours(struct grid_cell *gc, struct window_pane *wp) +{ + struct options *oo = wp->options; + struct style *style, *active_style; + int c; + + if (wp->flags & PANE_STYLECHANGED) { + 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 { - agc = &w->active_style; - wgc = &w->style; + active_style = &wp->cached_active_style; + style = &wp->cached_style; } - pgc = &wp->colgc; if (gc->fg == 8) { - if (pgc->fg != 8) - gc->fg = pgc->fg; - else if (wp == w->active && agc->fg != 8) - gc->fg = agc->fg; + if (wp == wp->window->active && active_style->gc.fg != 8) + gc->fg = active_style->gc.fg; else - gc->fg = wgc->fg; + gc->fg = style->gc.fg; - if (gc->fg != 8 && - (c = window_pane_get_palette(wp, gc->fg)) != -1) - gc->fg = c; + if (gc->fg != 8) { + c = window_pane_get_palette(wp, gc->fg); + if (c != -1) + gc->fg = c; + } } if (gc->bg == 8) { - if (pgc->bg != 8) - gc->bg = pgc->bg; - else if (wp == w->active && agc->bg != 8) - gc->bg = agc->bg; + if (wp == wp->window->active && active_style->gc.bg != 8) + gc->bg = active_style->gc.bg; else - gc->bg = wgc->bg; + gc->bg = style->gc.bg; - if (gc->bg != 8 && - (c = window_pane_get_palette(wp, gc->bg)) != -1) - gc->bg = c; + if (gc->bg != 8) { + c = window_pane_get_palette(wp, gc->bg); + if (c != -1) + gc->bg = c; + } } } static void -tty_default_attributes(struct tty *tty, const struct window_pane *wp, u_int bg) +tty_default_attributes(struct tty *tty, struct window_pane *wp, u_int bg) { static struct grid_cell gc; diff --git a/utf8.c b/utf8.c index a91da360b7..b4e448f76a 100644 --- a/utf8.c +++ b/utf8.c @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -207,7 +208,13 @@ utf8_strvis(char *dst, const char *src, size_t len, int flag) /* Not a complete, valid UTF-8 character. */ src -= ud.have; } - if (src < end - 1) + if (src[0] == '$' && src < end - 1) { + if (isalpha((u_char)src[1]) || + src[1] == '_' || + src[1] == '{') + *dst++ = '\\'; + *dst++ = '$'; + } else if (src < end - 1) dst = vis(dst, src[0], flag, src[1]); else if (src < end) dst = vis(dst, src[0], flag, '\0'); @@ -408,69 +415,30 @@ utf8_cstrwidth(const char *s) return (width); } -/* Trim UTF-8 string to width. Caller frees. */ +/* Pad UTF-8 string to width on the left. Caller frees. */ char * -utf8_trimcstr(const char *s, u_int width) -{ - struct utf8_data *tmp, *next; - char *out; - u_int at; - - tmp = utf8_fromcstr(s); - - at = 0; - for (next = tmp; next->size != 0; next++) { - if (at + next->width > width) { - next->size = 0; - break; - } - at += next->width; - } - - out = utf8_tocstr(tmp); - free(tmp); - return (out); -} - -/* Trim UTF-8 string to width. Caller frees. */ -char * -utf8_rtrimcstr(const char *s, u_int width) +utf8_padcstr(const char *s, u_int width) { - struct utf8_data *tmp, *next, *end; - char *out; - u_int at; - - tmp = utf8_fromcstr(s); - - for (end = tmp; end->size != 0; end++) - /* nothing */; - if (end == tmp) { - free(tmp); - return (xstrdup("")); - } - next = end - 1; - - at = 0; - for (;;) { - if (at + next->width > width) { - next++; - break; - } - at += next->width; + size_t slen; + char *out; + u_int n, i; - if (next == tmp) - break; - next--; - } + n = utf8_cstrwidth(s); + if (n >= width) + return (xstrdup(s)); - out = utf8_tocstr(next); - free(tmp); + slen = strlen(s); + out = xmalloc(slen + 1 + (width - n)); + memcpy(out, s, slen); + for (i = n; i < width; i++) + out[slen++] = ' '; + out[slen] = '\0'; return (out); } -/* Pad UTF-8 string to width. Caller frees. */ +/* Pad UTF-8 string to width on the right. Caller frees. */ char * -utf8_padcstr(const char *s, u_int width) +utf8_rpadcstr(const char *s, u_int width) { size_t slen; char *out; @@ -482,9 +450,29 @@ utf8_padcstr(const char *s, u_int width) slen = strlen(s); out = xmalloc(slen + 1 + (width - n)); - memcpy(out, s, slen); - for (i = n; i < width; i++) - out[slen++] = ' '; - out[slen] = '\0'; + for (i = 0; i < width - n; i++) + out[i] = ' '; + memcpy(out + i, s, slen); + out[i + slen] = '\0'; return (out); } + +int +utf8_cstrhas(const char *s, const struct utf8_data *ud) +{ + struct utf8_data *copy, *loop; + int found = 0; + + copy = utf8_fromcstr(s); + for (loop = copy; loop->size != 0; loop++) { + if (loop->size != ud->size) + continue; + if (memcmp(loop->data, ud->data, loop->size) == 0) { + found = 1; + break; + } + } + free(copy); + + return (found); +} diff --git a/window-buffer.c b/window-buffer.c index 927992d78f..5c89734190 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -24,22 +24,39 @@ #include "tmux.h" -static struct screen *window_buffer_init(struct window_pane *, +static struct screen *window_buffer_init(struct window_mode_entry *, struct cmd_find_state *, struct args *); -static void window_buffer_free(struct window_pane *); -static void window_buffer_resize(struct window_pane *, u_int, +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_key(struct window_pane *, - struct client *, struct session *, key_code, - struct mouse_event *); +static void window_buffer_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); #define WINDOW_BUFFER_DEFAULT_COMMAND "paste-buffer -b '%%'" #define WINDOW_BUFFER_DEFAULT_FORMAT \ "#{buffer_size} bytes (#{t:buffer_created})" +static const struct menu_item window_buffer_menu_items[] = { + { "Paste", 'p', NULL }, + { "Paste Tagged", 'P', NULL }, + { "", KEYC_NONE, NULL }, + { "Tag", 't', NULL }, + { "Tag All", '\024', NULL }, + { "Tag None", 'T', NULL }, + { "", KEYC_NONE, NULL }, + { "Delete", 'd', NULL }, + { "Delete Tagged", 'D', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + const struct window_mode window_buffer_mode = { .name = "buffer-mode", + .default_format = WINDOW_BUFFER_DEFAULT_FORMAT, .init = window_buffer_init, .free = window_buffer_free, @@ -57,6 +74,7 @@ static const char *window_buffer_sort_list[] = { "name", "size" }; +static struct mode_tree_sort_criteria *window_buffer_sort; struct window_buffer_itemdata { const char *name; @@ -65,6 +83,9 @@ struct window_buffer_itemdata { }; struct window_buffer_modedata { + struct window_pane *wp; + struct cmd_find_state fs; + struct mode_tree_data *data; char *command; char *format; @@ -92,43 +113,29 @@ window_buffer_free_item(struct window_buffer_itemdata *item) } static int -window_buffer_cmp_name(const void *a0, const void *b0) -{ - const struct window_buffer_itemdata *const *a = a0; - const struct window_buffer_itemdata *const *b = b0; - - return (strcmp((*a)->name, (*b)->name)); -} - -static int -window_buffer_cmp_time(const void *a0, const void *b0) -{ - const struct window_buffer_itemdata *const *a = a0; - const struct window_buffer_itemdata *const *b = b0; - - if ((*a)->order > (*b)->order) - return (-1); - if ((*a)->order < (*b)->order) - return (1); - return (strcmp((*a)->name, (*b)->name)); -} - -static int -window_buffer_cmp_size(const void *a0, const void *b0) +window_buffer_cmp(const void *a0, const void *b0) { - const struct window_buffer_itemdata *const *a = a0; - const struct window_buffer_itemdata *const *b = b0; - - if ((*a)->size > (*b)->size) - return (-1); - if ((*a)->size < (*b)->size) - return (1); - return (strcmp((*a)->name, (*b)->name)); + const struct window_buffer_itemdata *const *a = a0; + const struct window_buffer_itemdata *const *b = b0; + int result = 0; + + if (window_buffer_sort->field == WINDOW_BUFFER_BY_TIME) + result = (*b)->order - (*a)->order; + else if (window_buffer_sort->field == WINDOW_BUFFER_BY_SIZE) + result = (*b)->size - (*a)->size; + + /* Use WINDOW_BUFFER_BY_NAME as default order and tie breaker. */ + if (result == 0) + result = strcmp((*a)->name, (*b)->name); + + if (window_buffer_sort->reversed) + result = -result; + return (result); } static void -window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag, - const char *filter) +window_buffer_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, + __unused uint64_t *tag, const char *filter) { struct window_buffer_modedata *data = modedata; struct window_buffer_itemdata *item; @@ -136,6 +143,9 @@ window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag, struct paste_buffer *pb; char *text, *cp; struct format_tree *ft; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; for (i = 0; i < data->item_size; i++) window_buffer_free_item(data->item_list[i]); @@ -151,19 +161,14 @@ window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag, item->order = paste_buffer_order(pb); } - switch (sort_type) { - case WINDOW_BUFFER_BY_NAME: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_buffer_cmp_name); - break; - case WINDOW_BUFFER_BY_TIME: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_buffer_cmp_time); - break; - case WINDOW_BUFFER_BY_SIZE: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_buffer_cmp_size); - break; + window_buffer_sort = sort_crit; + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_buffer_cmp); + + if (cmd_find_valid_state(&data->fs)) { + s = data->fs.s; + wl = data->fs.wl; + wp = data->fs.wp; } for (i = 0; i < data->item_size; i++) { @@ -173,6 +178,7 @@ window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag, if (pb == NULL) continue; ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, s, wl, wp); format_defaults_paste_buffer(ft, pb); if (filter != NULL) { @@ -195,33 +201,27 @@ window_buffer_build(void *modedata, u_int sort_type, __unused uint64_t *tag, } -static struct screen * -window_buffer_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) +static void +window_buffer_draw(__unused void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) { struct window_buffer_itemdata *item = itemdata; struct paste_buffer *pb; - static struct screen s; - struct screen_write_ctx ctx; char line[1024]; const char *pdata, *end, *cp; size_t psize, at; - u_int i; + u_int i, cx = ctx->s->cx, cy = ctx->s->cy; pb = paste_get_name(item->name); if (pb == NULL) - return (NULL); - - screen_init(&s, sx, sy, 0); - - screen_write_start(&ctx, NULL, &s); - screen_write_clearscreen(&ctx, 8); + return; pdata = end = paste_buffer_data(pb, &psize); for (i = 0; i < sy; i++) { at = 0; while (end != pdata + psize && *end != '\n') { if ((sizeof line) - at > 5) { - cp = vis(line + at, *end, VIS_TAB|VIS_OCTAL, 0); + cp = vis(line + at, *end, VIS_OCTAL|VIS_TAB, 0); at = cp - line; } end++; @@ -231,17 +231,14 @@ window_buffer_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) line[at] = '\0'; if (*line != '\0') { - screen_write_cursormove(&ctx, 0, i); - screen_write_puts(&ctx, &grid_default_cell, "%s", line); + screen_write_cursormove(ctx, cx, cy + i, 0); + screen_write_puts(ctx, &grid_default_cell, "%s", line); } if (end == pdata + psize) break; end++; } - - screen_write_stop(&ctx); - return (&s); } static int @@ -260,14 +257,30 @@ window_buffer_search(__unused void *modedata, void *itemdata, const char *ss) return (memmem(bufdata, bufsize, ss, strlen(ss)) != NULL); } +static void +window_buffer_menu(void *modedata, struct client *c, key_code key) +{ + struct window_buffer_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_buffer_key(wme, c, NULL, NULL, key, NULL); +} + static struct screen * -window_buffer_init(struct window_pane *wp, __unused struct cmd_find_state *fs, +window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, struct args *args) { + struct window_pane *wp = wme->wp; struct window_buffer_modedata *data; struct screen *s; - wp->modedata = data = xcalloc(1, sizeof *data); + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + cmd_find_copy_state(&data->fs, fs); if (args == NULL || !args_has(args, 'F')) data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT); @@ -279,8 +292,10 @@ window_buffer_init(struct window_pane *wp, __unused struct cmd_find_state *fs, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_buffer_build, - window_buffer_draw, window_buffer_search, data, - window_buffer_sort_list, nitems(window_buffer_sort_list), &s); + window_buffer_draw, window_buffer_search, window_buffer_menu, 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); mode_tree_draw(data->data); @@ -289,9 +304,9 @@ window_buffer_init(struct window_pane *wp, __unused struct cmd_find_state *fs, } static void -window_buffer_free(struct window_pane *wp) +window_buffer_free(struct window_mode_entry *wme) { - struct window_buffer_modedata *data = wp->modedata; + struct window_buffer_modedata *data = wme->data; u_int i; if (data == NULL) @@ -310,15 +325,16 @@ window_buffer_free(struct window_pane *wp) } static void -window_buffer_resize(struct window_pane *wp, u_int sx, u_int sy) +window_buffer_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { - struct window_buffer_modedata *data = wp->modedata; + struct window_buffer_modedata *data = wme->data; mode_tree_resize(data->data, sx, sy); } static void -window_buffer_do_delete(void* modedata, void *itemdata, __unused key_code key) +window_buffer_do_delete(void* modedata, void *itemdata, + __unused struct client *c, __unused key_code key) { struct window_buffer_modedata *data = modedata; struct window_buffer_itemdata *item = itemdata; @@ -331,52 +347,54 @@ window_buffer_do_delete(void* modedata, void *itemdata, __unused key_code key) } static void -window_buffer_key(struct window_pane *wp, struct client *c, - __unused struct session *s, key_code key, struct mouse_event *m) +window_buffer_do_paste(void* modedata, void *itemdata, struct client *c, + __unused key_code key) { - struct window_buffer_modedata *data = wp->modedata; + struct window_buffer_modedata *data = modedata; + struct window_buffer_itemdata *item = itemdata; + struct paste_buffer *pb; + + if ((pb = paste_get_name(item->name)) != NULL) + mode_tree_run_command(c, NULL, data->command, item->name); +} + +static void +window_buffer_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_buffer_modedata *data = wme->data; + struct mode_tree_data *mtd = data->data; struct window_buffer_itemdata *item; - char *command, *name; int finished; - /* - * t = toggle tag - * T = tag none - * C-t = tag all - * q = exit - * O = change sort order - * - * d = delete buffer - * D = delete tagged buffers - * Enter = paste buffer - */ - - finished = mode_tree_key(data->data, c, &key, m); + finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { case 'd': - item = mode_tree_get_current(data->data); - window_buffer_do_delete(data, item, key); - mode_tree_build(data->data); + item = mode_tree_get_current(mtd); + window_buffer_do_delete(data, item, c, key); + mode_tree_build(mtd); break; case 'D': - mode_tree_each_tagged(data->data, window_buffer_do_delete, key, - 0); - mode_tree_build(data->data); + mode_tree_each_tagged(mtd, window_buffer_do_delete, c, key, 0); + mode_tree_build(mtd); + break; + case 'P': + mode_tree_each_tagged(mtd, window_buffer_do_paste, c, key, 0); + finished = 1; break; + case 'p': case '\r': - item = mode_tree_get_current(data->data); - command = xstrdup(data->command); - name = xstrdup(item->name); - window_pane_reset_mode(wp); - mode_tree_run_command(c, NULL, command, name); - free(name); - free(command); - return; + item = mode_tree_get_current(mtd); + window_buffer_do_paste(data, item, c, key); + finished = 1; + break; } if (finished || paste_get_top(NULL) == NULL) window_pane_reset_mode(wp); else { - mode_tree_draw(data->data); + mode_tree_draw(mtd); wp->flags |= PANE_REDRAW; } } diff --git a/window-client.c b/window-client.c index 214d3b7cfa..2ca9c01217 100644 --- a/window-client.c +++ b/window-client.c @@ -25,14 +25,14 @@ #include "tmux.h" -static struct screen *window_client_init(struct window_pane *, +static struct screen *window_client_init(struct window_mode_entry *, struct cmd_find_state *, struct args *); -static void window_client_free(struct window_pane *); -static void window_client_resize(struct window_pane *, u_int, +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_key(struct window_pane *, - struct client *, struct session *, key_code, - struct mouse_event *); +static void window_client_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); #define WINDOW_CLIENT_DEFAULT_COMMAND "detach-client -t '%%'" @@ -40,8 +40,22 @@ static void window_client_key(struct window_pane *, "session #{session_name} " \ "(#{client_width}x#{client_height}, #{t:client_activity})" +static const struct menu_item window_client_menu_items[] = { + { "Detach", 'd', NULL }, + { "Detach Tagged", 'D', 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_client_mode = { .name = "client-mode", + .default_format = WINDOW_CLIENT_DEFAULT_FORMAT, .init = window_client_init, .free = window_client_free, @@ -61,12 +75,15 @@ static const char *window_client_sort_list[] = { "creation", "activity" }; +static struct mode_tree_sort_criteria *window_client_sort; struct window_client_itemdata { struct client *c; }; struct window_client_modedata { + struct window_pane *wp; + struct mode_tree_data *data; char *format; char *command; @@ -94,60 +111,48 @@ window_client_free_item(struct window_client_itemdata *item) } static int -window_client_cmp_name(const void *a0, const void *b0) +window_client_cmp(const void *a0, const void *b0) { - const struct window_client_itemdata *const *a = a0; - const struct window_client_itemdata *const *b = b0; - - return (strcmp((*a)->c->name, (*b)->c->name)); -} - -static int -window_client_cmp_size(const void *a0, const void *b0) -{ - const struct window_client_itemdata *const *a = a0; - const struct window_client_itemdata *const *b = b0; - - if ((*a)->c->tty.sx < (*b)->c->tty.sx) - return (-1); - if ((*a)->c->tty.sx > (*b)->c->tty.sx) - return (1); - if ((*a)->c->tty.sy < (*b)->c->tty.sy) - return (-1); - if ((*a)->c->tty.sy > (*b)->c->tty.sy) - return (1); - return (strcmp((*a)->c->name, (*b)->c->name)); -} + const struct window_client_itemdata *const *a = a0; + const struct window_client_itemdata *const *b = b0; + const struct window_client_itemdata *itema = *a; + const struct window_client_itemdata *itemb = *b; + struct client *ca = itema->c; + struct client *cb = itemb->c; + int result = 0; + + switch (window_client_sort->field) { + case WINDOW_CLIENT_BY_SIZE: + result = ca->tty.sx - cb->tty.sx; + if (result == 0) + result = ca->tty.sy - cb->tty.sy; + break; + case WINDOW_CLIENT_BY_CREATION_TIME: + if (timercmp(&ca->creation_time, &cb->creation_time, >)) + result = -1; + else if (timercmp(&ca->creation_time, &cb->creation_time, <)) + result = 1; + break; + case WINDOW_CLIENT_BY_ACTIVITY_TIME: + if (timercmp(&ca->activity_time, &cb->activity_time, >)) + result = -1; + else if (timercmp(&ca->activity_time, &cb->activity_time, <)) + result = 1; + break; + } -static int -window_client_cmp_creation_time(const void *a0, const void *b0) -{ - const struct window_client_itemdata *const *a = a0; - const struct window_client_itemdata *const *b = b0; - - if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, >)) - return (-1); - if (timercmp(&(*a)->c->creation_time, &(*b)->c->creation_time, <)) - return (1); - return (strcmp((*a)->c->name, (*b)->c->name)); -} + /* Use WINDOW_CLIENT_BY_NAME as default order and tie breaker. */ + if (result == 0) + result = strcmp(ca->name, cb->name); -static int -window_client_cmp_activity_time(const void *a0, const void *b0) -{ - const struct window_client_itemdata *const *a = a0; - const struct window_client_itemdata *const *b = b0; - - if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, >)) - return (-1); - if (timercmp(&(*a)->c->activity_time, &(*b)->c->activity_time, <)) - return (1); - return (strcmp((*a)->c->name, (*b)->c->name)); + if (window_client_sort->reversed) + result = -result; + return (result); } static void -window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag, - const char *filter) +window_client_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, + __unused uint64_t *tag, const char *filter) { struct window_client_modedata *data = modedata; struct window_client_itemdata *item; @@ -171,24 +176,9 @@ window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag, c->references++; } - switch (sort_type) { - case WINDOW_CLIENT_BY_NAME: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_client_cmp_name); - break; - case WINDOW_CLIENT_BY_SIZE: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_client_cmp_size); - break; - case WINDOW_CLIENT_BY_CREATION_TIME: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_client_cmp_creation_time); - break; - case WINDOW_CLIENT_BY_ACTIVITY_TIME: - qsort(data->item_list, data->item_size, sizeof *data->item_list, - window_client_cmp_activity_time); - break; - } + window_client_sort = sort_crit; + qsort(data->item_list, data->item_size, sizeof *data->item_list, + window_client_cmp); for (i = 0; i < data->item_size; i++) { item = data->item_list[i]; @@ -210,47 +200,67 @@ window_client_build(void *modedata, u_int sort_type, __unused uint64_t *tag, } } -static struct screen * -window_client_draw(__unused void *modedata, void *itemdata, u_int sx, u_int sy) +static void +window_client_draw(__unused void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) { struct window_client_itemdata *item = itemdata; struct client *c = item->c; + struct screen *s = ctx->s; struct window_pane *wp; - static struct screen s; - struct screen_write_ctx ctx; + u_int cx = s->cx, cy = s->cy, lines, at; if (c->session == NULL || (c->flags & (CLIENT_DEAD|CLIENT_DETACHING))) - return (NULL); + return; wp = c->session->curw->window->active; - screen_init(&s, sx, sy, 0); - - screen_write_start(&ctx, NULL, &s); - screen_write_clearscreen(&ctx, 8); + lines = status_line_size(c); + if (lines >= sy) + lines = 0; + if (status_at_line(c) == 0) + at = lines; + else + at = 0; - screen_write_preview(&ctx, &wp->base, sx, sy - 3); + screen_write_cursormove(ctx, cx, cy + at, 0); + screen_write_preview(ctx, &wp->base, sx, sy - 2 - lines); - screen_write_cursormove(&ctx, 0, sy - 2); - screen_write_hline(&ctx, sx, 0, 0); + if (at != 0) + screen_write_cursormove(ctx, cx, cy + 2, 0); + else + screen_write_cursormove(ctx, cx, cy + sy - 1 - lines, 0); + screen_write_hline(ctx, sx, 0, 0); - screen_write_cursormove(&ctx, 0, sy - 1); - if (c->old_status != NULL) - screen_write_copy(&ctx, c->old_status, 0, 0, sx, 1, NULL, NULL); + if (at != 0) + screen_write_cursormove(ctx, cx, cy, 0); else - screen_write_copy(&ctx, &c->status, 0, 0, sx, 1, NULL, NULL); + screen_write_cursormove(ctx, cx, cy + sy - lines, 0); + screen_write_fast_copy(ctx, &c->status.screen, 0, 0, sx, lines); +} - screen_write_stop(&ctx); - return (&s); +static void +window_client_menu(void *modedata, struct client *c, key_code key) +{ + struct window_client_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_client_key(wme, c, NULL, NULL, key, NULL); } static struct screen * -window_client_init(struct window_pane *wp, __unused struct cmd_find_state *fs, - struct args *args) +window_client_init(struct window_mode_entry *wme, + __unused struct cmd_find_state *fs, struct args *args) { + struct window_pane *wp = wme->wp; struct window_client_modedata *data; struct screen *s; - wp->modedata = data = xcalloc(1, sizeof *data); + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; if (args == NULL || !args_has(args, 'F')) data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT); @@ -262,8 +272,10 @@ window_client_init(struct window_pane *wp, __unused struct cmd_find_state *fs, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_client_build, - window_client_draw, NULL, data, window_client_sort_list, + window_client_draw, NULL, window_client_menu, 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); mode_tree_draw(data->data); @@ -272,9 +284,9 @@ window_client_init(struct window_pane *wp, __unused struct cmd_find_state *fs, } static void -window_client_free(struct window_pane *wp) +window_client_free(struct window_mode_entry *wme) { - struct window_client_modedata *data = wp->modedata; + struct window_client_modedata *data = wme->data; u_int i; if (data == NULL) @@ -293,15 +305,16 @@ window_client_free(struct window_pane *wp) } static void -window_client_resize(struct window_pane *wp, u_int sx, u_int sy) +window_client_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { - struct window_client_modedata *data = wp->modedata; + struct window_client_modedata *data = wme->data; mode_tree_resize(data->data, sx, sy); } static void -window_client_do_detach(void* modedata, void *itemdata, key_code key) +window_client_do_detach(void* modedata, void *itemdata, + __unused struct client *c, key_code key) { struct window_client_modedata *data = modedata; struct window_client_itemdata *item = itemdata; @@ -317,60 +330,41 @@ window_client_do_detach(void* modedata, void *itemdata, key_code key) } static void -window_client_key(struct window_pane *wp, struct client *c, - __unused struct session *s, key_code key, struct mouse_event *m) +window_client_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_client_modedata *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_client_modedata *data = wme->data; + struct mode_tree_data *mtd = data->data; struct window_client_itemdata *item; - char *command, *name; int finished; - /* - * t = toggle tag - * T = tag none - * C-t = tag all - * q = exit - * O = change sort order - * - * d = detach client - * D = detach tagged clients - * x = detach and kill client - * X = detach and kill tagged clients - * z = suspend client - * Z = suspend tagged clients - * Enter = detach client - */ - - finished = mode_tree_key(data->data, c, &key, m); + finished = mode_tree_key(mtd, c, &key, m, NULL, NULL); switch (key) { case 'd': case 'x': case 'z': - item = mode_tree_get_current(data->data); - window_client_do_detach(data, item, key); - mode_tree_build(data->data); + item = mode_tree_get_current(mtd); + window_client_do_detach(data, item, c, key); + mode_tree_build(mtd); break; case 'D': case 'X': case 'Z': - mode_tree_each_tagged(data->data, window_client_do_detach, key, - 0); - mode_tree_build(data->data); + mode_tree_each_tagged(mtd, window_client_do_detach, c, key, 0); + mode_tree_build(mtd); break; case '\r': - item = mode_tree_get_current(data->data); - command = xstrdup(data->command); - name = xstrdup(item->c->ttyname); - window_pane_reset_mode(wp); - mode_tree_run_command(c, NULL, command, name); - free(name); - free(command); - return; + item = mode_tree_get_current(mtd); + mode_tree_run_command(c, NULL, data->command, item->c->ttyname); + finished = 1; + break; } if (finished || server_client_how_many() == 0) window_pane_reset_mode(wp); else { - mode_tree_draw(data->data); + mode_tree_draw(mtd); wp->flags |= PANE_REDRAW; } } diff --git a/window-clock.c b/window-clock.c index 9ecc68a13c..45d4d47ba9 100644 --- a/window-clock.c +++ b/window-clock.c @@ -24,15 +24,16 @@ #include "tmux.h" -static struct screen *window_clock_init(struct window_pane *, +static struct screen *window_clock_init(struct window_mode_entry *, struct cmd_find_state *, struct args *); -static void window_clock_free(struct window_pane *); -static void window_clock_resize(struct window_pane *, u_int, u_int); -static void window_clock_key(struct window_pane *, struct client *, - struct session *, key_code, struct mouse_event *); +static void window_clock_free(struct window_mode_entry *); +static void window_clock_resize(struct window_mode_entry *, u_int, u_int); +static void window_clock_key(struct window_mode_entry *, struct client *, + struct session *, struct winlink *, key_code, + struct mouse_event *); static void window_clock_timer_callback(int, short, void *); -static void window_clock_draw_screen(struct window_pane *); +static void window_clock_draw_screen(struct window_mode_entry *); const struct window_mode window_clock_mode = { .name = "clock-mode", @@ -125,8 +126,9 @@ const char window_clock_table[14][5][5] = { static void window_clock_timer_callback(__unused int fd, __unused short events, void *arg) { - struct window_pane *wp = arg; - struct window_clock_mode_data *data = wp->modedata; + struct window_mode_entry *wme = arg; + struct window_pane *wp = wme->wp; + struct window_clock_mode_data *data = wme->data; struct tm now, then; time_t t; struct timeval tv = { .tv_sec = 1 }; @@ -134,6 +136,9 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) evtimer_del(&data->timer); evtimer_add(&data->timer, &tv); + if (TAILQ_FIRST(&wp->modes) != wme) + return; + t = time(NULL); gmtime_r(&t, &now); gmtime_r(&data->tim, &then); @@ -141,37 +146,38 @@ window_clock_timer_callback(__unused int fd, __unused short events, void *arg) return; data->tim = t; - window_clock_draw_screen(wp); - server_redraw_window(wp->window); + window_clock_draw_screen(wme); + wp->flags |= PANE_REDRAW; } static struct screen * -window_clock_init(struct window_pane *wp, __unused struct cmd_find_state *fs, - __unused struct args *args) +window_clock_init(struct window_mode_entry *wme, + __unused struct cmd_find_state *fs, __unused struct args *args) { + struct window_pane *wp = wme->wp; struct window_clock_mode_data *data; struct screen *s; struct timeval tv = { .tv_sec = 1 }; - wp->modedata = data = xmalloc(sizeof *data); + wme->data = data = xmalloc(sizeof *data); data->tim = time(NULL); - evtimer_set(&data->timer, window_clock_timer_callback, wp); + evtimer_set(&data->timer, window_clock_timer_callback, wme); evtimer_add(&data->timer, &tv); s = &data->screen; screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); s->mode &= ~MODE_CURSOR; - window_clock_draw_screen(wp); + window_clock_draw_screen(wme); return (s); } static void -window_clock_free(struct window_pane *wp) +window_clock_free(struct window_mode_entry *wme) { - struct window_clock_mode_data *data = wp->modedata; + struct window_clock_mode_data *data = wme->data; evtimer_del(&data->timer); screen_free(&data->screen); @@ -179,27 +185,28 @@ window_clock_free(struct window_pane *wp) } static void -window_clock_resize(struct window_pane *wp, u_int sx, u_int sy) +window_clock_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { - struct window_clock_mode_data *data = wp->modedata; + struct window_clock_mode_data *data = wme->data; struct screen *s = &data->screen; screen_resize(s, sx, sy, 0); - window_clock_draw_screen(wp); + window_clock_draw_screen(wme); } static void -window_clock_key(struct window_pane *wp, __unused struct client *c, - __unused struct session *sess, __unused key_code key, - __unused struct mouse_event *m) +window_clock_key(struct window_mode_entry *wme, __unused struct client *c, + __unused struct session *s, __unused struct winlink *wl, + __unused key_code key, __unused struct mouse_event *m) { - window_pane_reset_mode(wp); + window_pane_reset_mode(wme->wp); } static void -window_clock_draw_screen(struct window_pane *wp) +window_clock_draw_screen(struct window_mode_entry *wme) { - struct window_clock_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_clock_mode_data *data = wme->data; struct screen_write_ctx ctx; int colour, style; struct screen *s = &data->screen; @@ -231,7 +238,7 @@ window_clock_draw_screen(struct window_pane *wp) if (screen_size_x(s) >= strlen(tim) && screen_size_y(s) != 0) { x = (screen_size_x(s) / 2) - (strlen(tim) / 2); y = screen_size_y(s) / 2; - screen_write_cursormove(&ctx, x, y); + screen_write_cursormove(&ctx, x, y, 0); memcpy(&gc, &grid_default_cell, sizeof gc); gc.flags |= GRID_FLAG_NOPALETTE; @@ -267,7 +274,7 @@ window_clock_draw_screen(struct window_pane *wp) for (j = 0; j < 5; j++) { for (i = 0; i < 5; i++) { - screen_write_cursormove(&ctx, x + i, y + j); + screen_write_cursormove(&ctx, x + i, y + j, 0); if (window_clock_table[idx][j][i]) screen_write_putc(&ctx, &gc, ' '); } diff --git a/window-copy.c b/window-copy.c index 09ccc26cf9..16708d046d 100644 --- a/window-copy.c +++ b/window-copy.c @@ -24,86 +24,99 @@ #include "tmux.h" -static const char *window_copy_key_table(struct window_pane *); -static void window_copy_command(struct window_pane *, struct client *, - struct session *, struct args *, struct mouse_event *); -static struct screen *window_copy_init(struct window_pane *, +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 *, + struct mouse_event *); +static struct screen *window_copy_init(struct window_mode_entry *, struct cmd_find_state *, struct args *); -static void window_copy_free(struct window_pane *); -static int window_copy_pagedown(struct window_pane *, int); -static void window_copy_next_paragraph(struct window_pane *); -static void window_copy_previous_paragraph(struct window_pane *); -static void window_copy_resize(struct window_pane *, u_int, u_int); - -static void window_copy_redraw_selection(struct window_pane *, u_int); -static void window_copy_redraw_lines(struct window_pane *, u_int, u_int); -static void window_copy_redraw_screen(struct window_pane *); -static void window_copy_write_line(struct window_pane *, +static struct screen *window_copy_view_init(struct window_mode_entry *, + struct cmd_find_state *, struct args *); +static void window_copy_free(struct window_mode_entry *); +static void window_copy_resize(struct window_mode_entry *, u_int, u_int); +static void window_copy_formats(struct window_mode_entry *, + struct format_tree *); +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); +static void window_copy_redraw_screen(struct window_mode_entry *); +static void window_copy_write_line(struct window_mode_entry *, struct screen_write_ctx *, u_int); -static void window_copy_write_lines(struct window_pane *, +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_pane *, u_int, u_int); +static void window_copy_scroll_to(struct window_mode_entry *, u_int, u_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_marks(struct window_pane *, struct screen *); -static void window_copy_clear_marks(struct window_pane *); -static void window_copy_move_left(struct screen *, u_int *, u_int *); -static void window_copy_move_right(struct screen *, u_int *, u_int *); +static int window_copy_search_marks(struct window_mode_entry *, + struct screen *); +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 int window_copy_search_jump(struct window_pane *, struct grid *, - struct grid *, u_int, u_int, u_int, int, int, int); -static int window_copy_search(struct window_pane *, int); -static int window_copy_search_up(struct window_pane *); -static int window_copy_search_down(struct window_pane *); -static void window_copy_goto_line(struct window_pane *, const char *); -static void window_copy_update_cursor(struct window_pane *, u_int, u_int); -static void window_copy_start_selection(struct window_pane *); -static int window_copy_adjust_selection(struct window_pane *, u_int *, - u_int *); -static int window_copy_update_selection(struct window_pane *, int); -static void window_copy_synchronize_cursor(struct window_pane *wp); -static void *window_copy_get_selection(struct window_pane *, size_t *); -static void window_copy_copy_buffer(struct window_pane *, const char *, - void *, size_t); -static void window_copy_copy_pipe(struct window_pane *, struct session *, - const char *, const char *); -static void window_copy_copy_selection(struct window_pane *, const char *); -static void window_copy_append_selection(struct window_pane *, - const char *); -static void window_copy_clear_selection(struct window_pane *); -static void window_copy_copy_line(struct window_pane *, char **, size_t *, - u_int, u_int, u_int); -static int window_copy_in_set(struct window_pane *, 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); +static int window_copy_search(struct window_mode_entry *, int); +static int window_copy_search_up(struct window_mode_entry *); +static int window_copy_search_down(struct window_mode_entry *); +static void window_copy_goto_line(struct window_mode_entry *, const char *); +static void window_copy_update_cursor(struct window_mode_entry *, u_int, + u_int); +static void window_copy_start_selection(struct window_mode_entry *); +static int window_copy_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 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_copy_pipe(struct window_mode_entry *, + struct session *, const char *, const char *); +static void window_copy_copy_selection(struct window_mode_entry *, const char *); -static u_int window_copy_find_length(struct window_pane *, u_int); -static void window_copy_cursor_start_of_line(struct window_pane *); -static void window_copy_cursor_back_to_indentation(struct window_pane *); -static void window_copy_cursor_end_of_line(struct window_pane *); -static void window_copy_other_end(struct window_pane *); -static void window_copy_cursor_left(struct window_pane *); -static void window_copy_cursor_right(struct window_pane *); -static void window_copy_cursor_up(struct window_pane *, int); -static void window_copy_cursor_down(struct window_pane *, int); -static void window_copy_cursor_jump(struct window_pane *); -static void window_copy_cursor_jump_back(struct window_pane *); -static void window_copy_cursor_jump_to(struct window_pane *); -static void window_copy_cursor_jump_to_back(struct window_pane *); -static void window_copy_cursor_next_word(struct window_pane *, +static void window_copy_append_selection(struct window_mode_entry *); +static void window_copy_clear_selection(struct window_mode_entry *); +static void window_copy_copy_line(struct window_mode_entry *, char **, + size_t *, u_int, u_int, u_int); +static int window_copy_in_set(struct window_mode_entry *, u_int, u_int, const char *); -static void window_copy_cursor_next_word_end(struct window_pane *, +static u_int window_copy_find_length(struct window_mode_entry *, u_int); +static void window_copy_cursor_start_of_line(struct window_mode_entry *); +static void window_copy_cursor_back_to_indentation( + struct window_mode_entry *); +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_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 *); +static void window_copy_cursor_jump_back(struct window_mode_entry *); +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_previous_word(struct window_pane *, +static void window_copy_cursor_next_word_end(struct window_mode_entry *, const char *); -static void window_copy_scroll_up(struct window_pane *, u_int); -static void window_copy_scroll_down(struct window_pane *, u_int); -static void window_copy_rectangle_toggle(struct window_pane *); +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_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 *); const struct window_mode window_copy_mode = { .name = "copy-mode", @@ -113,6 +126,18 @@ const struct window_mode window_copy_mode = { .resize = window_copy_resize, .key_table = window_copy_key_table, .command = window_copy_command, + .formats = window_copy_formats, +}; + +const struct window_mode window_view_mode = { + .name = "view-mode", + + .init = window_copy_view_init, + .free = window_copy_free, + .resize = window_copy_resize, + .key_table = window_copy_key_table, + .command = window_copy_command, + .formats = window_copy_formats, }; enum { @@ -131,6 +156,22 @@ enum { WINDOW_COPY_REL_POS_BELOW, }; +enum window_copy_cmd_action { + WINDOW_COPY_CMD_NOTHING, + WINDOW_COPY_CMD_REDRAW, + WINDOW_COPY_CMD_CANCEL, +}; + +struct window_copy_cmd_state { + struct window_mode_entry *wme; + struct args *args; + struct mouse_event *m; + + struct client *c; + struct session *s; + struct winlink *wl; +}; + /* * Copy mode's visible screen (the "screen" field) is filled from one of two * sources: the original contents of the pane (used when we actually enter via @@ -151,12 +192,12 @@ struct window_copy_mode_data { struct screen *backing; int backing_written; /* backing display started */ - u_int oy; /* number of lines scrolled up */ + u_int oy; /* number of lines scrolled up */ - u_int selx; /* beginning of selection */ + u_int selx; /* beginning of selection */ u_int sely; - u_int endselx; /* end of selection */ + u_int endselx; /* end of selection */ u_int endsely; enum { @@ -165,18 +206,24 @@ struct window_copy_mode_data { CURSORDRAG_SEL, /* start is synchronized with cursor */ } cursordrag; + int modekeys; + enum { + LINE_SEL_NONE, + LINE_SEL_LEFT_RIGHT, + LINE_SEL_RIGHT_LEFT, + } lineflag; /* line selection mode */ int rectflag; /* in rectangle copy mode? */ int scroll_exit; /* exit on scroll to end? */ u_int cx; u_int cy; - u_int lastcx; /* position in last line w/ content */ - u_int lastsx; /* size of last line w/ content */ + u_int lastcx; /* position in last line w/ content */ + u_int lastsx; /* size of last line w/ content */ int searchtype; char *searchstr; - bitstr_t *searchmark; + bitstr_t *searchmark; u_int searchcount; int searchthis; int searchx; @@ -185,30 +232,46 @@ struct window_copy_mode_data { int jumptype; char jumpchar; + + struct event dragtimer; +#define WINDOW_COPY_DRAG_REPEAT_TIME 50000 }; -static struct screen * -window_copy_init(struct window_pane *wp, __unused struct cmd_find_state *fs, - __unused struct args *args) +static void +window_copy_scroll_timer(__unused int fd, __unused short events, void *arg) { - struct window_copy_mode_data *data; - struct screen *s; + struct window_mode_entry *wme = arg; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; + struct timeval tv = { + .tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME + }; - wp->modedata = data = xmalloc(sizeof *data); + evtimer_del(&data->dragtimer); - data->oy = 0; - data->cx = 0; - data->cy = 0; + if (TAILQ_FIRST(&wp->modes) != wme) + return; - data->cursordrag = CURSORDRAG_NONE; + if (data->cy == 0) { + evtimer_add(&data->dragtimer, &tv); + window_copy_cursor_up(wme, 1); + } else if (data->cy == screen_size_y(&data->screen) - 1) { + evtimer_add(&data->dragtimer, &tv); + window_copy_cursor_down(wme, 1); + } +} - data->lastcx = 0; - data->lastsx = 0; +static struct window_copy_mode_data * +window_copy_common_init(struct window_mode_entry *wme) +{ + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data; + struct screen *base = &wp->base; - data->backing_written = 0; + wme->data = data = xcalloc(1, sizeof *data); - data->rectflag = 0; - data->scroll_exit = 0; + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; if (wp->searchstr != NULL) { data->searchtype = WINDOW_COPY_SEARCHUP; @@ -220,63 +283,75 @@ window_copy_init(struct window_pane *wp, __unused struct cmd_find_state *fs, data->searchmark = NULL; data->searchx = data->searchy = data->searcho = -1; - if (wp->fd != -1) - bufferevent_disable(wp->event, EV_READ|EV_WRITE); - data->jumptype = WINDOW_COPY_OFF; data->jumpchar = '\0'; - s = &data->screen; - screen_init(s, screen_size_x(&wp->base), screen_size_y(&wp->base), 0); - s->sel.modekeys = options_get_number(wp->window->options, "mode-keys"); + screen_init(&data->screen, screen_size_x(base), screen_size_y(base), 0); + data->modekeys = options_get_number(wp->window->options, "mode-keys"); - data->backing = NULL; + evtimer_set(&data->dragtimer, window_copy_scroll_timer, wme); - return (s); + return (data); } -void -window_copy_init_from_pane(struct window_pane *wp, int scroll_exit) +static struct screen * +window_copy_init(struct window_mode_entry *wme, + __unused struct cmd_find_state *fs, struct args *args) { - struct window_copy_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - struct screen_write_ctx ctx; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data; + struct screen_write_ctx ctx; u_int i; - if (wp->mode != &window_copy_mode) - fatalx("not in copy mode"); + data = window_copy_common_init(wme); + + 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->scroll_exit = scroll_exit; - s->cx = data->cx; - s->cy = data->cy; + data->scroll_exit = args_has(args, 'e'); - screen_write_start(&ctx, NULL, s); - for (i = 0; i < screen_size_y(s); i++) - window_copy_write_line(wp, &ctx, i); - screen_write_cursormove(&ctx, data->cx, data->cy); + data->screen.cx = data->cx; + data->screen.cy = data->cy; + + screen_write_start(&ctx, NULL, &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); screen_write_stop(&ctx); + + return (&data->screen); } -void -window_copy_init_for_output(struct window_pane *wp) +static struct screen * +window_copy_view_init(struct window_mode_entry *wme, + __unused struct cmd_find_state *fs, __unused struct args *args) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data; + struct screen *base = &wp->base; + struct screen *s; + + data = window_copy_common_init(wme); + + data->backing = s = xmalloc(sizeof *data->backing); + screen_init(s, screen_size_x(base), screen_size_y(base), UINT_MAX); - data->backing = xmalloc(sizeof *data->backing); - screen_init(data->backing, screen_size_x(&wp->base), - screen_size_y(&wp->base), UINT_MAX); + return (&data->screen); } static void -window_copy_free(struct window_pane *wp) +window_copy_free(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; - if (wp->fd != -1) + evtimer_del(&data->dragtimer); + + if (wp->fd != -1 && --wp->disabled == 0) bufferevent_enable(wp->event, EV_READ|EV_WRITE); free(data->searchmark); @@ -304,7 +379,8 @@ window_copy_add(struct window_pane *wp, const char *fmt, ...) void window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) { - struct window_copy_mode_data *data = wp->modedata; + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data = wme->data; struct screen *backing = data->backing; struct screen_write_ctx back_ctx, ctx; struct grid_cell gc; @@ -339,10 +415,10 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) * (If there's any history at all, it has changed.) */ if (screen_hsize(data->backing)) - window_copy_redraw_lines(wp, 0, 1); + window_copy_redraw_lines(wme, 0, 1); /* Write the new lines. */ - window_copy_redraw_lines(wp, old_cy, backing->cy - old_cy + 1); + window_copy_redraw_lines(wme, old_cy, backing->cy - old_cy + 1); screen_write_stop(&ctx); } @@ -350,15 +426,18 @@ window_copy_vadd(struct window_pane *wp, const char *fmt, va_list ap) void window_copy_pageup(struct window_pane *wp, int half_page) { - struct window_copy_mode_data *data = wp->modedata; + window_copy_pageup1(TAILQ_FIRST(&wp->modes), half_page); +} + +static void +window_copy_pageup1(struct window_mode_entry *wme, int half_page) +{ + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int n, ox, oy, px, py; oy = screen_hsize(data->backing) + data->cy - data->oy; - ox = window_copy_find_length(wp, oy); - - if (s->sel.lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) - window_copy_other_end(wp); + ox = window_copy_find_length(wme, oy); if (data->cx != ox) { data->lastcx = data->cx; @@ -374,35 +453,37 @@ window_copy_pageup(struct window_pane *wp, int half_page) n = screen_size_y(s) - 2; } - if (data->oy + n > screen_hsize(data->backing)) + if (data->oy + n > screen_hsize(data->backing)) { data->oy = screen_hsize(data->backing); - else + if (data->cy < n) + data->cy = 0; + else + data->cy -= n; + } else data->oy += n; - if (!data->screen.sel.flag || !data->rectflag) { + if (data->screen.sel == NULL || !data->rectflag) { py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wp); + window_copy_cursor_end_of_line(wme); } - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); } static int -window_copy_pagedown(struct window_pane *wp, int half_page) +window_copy_pagedown(struct window_mode_entry *wme, int half_page, + int scroll_exit) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int n, ox, oy, px, py; oy = screen_hsize(data->backing) + data->cy - data->oy; - ox = window_copy_find_length(wp, oy); - - if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT && oy == data->sely) - window_copy_other_end(wp); + ox = window_copy_find_length(wme, oy); if (data->cx != ox) { data->lastcx = data->cx; @@ -418,69 +499,108 @@ window_copy_pagedown(struct window_pane *wp, int half_page) n = screen_size_y(s) - 2; } - if (data->oy < n) + if (data->oy < n) { data->oy = 0; - else + if (data->cy + (n - data->oy) >= screen_size_y(data->backing)) + data->cy = screen_size_y(data->backing) - 1; + else + data->cy += n - data->oy; + } else data->oy -= n; - if (!data->screen.sel.flag || !data->rectflag) { + if (data->screen.sel == NULL || !data->rectflag) { py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wp); + window_copy_cursor_end_of_line(wme); } - if (data->scroll_exit && data->oy == 0) + if (scroll_exit && data->oy == 0) return (1); - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); return (0); } static void -window_copy_previous_paragraph(struct window_pane *wp) +window_copy_previous_paragraph(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int oy; oy = screen_hsize(data->backing) + data->cy - data->oy; - while (oy > 0 && window_copy_find_length(wp, oy) == 0) + while (oy > 0 && window_copy_find_length(wme, oy) == 0) oy--; - while (oy > 0 && window_copy_find_length(wp, oy) > 0) + while (oy > 0 && window_copy_find_length(wme, oy) > 0) oy--; - window_copy_scroll_to(wp, 0, oy); + window_copy_scroll_to(wme, 0, oy); } static void -window_copy_next_paragraph(struct window_pane *wp) +window_copy_next_paragraph(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int maxy, ox, oy; oy = screen_hsize(data->backing) + data->cy - data->oy; maxy = screen_hsize(data->backing) + screen_size_y(s) - 1; - while (oy < maxy && window_copy_find_length(wp, oy) == 0) + while (oy < maxy && window_copy_find_length(wme, oy) == 0) oy++; - while (oy < maxy && window_copy_find_length(wp, oy) > 0) + while (oy < maxy && window_copy_find_length(wme, oy) > 0) oy++; - ox = window_copy_find_length(wp, oy); - window_copy_scroll_to(wp, ox, oy); + ox = window_copy_find_length(wme, oy); + window_copy_scroll_to(wme, ox, oy); } static void -window_copy_resize(struct window_pane *wp, u_int sx, u_int sy) +window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) { - struct window_copy_mode_data *data = wp->modedata; + 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); + + format_add(ft, "copy_cursor_x", "%d", data->cx); + format_add(ft, "copy_cursor_y", "%d", data->cy); + + format_add(ft, "selection_present", "%d", data->screen.sel != NULL); + if (data->screen.sel != NULL) { + format_add(ft, "selection_start_x", "%d", data->selx); + format_add(ft, "selection_start_y", "%d", data->sely); + format_add(ft, "selection_end_x", "%d", data->endselx); + format_add(ft, "selection_end_y", "%d", data->endsely); + } + + s = format_grid_word(data->screen.grid, data->cx, data->cy); + if (s != NULL) { + format_add(ft, "copy_cursor_word", "%s", s); + free(s); + } + + s = format_grid_line(data->screen.grid, data->cy); + if (s != NULL) { + format_add(ft, "copy_cursor_line", "%s", s); + free(s); + } +} + +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; screen_resize(s, sx, sy, 1); if (data->backing != &wp->base) @@ -493,483 +613,1448 @@ window_copy_resize(struct window_pane *wp, u_int sx, u_int sy) if (data->oy > screen_hsize(data->backing)) data->oy = screen_hsize(data->backing); - window_copy_clear_selection(wp); + 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); + data->searchx = data->cx; + data->searchy = data->cy; + data->searcho = data->oy; + + window_copy_redraw_screen(wme); +} + +static const char * +window_copy_key_table(struct window_mode_entry *wme) +{ + struct window_pane *wp = wme->wp; + + if (options_get_number(wp->window->options, "mode-keys") == MODEKEY_VI) + return ("copy-mode-vi"); + return ("copy-mode"); +} + +static enum window_copy_cmd_action +window_copy_cmd_append_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct session *s = cs->s; + + if (s != NULL) + window_copy_append_selection(wme); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_append_selection_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct session *s = cs->s; + + if (s != NULL) + window_copy_append_selection(wme); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_CANCEL); +} + +static enum window_copy_cmd_action +window_copy_cmd_back_to_indentation(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cursor_back_to_indentation(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_begin_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct mouse_event *m = cs->m; + struct window_copy_mode_data *data = wme->data; + + if (m != NULL) { + window_copy_start_drag(c, m); + return (WINDOW_COPY_CMD_NOTHING); + } + + data->lineflag = LINE_SEL_NONE; + window_copy_start_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_stop_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_bottom_line(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->cx = 0; + data->cy = screen_size_y(&data->screen) - 1; + + window_copy_update_selection(wme, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_cancel(__unused struct window_copy_cmd_state *cs) +{ + return (WINDOW_COPY_CMD_CANCEL); +} + +static enum window_copy_cmd_action +window_copy_cmd_clear_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +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; + + if (cs->args->argc == 2) + prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + + window_copy_start_selection(wme); + for (; np > 1; np--) + window_copy_cursor_down(wme, 0); + window_copy_cursor_end_of_line(wme); + + if (s != NULL) { + window_copy_copy_selection(wme, prefix); + + free(prefix); + return (WINDOW_COPY_CMD_CANCEL); + } + + free(prefix); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_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; + + if (cs->args->argc == 2) + prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + + window_copy_cursor_start_of_line(wme); + window_copy_start_selection(wme); + for (; np > 1; np--) + window_copy_cursor_down(wme, 0); + window_copy_cursor_end_of_line(wme); + + if (s != NULL) { + window_copy_copy_selection(wme, prefix); + + free(prefix); + return (WINDOW_COPY_CMD_CANCEL); + } + + free(prefix); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_selection_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 *prefix = NULL; + + if (cs->args->argc == 2) + prefix = format_single(NULL, cs->args->argv[1], c, s, wl, wp); + + if (s != NULL) + window_copy_copy_selection(wme, prefix); + + free(prefix); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_copy_selection_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_selection_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_copy_selection_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_CANCEL); +} + +static enum window_copy_cmd_action +window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_down(wme, 0); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_cursor_down_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int np = wme->prefix, cy; + + cy = data->cy; + for (; np != 0; np--) + window_copy_cursor_down(wme, 0); + if (cy == data->cy && data->oy == 0) + return (WINDOW_COPY_CMD_CANCEL); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_cursor_left(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_left(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_cursor_right(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_right(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_up(wme, 0); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_end_of_line(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cursor_end_of_line(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_halfpage_down(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int np = wme->prefix; + + for (; np != 0; np--) { + if (window_copy_pagedown(wme, 1, data->scroll_exit)) + return (WINDOW_COPY_CMD_CANCEL); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_halfpage_down_and_cancel(struct window_copy_cmd_state *cs) +{ + + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) { + if (window_copy_pagedown(wme, 1, 1)) + return (WINDOW_COPY_CMD_CANCEL); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_halfpage_up(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_pageup1(wme, 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +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; + u_int oy; + + oy = screen_hsize(data->backing) + 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->oy = 0; + + window_copy_update_selection(wme, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_history_top(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int oy; + + oy = screen_hsize(data->backing) + data->cy - data->oy; + if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) + window_copy_other_end(wme); + + data->cy = 0; + data->cx = 0; + data->oy = screen_hsize(data->backing); + + window_copy_update_selection(wme, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_jump_again(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; + + switch (data->jumptype) { + case WINDOW_COPY_JUMPFORWARD: + for (; np != 0; np--) + window_copy_cursor_jump(wme); + break; + case WINDOW_COPY_JUMPBACKWARD: + for (; np != 0; np--) + window_copy_cursor_jump_back(wme); + break; + case WINDOW_COPY_JUMPTOFORWARD: + for (; np != 0; np--) + window_copy_cursor_jump_to(wme); + break; + case WINDOW_COPY_JUMPTOBACKWARD: + for (; np != 0; np--) + window_copy_cursor_jump_to_back(wme); + break; + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_jump_reverse(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; + + switch (data->jumptype) { + case WINDOW_COPY_JUMPFORWARD: + for (; np != 0; np--) + window_copy_cursor_jump_back(wme); + break; + case WINDOW_COPY_JUMPBACKWARD: + for (; np != 0; np--) + window_copy_cursor_jump(wme); + break; + case WINDOW_COPY_JUMPTOFORWARD: + for (; np != 0; np--) + window_copy_cursor_jump_to_back(wme); + break; + case WINDOW_COPY_JUMPTOBACKWARD: + for (; np != 0; np--) + window_copy_cursor_jump_to(wme); + break; + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_middle_line(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->cx = 0; + data->cy = (screen_size_y(&data->screen) - 1) / 2; + + window_copy_update_selection(wme, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_previous_matching_bracket(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; + struct screen *s = data->backing; + char open[] = "{[(", close[] = "}])"; + char tried, found, start, *cp; + u_int px, py, xx, n; + struct grid_cell gc; + int failed; + + for (; np != 0; np--) { + /* Get cursor position and line length. */ + px = data->cx; + py = screen_hsize(s) + data->cy - data->oy; + xx = window_copy_find_length(wme, py); + if (xx == 0) + break; + + /* + * Get the current character. If not on a bracket, try the + * previous. If still not, then behave like previous-word. + */ + tried = 0; + retry: + grid_get_cell(s->grid, px, py, &gc); + if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING)) + cp = NULL; + else { + found = *gc.data.data; + cp = strchr(close, found); + } + if (cp == NULL) { + if (data->modekeys == MODEKEY_EMACS) { + if (!tried && px > 0) { + px--; + tried = 1; + goto retry; + } + window_copy_cursor_previous_word(wme, "}]) ", 1); + } + continue; + } + start = open[cp - close]; + + /* Walk backward until the matching bracket is reached. */ + n = 1; + failed = 0; + do { + if (px == 0) { + if (py == 0) { + failed = 1; + break; + } + do { + py--; + xx = window_copy_find_length(wme, py); + } while (xx == 0 && py > 0); + if (xx == 0 && py == 0) { + failed = 1; + break; + } + px = xx - 1; + } else + px--; + + grid_get_cell(s->grid, px, py, &gc); + if (gc.data.size == 1 && + (~gc.flags & GRID_FLAG_PADDING)) { + if (*gc.data.data == found) + n++; + else if (*gc.data.data == start) + n--; + } + } while (n != 0); + + /* Move the cursor to the found location if any. */ + if (!failed) + window_copy_scroll_to(wme, px, py); + } + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_next_matching_bracket(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; + struct screen *s = data->backing; + char open[] = "{[(", close[] = "}])"; + char tried, found, end, *cp; + u_int px, py, xx, yy, sx, sy, n; + struct grid_cell gc; + int failed; + struct grid_line *gl; + + for (; np != 0; np--) { + /* Get cursor position and line length. */ + px = data->cx; + py = screen_hsize(s) + data->cy - data->oy; + xx = window_copy_find_length(wme, py); + yy = screen_hsize(s) + screen_size_y(s) - 1; + if (xx == 0) + break; + + /* + * Get the current character. If not on a bracket, try the + * next. If still not, then behave like next-word. + */ + tried = 0; + retry: + grid_get_cell(s->grid, px, py, &gc); + if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING)) + cp = NULL; + else { + found = *gc.data.data; + + /* + * In vi mode, attempt to move to previous bracket if a + * closing bracket is found first. If this fails, + * return to the original cursor position. + */ + cp = strchr(close, found); + if (cp != NULL && data->modekeys == MODEKEY_VI) { + sx = data->cx; + sy = screen_hsize(s) + data->cy - data->oy; + + window_copy_scroll_to(wme, px, py); + 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); + break; + } + + cp = strchr(open, found); + } + if (cp == NULL) { + if (data->modekeys == MODEKEY_EMACS) { + if (!tried && px <= xx) { + px++; + tried = 1; + goto retry; + } + window_copy_cursor_next_word_end(wme, "{[( "); + continue; + } + /* For vi, continue searching for bracket until EOL. */ + if (px > xx) { + if (py == yy) + continue; + gl = grid_get_line(s->grid, py); + if (~gl->flags & GRID_LINE_WRAPPED) + continue; + if (gl->cellsize > s->grid->sx) + continue; + px = 0; + py++; + xx = window_copy_find_length(wme, py); + } else + px++; + goto retry; + } + end = close[cp - open]; + + /* Walk forward until the matching bracket is reached. */ + n = 1; + failed = 0; + do { + if (px > xx) { + if (py == yy) { + failed = 1; + break; + } + px = 0; + py++; + xx = window_copy_find_length(wme, py); + } else + px++; + + grid_get_cell(s->grid, px, py, &gc); + if (gc.data.size == 1 && + (~gc.flags & GRID_FLAG_PADDING)) { + if (*gc.data.data == found) + n++; + else if (*gc.data.data == end) + n--; + } + } while (n != 0); + + /* Move the cursor to the found location if any. */ + if (!failed) + window_copy_scroll_to(wme, px, py); + } + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_next_paragraph(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_next_paragraph(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_next_space(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_next_word(wme, " "); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_next_word_end(wme, " "); + return (WINDOW_COPY_CMD_NOTHING); +} + +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; + + ws = options_get_string(s->options, "word-separators"); + for (; np != 0; np--) + window_copy_cursor_next_word(wme, ws); + return (WINDOW_COPY_CMD_NOTHING); +} + +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; + + ws = options_get_string(s->options, "word-separators"); + for (; np != 0; np--) + window_copy_cursor_next_word_end(wme, ws); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_other_end(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + if ((np % 2) != 0) + window_copy_other_end(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_page_down(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int np = wme->prefix; + + for (; np != 0; np--) { + if (window_copy_pagedown(wme, 0, data->scroll_exit)) + return (WINDOW_COPY_CMD_CANCEL); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_page_down_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) { + if (window_copy_pagedown(wme, 0, 1)) + return (WINDOW_COPY_CMD_CANCEL); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_page_up(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_pageup1(wme, 0); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_previous_paragraph(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_previous_paragraph(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_previous_space(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_previous_word(wme, " ", 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +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; + + ws = options_get_string(s->options, "word-separators"); + for (; np != 0; np--) + window_copy_cursor_previous_word(wme, ws, 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_rectangle_toggle(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_toggle(wme); + + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_scroll_down(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_down(wme, 1); + if (data->scroll_exit && data->oy == 0) + return (WINDOW_COPY_CMD_CANCEL); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_scroll_down_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_down(wme, 1); + if (data->oy == 0) + return (WINDOW_COPY_CMD_CANCEL); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + u_int np = wme->prefix; + + for (; np != 0; np--) + window_copy_cursor_up(wme, 1); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_search_again(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 (data->searchtype == WINDOW_COPY_SEARCHUP) { + for (; np != 0; np--) + window_copy_search_up(wme); + } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { + for (; np != 0; np--) + window_copy_search_down(wme); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_search_reverse(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 (data->searchtype == WINDOW_COPY_SEARCHUP) { + for (; np != 0; np--) + window_copy_search_down(wme); + } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { + for (; np != 0; np--) + window_copy_search_up(wme); + } + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_select_line(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; + + data->lineflag = LINE_SEL_LEFT_RIGHT; + data->rectflag = 0; + + window_copy_cursor_start_of_line(wme); + window_copy_start_selection(wme); + for (; np > 1; np--) + window_copy_cursor_down(wme, 0); + window_copy_cursor_end_of_line(wme); + + return (WINDOW_COPY_CMD_REDRAW); +} + +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 window_copy_mode_data *data = wme->data; + const char *ws; + + data->lineflag = LINE_SEL_LEFT_RIGHT; + data->rectflag = 0; + + ws = options_get_string(s->options, "word-separators"); + window_copy_cursor_previous_word(wme, ws, 0); + window_copy_start_selection(wme); + window_copy_cursor_next_word_end(wme, ws); + + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_start_of_line(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cursor_start_of_line(wme); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_top_line(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + + data->cx = 0; + data->cy = 0; + + window_copy_update_selection(wme, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_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; + char *prefix = NULL; + + if (cs->args->argc == 3) + prefix = format_single(NULL, cs->args->argv[2], 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); + } + + free(prefix); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_copy_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_and_cancel(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + + window_copy_cmd_copy_pipe_no_clear(cs); + window_copy_clear_selection(wme); + return (WINDOW_COPY_CMD_CANCEL); +} - screen_write_start(&ctx, NULL, s); - window_copy_write_lines(wp, &ctx, 0, screen_size_y(s) - 1); - screen_write_stop(&ctx); +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]; - if (data->searchmark != NULL) - window_copy_search_marks(wp, NULL); - data->searchx = data->cx; - data->searchy = data->cy; - data->searcho = data->oy; + if (*argument != '\0') + window_copy_goto_line(wme, argument); + return (WINDOW_COPY_CMD_NOTHING); +} - window_copy_redraw_screen(wp); +static enum window_copy_cmd_action +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]; + + if (*argument != '\0') { + data->jumptype = WINDOW_COPY_JUMPBACKWARD; + data->jumpchar = *argument; + for (; np != 0; np--) + window_copy_cursor_jump_back(wme); + } + return (WINDOW_COPY_CMD_NOTHING); } -static const char * -window_copy_key_table(struct window_pane *wp) +static enum window_copy_cmd_action +window_copy_cmd_jump_forward(struct window_copy_cmd_state *cs) { - if (options_get_number(wp->window->options, "mode-keys") == MODEKEY_VI) - return ("copy-mode-vi"); - return ("copy-mode"); + 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]; + + if (*argument != '\0') { + data->jumptype = WINDOW_COPY_JUMPFORWARD; + data->jumpchar = *argument; + for (; np != 0; np--) + window_copy_cursor_jump(wme); + } + return (WINDOW_COPY_CMD_NOTHING); } -static void -window_copy_command(struct window_pane *wp, struct client *c, struct session *s, - struct args *args, struct mouse_event *m) -{ - struct window_copy_mode_data *data = wp->modedata; - struct screen *sn = &data->screen; - const char *command, *argument, *ws; - u_int np = wp->modeprefix; - int cancel = 0, redraw = 0; - char prefix; +static enum window_copy_cmd_action +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]; - if (args->argc == 0) - return; - command = args->argv[0]; + if (*argument != '\0') { + data->jumptype = WINDOW_COPY_JUMPTOBACKWARD; + data->jumpchar = *argument; + for (; np != 0; np--) + window_copy_cursor_jump_to_back(wme); + } + return (WINDOW_COPY_CMD_NOTHING); +} - if (m != NULL && m->valid) - window_copy_move_mouse(m); +static enum window_copy_cmd_action +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]; - if (args->argc == 1) { - if (strcmp(command, "append-selection") == 0) { - if (s != NULL) - window_copy_append_selection(wp, NULL); - window_copy_clear_selection(wp); - redraw = 1; - } - if (strcmp(command, "append-selection-and-cancel") == 0) { - if (s != NULL) - window_copy_append_selection(wp, NULL); - window_copy_clear_selection(wp); - redraw = 1; - cancel = 1; - } - if (strcmp(command, "back-to-indentation") == 0) - window_copy_cursor_back_to_indentation(wp); - if (strcmp(command, "begin-selection") == 0) { - if (m != NULL) - window_copy_start_drag(c, m); - else { - sn->sel.lineflag = LINE_SEL_NONE; - window_copy_start_selection(wp); - redraw = 1; - } - } - if (strcmp(command, "stop-selection") == 0) - data->cursordrag = CURSORDRAG_NONE; - if (strcmp(command, "bottom-line") == 0) { - data->cx = 0; - data->cy = screen_size_y(sn) - 1; - window_copy_update_selection(wp, 1); - redraw = 1; - } - if (strcmp(command, "cancel") == 0) - cancel = 1; - if (strcmp(command, "clear-selection") == 0) { - window_copy_clear_selection(wp); - redraw = 1; - } - if (strcmp(command, "copy-end-of-line") == 0) { - window_copy_start_selection(wp); - for (; np > 1; np--) - window_copy_cursor_down(wp, 0); - window_copy_cursor_end_of_line(wp); - redraw = 1; - - if (s != NULL) { - window_copy_copy_selection(wp, NULL); - cancel = 1; - } - } - if (strcmp(command, "copy-line") == 0) { - window_copy_cursor_start_of_line(wp); - window_copy_start_selection(wp); - for (; np > 1; np--) - window_copy_cursor_down(wp, 0); - window_copy_cursor_end_of_line(wp); - redraw = 1; - - if (s != NULL) { - window_copy_copy_selection(wp, NULL); - cancel = 1; - } - } - if (strcmp(command, "copy-selection") == 0) { - if (s != NULL) - window_copy_copy_selection(wp, NULL); - window_copy_clear_selection(wp); - redraw = 1; - } - if (strcmp(command, "copy-selection-and-cancel") == 0) { - if (s != NULL) - window_copy_copy_selection(wp, NULL); - window_copy_clear_selection(wp); - redraw = 1; - cancel = 1; - } - if (strcmp(command, "cursor-down") == 0) { - for (; np != 0; np--) - window_copy_cursor_down(wp, 0); - } - if (strcmp(command, "cursor-left") == 0) { - for (; np != 0; np--) - window_copy_cursor_left(wp); - } - if (strcmp(command, "cursor-right") == 0) { - for (; np != 0; np--) - window_copy_cursor_right(wp); - } - if (strcmp(command, "cursor-up") == 0) { - for (; np != 0; np--) - window_copy_cursor_up(wp, 0); - } - if (strcmp(command, "end-of-line") == 0) - window_copy_cursor_end_of_line(wp); - if (strcmp(command, "halfpage-down") == 0) { - for (; np != 0; np--) { - if (window_copy_pagedown(wp, 1)) { - cancel = 1; - break; - } - } - } - if (strcmp(command, "halfpage-up") == 0) { - for (; np != 0; np--) - window_copy_pageup(wp, 1); - } - if (strcmp(command, "history-bottom") == 0) { - data->cx = 0; - data->cy = screen_size_y(sn) - 1; - data->oy = 0; - window_copy_update_selection(wp, 1); - redraw = 1; - } - if (strcmp(command, "history-top") == 0) { - data->cx = 0; - data->cy = 0; - data->oy = screen_hsize(data->backing); - window_copy_update_selection(wp, 1); - redraw = 1; - } - if (strcmp(command, "jump-again") == 0) { - switch (data->jumptype) { - case WINDOW_COPY_JUMPFORWARD: - for (; np != 0; np--) - window_copy_cursor_jump(wp); - break; - case WINDOW_COPY_JUMPBACKWARD: - for (; np != 0; np--) - window_copy_cursor_jump_back(wp); - break; - case WINDOW_COPY_JUMPTOFORWARD: - for (; np != 0; np--) - window_copy_cursor_jump_to(wp); - break; - case WINDOW_COPY_JUMPTOBACKWARD: - for (; np != 0; np--) - window_copy_cursor_jump_to_back(wp); - break; - } - } - if (strcmp(command, "jump-reverse") == 0) { - switch (data->jumptype) { - case WINDOW_COPY_JUMPFORWARD: - for (; np != 0; np--) - window_copy_cursor_jump_back(wp); - break; - case WINDOW_COPY_JUMPBACKWARD: - for (; np != 0; np--) - window_copy_cursor_jump(wp); - break; - case WINDOW_COPY_JUMPTOFORWARD: - for (; np != 0; np--) - window_copy_cursor_jump_to_back(wp); - break; - case WINDOW_COPY_JUMPTOBACKWARD: - for (; np != 0; np--) - window_copy_cursor_jump_to(wp); - break; - } - } - if (strcmp(command, "middle-line") == 0) { - data->cx = 0; - data->cy = (screen_size_y(sn) - 1) / 2; - window_copy_update_selection(wp, 1); - redraw = 1; - } - if (strcmp(command, "next-paragraph") == 0) { - for (; np != 0; np--) - window_copy_next_paragraph(wp); - } - if (strcmp(command, "next-space") == 0) { - for (; np != 0; np--) - window_copy_cursor_next_word(wp, " "); - } - if (strcmp(command, "next-space-end") == 0) { - for (; np != 0; np--) - window_copy_cursor_next_word_end(wp, " "); - } - if (strcmp(command, "next-word") == 0) { - ws = options_get_string(s->options, "word-separators"); - for (; np != 0; np--) - window_copy_cursor_next_word(wp, ws); - } - if (strcmp(command, "next-word-end") == 0) { - ws = options_get_string(s->options, "word-separators"); - for (; np != 0; np--) - window_copy_cursor_next_word_end(wp, ws); - } - if (strcmp(command, "other-end") == 0) { - if ((np % 2) != 0) - window_copy_other_end(wp); - } - if (strcmp(command, "page-down") == 0) { - for (; np != 0; np--) { - if (window_copy_pagedown(wp, 0)) { - cancel = 1; - break; + if (*argument != '\0') { + data->jumptype = WINDOW_COPY_JUMPTOFORWARD; + data->jumpchar = *argument; + for (; np != 0; np--) + window_copy_cursor_jump_to(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); } - } - } - if (strcmp(command, "page-up") == 0) { - for (; np != 0; np--) - window_copy_pageup(wp, 0); - } - if (strcmp(command, "previous-paragraph") == 0) { - for (; np != 0; np--) - window_copy_previous_paragraph(wp); - } - if (strcmp(command, "previous-space") == 0) { - for (; np != 0; np--) - window_copy_cursor_previous_word(wp, " "); - } - if (strcmp(command, "previous-word") == 0) { - ws = options_get_string(s->options, "word-separators"); - for (; np != 0; np--) - window_copy_cursor_previous_word(wp, ws); - } - if (strcmp(command, "rectangle-toggle") == 0) { - sn->sel.lineflag = LINE_SEL_NONE; - window_copy_rectangle_toggle(wp); - } - if (strcmp(command, "scroll-down") == 0) { - for (; np != 0; np--) - window_copy_cursor_down(wp, 1); - if (data->scroll_exit && data->oy == 0) - cancel = 1; - } - if (strcmp(command, "scroll-up") == 0) { - for (; np != 0; np--) - window_copy_cursor_up(wp, 1); - } - if (strcmp(command, "search-again") == 0) { - if (data->searchtype == WINDOW_COPY_SEARCHUP) { - for (; np != 0; np--) - window_copy_search_up(wp); - } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { - for (; np != 0; np--) - window_copy_search_down(wp); - } - } - if (strcmp(command, "search-reverse") == 0) { - if (data->searchtype == WINDOW_COPY_SEARCHUP) { - for (; np != 0; np--) - window_copy_search_down(wp); - } else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) { - for (; np != 0; np--) - window_copy_search_up(wp); - } - } - if (strcmp(command, "select-line") == 0) { - sn->sel.lineflag = LINE_SEL_LEFT_RIGHT; - data->rectflag = 0; - window_copy_cursor_start_of_line(wp); - window_copy_start_selection(wp); - for (; np > 1; np--) - window_copy_cursor_down(wp, 0); - window_copy_cursor_end_of_line(wp); - redraw = 1; - } - if (strcmp(command, "select-word") == 0) { - sn->sel.lineflag = LINE_SEL_LEFT_RIGHT; - data->rectflag = 0; - ws = options_get_string(s->options, "word-separators"); - window_copy_cursor_previous_word(wp, ws); - window_copy_start_selection(wp); - window_copy_cursor_next_word_end(wp, ws); - redraw = 1; - } - if (strcmp(command, "start-of-line") == 0) - window_copy_cursor_start_of_line(wp); - if (strcmp(command, "top-line") == 0) { - data->cx = 0; - data->cy = 0; - window_copy_update_selection(wp, 1); - redraw = 1; - } - } else if (args->argc == 2 && *args->argv[1] != '\0') { - argument = args->argv[1]; - if (strcmp(command, "copy-pipe") == 0) { - if (s != NULL) - window_copy_copy_pipe(wp, s, NULL, argument); - } - if (strcmp(command, "copy-pipe-and-cancel") == 0) { - if (s != NULL) { - window_copy_copy_pipe(wp, s, NULL, argument); - cancel = 1; - } - } - if (strcmp(command, "goto-line") == 0) - window_copy_goto_line(wp, argument); - if (strcmp(command, "jump-backward") == 0) { - data->jumptype = WINDOW_COPY_JUMPBACKWARD; - data->jumpchar = *argument; - for (; np != 0; np--) - window_copy_cursor_jump_back(wp); - } - if (strcmp(command, "jump-forward") == 0) { - data->jumptype = WINDOW_COPY_JUMPFORWARD; - data->jumpchar = *argument; - for (; np != 0; np--) - window_copy_cursor_jump(wp); - } - if (strcmp(command, "jump-to-backward") == 0) { - data->jumptype = WINDOW_COPY_JUMPTOBACKWARD; - data->jumpchar = *argument; - for (; np != 0; np--) - window_copy_cursor_jump_to_back(wp); - } - if (strcmp(command, "jump-to-forward") == 0) { - data->jumptype = WINDOW_COPY_JUMPTOFORWARD; - data->jumpchar = *argument; - for (; np != 0; np--) - window_copy_cursor_jump_to(wp); - } - if (strcmp(command, "search-backward") == 0) { - data->searchtype = WINDOW_COPY_SEARCHUP; - free(data->searchstr); - data->searchstr = xstrdup(argument); - for (; np != 0; np--) - window_copy_search_up(wp); - } - if (strcmp(command, "search-forward") == 0) { - data->searchtype = WINDOW_COPY_SEARCHDOWN; - free(data->searchstr); - data->searchstr = xstrdup(argument); - for (; np != 0; np--) - window_copy_search_down(wp); - } - if (strcmp(command, "search-backward-incremental") == 0) { - prefix = *argument++; - if (data->searchx == -1 || data->searchy == -1) { - data->searchx = data->cx; - data->searchy = data->cy; - data->searcho = data->oy; - } else if (data->searchstr != NULL && - strcmp(argument, data->searchstr) != 0) { - data->cx = data->searchx; - data->cy = data->searchy; - data->oy = data->searcho; - redraw = 1; - } - if (*argument == '\0') { - window_copy_clear_marks(wp); - redraw = 1; - } else if (prefix == '=' || prefix == '-') { - data->searchtype = WINDOW_COPY_SEARCHUP; free(data->searchstr); - data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wp)) { - window_copy_clear_marks(wp); - redraw = 1; - } - } else if (prefix == '+') { - data->searchtype = WINDOW_COPY_SEARCHDOWN; + data->searchstr = expanded; + } else { free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wp)) { - window_copy_clear_marks(wp); - redraw = 1; - } } } - if (strcmp(command, "search-forward-incremental") == 0) { - prefix = *argument++; - if (data->searchx == -1 || data->searchy == -1) { - data->searchx = data->cx; - data->searchy = data->cy; - data->searcho = data->oy; - } else if (data->searchstr != NULL && - strcmp(argument, data->searchstr) != 0) { - data->cx = data->searchx; - data->cy = data->searchy; - data->oy = data->searcho; - redraw = 1; - } - if (*argument == '\0') { - window_copy_clear_marks(wp); - redraw = 1; - } else if (prefix == '=' || prefix == '+') { - data->searchtype = WINDOW_COPY_SEARCHDOWN; - free(data->searchstr); - data->searchstr = xstrdup(argument); - if (!window_copy_search_down(wp)) { - window_copy_clear_marks(wp); - redraw = 1; + } + if (data->searchstr != NULL) { + data->searchtype = WINDOW_COPY_SEARCHUP; + for (; np != 0; np--) + window_copy_search_up(wme); + } + 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); } - } else if (prefix == '-') { - data->searchtype = WINDOW_COPY_SEARCHUP; + free(data->searchstr); + data->searchstr = expanded; + } else { free(data->searchstr); data->searchstr = xstrdup(argument); - if (!window_copy_search_up(wp)) { - window_copy_clear_marks(wp); - redraw = 1; - } } } } + if (data->searchstr != NULL) { + data->searchtype = WINDOW_COPY_SEARCHDOWN; + for (; np != 0; np--) + window_copy_search_down(wme); + } + 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 *ss = data->searchstr; + char prefix; + enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING; + + prefix = *argument++; + 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) { + data->cx = data->searchx; + data->cy = data->searchy; + data->oy = data->searcho; + action = WINDOW_COPY_CMD_REDRAW; + } + if (*argument == '\0') { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + switch (prefix) { + case '=': + case '-': + data->searchtype = WINDOW_COPY_SEARCHUP; + free(data->searchstr); + data->searchstr = xstrdup(argument); + if (!window_copy_search_up(wme)) { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + break; + case '+': + data->searchtype = WINDOW_COPY_SEARCHDOWN; + free(data->searchstr); + data->searchstr = xstrdup(argument); + if (!window_copy_search_down(wme)) { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + break; + } + return (action); +} + +static enum window_copy_cmd_action +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 *ss = data->searchstr; + char prefix; + enum window_copy_cmd_action action = WINDOW_COPY_CMD_NOTHING; + + prefix = *argument++; + 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) { + data->cx = data->searchx; + data->cy = data->searchy; + data->oy = data->searcho; + action = WINDOW_COPY_CMD_REDRAW; + } + if (*argument == '\0') { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + switch (prefix) { + case '=': + case '+': + data->searchtype = WINDOW_COPY_SEARCHDOWN; + free(data->searchstr); + data->searchstr = xstrdup(argument); + if (!window_copy_search_down(wme)) { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + break; + case '-': + data->searchtype = WINDOW_COPY_SEARCHUP; + free(data->searchstr); + data->searchstr = xstrdup(argument); + if (!window_copy_search_up(wme)) { + window_copy_clear_marks(wme); + return (WINDOW_COPY_CMD_REDRAW); + } + } + return (action); +} + +static const struct { + const char *command; + int minargs; + int maxargs; + int ismotion; + 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 }, +}; + +static void +window_copy_command(struct window_mode_entry *wme, struct client *c, + struct session *s, struct winlink *wl, struct args *args, + struct mouse_event *m) +{ + struct window_copy_mode_data *data = wme->data; + struct window_copy_cmd_state cs; + enum window_copy_cmd_action action; + const char *command; + u_int i; + int ismotion = 0, keys; + + if (args->argc == 0) + return; + command = args->argv[0]; + + if (m != NULL && m->valid && !MOUSE_WHEEL(m->b)) + window_copy_move_mouse(m); + + cs.wme = wme; + cs.args = args; + cs.m = m; + + cs.c = c; + cs.s = s; + cs.wl = wl; + + 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) + break; + ismotion = window_copy_cmd_table[i].ismotion; + action = window_copy_cmd_table[i].f (&cs); + break; + } + } if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) { - window_copy_clear_marks(wp); - redraw = 1; - data->searchx = data->searchy = -1; + keys = options_get_number(wme->wp->window->options, "mode-keys"); + if (keys != MODEKEY_VI || !ismotion) { + 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; } + wme->prefix = 1; - if (cancel) - window_pane_reset_mode(wp); - else if (redraw) - window_copy_redraw_screen(wp); - wp->modeprefix = 1; + if (action == WINDOW_COPY_CMD_CANCEL) + window_pane_reset_mode(wme->wp); + else if (action == WINDOW_COPY_CMD_REDRAW) + window_copy_redraw_screen(wme); } static void -window_copy_scroll_to(struct window_pane *wp, u_int px, u_int py) +window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; u_int offset, gap; data->cx = px; - gap = gd->sy / 4; - if (py < gd->sy) { - offset = 0; - data->cy = py; - } else if (py > gd->hsize + gd->sy - gap) { - offset = gd->hsize; - data->cy = py - gd->hsize; - } else { - offset = py + gap - gd->sy; - data->cy = py - offset; + if (py >= gd->hsize - data->oy && py < gd->hsize - data->oy + gd->sy) + data->cy = py - (gd->hsize - data->oy); + else { + gap = gd->sy / 4; + if (py < gd->sy) { + offset = 0; + data->cy = py; + } else if (py > gd->hsize + gd->sy - gap) { + offset = gd->hsize; + data->cy = py - gd->hsize; + } else { + offset = py + gap - gd->sy; + data->cy = py - offset; + } + data->oy = gd->hsize - offset; } - data->oy = gd->hsize - offset; - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); } static int @@ -1044,11 +2129,16 @@ window_copy_search_rl(struct grid *gd, } static void -window_copy_move_left(struct screen *s, u_int *fx, u_int *fy) +window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag) { if (*fx == 0) { /* left */ - if (*fy == 0) /* top */ + if (*fy == 0) { /* top */ + if (wrapflag) { + *fx = screen_size_x(s) - 1; + *fy = screen_hsize(s) + screen_size_y(s); + } return; + } *fx = screen_size_x(s) - 1; *fy = *fy - 1; } else @@ -1056,11 +2146,16 @@ window_copy_move_left(struct screen *s, u_int *fx, u_int *fy) } static void -window_copy_move_right(struct screen *s, u_int *fx, u_int *fy) +window_copy_move_right(struct screen *s, u_int *fx, u_int *fy, int wrapflag) { if (*fx == screen_size_x(s) - 1) { /* right */ - if (*fy == screen_hsize(s) + screen_size_y(s)) /* bottom */ + if (*fy == screen_hsize(s) + screen_size_y(s)) { /* bottom */ + if (wrapflag) { + *fx = 0; + *fy = 0; + } return; + } *fx = 0; *fy = *fy + 1; } else @@ -1085,7 +2180,7 @@ window_copy_is_lowercase(const char *ptr) * not found. */ static int -window_copy_search_jump(struct window_pane *wp, struct grid *gd, +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) { @@ -1114,11 +2209,11 @@ window_copy_search_jump(struct window_pane *wp, struct grid *gd, } if (found) { - window_copy_scroll_to(wp, px, i); + window_copy_scroll_to(wme, px, i); return (1); } if (wrap) { - return (window_copy_search_jump(wp, gd, sgd, + return (window_copy_search_jump(wme, gd, sgd, direction ? 0 : gd->sx - 1, direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0, direction)); @@ -1131,9 +2226,10 @@ window_copy_search_jump(struct window_pane *wp, struct grid *gd, * down. */ static int -window_copy_search(struct window_pane *wp, int direction) +window_copy_search(struct window_mode_entry *wme, int direction) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing, ss; struct screen_write_ctx ctx; struct grid *gd = s->grid; @@ -1151,33 +2247,30 @@ window_copy_search(struct window_pane *wp, int direction) screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", data->searchstr); screen_write_stop(&ctx); - if (direction) - window_copy_move_right(s, &fx, &fy); - else - window_copy_move_left(s, &fx, &fy); - window_copy_clear_selection(wp); - wrapflag = options_get_number(wp->window->options, "wrap-search"); cis = window_copy_is_lowercase(data->searchstr); - if (direction) + if (direction) { + 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(wp, gd, ss.grid, fx, fy, endline, cis, + } + found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis, wrapflag, direction); - if (window_copy_search_marks(wp, &ss)) - window_copy_redraw_screen(wp); + if (window_copy_search_marks(wme, &ss)) + window_copy_redraw_screen(wme); screen_free(&ss); return (found); } static int -window_copy_search_marks(struct window_pane *wp, struct screen *ssp) +window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing, ss; struct screen_write_ctx ctx; struct grid *gd = s->grid; @@ -1231,47 +2324,50 @@ window_copy_search_marks(struct window_pane *wp, struct screen *ssp) } static void -window_copy_clear_marks(struct window_pane *wp) +window_copy_clear_marks(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; free(data->searchmark); data->searchmark = NULL; } static int -window_copy_search_up(struct window_pane *wp) +window_copy_search_up(struct window_mode_entry *wme) { - return (window_copy_search(wp, 0)); + return (window_copy_search(wme, 0)); } static int -window_copy_search_down(struct window_pane *wp) +window_copy_search_down(struct window_mode_entry *wme) { - return (window_copy_search(wp, 1)); + return (window_copy_search(wme, 1)); } static void -window_copy_goto_line(struct window_pane *wp, const char *linestr) +window_copy_goto_line(struct window_mode_entry *wme, const char *linestr) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; const char *errstr; - u_int lineno; + int lineno; - lineno = strtonum(linestr, 0, screen_hsize(data->backing), &errstr); + lineno = strtonum(linestr, -1, INT_MAX, &errstr); if (errstr != NULL) return; + if (lineno < 0 || (u_int)lineno > screen_hsize(data->backing)) + lineno = screen_hsize(data->backing); data->oy = lineno; - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); } static void -window_copy_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, - u_int py) +window_copy_write_line(struct window_mode_entry *wme, + struct screen_write_ctx *ctx, u_int py) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; struct grid_cell gc; @@ -1281,7 +2377,7 @@ window_copy_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, style_apply(&gc, oo, "mode-style"); gc.flags |= GRID_FLAG_NOPALETTE; - if (py == 0) { + if (py == 0 && s->rupper < s->rlower) { if (data->searchmark == NULL) { size = xsnprintf(hdr, sizeof hdr, "[%u/%u]", data->oy, screen_hsize(data->backing)); @@ -1299,13 +2395,13 @@ window_copy_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, } if (size > screen_size_x(s)) size = screen_size_x(s); - screen_write_cursormove(ctx, screen_size_x(s) - size, 0); + screen_write_cursormove(ctx, screen_size_x(s) - size, 0, 0); screen_write_puts(ctx, &gc, "%s", hdr); } else size = 0; if (size < screen_size_x(s)) { - screen_write_cursormove(ctx, 0, py); + 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); @@ -1313,25 +2409,25 @@ window_copy_write_line(struct window_pane *wp, struct screen_write_ctx *ctx, 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); + screen_write_cursormove(ctx, screen_size_x(s) - 1, py, 0); screen_write_putc(ctx, &gc, '$'); } } static void -window_copy_write_lines(struct window_pane *wp, struct screen_write_ctx *ctx, - u_int py, u_int ny) +window_copy_write_lines(struct window_mode_entry *wme, + struct screen_write_ctx *ctx, u_int py, u_int ny) { u_int yy; for (yy = py; yy < py + ny; yy++) - window_copy_write_line(wp, ctx, py); + window_copy_write_line(wme, ctx, py); } static void -window_copy_redraw_selection(struct window_pane *wp, u_int old_y) +window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int new_y, start, end; new_y = data->cy; @@ -1342,35 +2438,36 @@ window_copy_redraw_selection(struct window_pane *wp, u_int old_y) start = new_y; end = old_y; } - window_copy_redraw_lines(wp, start, end - start + 1); + window_copy_redraw_lines(wme, start, end - start + 1); } static void -window_copy_redraw_lines(struct window_pane *wp, u_int py, u_int ny) +window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen_write_ctx ctx; u_int i; screen_write_start(&ctx, wp, NULL); for (i = py; i < py + ny; i++) - window_copy_write_line(wp, &ctx, i); - screen_write_cursormove(&ctx, data->cx, data->cy); + window_copy_write_line(wme, &ctx, i); + screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } static void -window_copy_redraw_screen(struct window_pane *wp) +window_copy_redraw_screen(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; - window_copy_redraw_lines(wp, 0, screen_size_y(&data->screen)); + window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen)); } static void -window_copy_synchronize_cursor(struct window_pane *wp) +window_copy_synchronize_cursor(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int xx, yy; xx = data->cx; @@ -1391,9 +2488,10 @@ window_copy_synchronize_cursor(struct window_pane *wp) } static void -window_copy_update_cursor(struct window_pane *wp, u_int cx, u_int cy) +window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct screen_write_ctx ctx; u_int old_cx, old_cy; @@ -1401,21 +2499,20 @@ window_copy_update_cursor(struct window_pane *wp, u_int cx, u_int cy) old_cx = data->cx; old_cy = data->cy; data->cx = cx; data->cy = cy; if (old_cx == screen_size_x(s)) - window_copy_redraw_lines(wp, old_cy, 1); + window_copy_redraw_lines(wme, old_cy, 1); if (data->cx == screen_size_x(s)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_redraw_lines(wme, data->cy, 1); else { screen_write_start(&ctx, wp, NULL); - screen_write_cursormove(&ctx, data->cx, data->cy); + screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } } static void -window_copy_start_selection(struct window_pane *wp) +window_copy_start_selection(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; - struct screen *s = &data->screen; + struct window_copy_mode_data *data = wme->data; data->selx = data->cx; data->sely = screen_hsize(data->backing) + data->cy - data->oy; @@ -1425,14 +2522,14 @@ window_copy_start_selection(struct window_pane *wp) data->cursordrag = CURSORDRAG_ENDSEL; - s->sel.flag = 1; - window_copy_update_selection(wp, 1); + window_copy_set_selection(wme, 1); } static int -window_copy_adjust_selection(struct window_pane *wp, u_int *selx, u_int *sely) +window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx, + u_int *sely) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int sx, sy, ty; int relpos; @@ -1462,29 +2559,38 @@ window_copy_adjust_selection(struct window_pane *wp, u_int *selx, u_int *sely) } static int -window_copy_update_selection(struct window_pane *wp, int may_redraw) +window_copy_update_selection(struct window_mode_entry *wme, int may_redraw) +{ + 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)); +} + +static int +window_copy_set_selection(struct window_mode_entry *wme, int may_redraw) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct options *oo = wp->window->options; struct grid_cell gc; u_int sx, sy, cy, endsx, endsy; int startrelpos, endrelpos; - if (!s->sel.flag && s->sel.lineflag == LINE_SEL_NONE) - return (0); - - window_copy_synchronize_cursor(wp); + window_copy_synchronize_cursor(wme); /* Adjust the selection. */ sx = data->selx; sy = data->sely; - startrelpos = window_copy_adjust_selection(wp, &sx, &sy); + startrelpos = window_copy_adjust_selection(wme, &sx, &sy); /* Adjust the end of selection. */ endsx = data->endselx; endsy = data->endsely; - endrelpos = window_copy_adjust_selection(wp, &endsx, &endsy); + endrelpos = window_copy_adjust_selection(wme, &endsx, &endsy); /* Selection is outside of the current screen */ if (startrelpos == endrelpos && @@ -1496,7 +2602,8 @@ window_copy_update_selection(struct window_pane *wp, int may_redraw) /* Set colours and selection. */ style_apply(&gc, oo, "mode-style"); gc.flags |= GRID_FLAG_NOPALETTE; - screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag, &gc); + screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag, + data->modekeys, &gc); if (data->rectflag && may_redraw) { /* @@ -1507,14 +2614,17 @@ window_copy_update_selection(struct window_pane *wp, int may_redraw) cy = data->cy; if (data->cursordrag == CURSORDRAG_ENDSEL) { if (sy < cy) - window_copy_redraw_lines(wp, sy, cy - sy + 1); + window_copy_redraw_lines(wme, sy, cy - sy + 1); else - window_copy_redraw_lines(wp, cy, sy - cy + 1); + window_copy_redraw_lines(wme, cy, sy - cy + 1); } else { - if (endsy < cy) - window_copy_redraw_lines(wp, endsy, cy - endsy + 1); - else - window_copy_redraw_lines(wp, cy, endsy - cy + 1); + if (endsy < cy) { + window_copy_redraw_lines(wme, endsy, + cy - endsy + 1); + } else { + window_copy_redraw_lines(wme, cy, + endsy - cy + 1); + } } } @@ -1522,17 +2632,18 @@ window_copy_update_selection(struct window_pane *wp, int may_redraw) } static void * -window_copy_get_selection(struct window_pane *wp, size_t *len) +window_copy_get_selection(struct window_mode_entry *wme, size_t *len) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; char *buf; size_t off; u_int i, xx, yy, sx, sy, ex, ey, ey_last; - u_int firstsx, lastex, restex, restsx; + u_int firstsx, lastex, restex, restsx, selx; int keys; - if (!s->sel.flag && s->sel.lineflag == LINE_SEL_NONE) + if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) return (NULL); buf = xmalloc(1); @@ -1557,7 +2668,7 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) } /* Trim ex to end of line. */ - ey_last = window_copy_find_length(wp, ey); + ey_last = window_copy_find_length(wme, ey); if (ex > ey_last) ex = ey_last; @@ -1580,7 +2691,11 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) * Need to ignore the column with the cursor in it, which for * rectangular copy means knowing which side the cursor is on. */ - if (data->selx < data->cx) { + if (data->cursordrag == CURSORDRAG_ENDSEL) + selx = data->selx; + else + selx = data->endselx; + if (selx < data->cx) { /* Selection start is on the left. */ if (keys == MODEKEY_EMACS) { lastex = data->cx; @@ -1590,12 +2705,12 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) lastex = data->cx + 1; restex = data->cx + 1; } - firstsx = data->selx; - restsx = data->selx; + firstsx = selx; + restsx = selx; } else { /* Cursor is on the left. */ - lastex = data->selx + 1; - restex = data->selx + 1; + lastex = selx + 1; + restex = selx + 1; firstsx = data->cx; restsx = data->cx; } @@ -1611,7 +2726,7 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) /* Copy the lines. */ for (i = sy; i <= ey; i++) { - window_copy_copy_line(wp, &buf, &off, i, + window_copy_copy_line(wme, &buf, &off, i, (i == sy ? firstsx : restsx), (i == ey ? lastex : restex)); } @@ -1628,10 +2743,11 @@ window_copy_get_selection(struct window_pane *wp, size_t *len) } static void -window_copy_copy_buffer(struct window_pane *wp, const char *bufname, void *buf, - size_t len) +window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, + void *buf, size_t len) { - struct screen_write_ctx ctx; + struct window_pane *wp = wme->wp; + struct screen_write_ctx ctx; if (options_get_number(global_options, "set-clipboard") != 0) { screen_write_start(&ctx, wp, NULL); @@ -1640,54 +2756,48 @@ window_copy_copy_buffer(struct window_pane *wp, const char *bufname, void *buf, notify_pane("pane-set-clipboard", wp); } - if (paste_set(buf, len, bufname, NULL) != 0) - free(buf); + paste_add(prefix, buf, len); } static void -window_copy_copy_pipe(struct window_pane *wp, struct session *s, - const char *bufname, const char *arg) +window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, + const char *prefix, const char *command) { void *buf; size_t len; struct job *job; - char *expanded; - buf = window_copy_get_selection(wp, &len); + buf = window_copy_get_selection(wme, &len); if (buf == NULL) return; - expanded = format_single(NULL, arg, NULL, s, NULL, wp); - job = job_run(expanded, s, NULL, NULL, NULL, NULL, NULL); - bufferevent_write(job->event, buf, len); - - free(expanded); - window_copy_copy_buffer(wp, bufname, buf, len); + 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_copy_selection(struct window_pane *wp, const char *bufname) +window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix) { - void *buf; + char *buf; size_t len; - buf = window_copy_get_selection(wp, &len); - if (buf == NULL) - return; - - window_copy_copy_buffer(wp, bufname, buf, len); + buf = window_copy_get_selection(wme, &len); + if (buf != NULL) + window_copy_copy_buffer(wme, prefix, buf, len); } static void -window_copy_append_selection(struct window_pane *wp, const char *bufname) +window_copy_append_selection(struct window_mode_entry *wme) { + struct window_pane *wp = wme->wp; char *buf; struct paste_buffer *pb; - const char *bufdata; + const char *bufdata, *bufname = NULL; size_t len, bufsize; struct screen_write_ctx ctx; - buf = window_copy_get_selection(wp, &len); + buf = window_copy_get_selection(wme, &len); if (buf == NULL) return; @@ -1698,10 +2808,7 @@ window_copy_append_selection(struct window_pane *wp, const char *bufname) notify_pane("pane-set-clipboard", wp); } - if (bufname == NULL || *bufname == '\0') - pb = paste_get_top(&bufname); - else - pb = paste_get_name(bufname); + pb = paste_get_top(&bufname); if (pb != NULL) { bufdata = paste_buffer_data(pb, &bufsize); buf = xrealloc(buf, len + bufsize); @@ -1714,10 +2821,10 @@ window_copy_append_selection(struct window_pane *wp, const char *bufname) } static void -window_copy_copy_line(struct window_pane *wp, char **buf, size_t *off, u_int sy, - u_int sx, u_int ex) +window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, + u_int sy, u_int sx, u_int ex) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; struct grid_cell gc; struct grid_line *gl; @@ -1732,7 +2839,7 @@ window_copy_copy_line(struct window_pane *wp, char **buf, size_t *off, u_int sy, * Work out if the line was wrapped at the screen edge and all of it is * on screen. */ - gl = &gd->linedata[sy]; + gl = grid_get_line(gd, sy); if (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx) wrapped = 1; @@ -1740,7 +2847,7 @@ window_copy_copy_line(struct window_pane *wp, char **buf, size_t *off, u_int sy, if (wrapped) xx = gl->cellsize; else - xx = window_copy_find_length(wp, sy); + xx = window_copy_find_length(wme, sy); if (ex > xx) ex = xx; if (sx > xx) @@ -1774,96 +2881,74 @@ window_copy_copy_line(struct window_pane *wp, char **buf, size_t *off, u_int sy, } static void -window_copy_clear_selection(struct window_pane *wp) +window_copy_clear_selection(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int px, py; screen_clear_selection(&data->screen); data->cursordrag = CURSORDRAG_NONE; + data->lineflag = LINE_SEL_NONE; py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if (data->cx > px) - window_copy_update_cursor(wp, px, data->cy); + window_copy_update_cursor(wme, px, data->cy); } static int -window_copy_in_set(struct window_pane *wp, u_int px, u_int py, const char *set) +window_copy_in_set(struct window_mode_entry *wme, u_int px, u_int py, + const char *set) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct grid_cell gc; - const struct utf8_data *ud; grid_get_cell(data->backing->grid, px, py, &gc); - - ud = &gc.data; - if (ud->size != 1 || (gc.flags & GRID_FLAG_PADDING)) - return (0); - if (*ud->data == 0x00 || *ud->data == 0x7f) + if (gc.flags & GRID_FLAG_PADDING) return (0); - return (strchr(set, *ud->data) != NULL); + return (utf8_cstrhas(set, &gc.data)); } static u_int -window_copy_find_length(struct window_pane *wp, u_int py) +window_copy_find_length(struct window_mode_entry *wme, u_int py) { - struct window_copy_mode_data *data = wp->modedata; - struct screen *s = data->backing; - struct grid_cell gc; - u_int px; + struct window_copy_mode_data *data = wme->data; - /* - * If the pane has been resized, its grid can contain old overlong - * lines. grid_peek_cell does not allow accessing cells beyond the - * width of the grid, and screen_write_copy treats them as spaces, so - * ignore them here too. - */ - px = s->grid->linedata[py].cellsize; - if (px > screen_size_x(s)) - px = screen_size_x(s); - while (px > 0) { - grid_get_cell(s->grid, px - 1, py, &gc); - if (gc.data.size != 1 || *gc.data.data != ' ') - break; - px--; - } - return (px); + return (grid_line_length(data->backing->grid, py)); } static void -window_copy_cursor_start_of_line(struct window_pane *wp) +window_copy_cursor_start_of_line(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct screen *s = &data->screen; struct grid *gd = back_s->grid; u_int py; - if (data->cx == 0 && s->sel.lineflag == LINE_SEL_NONE) { + if (data->cx == 0 && data->lineflag == LINE_SEL_NONE) { py = screen_hsize(back_s) + data->cy - data->oy; while (py > 0 && - gd->linedata[py-1].flags & GRID_LINE_WRAPPED) { - window_copy_cursor_up(wp, 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(wp, 0, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, 0, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } static void -window_copy_cursor_back_to_indentation(struct window_pane *wp) +window_copy_cursor_back_to_indentation(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int px, py, xx; struct grid_cell gc; px = 0; py = screen_hsize(data->backing) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); while (px < xx) { grid_get_cell(data->backing->grid, px, py, &gc); @@ -1872,56 +2957,58 @@ window_copy_cursor_back_to_indentation(struct window_pane *wp) px++; } - window_copy_update_cursor(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } static void -window_copy_cursor_end_of_line(struct window_pane *wp) +window_copy_cursor_end_of_line(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; - struct screen *s = &data->screen; struct grid *gd = back_s->grid; + struct grid_line *gl; u_int px, py; py = screen_hsize(back_s) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); - if (data->cx == px && s->sel.lineflag == LINE_SEL_NONE) { - if (data->screen.sel.flag && data->rectflag) + if (data->cx == px && data->lineflag == LINE_SEL_NONE) { + if (data->screen.sel != NULL && data->rectflag) px = screen_size_x(back_s); - if (gd->linedata[py].flags & GRID_LINE_WRAPPED) { - while (py < gd->sy + gd->hsize && - gd->linedata[py].flags & GRID_LINE_WRAPPED) { - window_copy_cursor_down(wp, 0); - py = screen_hsize(back_s) - + data->cy - data->oy; + 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(wp, py); + px = window_copy_find_length(wme, py); } } - window_copy_update_cursor(wp, px, data->cy); + window_copy_update_cursor(wme, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } static void -window_copy_other_end(struct window_pane *wp) +window_copy_other_end(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int selx, sely, cy, yy, hsize; - if (!s->sel.flag && s->sel.lineflag == LINE_SEL_NONE) + if (s->sel == NULL && data->lineflag == LINE_SEL_NONE) return; - if (s->sel.lineflag == LINE_SEL_LEFT_RIGHT) - s->sel.lineflag = LINE_SEL_RIGHT_LEFT; - else if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT) - s->sel.lineflag = LINE_SEL_LEFT_RIGHT; + if (data->lineflag == LINE_SEL_LEFT_RIGHT) + data->lineflag = LINE_SEL_RIGHT_LEFT; + else if (data->lineflag == LINE_SEL_RIGHT_LEFT) + data->lineflag = LINE_SEL_LEFT_RIGHT; switch (data->cursordrag) { case CURSORDRAG_NONE: @@ -1955,14 +3042,14 @@ window_copy_other_end(struct window_pane *wp) } else data->cy = cy + sely - yy; - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); } static void -window_copy_cursor_left(struct window_pane *wp) +window_copy_cursor_left(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int py, cx; struct grid_cell gc; @@ -1975,32 +3062,32 @@ window_copy_cursor_left(struct window_pane *wp) cx--; } if (cx == 0 && py > 0) { - window_copy_cursor_up(wp, 0); - window_copy_cursor_end_of_line(wp); + window_copy_cursor_up(wme, 0); + window_copy_cursor_end_of_line(wme); } else if (cx > 0) { - window_copy_update_cursor(wp, cx - 1, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, cx - 1, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } } static void -window_copy_cursor_right(struct window_pane *wp) +window_copy_cursor_right(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int px, py, yy, cx, cy; struct grid_cell gc; py = screen_hsize(data->backing) + data->cy - data->oy; yy = screen_hsize(data->backing) + screen_size_y(data->backing) - 1; - if (data->screen.sel.flag && data->rectflag) + if (data->screen.sel != NULL && data->rectflag) px = screen_size_x(&data->screen); else - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if (data->cx >= px && py < yy) { - window_copy_cursor_start_of_line(wp); - window_copy_cursor_down(wp, 0); + 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; @@ -2010,123 +3097,123 @@ window_copy_cursor_right(struct window_pane *wp) break; cx++; } - window_copy_update_cursor(wp, cx, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, cx, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } } static void -window_copy_cursor_up(struct window_pane *wp, int scroll_only) +window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; oy = screen_hsize(data->backing) + data->cy - data->oy; - ox = window_copy_find_length(wp, oy); + ox = window_copy_find_length(wme, oy); if (data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } - if (s->sel.lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) - window_copy_other_end(wp); + if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely) + window_copy_other_end(wme); - data->cx = data->lastcx; if (scroll_only || data->cy == 0) { - window_copy_scroll_down(wp, 1); + data->cx = data->lastcx; + window_copy_scroll_down(wme, 1); if (scroll_only) { if (data->cy == screen_size_y(s) - 1) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_redraw_lines(wme, data->cy, 1); else - window_copy_redraw_lines(wp, data->cy, 2); + window_copy_redraw_lines(wme, data->cy, 2); } } else { - window_copy_update_cursor(wp, data->cx, data->cy - 1); - if (window_copy_update_selection(wp, 1)) { + window_copy_update_cursor(wme, data->lastcx, data->cy - 1); + if (window_copy_update_selection(wme, 1)) { if (data->cy == screen_size_y(s) - 1) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_redraw_lines(wme, data->cy, 1); else - window_copy_redraw_lines(wp, data->cy, 2); + window_copy_redraw_lines(wme, data->cy, 2); } } - if (!data->screen.sel.flag || !data->rectflag) { + if (data->screen.sel == NULL || !data->rectflag) { py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wp); + window_copy_cursor_end_of_line(wme); } - if (s->sel.lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wp); - else if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wp); + if (data->lineflag == LINE_SEL_LEFT_RIGHT) + window_copy_cursor_end_of_line(wme); + else if (data->lineflag == LINE_SEL_RIGHT_LEFT) + window_copy_cursor_start_of_line(wme); } static void -window_copy_cursor_down(struct window_pane *wp, int scroll_only) +window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; u_int ox, oy, px, py; oy = screen_hsize(data->backing) + data->cy - data->oy; - ox = window_copy_find_length(wp, oy); + ox = window_copy_find_length(wme, oy); if (data->cx != ox) { data->lastcx = data->cx; data->lastsx = ox; } - if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely) - window_copy_other_end(wp); + if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely) + window_copy_other_end(wme); - data->cx = data->lastcx; if (scroll_only || data->cy == screen_size_y(s) - 1) { - window_copy_scroll_up(wp, 1); + data->cx = data->lastcx; + window_copy_scroll_up(wme, 1); if (scroll_only && data->cy > 0) - window_copy_redraw_lines(wp, data->cy - 1, 2); + window_copy_redraw_lines(wme, data->cy - 1, 2); } else { - window_copy_update_cursor(wp, data->cx, data->cy + 1); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy - 1, 2); + window_copy_update_cursor(wme, data->lastcx, data->cy + 1); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy - 1, 2); } - if (!data->screen.sel.flag || !data->rectflag) { + if (data->screen.sel == NULL || !data->rectflag) { py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if ((data->cx >= data->lastsx && data->cx != px) || data->cx > px) - window_copy_cursor_end_of_line(wp); + window_copy_cursor_end_of_line(wme); } - if (s->sel.lineflag == LINE_SEL_LEFT_RIGHT) - window_copy_cursor_end_of_line(wp); - else if (s->sel.lineflag == LINE_SEL_RIGHT_LEFT) - window_copy_cursor_start_of_line(wp); + if (data->lineflag == LINE_SEL_LEFT_RIGHT) + window_copy_cursor_end_of_line(wme); + else if (data->lineflag == LINE_SEL_RIGHT_LEFT) + window_copy_cursor_start_of_line(wme); } static void -window_copy_cursor_jump(struct window_pane *wp) +window_copy_cursor_jump(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_cell gc; u_int px, py, xx; px = data->cx + 1; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); 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(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + 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++; @@ -2134,9 +3221,9 @@ window_copy_cursor_jump(struct window_pane *wp) } static void -window_copy_cursor_jump_back(struct window_pane *wp) +window_copy_cursor_jump_back(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_cell gc; u_int px, py; @@ -2151,9 +3238,9 @@ window_copy_cursor_jump_back(struct window_pane *wp) 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(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + 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) @@ -2163,24 +3250,24 @@ window_copy_cursor_jump_back(struct window_pane *wp) } static void -window_copy_cursor_jump_to(struct window_pane *wp) +window_copy_cursor_jump_to(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_cell gc; u_int px, py, xx; px = data->cx + 2; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); 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(wp, px - 1, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + 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++; @@ -2188,9 +3275,9 @@ window_copy_cursor_jump_to(struct window_pane *wp) } static void -window_copy_cursor_jump_to_back(struct window_pane *wp) +window_copy_cursor_jump_to_back(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; struct grid_cell gc; u_int px, py; @@ -2208,9 +3295,9 @@ window_copy_cursor_jump_to_back(struct window_pane *wp) 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(wp, px + 1, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + 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) @@ -2220,16 +3307,17 @@ window_copy_cursor_jump_to_back(struct window_pane *wp) } static void -window_copy_cursor_next_word(struct window_pane *wp, const char *separators) +window_copy_cursor_next_word(struct window_mode_entry *wme, + const char *separators) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; struct screen *back_s = data->backing; u_int px, py, xx, yy; int expected = 0; px = data->cx; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; /* @@ -2240,32 +3328,33 @@ window_copy_cursor_next_word(struct window_pane *wp, const char *separators) */ do { while (px > xx || - window_copy_in_set(wp, px, py, separators) == expected) { + 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(wp, 0); + window_copy_cursor_down(wme, 0); px = 0; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); } else px++; } expected = !expected; } while (expected == 1); - window_copy_update_cursor(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } static void -window_copy_cursor_next_word_end(struct window_pane *wp, +window_copy_cursor_next_word_end(struct window_mode_entry *wme, const char *separators) { - struct window_copy_mode_data *data = wp->modedata; + 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; @@ -2273,11 +3362,11 @@ window_copy_cursor_next_word_end(struct window_pane *wp, px = data->cx; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); yy = screen_hsize(back_s) + screen_size_y(back_s) - 1; keys = options_get_number(oo, "mode-keys"); - if (keys == MODEKEY_VI && !window_copy_in_set(wp, px, py, separators)) + if (keys == MODEKEY_VI && !window_copy_in_set(wme, px, py, separators)) px++; /* @@ -2288,16 +3377,16 @@ window_copy_cursor_next_word_end(struct window_pane *wp, */ do { while (px > xx || - window_copy_in_set(wp, px, py, separators) == expected) { + 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(wp, 0); + window_copy_cursor_down(wme, 0); px = 0; py = screen_hsize(back_s) + data->cy - data->oy; - xx = window_copy_find_length(wp, py); + xx = window_copy_find_length(wme, py); } else px++; } @@ -2307,54 +3396,62 @@ window_copy_cursor_next_word_end(struct window_pane *wp, if (keys == MODEKEY_VI && px != 0) px--; - window_copy_update_cursor(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } /* Move to the previous place where a word begins. */ static void -window_copy_cursor_previous_word(struct window_pane *wp, - const char *separators) +window_copy_cursor_previous_word(struct window_mode_entry *wme, + const char *separators, int already) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int px, py; px = data->cx; py = screen_hsize(data->backing) + data->cy - data->oy; /* Move back to the previous word character. */ - for (;;) { - if (px > 0) { - px--; - if (!window_copy_in_set(wp, 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(wp, 0); - - py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + 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(wp, px - 1, py, separators)) + while (px > 0 && !window_copy_in_set(wme, px - 1, py, separators)) px--; out: - window_copy_update_cursor(wp, px, data->cy); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_lines(wp, data->cy, 1); + window_copy_update_cursor(wme, px, data->cy); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_lines(wme, data->cy, 1); } static void -window_copy_scroll_up(struct window_pane *wp, u_int ny) +window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct screen_write_ctx ctx; @@ -2364,27 +3461,28 @@ window_copy_scroll_up(struct window_pane *wp, u_int ny) return; data->oy -= ny; - window_copy_update_selection(wp, 0); + window_copy_update_selection(wme, 0); screen_write_start(&ctx, wp, NULL); - screen_write_cursormove(&ctx, 0, 0); + screen_write_cursormove(&ctx, 0, 0, 0); screen_write_deleteline(&ctx, ny, 8); - window_copy_write_lines(wp, &ctx, screen_size_y(s) - ny, ny); - window_copy_write_line(wp, &ctx, 0); + window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny); + window_copy_write_line(wme, &ctx, 0); if (screen_size_y(s) > 1) - window_copy_write_line(wp, &ctx, 1); + window_copy_write_line(wme, &ctx, 1); if (screen_size_y(s) > 3) - window_copy_write_line(wp, &ctx, screen_size_y(s) - 2); - if (s->sel.flag && screen_size_y(s) > ny) - window_copy_write_line(wp, &ctx, screen_size_y(s) - ny - 1); - screen_write_cursormove(&ctx, data->cx, data->cy); + window_copy_write_line(wme, &ctx, screen_size_y(s) - 2); + if (s->sel != NULL && screen_size_y(s) > ny) + window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1); + screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } static void -window_copy_scroll_down(struct window_pane *wp, u_int ny) +window_copy_scroll_down(struct window_mode_entry *wme, u_int ny) { - struct window_copy_mode_data *data = wp->modedata; + struct window_pane *wp = wme->wp; + struct window_copy_mode_data *data = wme->data; struct screen *s = &data->screen; struct screen_write_ctx ctx; @@ -2397,107 +3495,155 @@ window_copy_scroll_down(struct window_pane *wp, u_int ny) return; data->oy += ny; - window_copy_update_selection(wp, 0); + window_copy_update_selection(wme, 0); screen_write_start(&ctx, wp, NULL); - screen_write_cursormove(&ctx, 0, 0); + screen_write_cursormove(&ctx, 0, 0, 0); screen_write_insertline(&ctx, ny, 8); - window_copy_write_lines(wp, &ctx, 0, ny); - if (s->sel.flag && screen_size_y(s) > ny) - window_copy_write_line(wp, &ctx, ny); + window_copy_write_lines(wme, &ctx, 0, ny); + if (s->sel != NULL && screen_size_y(s) > ny) + window_copy_write_line(wme, &ctx, ny); else if (ny == 1) /* nuke position */ - window_copy_write_line(wp, &ctx, 1); - screen_write_cursormove(&ctx, data->cx, data->cy); + window_copy_write_line(wme, &ctx, 1); + screen_write_cursormove(&ctx, data->cx, data->cy, 0); screen_write_stop(&ctx); } -void -window_copy_add_formats(struct window_pane *wp, struct format_tree *ft) -{ - struct window_copy_mode_data *data = wp->modedata; - struct screen *s = &data->screen; - - if (wp->mode != &window_copy_mode) - return; - - format_add(ft, "selection_present", "%d", s->sel.flag); - format_add(ft, "scroll_position", "%d", data->oy); -} - static void -window_copy_rectangle_toggle(struct window_pane *wp) +window_copy_rectangle_toggle(struct window_mode_entry *wme) { - struct window_copy_mode_data *data = wp->modedata; + struct window_copy_mode_data *data = wme->data; u_int px, py; data->rectflag = !data->rectflag; py = screen_hsize(data->backing) + data->cy - data->oy; - px = window_copy_find_length(wp, py); + px = window_copy_find_length(wme, py); if (data->cx > px) - window_copy_update_cursor(wp, px, data->cy); + window_copy_update_cursor(wme, px, data->cy); - window_copy_update_selection(wp, 1); - window_copy_redraw_screen(wp); + window_copy_update_selection(wme, 1); + window_copy_redraw_screen(wme); } static void window_copy_move_mouse(struct mouse_event *m) { - struct window_pane *wp; - u_int x, y; + struct window_pane *wp; + struct window_mode_entry *wme; + u_int x, y; wp = cmd_mouse_pane(m, NULL, NULL); - if (wp == NULL || wp->mode != &window_copy_mode) + if (wp == NULL) + return; + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL) + return; + if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode) return; if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) return; - window_copy_update_cursor(wp, x, y); + window_copy_update_cursor(wme, x, y); } void window_copy_start_drag(struct client *c, struct mouse_event *m) { - struct window_pane *wp; - u_int x, y; + struct window_pane *wp; + struct window_mode_entry *wme; + u_int x, y; if (c == NULL) return; wp = cmd_mouse_pane(m, NULL, NULL); - if (wp == NULL || wp->mode != &window_copy_mode) + if (wp == NULL) + return; + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL) + return; + if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode) return; if (cmd_mouse_at(wp, m, &x, &y, 1) != 0) return; c->tty.mouse_drag_update = window_copy_drag_update; - c->tty.mouse_drag_release = NULL; /* will fire MouseDragEnd key */ + c->tty.mouse_drag_release = window_copy_drag_release; - window_copy_update_cursor(wp, x, y); - window_copy_start_selection(wp); - window_copy_redraw_screen(wp); + window_copy_update_cursor(wme, x, y); + window_copy_start_selection(wme); + window_copy_redraw_screen(wme); + + window_copy_drag_update(c, m); } static void -window_copy_drag_update(__unused struct client *c, struct mouse_event *m) +window_copy_drag_update(struct client *c, struct mouse_event *m) { struct window_pane *wp; + struct window_mode_entry *wme; struct window_copy_mode_data *data; - u_int x, y, old_cy; + u_int x, y, old_cx, old_cy; + struct timeval tv = { + .tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME + }; + + if (c == NULL) + return; wp = cmd_mouse_pane(m, NULL, NULL); - if (wp == NULL || wp->mode != &window_copy_mode) + if (wp == NULL) + return; + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL) + return; + if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode) return; - data = wp->modedata; + + data = wme->data; + evtimer_del(&data->dragtimer); if (cmd_mouse_at(wp, m, &x, &y, 0) != 0) return; + old_cx = data->cx; old_cy = data->cy; - window_copy_update_cursor(wp, x, y); - if (window_copy_update_selection(wp, 1)) - window_copy_redraw_selection(wp, old_cy); + window_copy_update_cursor(wme, x, y); + if (window_copy_update_selection(wme, 1)) + window_copy_redraw_selection(wme, old_cy); + if (old_cy != data->cy || old_cx == data->cx) { + if (y == 0) { + evtimer_add(&data->dragtimer, &tv); + window_copy_cursor_up(wme, 1); + } else if (y == screen_size_y(&data->screen) - 1) { + evtimer_add(&data->dragtimer, &tv); + window_copy_cursor_down(wme, 1); + } + } +} + +static void +window_copy_drag_release(struct client *c, struct mouse_event *m) +{ + struct window_pane *wp; + struct window_mode_entry *wme; + struct window_copy_mode_data *data; + + if (c == NULL) + return; + + wp = cmd_mouse_pane(m, NULL, NULL); + if (wp == NULL) + return; + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL) + return; + if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode) + return; + + data = wme->data; + evtimer_del(&data->dragtimer); } diff --git a/window-tree.c b/window-tree.c index a3ab6356ec..4f4cbaabe5 100644 --- a/window-tree.c +++ b/window-tree.c @@ -18,18 +18,20 @@ #include +#include #include #include #include "tmux.h" -static struct screen *window_tree_init(struct window_pane *, +static struct screen *window_tree_init(struct window_mode_entry *, struct cmd_find_state *, struct args *); -static void window_tree_free(struct window_pane *); -static void window_tree_resize(struct window_pane *, u_int, u_int); -static void window_tree_key(struct window_pane *, - struct client *, struct session *, key_code, - struct mouse_event *); +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_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); #define WINDOW_TREE_DEFAULT_COMMAND "switch-client -t '%%'" @@ -43,14 +45,33 @@ static void window_tree_key(struct window_pane *, "#{?#{==:#{window_panes},1}, \"#{pane_title}\",}" \ "," \ "#{session_windows} windows" \ - "#{?session_grouped, (group ,}" \ - "#{session_group}#{?session_grouped,),}" \ + "#{?session_grouped, " \ + "(group #{session_group}: " \ + "#{session_group_list})," \ + "}" \ "#{?session_attached, (attached),}" \ "}" \ "}" +static const struct menu_item window_tree_menu_items[] = { + { "Select", 'E', NULL }, + { "Expand", 'R', NULL }, + { "", KEYC_NONE, NULL }, + { "Tag", 't', NULL }, + { "Tag All", '\024', NULL }, + { "Tag None", 'T', NULL }, + { "", KEYC_NONE, NULL }, + { "Kill", 'x', NULL }, + { "Kill Tagged", 'X', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + const struct window_mode window_tree_mode = { .name = "tree-mode", + .default_format = WINDOW_TREE_DEFAULT_FORMAT, .init = window_tree_init, .free = window_tree_free, @@ -68,6 +89,7 @@ static const char *window_tree_sort_list[] = { "name", "time" }; +static struct mode_tree_sort_criteria *window_tree_sort; enum window_tree_type { WINDOW_TREE_NONE, @@ -91,17 +113,23 @@ struct window_tree_modedata { struct mode_tree_data *data; char *format; char *command; + int squash_groups; struct window_tree_itemdata **item_list; u_int item_size; - struct client *client; const char *entered; struct cmd_find_state fs; enum window_tree_type type; int offset; + + int left; + int right; + u_int start; + u_int end; + u_int each; }; static void @@ -157,62 +185,92 @@ window_tree_free_item(struct window_tree_itemdata *item) } static int -window_tree_cmp_session_name(const void *a0, const void *b0) +window_tree_cmp_session(const void *a0, const void *b0) { - const struct session *const *a = a0; - const struct session *const *b = b0; + const struct session *const *a = a0; + const struct session *const *b = b0; + const struct session *sa = *a; + const struct session *sb = *b; + int result = 0; - return (strcmp((*a)->name, (*b)->name)); -} - -static int -window_tree_cmp_session_time(const void *a0, const void *b0) -{ - const struct session *const *a = a0; - const struct session *const *b = b0; + switch (window_tree_sort->field) { + case WINDOW_TREE_BY_INDEX: + result = sa->id - sb->id; + break; + case WINDOW_TREE_BY_TIME: + if (timercmp(&sa->activity_time, &sb->activity_time, >)) { + result = -1; + break; + } + if (timercmp(&sa->activity_time, &sb->activity_time, <)) { + result = 1; + break; + } + /* FALLTHROUGH */ + case WINDOW_TREE_BY_NAME: + result = strcmp(sa->name, sb->name); + break; + } - if (timercmp(&(*a)->activity_time, &(*b)->activity_time, >)) - return (-1); - if (timercmp(&(*a)->activity_time, &(*b)->activity_time, <)) - return (1); - return (strcmp((*a)->name, (*b)->name)); + if (window_tree_sort->reversed) + result = -result; + return (result); } static int -window_tree_cmp_window_name(const void *a0, const void *b0) +window_tree_cmp_window(const void *a0, const void *b0) { - const struct winlink *const *a = a0; - const struct winlink *const *b = b0; - - return (strcmp((*a)->window->name, (*b)->window->name)); -} + const struct winlink *const *a = a0; + const struct winlink *const *b = b0; + const struct winlink *wla = *a; + const struct winlink *wlb = *b; + struct window *wa = wla->window; + struct window *wb = wlb->window; + int result = 0; + + switch (window_tree_sort->field) { + case WINDOW_TREE_BY_INDEX: + result = wla->idx - wlb->idx; + break; + case WINDOW_TREE_BY_TIME: + if (timercmp(&wa->activity_time, &wb->activity_time, >)) { + result = -1; + break; + } + if (timercmp(&wa->activity_time, &wb->activity_time, <)) { + result = 1; + break; + } + /* FALLTHROUGH */ + case WINDOW_TREE_BY_NAME: + result = strcmp(wa->name, wb->name); + break; + } -static int -window_tree_cmp_window_time(const void *a0, const void *b0) -{ - const struct winlink *const *a = a0; - const struct winlink *const *b = b0; - - if (timercmp(&(*a)->window->activity_time, - &(*b)->window->activity_time, >)) - return (-1); - if (timercmp(&(*a)->window->activity_time, - &(*b)->window->activity_time, <)) - return (1); - return (strcmp((*a)->window->name, (*b)->window->name)); + if (window_tree_sort->reversed) + result = -result; + return (result); } static int -window_tree_cmp_pane_time(const void *a0, const void *b0) +window_tree_cmp_pane(const void *a0, const void *b0) { - const struct window_pane *const *a = a0; - const struct window_pane *const *b = b0; + const struct window_pane *const *a = a0; + const struct window_pane *const *b = b0; + int result; - if ((*a)->active_point < (*b)->active_point) - return (-1); - if ((*a)->active_point > (*b)->active_point) - return (1); - return (0); + if (window_tree_sort->field == WINDOW_TREE_BY_TIME) + result = (*a)->active_point - (*b)->active_point; + else { + /* + * Panes don't have names, so use number order for any other + * sort field. + */ + result = (*a)->id - (*b)->id; + } + if (window_tree_sort->reversed) + result = -result; + return (result); } static void @@ -258,8 +316,9 @@ window_tree_filter_pane(struct session *s, struct winlink *wl, } static int -window_tree_build_window(struct session *s, struct winlink *wl, void* modedata, - u_int sort_type, struct mode_tree_item *parent, const char *filter) +window_tree_build_window(struct session *s, struct winlink *wl, + void* modedata, struct mode_tree_sort_criteria *sort_crit, + struct mode_tree_item *parent, const char *filter) { struct window_tree_modedata *data = modedata; struct window_tree_itemdata *item; @@ -288,7 +347,8 @@ window_tree_build_window(struct session *s, struct winlink *wl, void* modedata, free(text); free(name); - wp = TAILQ_FIRST(&wl->window->panes); + if ((wp = TAILQ_FIRST(&wl->window->panes)) == NULL) + goto empty; if (TAILQ_NEXT(wp, entry) == NULL) { if (!window_tree_filter_pane(s, wl, wp, filter)) goto empty; @@ -307,16 +367,8 @@ window_tree_build_window(struct session *s, struct winlink *wl, void* modedata, if (n == 0) goto empty; - switch (sort_type) { - case WINDOW_TREE_BY_INDEX: - break; - case WINDOW_TREE_BY_NAME: - /* Panes don't have names, so leave in number order. */ - break; - case WINDOW_TREE_BY_TIME: - qsort(l, n, sizeof *l, window_tree_cmp_pane_time); - break; - } + window_tree_sort = sort_crit; + qsort(l, n, sizeof *l, window_tree_cmp_pane); for (i = 0; i < n; i++) window_tree_build_pane(s, wl, l[i], modedata, mti); @@ -332,7 +384,7 @@ window_tree_build_window(struct session *s, struct winlink *wl, void* modedata, static void window_tree_build_session(struct session *s, void* modedata, - u_int sort_type, const char *filter) + struct mode_tree_sort_criteria *sort_crit, const char *filter) { struct window_tree_modedata *data = modedata; struct window_tree_itemdata *item; @@ -364,20 +416,12 @@ window_tree_build_session(struct session *s, void* modedata, l = xreallocarray(l, n + 1, sizeof *l); l[n++] = wl; } - switch (sort_type) { - case WINDOW_TREE_BY_INDEX: - break; - case WINDOW_TREE_BY_NAME: - qsort(l, n, sizeof *l, window_tree_cmp_window_name); - break; - case WINDOW_TREE_BY_TIME: - qsort(l, n, sizeof *l, window_tree_cmp_window_time); - break; - } + window_tree_sort = sort_crit; + qsort(l, n, sizeof *l, window_tree_cmp_window); empty = 0; for (i = 0; i < n; i++) { - if (!window_tree_build_window(s, l[i], modedata, sort_type, mti, + if (!window_tree_build_window(s, l[i], modedata, sort_crit, mti, filter)) empty++; } @@ -390,13 +434,16 @@ window_tree_build_session(struct session *s, void* modedata, } static void -window_tree_build(void *modedata, u_int sort_type, uint64_t *tag, - const char *filter) +window_tree_build(void *modedata, struct mode_tree_sort_criteria *sort_crit, + uint64_t *tag, const char *filter) { struct window_tree_modedata *data = modedata; struct session *s, **l; + struct session_group *sg, *current; u_int n, i; + current = session_group_contains(data->fs.s); + for (i = 0; i < data->item_size; i++) window_tree_free_item(data->item_list[i]); free(data->item_list); @@ -406,22 +453,20 @@ window_tree_build(void *modedata, u_int sort_type, uint64_t *tag, l = NULL; n = 0; RB_FOREACH(s, sessions, &sessions) { + if (data->squash_groups && + (sg = session_group_contains(s)) != NULL) { + if ((sg == current && s != data->fs.s) || + (sg != current && s != TAILQ_FIRST(&sg->sessions))) + continue; + } l = xreallocarray(l, n + 1, sizeof *l); l[n++] = s; } - switch (sort_type) { - case WINDOW_TREE_BY_INDEX: - break; - case WINDOW_TREE_BY_NAME: - qsort(l, n, sizeof *l, window_tree_cmp_session_name); - break; - case WINDOW_TREE_BY_TIME: - qsort(l, n, sizeof *l, window_tree_cmp_session_time); - break; - } + window_tree_sort = sort_crit; + qsort(l, n, sizeof *l, window_tree_cmp_session); for (i = 0; i < n; i++) - window_tree_build_session(l[i], modedata, sort_type, filter); + window_tree_build_session(l[i], modedata, sort_crit, filter); free(l); switch (data->type) { @@ -434,11 +479,35 @@ window_tree_build(void *modedata, u_int sort_type, uint64_t *tag, *tag = (uint64_t)data->fs.wl; break; case WINDOW_TREE_PANE: - *tag = (uint64_t)data->fs.wp; + if (window_count_panes(data->fs.wl->window) == 1) + *tag = (uint64_t)data->fs.wl; + else + *tag = (uint64_t)data->fs.wp; break; } } +static void +window_tree_draw_label(struct screen_write_ctx *ctx, u_int px, u_int py, + u_int sx, u_int sy, const struct grid_cell *gc, const char *label) +{ + size_t len; + u_int ox, oy; + + len = strlen(label); + if (sx == 0 || sy == 1 || len > sx) + return; + ox = (sx - len + 1) / 2; + oy = (sy + 1) / 2; + + if (ox > 1 && ox + len < sx - 1 && sy >= 3) { + screen_write_cursormove(ctx, px + ox - 1, py + oy - 1, 0); + screen_write_box(ctx, len + 2, 3); + } + screen_write_cursormove(ctx, px + ox, py + oy, 0); + screen_write_puts(ctx, gc, "%s", label); +} + static void window_tree_draw_session(struct window_tree_modedata *data, struct session *s, struct screen_write_ctx *ctx, u_int sx, u_int sy) @@ -446,12 +515,12 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, struct options *oo = s->options; struct winlink *wl; struct window *w; + u_int cx = ctx->s->cx, cy = ctx->s->cy; u_int loop, total, visible, each, width, offset; u_int current, start, end, remaining, i; struct grid_cell gc; int colour, active_colour, left, right; char *label; - size_t len; total = winlink_count(&s->windows); @@ -509,17 +578,25 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, return; if (left) { - screen_write_cursormove(ctx, 2, 0); + data->left = cx + 2; + screen_write_cursormove(ctx, cx + 2, cy, 0); screen_write_vline(ctx, sy, 0, 0); - screen_write_cursormove(ctx, 0, sy / 2); + screen_write_cursormove(ctx, cx, cy + sy / 2, 0); screen_write_puts(ctx, &grid_default_cell, "<"); - } + } else + data->left = -1; if (right) { - screen_write_cursormove(ctx, sx - 3, 0); + data->right = cx + sx - 3; + screen_write_cursormove(ctx, cx + sx - 3, cy, 0); screen_write_vline(ctx, sy, 0, 0); - screen_write_cursormove(ctx, sx - 1, sy / 2); + screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); screen_write_puts(ctx, &grid_default_cell, ">"); - } + } else + data->right = -1; + + data->start = start; + data->end = end; + data->each = each; i = loop = 0; RB_FOREACH(wl, winlinks, &s->windows) { @@ -545,20 +622,18 @@ window_tree_draw_session(struct window_tree_modedata *data, struct session *s, else width = each - 1; - screen_write_cursormove(ctx, offset, 0); + screen_write_cursormove(ctx, cx + offset, cy, 0); screen_write_preview(ctx, &w->active->base, width, sy); xasprintf(&label, " %u:%s ", wl->idx, w->name); if (strlen(label) > width) xasprintf(&label, " %u ", wl->idx); - len = strlen(label) / 2; - screen_write_cursormove(ctx, offset + (each / 2) - len, sy / 2); - if (len < width) - screen_write_puts(ctx, &gc, "%s", label); + window_tree_draw_label(ctx, cx + offset, cy, width, sy, &gc, + label); free(label); if (loop != end - 1) { - screen_write_cursormove(ctx, offset + width, 0); + screen_write_cursormove(ctx, cx + offset + width, cy, 0); screen_write_vline(ctx, sy, 0, 0); } loop++; @@ -573,12 +648,12 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, { struct options *oo = s->options; struct window_pane *wp; + u_int cx = ctx->s->cx, cy = ctx->s->cy; u_int loop, total, visible, each, width, offset; u_int current, start, end, remaining, i; struct grid_cell gc; - int colour, active_colour, left, right; + int colour, active_colour, left, right, pane_idx; char *label; - size_t len; total = window_count_panes(w); @@ -636,17 +711,25 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, return; if (left) { - screen_write_cursormove(ctx, 2, 0); + data->left = cx + 2; + screen_write_cursormove(ctx, cx + 2, cy, 0); screen_write_vline(ctx, sy, 0, 0); - screen_write_cursormove(ctx, 0, sy / 2); + screen_write_cursormove(ctx, cx, cy + sy / 2, 0); screen_write_puts(ctx, &grid_default_cell, "<"); - } + } else + data->left = -1; if (right) { - screen_write_cursormove(ctx, sx - 3, 0); + data->right = cx + sx - 3; + screen_write_cursormove(ctx, cx + sx - 3, cy, 0); screen_write_vline(ctx, sy, 0, 0); - screen_write_cursormove(ctx, sx - 1, sy / 2); + screen_write_cursormove(ctx, cx + sx - 1, cy + sy / 2, 0); screen_write_puts(ctx, &grid_default_cell, ">"); - } + } else + data->right = -1; + + data->start = start; + data->end = end; + data->each = each; i = loop = 0; TAILQ_FOREACH(wp, &w->panes, entry) { @@ -671,18 +754,18 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, else width = each - 1; - screen_write_cursormove(ctx, offset, 0); + screen_write_cursormove(ctx, cx + offset, cy, 0); screen_write_preview(ctx, &wp->base, width, sy); - xasprintf(&label, " %u ", loop); - len = strlen(label) / 2; - screen_write_cursormove(ctx, offset + (each / 2) - len, sy / 2); - if (len < width) - screen_write_puts(ctx, &gc, "%s", label); + if (window_pane_index(wp, &pane_idx) != 0) + pane_idx = loop; + xasprintf(&label, " %u ", pane_idx); + window_tree_draw_label(ctx, cx + offset, cy, each, sy, &gc, + label); free(label); if (loop != end - 1) { - screen_write_cursormove(ctx, offset + width, 0); + screen_write_cursormove(ctx, cx + offset + width, cy, 0); screen_write_vline(ctx, sy, 0, 0); } loop++; @@ -691,39 +774,32 @@ window_tree_draw_window(struct window_tree_modedata *data, struct session *s, } } -static struct screen * -window_tree_draw(void *modedata, void *itemdata, u_int sx, u_int sy) +static void +window_tree_draw(void *modedata, void *itemdata, struct screen_write_ctx *ctx, + u_int sx, u_int sy) { struct window_tree_itemdata *item = itemdata; struct session *sp; struct winlink *wlp; struct window_pane *wp; - static struct screen s; - struct screen_write_ctx ctx; window_tree_pull_item(item, &sp, &wlp, &wp); if (wp == NULL) - return (NULL); - - screen_init(&s, sx, sy, 0); - screen_write_start(&ctx, NULL, &s); + return; switch (item->type) { case WINDOW_TREE_NONE: - return (0); + break; case WINDOW_TREE_SESSION: - window_tree_draw_session(modedata, sp, &ctx, sx, sy); + window_tree_draw_session(modedata, sp, ctx, sx, sy); break; case WINDOW_TREE_WINDOW: - window_tree_draw_window(modedata, sp, wlp->window, &ctx, sx, sy); + window_tree_draw_window(modedata, sp, wlp->window, ctx, sx, sy); break; case WINDOW_TREE_PANE: - screen_write_preview(&ctx, &wp->base, sx, sy); + screen_write_preview(ctx, &wp->base, sx, sy); break; } - - screen_write_stop(&ctx); - return (&s); } static int @@ -733,7 +809,8 @@ window_tree_search(__unused void *modedata, void *itemdata, const char *ss) struct session *s; struct winlink *wl; struct window_pane *wp; - const char *cmd; + char *cmd; + int retval; window_tree_pull_item(item, &s, &wl, &wp); @@ -754,19 +831,37 @@ window_tree_search(__unused void *modedata, void *itemdata, const char *ss) cmd = osdep_get_name(wp->fd, wp->tty); if (cmd == NULL || *cmd == '\0') return (0); - return (strstr(cmd, ss) != NULL); + retval = (strstr(cmd, ss) != NULL); + free(cmd); + return retval; } return (0); } +static void +window_tree_menu(void *modedata, struct client *c, key_code key) +{ + struct window_tree_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_tree_key(wme, c, NULL, NULL, key, NULL); +} + static struct screen * -window_tree_init(struct window_pane *wp, struct cmd_find_state *fs, +window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, struct args *args) { + struct window_pane *wp = wme->wp; struct window_tree_modedata *data; struct screen *s; - wp->modedata = data = xcalloc(1, sizeof *data); + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + data->references = 1; if (args_has(args, 's')) data->type = WINDOW_TREE_SESSION; @@ -776,9 +871,6 @@ window_tree_init(struct window_pane *wp, struct cmd_find_state *fs, data->type = WINDOW_TREE_PANE; memcpy(&data->fs, fs, sizeof data->fs); - data->wp = wp; - data->references = 1; - if (args == NULL || !args_has(args, 'F')) data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT); else @@ -787,10 +879,13 @@ window_tree_init(struct window_pane *wp, struct cmd_find_state *fs, data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND); else data->command = xstrdup(args->argv[0]); + data->squash_groups = !args_has(args, 'G'); data->data = mode_tree_start(wp, args, window_tree_build, - window_tree_draw, window_tree_search, data, window_tree_sort_list, + window_tree_draw, window_tree_search, window_tree_menu, 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); mode_tree_draw(data->data); @@ -808,8 +903,6 @@ window_tree_destroy(struct window_tree_modedata *data) if (--data->references != 0) return; - mode_tree_free(data->data); - for (i = 0; i < data->item_size; i++) window_tree_free_item(data->item_list[i]); free(data->item_list); @@ -821,21 +914,22 @@ window_tree_destroy(struct window_tree_modedata *data) } static void -window_tree_free(struct window_pane *wp) +window_tree_free(struct window_mode_entry *wme) { - struct window_tree_modedata *data = wp->modedata; + struct window_tree_modedata *data = wme->data; if (data == NULL) return; data->dead = 1; + mode_tree_free(data->data); window_tree_destroy(data); } static void -window_tree_resize(struct window_pane *wp, u_int sx, u_int sy) +window_tree_resize(struct window_mode_entry *wme, u_int sx, u_int sy) { - struct window_tree_modedata *data = wp->modedata; + struct window_tree_modedata *data = wme->data; mode_tree_resize(data->data, sx, sy); } @@ -879,7 +973,8 @@ window_tree_get_target(struct window_tree_itemdata *item, } static void -window_tree_command_each(void* modedata, void* itemdata, __unused key_code key) +window_tree_command_each(void* modedata, void* itemdata, struct client *c, + __unused key_code key) { struct window_tree_modedata *data = modedata; struct window_tree_itemdata *item = itemdata; @@ -888,7 +983,7 @@ window_tree_command_each(void* modedata, void* itemdata, __unused key_code key) name = window_tree_get_target(item, &fs); if (name != NULL) - mode_tree_run_command(data->client, &fs, data->entered, name); + mode_tree_run_command(c, &fs, data->entered, name); free(name); } @@ -912,16 +1007,12 @@ window_tree_command_callback(struct client *c, void *modedata, const char *s, { struct window_tree_modedata *data = modedata; - if (data->dead) + if (s == NULL || *s == '\0' || data->dead) return (0); - data->client = c; data->entered = s; - - mode_tree_each_tagged(data->data, window_tree_command_each, KEYC_NONE, - 1); - - data->client = NULL; + mode_tree_each_tagged(data->data, window_tree_command_each, c, + KEYC_NONE, 1); data->entered = NULL; data->references++; @@ -939,20 +1030,161 @@ window_tree_command_free(void *modedata) } static void -window_tree_key(struct window_pane *wp, struct client *c, - __unused struct session *s, key_code key, struct mouse_event *m) +window_tree_kill_each(__unused void* modedata, void* itemdata, + __unused struct client *c, __unused key_code key) { - struct window_tree_modedata *data = wp->modedata; - struct window_tree_itemdata *item; - char *command, *name, *prompt; + struct window_tree_itemdata *item = itemdata; + struct session *s; + struct winlink *wl; + struct window_pane *wp; + + window_tree_pull_item(item, &s, &wl, &wp); + + switch (item->type) { + case WINDOW_TREE_NONE: + break; + case WINDOW_TREE_SESSION: + if (s != NULL) { + server_destroy_session(s); + session_destroy(s, 1, __func__); + } + break; + case WINDOW_TREE_WINDOW: + if (wl != NULL) + server_kill_window(wl->window); + break; + case WINDOW_TREE_PANE: + if (wp != NULL) + server_kill_pane(wp); + break; + } +} + +static int +window_tree_kill_current_callback(struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_tree_modedata *data = modedata; + struct mode_tree_data *mtd = data->data; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + window_tree_kill_each(data, mode_tree_get_current(mtd), c, KEYC_NONE); + + data->references++; + cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); + + return (0); +} + +static int +window_tree_kill_tagged_callback(struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_tree_modedata *data = modedata; + struct mode_tree_data *mtd = data->data; + + 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(mtd, window_tree_kill_each, c, KEYC_NONE, 1); + + data->references++; + cmdq_append(c, cmdq_get_callback(window_tree_command_done, data)); + + return (0); +} + +static key_code +window_tree_mouse(struct window_tree_modedata *data, key_code key, u_int x, + struct window_tree_itemdata *item) +{ + struct session *s; + struct winlink *wl; + struct window_pane *wp; + u_int loop; + + if (key != KEYC_MOUSEDOWN1_PANE) + return (KEYC_NONE); + + if (data->left != -1 && x <= (u_int)data->left) + return ('<'); + if (data->right != -1 && x >= (u_int)data->right) + return ('>'); + + if (data->left != -1) + x -= data->left; + else if (x != 0) + x--; + if (x == 0 || data->end == 0) + x = 0; + else { + x = x / data->each; + if (data->start + x >= data->end) + x = data->end - 1; + } + + window_tree_pull_item(item, &s, &wl, &wp); + if (item->type == WINDOW_TREE_SESSION) { + if (s == NULL) + return (KEYC_NONE); + mode_tree_expand_current(data->data); + loop = 0; + RB_FOREACH(wl, winlinks, &s->windows) { + if (loop == data->start + x) + break; + loop++; + } + if (wl != NULL) + mode_tree_set_current(data->data, (uint64_t)wl); + return ('\r'); + } + if (item->type == WINDOW_TREE_WINDOW) { + if (wl == NULL) + return (KEYC_NONE); + mode_tree_expand_current(data->data); + loop = 0; + TAILQ_FOREACH(wp, &wl->window->panes, entry) { + if (loop == data->start + x) + break; + loop++; + } + if (wp != NULL) + mode_tree_set_current(data->data, (uint64_t)wp); + return ('\r'); + } + return (KEYC_NONE); +} + +static void +window_tree_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_tree_modedata *data = wme->data; + struct window_tree_itemdata *item, *new_item; + char *name, *prompt = NULL; struct cmd_find_state fs; int finished; - u_int tagged; + u_int tagged, x, y, idx; + struct session *ns; + struct winlink *nwl; + struct window_pane *nwp; item = mode_tree_get_current(data->data); - finished = mode_tree_key(data->data, c, &key, m); - if (item != mode_tree_get_current(data->data)) + finished = mode_tree_key(data->data, c, &key, m, &x, &y); + if (item != (new_item = mode_tree_get_current(data->data))) { + item = new_item; data->offset = 0; + } + if (KEYC_IS_MOUSE(key) && m != NULL) + key = window_tree_mouse(data, key, x, item); switch (key) { case '<': data->offset--; @@ -960,6 +1192,46 @@ window_tree_key(struct window_pane *wp, struct client *c, case '>': data->offset++; break; + case 'x': + window_tree_pull_item(item, &ns, &nwl, &nwp); + switch (item->type) { + case WINDOW_TREE_NONE: + break; + case WINDOW_TREE_SESSION: + if (ns == NULL) + break; + xasprintf(&prompt, "Kill session %s? ", ns->name); + break; + case WINDOW_TREE_WINDOW: + if (nwl == NULL) + break; + xasprintf(&prompt, "Kill window %u? ", nwl->idx); + break; + case WINDOW_TREE_PANE: + if (nwp == NULL || window_pane_index(nwp, &idx) != 0) + break; + xasprintf(&prompt, "Kill pane %u? ", idx); + break; + } + if (prompt == NULL) + break; + data->references++; + status_prompt_set(c, prompt, "", + window_tree_kill_current_callback, window_tree_command_free, + data, PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; + case 'X': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Kill %u tagged? ", tagged); + data->references++; + status_prompt_set(c, prompt, "", + window_tree_kill_tagged_callback, window_tree_command_free, + data, PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; case ':': tagged = mode_tree_count_tagged(data->data); if (tagged != 0) @@ -972,15 +1244,12 @@ window_tree_key(struct window_pane *wp, struct client *c, free(prompt); break; case '\r': - item = mode_tree_get_current(data->data); - command = xstrdup(data->command); name = window_tree_get_target(item, &fs); - window_pane_reset_mode(wp); if (name != NULL) - mode_tree_run_command(c, NULL, command, name); + mode_tree_run_command(c, NULL, data->command, name); + finished = 1; free(name); - free(command); - return; + break; } if (finished) window_pane_reset_mode(wp); diff --git a/window.c b/window.c index 7e3a8e53fc..242ad48d61 100644 --- a/window.c +++ b/window.c @@ -19,14 +19,15 @@ #include #include +#include #include #include #include +#include #include #include #include #include -#include #include #include @@ -60,20 +61,26 @@ static u_int next_window_pane_id; static u_int next_window_id; static u_int next_active_point; -static void window_destroy(struct window *); +/* 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; +}; static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); -static void window_pane_read_callback(struct bufferevent *, void *); -static void window_pane_error_callback(struct bufferevent *, short, void *); - -static int winlink_next_index(struct winlinks *, int); - -static struct window_pane *window_pane_choose_best(struct window_pane **, - u_int); - RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp); @@ -204,7 +211,6 @@ winlink_remove(struct winlinks *wwl, struct winlink *wl) } RB_REMOVE(winlinks, wwl, wl); - free(wl->status_text); free(wl); } @@ -300,13 +306,18 @@ window_update_activity(struct window *w) } struct window * -window_create(u_int sx, u_int sy) +window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) { struct window *w; + if (xpixel == 0) + xpixel = DEFAULT_XPIXEL; + if (ypixel == 0) + ypixel = DEFAULT_YPIXEL; + w = xcalloc(1, sizeof *w); - w->name = NULL; - w->flags = WINDOW_STYLECHANGED; + w->name = xstrdup(""); + w->flags = 0; TAILQ_INIT(&w->panes); w->active = NULL; @@ -316,6 +327,8 @@ window_create(u_int sx, u_int sy) w->sx = sx; w->sy = sy; + w->xpixel = xpixel; + w->ypixel = ypixel; w->options = options_create(global_w_options); @@ -330,36 +343,6 @@ window_create(u_int sx, u_int sy) return (w); } -struct window * -window_create_spawn(const char *name, int argc, char **argv, const char *path, - const char *shell, const char *cwd, struct environ *env, - struct termios *tio, u_int sx, u_int sy, u_int hlimit, char **cause) -{ - struct window *w; - struct window_pane *wp; - - w = window_create(sx, sy); - wp = window_add_pane(w, NULL, 0, hlimit); - layout_init(w, wp); - - if (window_pane_spawn(wp, argc, argv, path, shell, cwd, - env, tio, cause) != 0) { - window_destroy(w); - return (NULL); - } - - w->active = TAILQ_FIRST(&w->panes); - if (name != NULL) { - w->name = xstrdup(name); - options_set_number(w->options, "automatic-rename", 0); - } else - w->name = default_window_name(w); - - notify_window("window-pane-changed", w); - - return (w); -} - static void window_destroy(struct window *w) { @@ -373,16 +356,18 @@ window_destroy(struct window *w) layout_free_cell(w->saved_layout_root); free(w->old_layout); + window_destroy_panes(w); + if (event_initialized(&w->name_event)) evtimer_del(&w->name_event); if (event_initialized(&w->alerts_timer)) evtimer_del(&w->alerts_timer); + if (event_initialized(&w->offset_timer)) + event_del(&w->offset_timer); options_free(w->options); - window_destroy_panes(w); - free(w->name); free(w); } @@ -430,10 +415,49 @@ window_set_name(struct window *w, const char *new_name) } void -window_resize(struct window *w, u_int sx, u_int sy) +window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel) { + if (xpixel == 0) + xpixel = DEFAULT_XPIXEL; + if (ypixel == 0) + 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); w->sx = sx; w->sy = sy; + if (xpixel != -1) + w->xpixel = xpixel; + if (ypixel != -1) + w->ypixel = ypixel; +} + +void +window_pane_send_resize(struct window_pane *wp, int yadjust) +{ + struct window *w = wp->window; + struct winsize ws; + + if (wp->fd == -1) + return; + + memset(&ws, 0, sizeof ws); + ws.ws_col = wp->sx; + ws.ws_row = wp->sy + yadjust; + ws.ws_xpixel = w->xpixel * ws.ws_col; + ws.ws_ypixel = w->ypixel * ws.ws_row; + if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) +#ifdef __sun + /* + * Some versions of Solaris apparently can return an error when + * resizing; don't know why this happens, can't reproduce on + * other platforms and ignoring it doesn't seem to cause any + * issues. + */ + if (errno != EINVAL && errno != ENXIO) +#endif + fatal("ioctl failed"); } int @@ -449,56 +473,59 @@ window_has_pane(struct window *w, struct window_pane *wp) } int -window_set_active_pane(struct window *w, struct window_pane *wp) +window_set_active_pane(struct window *w, struct window_pane *wp, int notify) { - log_debug("%s: pane %%%u (was %%%u)", __func__, wp->id, w->active->id); + log_debug("%s: pane %%%u", __func__, wp->id); + if (wp == w->active) return (0); w->last = w->active; + w->active = wp; - while (!window_pane_visible(w->active)) { - w->active = TAILQ_PREV(w->active, window_panes, entry); - if (w->active == NULL) - w->active = TAILQ_LAST(&w->panes, window_panes); - if (w->active == wp) { - notify_window("window-pane-changed", w); - return (1); - } - } w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; - notify_window("window-pane-changed", w); + + tty_update_window_offset(w); + + if (notify) + notify_window("window-pane-changed", w); return (1); } void window_redraw_active_switch(struct window *w, struct window_pane *wp) { - const struct grid_cell *gc; + struct style *sy1, *sy2; + int c1, c2; if (wp == w->active) return; - /* - * If window-style and window-active-style are the same, we don't need - * to redraw panes when switching active panes. - */ - gc = options_get_style(w->options, "window-active-style"); - if (style_equal(gc, options_get_style(w->options, "window-style"))) - return; - - /* - * If the now active or inactive pane do not have a custom style or if - * the palette is different, they need to be redrawn. - */ - if (window_pane_get_palette(w->active, w->active->colgc.fg) != -1 || - window_pane_get_palette(w->active, w->active->colgc.bg) != -1 || - style_equal(&grid_default_cell, &w->active->colgc)) - w->active->flags |= PANE_REDRAW; - if (window_pane_get_palette(wp, wp->colgc.fg) != -1 || - window_pane_get_palette(wp, wp->colgc.bg) != -1 || - style_equal(&grid_default_cell, &wp->colgc)) - wp->flags |= PANE_REDRAW; + for (;;) { + /* + * 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)) + wp->flags |= PANE_REDRAW; + else { + c1 = window_pane_get_palette(wp, sy1->gc.fg); + c2 = window_pane_get_palette(wp, sy2->gc.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); + if (c1 != c2) + wp->flags |= PANE_REDRAW; + } + } + if (wp == w->active) + break; + wp = w->active; + } } struct window_pane * @@ -561,14 +588,11 @@ window_zoom(struct window_pane *wp) if (w->flags & WINDOW_ZOOMED) return (-1); - if (!window_pane_visible(wp)) - return (-1); - if (window_count_panes(w) == 1) return (-1); if (w->active != wp) - window_set_active_pane(w, wp); + window_set_active_pane(w, wp, 1); TAILQ_FOREACH(wp1, &w->panes, entry) { wp1->saved_layout_cell = wp1->layout_cell; @@ -600,15 +624,37 @@ window_unzoom(struct window *w) wp->layout_cell = wp->saved_layout_cell; wp->saved_layout_cell = NULL; } - layout_fix_panes(w, w->sx, w->sy); + layout_fix_panes(w); notify_window("window-layout-changed", w); return (0); } +int +window_push_zoom(struct window *w, int flag) +{ + log_debug("%s: @%u %d", __func__, w->id, + flag && (w->flags & WINDOW_ZOOMED)); + if (flag && (w->flags & WINDOW_ZOOMED)) + w->flags |= WINDOW_WASZOOMED; + else + w->flags &= ~WINDOW_WASZOOMED; + return (window_unzoom(w) == 0); +} + +int +window_pop_zoom(struct window *w) +{ + log_debug("%s: @%u %d", __func__, w->id, + !!(w->flags & WINDOW_WASZOOMED)); + if (w->flags & WINDOW_WASZOOMED) + return (window_zoom(w->active) == 0); + return (0); +} + struct window_pane * -window_add_pane(struct window *w, struct window_pane *other, int before, - u_int hlimit) +window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, + int flags) { struct window_pane *wp; @@ -619,12 +665,18 @@ window_add_pane(struct window *w, struct window_pane *other, int before, if (TAILQ_EMPTY(&w->panes)) { log_debug("%s: @%u at start", __func__, w->id); TAILQ_INSERT_HEAD(&w->panes, wp, entry); - } else if (before) { + } else if (flags & SPAWN_BEFORE) { log_debug("%s: @%u before %%%u", __func__, w->id, wp->id); - TAILQ_INSERT_BEFORE(other, wp, entry); + if (flags & SPAWN_FULLSIZE) + TAILQ_INSERT_HEAD(&w->panes, wp, entry); + else + TAILQ_INSERT_BEFORE(other, wp, entry); } else { log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); - TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); + if (flags & SPAWN_FULLSIZE) + TAILQ_INSERT_TAIL(&w->panes, wp, entry); + else + TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); } return (wp); } @@ -799,6 +851,8 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp = xcalloc(1, sizeof *wp); wp->window = w; + wp->options = options_create(w->options); + wp->flags = PANE_STYLECHANGED; wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); @@ -811,8 +865,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->fd = -1; wp->event = NULL; - wp->mode = NULL; - wp->modeprefix = 1; + TAILQ_INIT(&wp->modes); wp->layout_cell = NULL; @@ -827,8 +880,8 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->pipe_event = NULL; wp->saved_grid = NULL; - - memcpy(&wp->colgc, &grid_default_cell, sizeof wp->colgc); + wp->saved_cx = UINT_MAX; + wp->saved_cy = UINT_MAX; screen_init(&wp->base, sx, sy, hlimit); wp->screen = &wp->base; @@ -846,7 +899,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) static void window_pane_destroy(struct window_pane *wp) { - window_pane_reset_mode(wp); + window_pane_reset_mode_all(wp); free(wp->searchstr); if (wp->fd != -1) { @@ -859,6 +912,8 @@ window_pane_destroy(struct window_pane *wp) input_free(wp); + screen_free(&wp->status_screen); + screen_free(&wp->base); if (wp->saved_grid != NULL) grid_destroy(wp->saved_grid); @@ -873,6 +928,7 @@ window_pane_destroy(struct window_pane *wp) RB_REMOVE(window_pane_tree, &all_window_panes, wp); + options_free(wp->options); free((void *)wp->cwd); free(wp->shell); cmd_free_argv(wp->argc, wp->argv); @@ -880,139 +936,6 @@ window_pane_destroy(struct window_pane *wp) free(wp); } -int -window_pane_spawn(struct window_pane *wp, int argc, char **argv, - const char *path, const char *shell, const char *cwd, struct environ *env, - struct termios *tio, char **cause) -{ - struct winsize ws; - char *argv0, *cmd, **argvp; - const char *ptr, *first, *home; - struct termios tio2; -#ifdef HAVE_UTEMPTER - char s[32]; -#endif - int i; - sigset_t set, oldset; - - if (wp->fd != -1) { - bufferevent_free(wp->event); - close(wp->fd); - } - if (argc > 0) { - cmd_free_argv(wp->argc, wp->argv); - wp->argc = argc; - wp->argv = cmd_copy_argv(argc, argv); - } - if (shell != NULL) { - free(wp->shell); - wp->shell = xstrdup(shell); - } - if (cwd != NULL) { - free((void *)wp->cwd); - wp->cwd = xstrdup(cwd); - } - - cmd = cmd_stringify_argv(wp->argc, wp->argv); - log_debug("spawn: %s -- %s", wp->shell, cmd); - for (i = 0; i < wp->argc; i++) - log_debug("spawn: argv[%d] = %s", i, wp->argv[i]); - environ_log(env, "spawn: "); - - memset(&ws, 0, sizeof ws); - ws.ws_col = screen_size_x(&wp->base); - ws.ws_row = screen_size_y(&wp->base); - - sigfillset(&set); - sigprocmask(SIG_BLOCK, &set, &oldset); - switch (wp->pid = fdforkpty(ptm_fd, &wp->fd, wp->tty, NULL, &ws)) { - case -1: - wp->fd = -1; - - xasprintf(cause, "%s: %s", cmd, strerror(errno)); - free(cmd); - - sigprocmask(SIG_SETMASK, &oldset, NULL); - return (-1); - case 0: - proc_clear_signals(server_proc, 1); - sigprocmask(SIG_SETMASK, &oldset, NULL); - - if (chdir(wp->cwd) != 0) { - if ((home = find_home()) == NULL || chdir(home) != 0) - chdir("/"); - } - - if (tcgetattr(STDIN_FILENO, &tio2) != 0) - fatal("tcgetattr failed"); - if (tio != NULL) - memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); - tio2.c_cc[VERASE] = '\177'; -#ifdef IUTF8 - tio2.c_iflag |= IUTF8; -#endif - if (tcsetattr(STDIN_FILENO, TCSANOW, &tio2) != 0) - fatal("tcgetattr failed"); - - log_close(); - closefrom(STDERR_FILENO + 1); - - if (path != NULL) - environ_set(env, "PATH", "%s", path); - environ_set(env, "TMUX_PANE", "%%%u", wp->id); - environ_push(env); - - setenv("SHELL", wp->shell, 1); - ptr = strrchr(wp->shell, '/'); - - /* - * If given one argument, assume it should be passed to sh -c; - * with more than one argument, use execvp(). If there is no - * arguments, create a login shell. - */ - if (wp->argc > 0) { - if (wp->argc != 1) { - /* Copy to ensure argv ends in NULL. */ - argvp = cmd_copy_argv(wp->argc, wp->argv); - execvp(argvp[0], argvp); - fatal("execvp failed"); - } - first = wp->argv[0]; - - if (ptr != NULL && *(ptr + 1) != '\0') - xasprintf(&argv0, "%s", ptr + 1); - else - xasprintf(&argv0, "%s", wp->shell); - execl(wp->shell, argv0, "-c", first, (char *)NULL); - fatal("execl failed"); - } - if (ptr != NULL && *(ptr + 1) != '\0') - xasprintf(&argv0, "-%s", ptr + 1); - else - xasprintf(&argv0, "-%s", wp->shell); - execl(wp->shell, argv0, (char *)NULL); - fatal("execl failed"); - } - -#ifdef HAVE_UTEMPTER - xsnprintf(s, sizeof s, "tmux(%lu).%%%u", (long) getpid(), wp->id); - utempter_add_record(wp->fd, s); - kill(getpid(), SIGCHLD); -#endif - - sigprocmask(SIG_SETMASK, &oldset, NULL); - setblocking(wp->fd, 0); - - wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, - window_pane_error_callback, wp); - - bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); - bufferevent_enable(wp->event, EV_READ|EV_WRITE); - - free(cmd); - return (0); -} - static void window_pane_read_callback(__unused struct bufferevent *bufev, void *data) { @@ -1047,19 +970,36 @@ window_pane_error_callback(__unused struct bufferevent *bufev, server_destroy_pane(wp, 1); } +void +window_pane_set_event(struct window_pane *wp) +{ + setblocking(wp->fd, 0); + + wp->event = bufferevent_new(wp->fd, window_pane_read_callback, + NULL, window_pane_error_callback, wp); + + bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); + bufferevent_enable(wp->event, EV_READ|EV_WRITE); +} + void window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) { + struct window_mode_entry *wme; + if (sx == wp->sx && sy == wp->sy) return; 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); - if (wp->mode != NULL) - wp->mode->resize(wp, sx, sy); - wp->flags |= PANE_RESIZE; + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL && wme->mode->resize != NULL) + wme->mode->resize(wme, sx, sy); + + wp->flags |= (PANE_RESIZE|PANE_RESIZED); } /* @@ -1075,7 +1015,7 @@ window_pane_alternate_on(struct window_pane *wp, struct grid_cell *gc, if (wp->saved_grid != NULL) return; - if (!options_get_number(wp->window->options, "alternate-screen")) + if (!options_get_number(wp->options, "alternate-screen")) return; sx = screen_size_x(s); sy = screen_size_y(s); @@ -1103,9 +1043,24 @@ window_pane_alternate_off(struct window_pane *wp, struct grid_cell *gc, struct screen *s = &wp->base; u_int sx, sy; - if (wp->saved_grid == NULL) + if (!options_get_number(wp->options, "alternate-screen")) return; - if (!options_get_number(wp->window->options, "alternate-screen")) + + /* + * 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); @@ -1117,17 +1072,8 @@ window_pane_alternate_off(struct window_pane *wp, struct grid_cell *gc, if (sy > wp->saved_grid->sy) screen_resize(s, sx, wp->saved_grid->sy, 1); - /* Restore the grid, cursor position and cell. */ + /* Restore the saved grid. */ grid_duplicate_lines(s->grid, screen_hsize(s), wp->saved_grid, 0, sy); - if (cursor) - s->cx = wp->saved_cx; - if (s->cx > screen_size_x(s) - 1) - s->cx = screen_size_x(s) - 1; - if (cursor) - 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); /* * Turn history back on (so resize can use it) and then resize back to @@ -1178,7 +1124,7 @@ window_pane_reset_palette(struct window_pane *wp) } int -window_pane_get_palette(const struct window_pane *wp, int c) +window_pane_get_palette(struct window_pane *wp, int c) { int new; @@ -1211,7 +1157,7 @@ window_pane_mode_timer(__unused int fd, __unused short events, void *arg) if (wp->modelast < time(NULL) - WINDOW_MODE_TIMEOUT) { if (ioctl(wp->fd, FIONREAD, &n) == -1 || n > 0) - window_pane_reset_mode(wp); + window_pane_reset_mode_all(wp); } } @@ -1219,58 +1165,95 @@ int window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, struct cmd_find_state *fs, struct args *args) { - struct screen *s; - struct timeval tv = { .tv_sec = 10 }; + struct timeval tv = { .tv_sec = 10 }; + struct window_mode_entry *wme; - if (wp->mode != NULL) + if (!TAILQ_EMPTY(&wp->modes) && TAILQ_FIRST(&wp->modes)->mode == mode) return (1); - wp->mode = mode; wp->modelast = time(NULL); - evtimer_set(&wp->modetimer, window_pane_mode_timer, wp); - evtimer_add(&wp->modetimer, &tv); + if (TAILQ_EMPTY(&wp->modes)) { + evtimer_set(&wp->modetimer, window_pane_mode_timer, wp); + evtimer_add(&wp->modetimer, &tv); + } - if ((s = wp->mode->init(wp, fs, args)) != NULL) - wp->screen = s; + TAILQ_FOREACH(wme, &wp->modes, entry) { + if (wme->mode == mode) + break; + } + if (wme != NULL) { + TAILQ_REMOVE(&wp->modes, wme, entry); + TAILQ_INSERT_HEAD(&wp->modes, wme, entry); + } else { + wme = xcalloc(1, sizeof *wme); + wme->wp = wp; + wme->mode = mode; + wme->prefix = 1; + TAILQ_INSERT_HEAD(&wp->modes, wme, entry); + wme->screen = wme->mode->init(wme, fs, args); + } + + wp->screen = wme->screen; wp->flags |= (PANE_REDRAW|PANE_CHANGED); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); + return (0); } void window_pane_reset_mode(struct window_pane *wp) { - if (wp->mode == NULL) - return; + struct window_mode_entry *wme, *next; - evtimer_del(&wp->modetimer); + if (TAILQ_EMPTY(&wp->modes)) + return; - wp->mode->free(wp); - wp->mode = NULL; - wp->modeprefix = 1; + wme = TAILQ_FIRST(&wp->modes); + TAILQ_REMOVE(&wp->modes, wme, entry); + wme->mode->free(wme); + free(wme); - wp->screen = &wp->base; + 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); + wp->screen = next->screen; + if (next->mode->resize != NULL) + next->mode->resize(next, wp->sx, wp->sy); + } wp->flags |= (PANE_REDRAW|PANE_CHANGED); server_status_window(wp->window); notify_pane("pane-mode-changed", wp); } +void +window_pane_reset_mode_all(struct window_pane *wp) +{ + while (!TAILQ_EMPTY(&wp->modes)) + window_pane_reset_mode(wp); +} + void window_pane_key(struct window_pane *wp, struct client *c, struct session *s, - key_code key, struct mouse_event *m) + struct winlink *wl, key_code key, struct mouse_event *m) { - struct window_pane *wp2; + struct window_mode_entry *wme; + struct window_pane *wp2; if (KEYC_IS_MOUSE(key) && m == NULL) return; - if (wp->mode != NULL) { + wme = TAILQ_FIRST(&wp->modes); + if (wme != NULL) { wp->modelast = time(NULL); - if (wp->mode->key != NULL) - wp->mode->key(wp, c, s, (key & ~KEYC_XTERM), m); + if (wme->mode->key != NULL) + wme->mode->key(wme, c, s, wl, (key & ~KEYC_XTERM), m); return; } @@ -1283,11 +1266,11 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, return; if (options_get_number(wp->window->options, "synchronize-panes")) { TAILQ_FOREACH(wp2, &wp->window->panes, entry) { - if (wp2 == wp || wp2->mode != NULL) - continue; - if (wp2->fd == -1 || wp2->flags & PANE_INPUTOFF) - continue; - if (window_pane_visible(wp2)) + if (wp2 != wp && + TAILQ_EMPTY(&wp2->modes) && + wp2->fd != -1 && + (~wp2->flags & PANE_INPUTOFF) && + window_pane_visible(wp2)) input_key(wp2, key, NULL); } } @@ -1296,37 +1279,54 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s, int window_pane_visible(struct window_pane *wp) { - struct window *w = wp->window; - - if (wp->layout_cell == NULL) - return (0); - - if (wp->xoff >= w->sx || wp->yoff >= w->sy) - return (0); - if (wp->xoff + wp->sx > w->sx || wp->yoff + wp->sy > w->sy) - return (0); - return (1); + if (~wp->window->flags & WINDOW_ZOOMED) + return (1); + return (wp == wp->window->active); } u_int -window_pane_search(struct window_pane *wp, const char *searchstr) +window_pane_search(struct window_pane *wp, const char *term, int regex, + int ignore) { struct screen *s = &wp->base; - char *newsearchstr, *line; + regex_t r; + char *new = NULL, *line; u_int i; + int flags = 0, found; + size_t n; - xasprintf(&newsearchstr, "*%s*", searchstr); + if (!regex) { + if (ignore) + flags |= FNM_CASEFOLD; + xasprintf(&new, "*%s*", term); + } else { + if (ignore) + flags |= REG_ICASE; + if (regcomp(&r, term, flags|REG_EXTENDED) != 0) + return (0); + } for (i = 0; i < screen_size_y(s); i++) { line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s)); - if (fnmatch(newsearchstr, line, 0) == 0) { - free(line); - break; + for (n = strlen(line); n > 0; n--) { + if (!isspace((u_char)line[n - 1])) + break; + line[n - 1] = '\0'; } + log_debug("%s: %s", __func__, line); + if (!regex) + found = (fnmatch(new, line, 0) == 0); + else + found = (regexec(&r, line, 0, NULL, 0) == 0); free(line); + if (found) + break; } + if (!regex) + free(new); + else + regfree(&r); - free(newsearchstr); if (i == screen_size_y(s)) return (0); return (i + 1); @@ -1358,26 +1358,36 @@ window_pane_choose_best(struct window_pane **list, u_int size) struct window_pane * window_pane_find_up(struct window_pane *wp) { + struct window *w; struct window_pane *next, *best, **list; u_int edge, left, right, end, size; int status, found; - if (wp == NULL || !window_pane_visible(wp)) + if (wp == NULL) return (NULL); - status = options_get_number(wp->window->options, "pane-border-status"); + w = wp->window; + status = options_get_number(w->options, "pane-border-status"); list = NULL; size = 0; edge = wp->yoff; - if (edge == (status == 1 ? 1 : 0)) - edge = wp->window->sy + 1 - (status == 2 ? 1 : 0); + if (status == PANE_STATUS_TOP) { + if (edge == 1) + edge = w->sy + 1; + } else if (status == PANE_STATUS_BOTTOM) { + if (edge == 0) + edge = w->sy; + } else { + if (edge == 0) + edge = w->sy + 1; + } left = wp->xoff; right = wp->xoff + wp->sx; - TAILQ_FOREACH(next, &wp->window->panes, entry) { - if (next == wp || !window_pane_visible(next)) + TAILQ_FOREACH(next, &w->panes, entry) { + if (next == wp) continue; if (next->yoff + next->sy + 1 != edge) continue; @@ -1405,26 +1415,36 @@ window_pane_find_up(struct window_pane *wp) struct window_pane * window_pane_find_down(struct window_pane *wp) { + struct window *w; struct window_pane *next, *best, **list; u_int edge, left, right, end, size; int status, found; - if (wp == NULL || !window_pane_visible(wp)) + if (wp == NULL) return (NULL); - status = options_get_number(wp->window->options, "pane-border-status"); + w = wp->window; + status = options_get_number(w->options, "pane-border-status"); list = NULL; size = 0; edge = wp->yoff + wp->sy + 1; - if (edge >= wp->window->sy - (status == 2 ? 1 : 0)) - edge = (status == 1 ? 1 : 0); + if (status == PANE_STATUS_TOP) { + if (edge >= w->sy) + edge = 1; + } else if (status == PANE_STATUS_BOTTOM) { + if (edge >= w->sy - 1) + edge = 0; + } else { + if (edge >= w->sy) + edge = 0; + } left = wp->xoff; right = wp->xoff + wp->sx; - TAILQ_FOREACH(next, &wp->window->panes, entry) { - if (next == wp || !window_pane_visible(next)) + TAILQ_FOREACH(next, &w->panes, entry) { + if (next == wp) continue; if (next->yoff != edge) continue; @@ -1452,25 +1472,27 @@ window_pane_find_down(struct window_pane *wp) struct window_pane * window_pane_find_left(struct window_pane *wp) { + struct window *w; struct window_pane *next, *best, **list; u_int edge, top, bottom, end, size; int found; - if (wp == NULL || !window_pane_visible(wp)) + if (wp == NULL) return (NULL); + w = wp->window; list = NULL; size = 0; edge = wp->xoff; if (edge == 0) - edge = wp->window->sx + 1; + edge = w->sx + 1; top = wp->yoff; bottom = wp->yoff + wp->sy; - TAILQ_FOREACH(next, &wp->window->panes, entry) { - if (next == wp || !window_pane_visible(next)) + TAILQ_FOREACH(next, &w->panes, entry) { + if (next == wp) continue; if (next->xoff + next->sx + 1 != edge) continue; @@ -1498,25 +1520,27 @@ window_pane_find_left(struct window_pane *wp) struct window_pane * window_pane_find_right(struct window_pane *wp) { + struct window *w; struct window_pane *next, *best, **list; u_int edge, top, bottom, end, size; int found; - if (wp == NULL || !window_pane_visible(wp)) + if (wp == NULL) return (NULL); + w = wp->window; list = NULL; size = 0; edge = wp->xoff + wp->sx + 1; - if (edge >= wp->window->sx) + if (edge >= w->sx) edge = 0; top = wp->yoff; bottom = wp->yoff + wp->sy; - TAILQ_FOREACH(next, &wp->window->panes, entry) { - if (next == wp || !window_pane_visible(next)) + TAILQ_FOREACH(next, &w->panes, entry) { + if (next == wp) continue; if (next->xoff != edge) continue; @@ -1561,6 +1585,8 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) { int idx, last; + if (wl == NULL) + return (-1); idx = wl->idx + 1; /* Find the next free index. */ @@ -1580,3 +1606,51 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) return (idx); } + +static void +window_pane_input_callback(struct client *c, int closed, void *data) +{ + struct window_pane_input_data *cdata = data; + struct window_pane *wp; + struct evbuffer *evb = c->stdin_data; + u_char *buf = EVBUFFER_DATA(evb); + size_t len = EVBUFFER_LENGTH(evb); + + wp = window_pane_find_by_id(cdata->wp); + if (wp == NULL || closed || c->flags & CLIENT_DEAD) { + if (wp == NULL) + c->flags |= CLIENT_EXIT; + evbuffer_drain(evb, len); + + c->stdin_callback = NULL; + server_client_unref(c); + + cmdq_continue(cdata->item); + free(cdata); + + return; + } + + input_parse_buffer(wp, buf, len); + evbuffer_drain(evb, len); +} + +int +window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, + char **cause) +{ + struct client *c = item->client; + struct window_pane_input_data *cdata; + + if (~wp->flags & PANE_EMPTY) { + *cause = xstrdup("pane is not empty"); + return (-1); + } + + cdata = xmalloc(sizeof *cdata); + cdata->item = item; + cdata->wp = wp->id; + + return (server_set_stdin_callback(c, window_pane_input_callback, cdata, + cause)); +} diff --git a/xmalloc.c b/xmalloc.c index 22ea3540cf..d11d8dc78d 100644 --- a/xmalloc.c +++ b/xmalloc.c @@ -71,6 +71,20 @@ xreallocarray(void *ptr, size_t nmemb, size_t size) return new_ptr; } +void * +xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size) +{ + void *new_ptr; + + if (nmemb == 0 || size == 0) + fatalx("xrecallocarray: zero size"); + new_ptr = recallocarray(ptr, oldnmemb, nmemb, size); + if (new_ptr == NULL) + fatalx("xrecallocarray: allocating %zu * %zu bytes: %s", + nmemb, size, strerror(errno)); + return new_ptr; +} + char * xstrdup(const char *str) { @@ -111,7 +125,7 @@ xvasprintf(char **ret, const char *fmt, va_list ap) i = vasprintf(ret, fmt, ap); - if (i < 0 || *ret == NULL) + if (i == -1) fatalx("xasprintf: %s", strerror(errno)); return i; diff --git a/xmalloc.h b/xmalloc.h index dd254e7b93..d11d4bf1e3 100644 --- a/xmalloc.h +++ b/xmalloc.h @@ -27,6 +27,7 @@ void *xmalloc(size_t); void *xcalloc(size_t, size_t); void *xrealloc(void *, size_t); void *xreallocarray(void *, size_t, size_t); +void *xrecallocarray(void *, size_t, size_t, size_t); char *xstrdup(const char *); char *xstrndup(const char *, size_t); int xasprintf(char **, const char *, ...)