├── .gitignore ├── .yardopts ├── LICENSE ├── README.md ├── lib ├── tmux.rb └── tmux │ ├── buffer.rb │ ├── client.rb │ ├── exception.rb │ ├── exception │ ├── basic_exception.rb │ ├── in_tmux.rb │ ├── index_in_use.rb │ ├── unknown_command.rb │ └── unsupported_version.rb │ ├── filterable_hash.rb │ ├── options.rb │ ├── options │ ├── attr_option.rb │ ├── bell_action_option.rb │ ├── boolean_option.rb │ ├── char_array_option.rb │ ├── clock_mode_style_option.rb │ ├── color_option.rb │ ├── justification_option.rb │ ├── keymap_option.rb │ ├── number_option.rb │ ├── option.rb │ ├── string_option.rb │ ├── symbol_option.rb │ └── word_array_option.rb │ ├── options_list.rb │ ├── pane.rb │ ├── server.rb │ ├── session.rb │ ├── status_bar.rb │ ├── status_bar │ └── field.rb │ ├── version.rb │ ├── widget.rb │ ├── widgets │ └── progress_bar.rb │ ├── window.rb │ └── window │ ├── status.rb │ └── status │ └── state.rb ├── screenshots └── progress_bar.png └── tmux-ruby.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | .yardoc/ 2 | doc/ 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --tag "tmux:tmux command" --tag "tmuxver:Required tmux version" --hide-void-return -m markdown --private --protected --default-return Undefined 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Dominik Honnef 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This project is abandoned. It will most likely not work correctly 2 | with current versions of tmux. Sorry.** 3 | 4 | # Widgets 5 | 6 | - Progress bar ({Tmux::Widgets::ProgressBar ProgressBar}) 7 | 8 | # Semantic Versioning 9 | 10 | tmux-ruby uses [Semantic Versioning](http://semver.org/) 11 | 12 | 13 | # Documentation 14 | 15 | tmux-ruby uses [YARD](http://github.com/lsegal/yard) for documenting 16 | its codebase. 17 | 18 | 19 | # Contributing 20 | 21 | * Fork the project. 22 | * Make your feature addition or bug fix. 23 | * Commit, do not mess with rakefile, version, or history. (if you want 24 | to have your own version, that is fine but bump version in a commit 25 | by itself I can ignore when I pull) 26 | * Send me a pull request. Bonus points for topic branches. 27 | -------------------------------------------------------------------------------- /lib/tmux.rb: -------------------------------------------------------------------------------- 1 | require "date" 2 | require "tmux/filterable_hash" 3 | require "tmux/exception" 4 | require "tmux/server" 5 | require "tmux/session" 6 | require "tmux/client" 7 | require "tmux/window" 8 | require "tmux/pane" 9 | require "tmux/buffer" 10 | require "tmux/status_bar" 11 | require "tmux/options_list" 12 | require "tmux/options" 13 | require "tmux/widget" 14 | require "tmux/version" 15 | 16 | # @todo Support querying and modifying keymaps 17 | module Tmux 18 | # The newest version of tmux we officially support 19 | TMUX_VERSION = "1.3".freeze 20 | 21 | @binary = `which tmux`.chomp 22 | @verbose = false 23 | 24 | class << self 25 | # Path of the tmux binary. 26 | # @return [String] 27 | attr_accessor :binary 28 | 29 | # Print verbose information on $stderr? 30 | # @return [Boolean] 31 | attr_accessor :verbose 32 | alias_method :verbose?, :verbose 33 | 34 | # Invokes a tmux command and returns all output. 35 | # 36 | # @param [String] cmd Command to invoke 37 | # @param [Boolean] unset_tmux If true, unsets $TMUX before calling 38 | # tmux, to allow nesting 39 | # @return [String] all output 40 | # @raise [Exception::UnknownCommand] 41 | # @api private 42 | def invoke_command(cmd, unset_tmux = false) 43 | command = "" 44 | command << "TMUX='' " if unset_tmux 45 | command << "#{@binary} #{cmd}" 46 | 47 | $stderr.puts(command) if verbose? 48 | ret = `#{command} 2>&1` 49 | if ret.start_with?("unknown command:") 50 | raise Exception::UnknownCommand, ret.split(":", 2).last.strip 51 | else 52 | return ret 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/tmux/buffer.rb: -------------------------------------------------------------------------------- 1 | require "filesize" 2 | require "tempfile" 3 | 4 | module Tmux 5 | class Buffer 6 | # @return [Number] 7 | attr_reader :number 8 | # @return [Session] 9 | attr_reader :session 10 | # @return [Filesize] 11 | def initialize(number, session) 12 | @number, @session, @size = number, session 13 | unless server.version >= "1.3" 14 | # we do not need a temporary file for tmux versions that can 15 | # directly load/save from/to stdin/stdout 16 | @file = Tempfile.new("buffer") 17 | end 18 | end 19 | 20 | # @!attribute [r] size 21 | # 22 | # @param [Boolean] force_reload Ignore frozen state if true 23 | # @return [Filesize] 24 | def size(force_reload = false) 25 | if @size && !force_reload 26 | @size 27 | else 28 | Filesize.new(@session.buffers_information[number][:size].to_i) 29 | end 30 | end 31 | 32 | # Receive the buffer's data. 33 | # 34 | # @param [Boolean] force_reload Ignore frozen state if true 35 | # @return [String] 36 | def data(force_reload = false) 37 | # note: we cannot use show-buffer because that would escape tabstops 38 | if @data && !force_reload 39 | @data 40 | else 41 | if server.version >= "1.3" 42 | return server.invoke_command "save-buffer -b #@number #{target_argument} -" 43 | else 44 | server.invoke_command "save-buffer -b #@number #{target_argument} #{@file.path}" 45 | return @file.read 46 | end 47 | end 48 | end 49 | 50 | # Set the buffer's data. 51 | # 52 | # @param [String] new_data 53 | # @return [String] 54 | def data=(new_data) 55 | # FIXME maybe some more escaping? 56 | server.invoke_command "set-buffer -b #@number #{target_argument} \"#{new_data}\"" 57 | @data = data(true) if @frozen 58 | @size = size(true) 59 | end 60 | 61 | # Saves the contents of a buffer. 62 | # 63 | # @param [String] file The file to write to 64 | # @param [Boolean] append Append to instead of overwriting the file 65 | # @tmux save-buffer 66 | # @return [void] 67 | def save(file, append = false) 68 | flag = append ? "-a" : "" 69 | server.invoke_command "save-buffer #{flag} -b #@number #{target_argument} #{file}" 70 | end 71 | alias_method :write, :save 72 | 73 | # By default, Buffer will not cache its data but instead query it each time. 74 | # By calling this method, the data will be cached and not updated anymore. 75 | # 76 | # @return [void] 77 | def freeze! 78 | @frozen = true 79 | @data = data 80 | @size = size 81 | end 82 | 83 | # @!attribute [r] server 84 | # 85 | # @return [Server] 86 | def server 87 | @session.server 88 | end 89 | 90 | # Deletes a buffer. 91 | # 92 | # @tmux delete-buffer 93 | # @return [void] 94 | def delete 95 | freeze! # so we can still access its old value 96 | server.invoke_command "delete-buffer -b #@number #{target_argument}" 97 | end 98 | 99 | # @return [String] The content of a buffer 100 | def to_s 101 | text 102 | end 103 | 104 | # Pastes the content of a buffer into a {Window window} or {Pane pane}. 105 | # 106 | # @param [Window] target The {Pane pane} or {Window window} to 107 | # paste the buffer into. Note: {Pane Panes} as as target are only 108 | # supported since tmux version 1.3. 109 | # @param [Boolean] pop If true, delete the buffer from the stack 110 | # @param [Boolean] translate If true, any linefeed (LF) characters 111 | # in the paste buffer are replaced with carriage returns (CR) 112 | # @param [String] separator Replace any linefeed (LF) in the 113 | # buffer with this separator. +translate+ must be false. 114 | # 115 | # @tmux paste-buffer 116 | # @tmuxver >=1.3 for pasting to {Pane panes} 117 | # @return [void] 118 | # @see Window#paste 119 | # @see Pane#paste 120 | def paste(target = nil, pop = false, translate = true, separator = nil) 121 | if server.version < "1.3" 122 | if separator || target.is_a?(Pane) 123 | raise Exception::UnsupportedVersion, "1.3" 124 | end 125 | end 126 | 127 | flag_pop = pop ? "-d" : "" 128 | flag_translate = translate ? "" : "-r" 129 | flag_separator = separator ? "" : "-s \"#{separator}\"" # FIXME escape 130 | window_param = target ? "-t #{target.identifier}" : "" 131 | server.invoke_command "paste-buffer #{flag_pop} #{flag_translate} #{flag_separator} #{window_param}" 132 | end 133 | 134 | private 135 | def target_argument 136 | if server.version < "1.5" 137 | "-t #{@session.identifier}" 138 | else 139 | "" 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/tmux/client.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | class Client 3 | # @return [Server] 4 | attr_reader :server 5 | 6 | # @return [String] 7 | attr_reader :device 8 | 9 | def initialize(server, device) 10 | @server, @device = server, device 11 | end 12 | 13 | # @!attribute [r] identifier 14 | # @return [String] 15 | def identifier 16 | @device 17 | end 18 | 19 | # @!attribute session 20 | # 21 | # Setting this will make a client switch to another {Session session}. 22 | # 23 | # @tmux switch-client 24 | # @return [Session] 25 | def session 26 | @server.clients_information[@device][:session] 27 | end 28 | 29 | def session=(new_session) 30 | @server.invoke_command "switch-client -c #@device -t #{new_session.number}" 31 | end 32 | 33 | # @!attribute [r] width 34 | # 35 | # @return [Integer] 36 | def width 37 | @server.clients_information[@device][:width] 38 | end 39 | 40 | # @!attribute [r] height 41 | # @return [Integer] 42 | def height 43 | @server.clients_information[@device][:height] 44 | end 45 | 46 | # @!attribute [r] term 47 | # 48 | # $TERM of a client. 49 | # 50 | # @return [String] 51 | def term 52 | @server.clients_information[@device][:term] 53 | end 54 | 55 | # @!attribute [r] utf8 56 | # True if the terminal is using UTF-8. 57 | # 58 | # @return [Boolean] 59 | def utf8 60 | @server.clients_information[@device][:utf8] 61 | end 62 | alias_method :utf8?, :utf8 63 | 64 | # Detaches a client from tmux. 65 | # 66 | # @tmux detach-client 67 | # @return [void] 68 | def detach 69 | @server.invoke_command "detach-client -t #@device" 70 | end 71 | 72 | # Locks a client. 73 | # 74 | # @tmux lock-client 75 | # @return [void] 76 | # @tmuxver >=1.1 77 | def lock 78 | @server.check_for_version!("1.1") 79 | 80 | @server.invoke_command "lock-client -t #@device" 81 | end 82 | 83 | # Suspends a client. 84 | # 85 | # @tmux suspend-client 86 | # @return [void] 87 | def suspend 88 | @server.invoke_command "suspend-client -c #@device" 89 | end 90 | 91 | # Refreshs a client. 92 | # 93 | # @tmux refresh-client 94 | # @return [void] 95 | def refresh 96 | @server.invoke_command "refresh-client -t #@device" 97 | end 98 | 99 | # @!attribute [r] messages 100 | # 101 | # @tmux show-messages 102 | # @return [Array] A log of messages 103 | # @tmuxver >=1.2 104 | def messages 105 | @server.check_for_version!("1.2") 106 | 107 | @server.invoke_command("show-messages -t #@device").split("\n") 108 | end 109 | 110 | # Displays a visible indicator of each {Pane pane} shown by a client. 111 | # 112 | # @tmux display-panes 113 | # @return [void] 114 | # @tmuxver >=1.0 115 | def display_panes 116 | @server.check_for_version!("1.0") 117 | 118 | @server.invoke_command("display-panes -t #@device") 119 | end 120 | 121 | # @!attribute [r] current_window 122 | # 123 | # @return [Window] The currently displayed {Window window}. 124 | # @tmuxver >=1.2 125 | def current_window 126 | @server.check_for_version!("1.2") 127 | 128 | num = message_stdout("#I") 129 | session.windows[num.to_i] 130 | end 131 | 132 | # @!attribute [r] current_pane 133 | # 134 | # @return [Pane] The currently displayed {Pane pane}. 135 | # @tmuxver >=1.2 136 | def current_pane 137 | @server.check_for_version!("1.2") 138 | 139 | output = @server.invoke_command "display-message -p -t #@device" 140 | current_pane = output.match(/^.+?, current pane (\d+) . .+$/)[1] 141 | current_window.panes[current_pane.to_i] 142 | end 143 | 144 | # Displays a message. 145 | # 146 | # @param [String] text The message to display 147 | # @tmux display-message 148 | # @return [void] 149 | # @tmuxver >=1.0 150 | def message(text) 151 | @server.check_for_version!("1.0") 152 | 153 | client_switch = @server.version < "1.5" ? "-t" : "-c" 154 | @server.invoke_command "display-message #{client_switch} #@device \"#{text}\"" 155 | end 156 | 157 | # @api private 158 | def message_stdout(text) 159 | @server.check_for_version!("1.0") 160 | 161 | client_switch = @server.version < "1.5" ? "-t" : "-c" 162 | @server.invoke_command "display-message -p #{client_switch} #@device \"#{text}\"".chomp 163 | end 164 | 165 | # Opens a prompt inside a client allowing a {Window window} index to be entered interactively. 166 | # 167 | # @tmux command-prompt + select-window 168 | # @return [void] 169 | def select_interactively 170 | command_prompt "select-window -t:%%", ["index"] 171 | end 172 | 173 | # Opens a command prompt in the client. This may be used to 174 | # execute commands interactively. 175 | # 176 | # @param [String] template The template is used as the command to 177 | # execute. Before the command is executed, the first occurrence 178 | # of the string '%%' and all occurrences of '%1' are replaced by 179 | # the response to the first prompt, the second '%%' and all '%2' 180 | # are replaced with the response to the second prompt, and so on 181 | # for further prompts. Up to nine prompt responses may be 182 | # replaced ('%1' to '%9') 183 | # 184 | # @param [Array] prompts prompts is a list 185 | # of prompts which are displayed in order; otherwise a single 186 | # prompt is displayed, constructed from template 187 | # 188 | # @return [void] 189 | # @tmux command-prompt 190 | # @todo escape prompts and template 191 | def command_prompt(template, prompts = []) 192 | prompts = prompts.join(",") 193 | flags = [] 194 | flags << "-p #{prompts}" unless prompts.empty? 195 | flags << "-t #{identifier}" 196 | flags << "\"#{template}\"" 197 | @server.invoke_command "command-prompt #{flags.join(" ")}" 198 | end 199 | end 200 | end 201 | -------------------------------------------------------------------------------- /lib/tmux/exception.rb: -------------------------------------------------------------------------------- 1 | require "tmux/exception/basic_exception" 2 | require "tmux/exception/in_tmux" 3 | require "tmux/exception/index_in_use" 4 | require "tmux/exception/unknown_command" 5 | require "tmux/exception/unsupported_version" 6 | -------------------------------------------------------------------------------- /lib/tmux/exception/basic_exception.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Exception 3 | class BasicException < StandardError 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tmux/exception/in_tmux.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Exception 3 | class InTmux < RuntimeError 4 | def initialize(command) 5 | super("This command should not be run from inside tmux: #{command}") 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tmux/exception/index_in_use.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Exception 3 | class IndexInUse < RuntimeError 4 | def initialize(args) 5 | super "Index '%s' in session '%s' in use" % [args.last, args.first.identifier] 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tmux/exception/unknown_command.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Exception 3 | class UnknownCommand < RuntimeError 4 | def initialize(command) 5 | super("unknown command: #{command}") 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tmux/exception/unsupported_version.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Exception 3 | # Raised if a version requirement isn't met 4 | class UnsupportedVersion < BasicException 5 | # @param [String] version The required version 6 | def initialize(version = nil) 7 | if message 8 | version = "Required tmux version: #{version}" 9 | end 10 | 11 | super 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tmux/filterable_hash.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # @api private 3 | module FilterableHash 4 | # @param [Hash] search 5 | # @return [Hash] 6 | # @api private 7 | def filter(search) 8 | self.select { |key, value| 9 | value.all? { |v_key, v_value| 10 | !search.has_key?(v_key) || v_value == search[v_key] 11 | } 12 | } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tmux/options.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | 3 | module Options 4 | require "tmux/options/option" 5 | require "tmux/options/number_option" 6 | require "tmux/options/string_option" 7 | require "tmux/options/boolean_option" 8 | require "tmux/options/word_array_option" 9 | require "tmux/options/char_array_option" 10 | require "tmux/options/symbol_option" 11 | require "tmux/options/bell_action_option" 12 | require "tmux/options/color_option" 13 | require "tmux/options/attr_option" 14 | require "tmux/options/keymap_option" 15 | require "tmux/options/justification_option" 16 | require "tmux/options/clock_mode_style_option" 17 | 18 | # Table with option names and their appropriate typecasts. 19 | Mapping = { 20 | "base-index" => NumberOption, 21 | "bell-action" => BellActionOption, 22 | "buffer-limit" => NumberOption, 23 | "default-command" => StringOption, 24 | "default-path" => StringOption, 25 | "default-shell" => StringOption, 26 | "default-terminal" => StringOption, 27 | "detach-on-destroy" => BooleanOption, 28 | "display-panes-colour" => ColorOption, 29 | "display-panes-active-colour" => ColorOption, 30 | "display-panes-time" => NumberOption, 31 | "display-time" => NumberOption, 32 | "history-limit" => NumberOption, 33 | "lock-after-time" => NumberOption, 34 | "lock-command" => StringOption, 35 | "lock-server" => BooleanOption, 36 | "message-attr" => AttrOption, 37 | "message-bg" => ColorOption, 38 | "message-fg" => ColorOption, 39 | "message-limit" => NumberOption, 40 | "mouse-select-pane" => BooleanOption, 41 | "pane-active-border-bg" => ColorOption, 42 | "pane-active-border-fg" => ColorOption, 43 | "pane-border-bg" => ColorOption, 44 | "pane-border-fg" => ColorOption, 45 | "prefix" => SymbolOption, # C-b # TODO keycombo 46 | "repeat-time" => NumberOption, 47 | "set-remain-on-exit" => BooleanOption, 48 | "set-titles" => BooleanOption, 49 | "set-titles-string" => StringOption, 50 | "status" => BooleanOption, 51 | "status-attr" => AttrOption, 52 | "status-bg" => ColorOption, 53 | "status-fg" => ColorOption, 54 | "status-interval" => NumberOption, 55 | "status-justify" => JustificationOption, 56 | "status-keys" => KeymapOption, 57 | "status-left" => StringOption, 58 | "status-left-attr" => AttrOption, 59 | "status-left-bg" => ColorOption, 60 | "status-left-fg" => ColorOption, 61 | "status-left-length" => NumberOption, 62 | "status-right" => StringOption, 63 | "status-right-attr" => AttrOption, 64 | "status-right-bg" => ColorOption, 65 | "status-right-fg" => ColorOption, 66 | "status-right-length" => NumberOption, 67 | "status-utf8" => BooleanOption, 68 | "terminal-overrides" => StringOption, #TODO "*88col*:colors=88,*256col*:colors=256" 69 | "update-environment" => WordArrayOption, 70 | "visual-activity" => BooleanOption, 71 | "visual-bell" => BooleanOption, 72 | "visual-content" => BooleanOption, 73 | "escape-time" => NumberOption, 74 | "quiet" => BooleanOption, 75 | "aggressive-resize" => BooleanOption, 76 | "alternate-screen" => BooleanOption, 77 | "automatic-rename" => BooleanOption, 78 | "clock-mode-colour" => ColorOption, 79 | "clock-mode-style" => ClockModeStyleOption, 80 | "force-height" => NumberOption, 81 | "force-width" => NumberOption, 82 | "main-pane-height" => NumberOption, 83 | "main-pane-width" => NumberOption, 84 | "mode-attr" => AttrOption, 85 | "mode-bg" => ColorOption, 86 | "mode-fg" => ColorOption, 87 | "mode-keys" => KeymapOption, 88 | "mode-mouse" => BooleanOption, 89 | "monitor-activity" => BooleanOption, 90 | "monitor-content" => StringOption, 91 | "remain-on-exit" => BooleanOption, 92 | "synchronize-panes" => BooleanOption, 93 | "utf8" => BooleanOption, 94 | "window-status-alert-attr" => AttrOption, 95 | "window-status-alert-bg" => ColorOption, 96 | "window-status-alert-fg" => ColorOption, 97 | "window-status-attr" => AttrOption, 98 | "window-status-bg" => ColorOption, 99 | "window-status-current-attr" => AttrOption, 100 | "window-status-current-bg" => ColorOption, 101 | "window-status-current-fg" => ColorOption, 102 | "window-status-current-format" => StringOption, 103 | "window-status-fg" => ColorOption, 104 | "window-status-format" => StringOption, 105 | "word-separators" => CharArrayOption, 106 | "xterm-keys" => BooleanOption, 107 | } 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/tmux/options/attr_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/symbol_option" 2 | module Tmux 3 | module Options 4 | # @todo maintain list of valid attributes 5 | # @api private 6 | # @see Option 7 | class AttrOption < SymbolOption 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/tmux/options/bell_action_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/symbol_option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class BellActionOption < SymbolOption 7 | class << self 8 | # @param (see Option.to_tmux) 9 | # @return (see Option.to_tmux) 10 | # @see Option.to_tmux 11 | # @api private 12 | def to_tmux(value) 13 | raise ArgumentError unless [:any, :none, :current, :default].include?(value) 14 | super 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/tmux/options/boolean_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class BooleanOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Boolean] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || value == "on" 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value ? "on" : "off" 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options/char_array_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class CharArrayOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Array, Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || StringOption.from_tmux(value).split("") 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value.join("") 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options/clock_mode_style_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/symbol_option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class ClockModeStyleOption < SymbolOption 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || {12 => :twelve, 24 => :twenty_four}[value] 14 | end 15 | 16 | # @param [Symbol<:twelve, :twenty_four>] value 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | raise ArgumentError unless [:twelve, :twenty_four].include?(value) 22 | super 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/tmux/options/color_option.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "tmux/options/symbol_option" 3 | module Tmux 4 | module Options 5 | # @api private 6 | # @see Option 7 | class ColorOption < SymbolOption 8 | class << self 9 | # @param [Symbol<:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :colour0 – colour255, :default>] value 10 | # @return (see Option.to_tmux) 11 | # @see Option.to_tmux 12 | # @api private 13 | def to_tmux(value) 14 | if ![:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white, :default].include?(value) && 15 | value !~ /^colour\d+$/ 16 | raise ArgumentError 17 | end 18 | super 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tmux/options/justification_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/symbol_option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class JustificationOption < SymbolOption 7 | class << self 8 | # @param [Symbol<:left, :right, :centre, :default>] value 9 | # @return (see Option.to_tmux) 10 | # @see Option.to_tmux 11 | # @api private 12 | def to_tmux(value) 13 | raise ArgumentError unless [:left, :right, :centre, :default].include?(value) 14 | super 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/tmux/options/keymap_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/symbol_option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class KeymapOption < SymbolOption 7 | class << self 8 | # @param [Symbol<:emacs, :vi, :default>] value 9 | # @return (see Option.to_tmux) 10 | # @see Option.to_tmux 11 | # @api private 12 | def to_tmux(value) 13 | raise ArgumentError unless [:emacs, :vi, :default].include?(value) 14 | super 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/tmux/options/number_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class NumberOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Number, Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || value.to_i 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value.to_s 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options/option.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | module Options 3 | # This class is the base for all typecasting in ruby-tmux. It will 4 | # handle `default` and `none`. All other conversions have to be 5 | # done by classes inheriting from this one. You should never have 6 | # to instantiate or work with any of those classes yourself. 7 | # 8 | # @api private 9 | # @abstract 10 | class Option 11 | class << self 12 | # Takes an option value from tmux and converts it to an appropriate Ruby object. 13 | # 14 | # @param [String] value the value to cast 15 | # @return [Object, Symbol] Either the specific Ruby object, or either `:default` or `:none` 16 | # @api private 17 | # @see Subclasses 18 | def from_tmux(value) 19 | if [:default, :none].include?(value) 20 | return value.to_sym 21 | end 22 | end 23 | 24 | # Converts a Ruby object to a value for tmux. 25 | # 26 | # @param [Object] value the value to cast 27 | # @return [String] 28 | # @api private 29 | # @see Subclasses 30 | def to_tmux(value) 31 | if [:default, :none].include?(value) 32 | return value.to_s 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/tmux/options/string_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class StringOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [String, Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || value.gsub(/^"|"$/, "") 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value.inspect.gsub(/^"|"$/, "") 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options/symbol_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class SymbolOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || value.to_sym 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value.to_s 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options/word_array_option.rb: -------------------------------------------------------------------------------- 1 | require "tmux/options/option" 2 | module Tmux 3 | module Options 4 | # @api private 5 | # @see Option 6 | class WordArrayOption < Option 7 | class << self 8 | # @param (see Option.from_tmux) 9 | # @return [Array, Symbol] 10 | # @api private 11 | # @see Option.from_tmux 12 | def from_tmux(value) 13 | super || StringOption.from_tmux(value).split(" ") 14 | end 15 | 16 | # @param (see Option.to_tmux) 17 | # @return (see Option.to_tmux) 18 | # @see Option.to_tmux 19 | # @api private 20 | def to_tmux(value) 21 | super || value.join(" ") 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/tmux/options_list.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # OptionsList offers an easy way of querying and setting tmux 3 | # options, taking care of typecasting. Note: You should not have to 4 | # instantiate this class but use the respective `::options` and 5 | # `#options` methods instead. 6 | # 7 | # @see Server#options 8 | # @see Session.options 9 | # @see Session#options 10 | # @see Window.options 11 | # @see Window#options 12 | class OptionsList 13 | include Enumerable 14 | # @param [Symbol<:server, :session, :window>] kind Which options to operate on 15 | # @param [Server, Session, Window] target The target to operate 16 | # on. Should be an instance of {Server} for global options 17 | # @param [Boolean] global Operate on global options? 18 | def initialize(kind, target, global = false) 19 | @kind = kind 20 | @global = global 21 | @target = target 22 | end 23 | 24 | # Calls block once for each option. 25 | # 26 | # @yield [option, value] 27 | # @yieldparam [String] option Name of the option 28 | # @yieldparam [Object] value Value of the option 29 | # @return [OptionsList] self 30 | def each 31 | get_matching(//).each do |key, value| 32 | yield [key, value] 33 | end 34 | self 35 | end 36 | 37 | # @param [Regexp] regexp The regexp which all returned option names have 38 | # to match 39 | # @param [Boolean, nil] global Operate on global options? Inherits from @global if nil 40 | # @return [Hash] Returns a hash of all options 41 | # that match `regexp`, and their values. 42 | # @api private 43 | def get_matching(regexp, global = nil) 44 | option_lines = server.invoke_command("show-options #{argument_string(global)}").each_line.select { |line| 45 | line =~ /^#{regexp}/ 46 | } 47 | 48 | values = {} 49 | option_lines.each do |option_line| 50 | option, value = option_line.chomp.split(" ", 2) 51 | mapping = Options::Mapping[option] 52 | value = mapping ? mapping.from_tmux(value) : value 53 | values[option] = value 54 | end 55 | 56 | values 57 | end 58 | 59 | # Returns the value of an option. If the OptionsList does not 60 | # operate on global options, but the requested option could not be 61 | # found locally, it will be searched for globally, obeying option 62 | # inheritance of Tmux. 63 | # 64 | # @param [String] option Name of the option 65 | # @return [Object] 66 | def get(option) 67 | value = get_matching(option).values.first 68 | if value.nil? && !@global 69 | return get_matching(option, true).values.first 70 | else 71 | value 72 | end 73 | end 74 | 75 | # Sets an option. 76 | # 77 | # @param [String] option Name of the option 78 | # @param [Object] value New value of the option. Will 79 | # automatically be converted to a string valid to Tmux. 80 | # @return [Object] `value` 81 | # @raise [RuntimeError] Raised if the new value is invalid 82 | def set(option, value) 83 | mapping = Options::Mapping[option] 84 | value = mapping.to_tmux(value) if mapping 85 | ret = server.invoke_command "set-option #{argument_string} #{option} \"#{value}\"" 86 | if ret =~ /^value is invalid:/ 87 | raise RuntimeError, ret 88 | end 89 | value 90 | end 91 | 92 | # Unsets an option. Note: global options cannot be unset. 93 | # 94 | # @param [String] option Name of the option 95 | # @raise [RuntimeError] Raised if you try to unset a global option. 96 | # @return [void] 97 | def unset(option) 98 | raise RuntimeError, "Cannot unset global option" if @global 99 | server.invoke_command "set-option #{argument_string(nil, ["-u"])} #{option}" 100 | end 101 | 102 | # Unknown methods will be treated as {#get getters} and {#set 103 | # setters} for options. Dashes in option names have to be replaced 104 | # with underscores. 105 | # 106 | # @return [void] 107 | def method_missing(m, *args) 108 | option = m.to_s.tr("_", "-") 109 | if option[-1..-1] == "=" 110 | option = option[0..-2] 111 | set(option, args.first) 112 | else 113 | get(option) 114 | end 115 | end 116 | 117 | # @return [String, nil] 118 | # @api private 119 | def kind_flag 120 | { 121 | :server => "-s", 122 | :session => nil, 123 | :window => "-w", 124 | }[@kind] 125 | end 126 | private :kind_flag 127 | 128 | # @param [Boolean, nil] global Operate on global options? Inherits from @global if nil 129 | # @param [Array] inject Flags to inject into the argument string 130 | # @return [String] 131 | # @api private 132 | def argument_string(global = nil, inject = []) 133 | global = @global if global.nil? 134 | flags = [] 135 | flags << "-g" if global 136 | flags << kind_flag 137 | flags.concat inject 138 | flags << "-t #{@target.identifier}" if !global && @target && !@target.is_a?(Server) 139 | 140 | flags.compact.join(" ") 141 | end 142 | private :argument_string 143 | 144 | # @return [Server] 145 | def server 146 | @target.server 147 | end 148 | private :server 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/tmux/pane.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # A {Window window} occupies the entire 3 | # screen and may be split into rectangular {Pane panes}, each of 4 | # which is a separate pseudo terminal (the pty(4) manual page 5 | # documents the technical details of pseudo terminals). 6 | class Pane 7 | include Comparable 8 | 9 | # @return [Window] 10 | attr_reader :window 11 | # @return [Number] 12 | attr_reader :number 13 | def initialize(window, number) 14 | @window, @number = window, number 15 | end 16 | 17 | # @return [Boolean] 18 | def ==(other) 19 | self.class == other.class && @window == other.window && @number = other.number 20 | end 21 | 22 | # @return [Number] 23 | def hash 24 | [@window.hash, @number].hash 25 | end 26 | 27 | # @return [Boolean] 28 | def eql?(other) 29 | self == other 30 | end 31 | 32 | def <=>(other) 33 | return nil unless other.is_a?(Pane) 34 | [@window, @number] <=> [other.window, other.number] 35 | end 36 | 37 | # @!attribute [r] server 38 | # 39 | # @return [Server] 40 | def server 41 | @window.server 42 | end 43 | 44 | # @!attribute [r] identifier 45 | # 46 | # @return [String] 47 | def identifier 48 | @window.identifier + "." + @number.to_s 49 | end 50 | 51 | # @!attribute [r] width 52 | # 53 | # @return [Integer] 54 | # @tmuxver >=1.1 55 | def width 56 | server.check_for_version!("1.1") 57 | 58 | @window.panes_information[@number][:width] 59 | end 60 | 61 | # @!attribute [r] height 62 | # 63 | # @return [Integer] 64 | # @tmuxver >=1.1 65 | def height 66 | server.check_for_version!("1.1") 67 | 68 | @window.panes_information[@number][:height] 69 | end 70 | 71 | # @!attribute [r] max_history_size 72 | # 73 | # @return [Integer] 74 | # @tmuxver >=1.1 75 | def max_history_size 76 | server.check_for_version!("1.1") 77 | 78 | @window.panes_information[@number][:max_history] 79 | end 80 | 81 | # @!attribute [r] current_history_size 82 | # 83 | # @return [Integer] 84 | # @tmuxver >=1.1 85 | def current_history_size 86 | server.check_for_version!("1.1") 87 | 88 | @window.panes_information[@number][:cur_history] 89 | end 90 | 91 | # @!attribute [r] memory_usage 92 | # 93 | # @return [Filesize] 94 | # @tmuxver >=1.1 95 | def memory_usage 96 | server.check_for_version!("1.1") 97 | 98 | @window.panes_information[@number][:memory] 99 | end 100 | 101 | # @!attribute [r] active 102 | # 103 | # @return [Boolean] True if the pane is the currently selected one 104 | # in its window. 105 | # @tmuxver >=1.4 106 | def active 107 | server.check_for_version!("1.4") 108 | 109 | @window.panes_information[@number][:active] 110 | end 111 | alias_method :active?, :active 112 | 113 | # @group Modes 114 | 115 | # Enter copy mode. 116 | # 117 | # @return [void] 118 | # @tmuxver >=1.0 119 | # @tmux copy-mode 120 | def copy_mode 121 | server.check_for_version!("1.0") 122 | 123 | server.invoke_command "copy-mode -t #{identifier}" 124 | end 125 | 126 | # Displays a clock in the pane. 127 | # 128 | # @return [void] 129 | # @tmuxver >=1.0 130 | # @tmux clock-mode 131 | def clock_mode 132 | server.check_for_version!("1.0") 133 | 134 | server.invoke_command "clock-mode -t #{identifier}" 135 | end 136 | alias_method :show_clock, :clock_mode 137 | 138 | # @endgroup 139 | 140 | # Breaks the pane off from its containing {Window window} to make 141 | # it the only pane in a new {Window window}. 142 | # 143 | # @param [Boolean] select If true, the new {Window window} will be 144 | # selected automatically 145 | # @return [Pane] 146 | # @tmuxver >=1.0 147 | # @tmux break-pane 148 | def break(select = true) 149 | server.check_for_version!("1.0") 150 | 151 | server.invoke_command "break-pane -t #{identifier}" 152 | num_window, num_pane = @window.session.any_client.message_stdout("#I:#P").split(":") 153 | session = @window.session 154 | window = Window.new(session, num_window) 155 | pane = Pane.new(window, num_pane) 156 | unless select 157 | session.select_last_window 158 | end 159 | return pane 160 | end 161 | 162 | # @group Killing 163 | 164 | # Kills the pane. 165 | # 166 | # @tmux kill-pane 167 | # @return [void] 168 | # @tmuxver >=1.0 169 | def kill 170 | server.check_for_version!("1.0") 171 | 172 | server.invoke_command "kill-pane -t #{identifier}" 173 | end 174 | 175 | # Kills all other panes. 176 | # 177 | # @tmux kill-pane -a 178 | # @return [void] 179 | # @tmuxver >=1.1 180 | def kill_others 181 | server.check_for_version!("1.1") 182 | 183 | server.invoke_command "kill-pane -a -t #{identifier}" 184 | end 185 | 186 | # @endgroup 187 | 188 | # Removes and frees the history of the pane. 189 | # 190 | # @tmux clear-history 191 | # @return [void] 192 | # @tmuxver >=1.0 193 | def clear_history 194 | server.check_for_version!("1.0") 195 | 196 | server.invoke_command "clear-history -t #{identifier}" 197 | end 198 | 199 | # Swaps the pane with another one. 200 | # 201 | # @param [Pane] pane The pane to swap with. 202 | # @return [void] 203 | # @tmuxver >=1.0 204 | def swap_with(pane) 205 | server.check_for_version!("1.0") 206 | 207 | server.invoke_command "swap-pane -s #{identifier} -t #{pane.identifier}" 208 | end 209 | 210 | # @group Input 211 | 212 | # Sends a key to the pane. 213 | # 214 | # @param [String] key 215 | # @see #send_keys 216 | # @return [void] 217 | # @tmuxver >=1.0 218 | def send_key(key) 219 | server.check_for_version!("1.0") 220 | 221 | send_keys([key]) 222 | end 223 | 224 | # Sends keys to the pane. 225 | # 226 | # @param [Array] keys 227 | # @return [void] 228 | # @tmuxver >=1.0 229 | def send_keys(keys) 230 | server.check_for_version!("1.0") 231 | 232 | keychain = [] 233 | keys.each do |key| 234 | case key 235 | when '"' 236 | keychain << '"\\' + key + '"' 237 | else 238 | keychain << '"' + key + '"' 239 | end 240 | end 241 | server.invoke_command "send-keys -t #{identifier} #{keychain.join(" ")}" 242 | end 243 | 244 | # Runs a command in the pane. Note: this is experimental, hacky 245 | # and might and will break. 246 | # 247 | # @param [String] command 248 | # @return [void] 249 | # @tmuxver >=1.0 250 | def run(command) 251 | server.check_for_version!("1.0") 252 | 253 | write(command) 254 | send_key "Enter" 255 | end 256 | 257 | # Writes text to the pane. This is basically the same as {Pane#run}, 258 | # but without sending a final Return. 259 | # 260 | # @param [String] text 261 | # @tmuxver >=1.0 262 | # @return [void] 263 | # @see Pane#run 264 | def write(text) 265 | server.check_for_version!("1.0") 266 | 267 | send_keys(text.split("")) 268 | end 269 | 270 | # Pastes a {Buffer buffer} into the pane. 271 | # 272 | # @param [Buffer] buffer The {Buffer buffer} to paste 273 | # @param pop (see Buffer#paste) 274 | # @param translate (see Buffer#paste) 275 | # @param separator (see Buffer#paste) 276 | # @return [void] 277 | # @tmux paste-buffer 278 | # @see Buffer#paste 279 | # @see Window#paste 280 | # @tmuxver >=1.3 281 | def paste(buffer, pop = false, translate = true, separator = nil) 282 | server.check_for_version!("1.3") 283 | 284 | buffer.paste(self, pop, translate, separator) 285 | end 286 | 287 | # @endgroup 288 | 289 | # Split the pane and move an existing pane into the new area. 290 | # 291 | # @param [Pane] pane The {Pane pane} to join 292 | # 293 | # @option args [Boolean] :make_active (true) Switch to the newly generated pane 294 | # @option args [Symbol<:vertical, :horizontal>] :direction (:vertical) The direction to split in 295 | # @option args [Number] :size Size of the new pane in lines (for vertical split) or in cells (for horizontal split) 296 | # @option args [Number] :percentage Size of the new pane in percent. 297 | def join(pane, args = {}) 298 | server.check_for_version!("1.2") 299 | args = { 300 | :make_active => true, 301 | :direction => :vertical, 302 | }.merge(args) 303 | flags = split_or_join_flags(args) 304 | flags << "-s #{pane.identifier}" 305 | flags << "-t #{identifier}" 306 | 307 | server.invoke_command "join-pane #{flags.join(" ")} " 308 | if args[:make_active] 309 | num = @window.session.any_client.message_stdout("#P") 310 | return Pane.new(@window, num) 311 | else 312 | return nil 313 | end 314 | end 315 | 316 | # join-pane [-dhv] [-l size | -p percentage] [-s src-pane] [-t dst-pane] 317 | # split-window [-dhv] [-l size | -p percentage] [-t target-pane] [shell-command] 318 | 319 | def split_or_join_flags(args) 320 | flags = [] 321 | flags << "-d" unless args[:make_active] 322 | flags << case args[:direction] 323 | when :vertical 324 | "-v" 325 | when :horizontal 326 | "-h" 327 | else 328 | raise ArgumentError 329 | end 330 | 331 | raise ArgumentError if args[:size] && args[:percentage] 332 | if args[:size] 333 | flags << "-l #{args[:size]}" 334 | elsif args[:percentage] 335 | flags << "-p #{args[:percentage]}" 336 | end 337 | 338 | return flags 339 | end 340 | private :split_or_join_flags 341 | 342 | # Splits the pane. 343 | # 344 | # @return [Pane, nil] Returns the newly created pane, but only if 345 | # :make_active is true or if using tmux >=1.4. See 346 | # http://sourceforge.net/tracker/?func=detail&aid=3030471&group_id=200378&atid=973265 347 | # for more information. 348 | # 349 | # @option args [Boolean] :make_active (true) Switch to the newly generated pane 350 | # @option args [Symbol<:vertical, :horizontal>] :direction (:vertical) The direction to split in 351 | # @option args [Number] :size Size of the new pane in lines (for vertical split) or in cells (for horizontal split) 352 | # @option args [Number] :percentage Size of the new pane in percent. 353 | # @option args [String] :command Command to run in the new pane (optional) 354 | # 355 | # @tmux split-window 356 | # @tmuxver >=1.2 357 | def split(args = {}) 358 | server.check_for_version!("1.2") 359 | args = { 360 | :make_active => true, 361 | :direction => :vertical, 362 | }.merge(args) 363 | 364 | # Since tmux 1.4 we have the last-pane command, which allows us 365 | # to temporarily select the new pane, get its identifer and 366 | # select the last pane again. 367 | temporarily_make_active = false 368 | if server.version >= "1.4" && !args[:make_active] 369 | args[:make_active] = true 370 | temporarily_make_active = true 371 | end 372 | 373 | flags = split_or_join_flags(args) 374 | 375 | flags << "-t #{identifier}" 376 | flags << '"' + args[:command] + '"' if args[:command] # TODO escape 377 | 378 | server.invoke_command "split-window #{flags.join(" ")} " 379 | if args[:make_active] 380 | num = @window.session.any_client.message_stdout("#P").chomp 381 | 382 | if temporarily_make_active 383 | @window.select_last_pane(:never) 384 | end 385 | 386 | return Pane.new(@window, num) 387 | else 388 | return nil 389 | end 390 | end 391 | 392 | # Resizes the pane. 393 | # 394 | # @param [Symbol<:up, :down, :left, :right>] direction Direction 395 | # in which to resize 396 | # @param [Number] adjustment How many lines or cells to resize. 397 | # @return [void] 398 | def resize(direction, adjustment = 1) 399 | raise ArgumentError unless [:up, :down, :left, :right].include?(direction) 400 | direction = direction.to_s.upcase[0..0] 401 | server.invoke_command "resize-pane -#{direction} -t #{identifier} #{adjustment}" 402 | end 403 | 404 | # @group Selecting 405 | 406 | # @param [Symbol<:up, :down, :left, :right>] direction direction to move to 407 | # @param [Symbol<:never, :if_same_window, :always>] return_new whether to return the pane we moved 408 | # to. 409 | # 410 | # Note: In tmux versions prior to 1.4, :always can lead to flickering 411 | # Note: Since tmux version 1.4, :always is forced 412 | # @tmuxver >=1.3 413 | # @return [Pane, nil] 414 | def select_direction(direction, return_new = :if_same_window) 415 | raise ArgumentError unless [:up, :down, :left, :right].include?(direction) 416 | direction = direction.to_s.upcase[0..0] 417 | server.invoke_command "select-pane -#{direction} -t #{identifier}" 418 | 419 | return @window.current_pane(return_new) 420 | end 421 | 422 | # @tmuxver (see Tmux::Pane#select_direction) 423 | # @param return_new (see Tmux::Pane#select_direction) 424 | # @return (see Tmux::Pane#select_direction) 425 | # @see Pane#select_direction 426 | def select_up(return_new = :if_same_window) 427 | select_direction(:up, return_new) 428 | end 429 | 430 | # @tmuxver (see Tmux::Pane#select_direction) 431 | # @param return_new (see Tmux::Pane#select_direction) 432 | # @return (see Tmux::Pane#select_direction) 433 | # @see Pane#select_direction 434 | def select_down(return_new = :if_same_window) 435 | select_direction(:down, return_new) 436 | end 437 | 438 | # @tmuxver (see Tmux::Pane#select_direction) 439 | # @param return_new (see Tmux::Pane#select_direction) 440 | # @return (see Tmux::Pane#select_direction) 441 | # @see Pane#select_direction 442 | def select_left(return_new = :if_same_window) 443 | select_direction(:left, return_new) 444 | end 445 | 446 | # @tmuxver (see Tmux::Pane#select_direction) 447 | # @param return_new (see Tmux::Pane#select_direction) 448 | # @return (see Tmux::Pane#select_direction) 449 | # @see Pane#select_direction 450 | def select_right(return_new = :if_same_window) 451 | select_direction(:right, return_new) 452 | end 453 | 454 | # @return [Pane, nil] 455 | # @param [Number] num how many panes to move down. Note: will be ignored on tmux versions <1.3 456 | # @param return_new (see Tmux::Pane#select_direction) 457 | # @tmuxver >=1.3 for `num` parameter 458 | # @tmux down-pane or select-pane -t:+ 459 | def select_next(num = 1, return_new = :if_same_window) 460 | if server.version > "1.2" 461 | server.invoke_command "select-pane -t #{@window.identifier}.+#{num}" 462 | else 463 | server.invoke_command "down-pane -t #{identifier}" 464 | end 465 | 466 | return @window.current_pane(return_new) 467 | end 468 | 469 | # @return [Pane, nil] 470 | # @param [Number] num how many panes to move up. Note: will be ignored on tmux versions <1.3 471 | # @param return_new (see Tmux::Pane#select_direction) 472 | # @tmuxver >=1.3 for `num` parameter 473 | # @tmux up-pane or select-pane -t:- 474 | def select_previous(num = 1, return_new = :if_same_window) 475 | if server.version > "1.2" 476 | server.invoke_command "select-pane -t #{@window.identifier}.-#{num}" 477 | else 478 | server.invoke_command "up-pane -t #{identifier}" 479 | end 480 | 481 | return @window.current_pane(return_new) 482 | end 483 | 484 | # Selects the pane. 485 | # 486 | # @return [void] 487 | # @tmuxver >=1.0 488 | def select 489 | server.check_for_version!("1.0") 490 | 491 | server.invoke_command "select-pane -t #{identifier}" 492 | end 493 | 494 | # @endgroup 495 | end 496 | end 497 | -------------------------------------------------------------------------------- /lib/tmux/server.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | module Tmux 3 | class Server 4 | include Comparable 5 | 6 | # Creates a new session 7 | # 8 | # @option args [Boolean] :attach (false) Attach to the new session? 9 | # @option args [String] :name (nil) Name of the new session. Will 10 | # be automatically generated of nil. 11 | # @option args [String] :window_name (nil) Name of the initial 12 | # window. Cannot be used when grouping sessions. 13 | # @option args [Session] :group_with (nil) Group with this 14 | # session, sharing all windows. 15 | # @option args [String] :command (nil) Execute this command in the 16 | # initial window. Cannot be used when grouping sessions. 17 | # 18 | # @return [Session, nil] Returns the new {Session session} if a 19 | # `:name` has been given and if `:attach` is false 20 | # 21 | # @raise [ArgumentError] if combining `:group_with` and `:window_name` 22 | # or :command 23 | # 24 | # @tmuxver >=1.4 25 | def create_session(args = {}) 26 | check_for_version!("1.4") 27 | 28 | if args[:group_with] && (args[:window_name] || args[:command]) 29 | raise ArgumentError, "Cannot combine :group_with and :window_name or :command" 30 | end 31 | 32 | # FIXME shell escape names 33 | flags = [] 34 | flags << "-d" unless args[:attach] 35 | flags << "-n '#{args[:window_name]}'" if args[:window_name] 36 | flags << "-s '#{args[:name]}'" if args[:name] 37 | flags << "-t '#{args[:group_with].name}'" if args[:group_with] 38 | flags << args[:command] if args[:command] 39 | 40 | command = "new-session #{flags.join(" ")}" 41 | 42 | ret = invoke_command(command, true) 43 | if ret.start_with?("duplicate session:") 44 | raise RuntimeError, ret 45 | elsif ret.start_with?("sessions should be nested with care.") 46 | raise Exception::InTmux("new-session") 47 | else 48 | if args[:name] and !args[:attach] 49 | return Session.new(self, args[:name]) 50 | end 51 | end 52 | end 53 | 54 | # @return [String] 55 | attr_reader :socket 56 | # @return [OptionsList] 57 | attr_reader :options 58 | # @param [String] socket A socket *name*. 59 | def initialize(socket = "default") 60 | @socket = socket 61 | @options = OptionsList.new(:server, self, false) 62 | end 63 | 64 | # @return [-1, 0, 1] 65 | def <=>(other) 66 | return nil unless other.is_a?(Server) 67 | @socket <=> other.socket 68 | end 69 | 70 | # @!attribute [r] server 71 | # 72 | # @return [Server] Returns self. This is useful for other classes 73 | # which can operate on Server, {Session}, {Window}, {Pane} and so 74 | # on 75 | def server 76 | self 77 | end 78 | 79 | # Invokes a tmux command. 80 | # 81 | # @param [String] command The command to invoke 82 | # @return [void] 83 | def invoke_command(command, unset_tmux = false) 84 | Tmux.invoke_command("-L #@socket #{command}", unset_tmux) 85 | end 86 | 87 | # Kills a server and thus all {Session sessions}, {Window windows} and {Client clients}. 88 | # 89 | # @tmux kill-server 90 | # @return [void] 91 | def kill 92 | invoke_command "kill-server" 93 | end 94 | 95 | # Sources a file, that is load and evaluate it in tmux. 96 | # 97 | # @param [String] file Name of the file to source 98 | # @tmux source-file 99 | # @return [void] 100 | def source_file(file) 101 | invoke_command "source-file #{file}" 102 | end 103 | alias_method :load, :source_file 104 | 105 | # @tmux list-sessions 106 | # @param [Hash] search Filters the resulting hash using {FilterableHash#filter} 107 | # @return [Hash] A hash with information for all sessions 108 | def sessions_information(search = {}) 109 | hash = {} 110 | output = invoke_command "list-sessions" 111 | output.each_line do |session| 112 | params = session.match(/^(?[\w-]+?): (?\d+) windows \(created (?.+?)\) \[(?\d+)x(?\d+)\](?: \((?attached)\))?$/) 113 | 114 | name = params[:name] 115 | num_windows = params[:num_windows].to_i 116 | creation_time = Date.parse(params[:creation_time]) 117 | width = params[:width].to_i 118 | height = params[:height].to_i 119 | attached = !!params[:attached] 120 | 121 | hash[name] = { 122 | :name => name, 123 | :num_windows => num_windows, 124 | :creation_time => creation_time, 125 | :width => width, 126 | :height => height, 127 | :attached => attached, 128 | } 129 | end 130 | hash.extend FilterableHash 131 | hash.filter(search) 132 | end 133 | 134 | # @!attribute [r] sessions 135 | # 136 | # @tmux list-sessions 137 | # @return [Array] All {Session sessions} 138 | def sessions(search = {}) 139 | sessions_information(search).map do |name, information| 140 | Session.new(self, name) 141 | end 142 | end 143 | 144 | # @!attribute [r] session 145 | # 146 | # @return [Session] The first {Session session}. This is 147 | # especially useful if working with a server that only has one 148 | # {Session session}. 149 | def session 150 | sessions.first 151 | end 152 | 153 | # @tmux list-clients 154 | # @param [Hash] search Filters the resulting hash using {FilterableHash#filter} 155 | # @return [Hash] A hash with information for all clients 156 | def clients_information(search = {}) 157 | clients = invoke_command "list-clients" 158 | hash = {} 159 | clients.each_line do |client| 160 | params = client.strip.match(/^(?.+?): (?.+?) \[(?\d+)x(?\d+) (?.+?)\](?: \((?utf8)\))?$/) 161 | device = params[:device] 162 | session = sessions(:name => params[:session]).first 163 | width = params[:width].to_i 164 | height = params[:height].to_i 165 | term = params[:term] 166 | utf8 = !!params[:utf8] 167 | 168 | hash[device] = { 169 | :device => device, 170 | :session => session, 171 | :width => width, 172 | :height => height, 173 | :term => term, 174 | :utf8 => utf8, 175 | } 176 | end 177 | hash.extend FilterableHash 178 | hash.filter(search) 179 | end 180 | 181 | # @!attribute [r] clients 182 | # 183 | # @tmux list-clients 184 | # @return [Array] 185 | def clients(search = {}) 186 | clients_information(search).map { |device, information| 187 | Client.new(self, device) 188 | } 189 | end 190 | 191 | # @!attribute [r] info 192 | # 193 | # @tmux server-info 194 | # @return [String] Information about the server 195 | def info 196 | invoke_command "server-info" 197 | end 198 | 199 | # @!attribute [r] version 200 | # @return [String] Version of the tmux server 201 | def version 202 | @version ||= info.lines.first.split(",").first[/([.\d]+)/] 203 | end 204 | 205 | # Returns all buffers of all sessions. 206 | # 207 | # @return [Array] 208 | # @tmux list-buffers 209 | # @see Session#buffers 210 | def buffers 211 | if @server.version < "1.5" 212 | # Manually walk all sessions 213 | sessions.flat_map {|s| s.buffers} 214 | else 215 | # There's only a global stack, so any session will do 216 | session.buffers 217 | end 218 | end 219 | 220 | # Checks if a version requirement is being met 221 | # 222 | # @param [String] required The version at least required 223 | # @raise [Exception::UnsupportedVersion] Raised if a version requirement isn't met 224 | # @return [void] 225 | def check_for_version!(required) 226 | if required > version 227 | raise Exception::UnsupportedVersion, required 228 | end 229 | end 230 | end 231 | end 232 | -------------------------------------------------------------------------------- /lib/tmux/session.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # A session is a single collection of pseudo terminals under the 3 | # management of {Tmux tmux}. Each session has one or more {Window 4 | # windows} linked to it. A {Window window} occupies the entire 5 | # screen and may be split into rectangular {Pane panes}, each of 6 | # which is a separate pseudo terminal (the pty(4) manual page 7 | # documents the technical details of pseudo terminals). Any number 8 | # of tmux instances may connect to the same session, and any number 9 | # of {Window windows} may be present in the same session. Once all 10 | # sessions are {Session#kill killed}, tmux exits. 11 | class Session 12 | include Comparable 13 | 14 | # @return [Options] 15 | def self.options(session) 16 | OptionsList.new(:session, session, true) 17 | end 18 | 19 | # Creates a new {Window window}. 20 | # 21 | # @option args [Boolean] :after_number (false) If true, the new 22 | # {Window window} will be inserted at the next index up from the 23 | # specified number (or the {Client#current_window current} 24 | # {Window window}), moving {Window windows} up if necessary 25 | # @option args [Boolean] :kill_existing (false) Kill an existing 26 | # {Window window} if it conflicts with a desired number 27 | # @option args [Boolean] :make_active (true) Switch to the newly 28 | # generated {Window window} 29 | # @option args [String] :name Name of the new {Window window} 30 | # (optional) 31 | # @option args [Number] :number Number of the new {Window window} 32 | # (optional) 33 | # @option args [String] :command Command to run in the new {Window 34 | # window} (optional) 35 | # 36 | # @tmux new-window 37 | # @return [Window] The newly created {Window window} 38 | def create_window(args = {}) 39 | args = { 40 | :kill_existing => false, 41 | :make_active => true, 42 | :after_number => false, 43 | }.merge(args) 44 | 45 | flags = [] 46 | # flags << "-d" unless args[:make_active] 47 | flags << "-a" if args[:after_number] 48 | flags << "-k" if args[:kill_existing] 49 | flags << "-n '#{args[:name]}'" if args[:name] # FIXME escaping 50 | flags << if args[:number] 51 | "-t #{identifier}:#{args[:number]}" 52 | else 53 | "-t #{identifier}" 54 | end 55 | flags << args[:command] if args[:command] 56 | 57 | @server.invoke_command "new-window #{flags.join(" ")}" 58 | new_window = current_window 59 | unless args[:make_active] 60 | select_last_window 61 | end 62 | # return Window.new(self, num) 63 | return new_window 64 | end 65 | 66 | 67 | # @!attribute [r] current_window 68 | # 69 | # @see Client#current_window 70 | # @return (see Client#current_window) 71 | def current_window 72 | # TODO figure out when tmux added "(active)" in list-windows and 73 | # use that when possible 74 | any_client.current_window 75 | end 76 | 77 | # @!attribute [r] current_pane 78 | # 79 | # @see Client#current_pane 80 | # @return (see Client#current_pane) 81 | def current_pane 82 | any_client.current_pane 83 | end 84 | 85 | # Returns a {Client client} that is displaying the session. 86 | # 87 | # @return [Client, nil] A {Client client} that is displaying the session. 88 | def any_client 89 | @server.clients({:session => self}).first 90 | end 91 | 92 | # @return [Boolean] 93 | def ==(other) 94 | self.class == other.class && @server == other.server && @name == other.name 95 | end 96 | 97 | # @return [Number] 98 | def hash 99 | [@server.hash, @number].hash 100 | end 101 | 102 | # @return [Boolean] 103 | def eql?(other) 104 | self == other 105 | end 106 | 107 | def <=>(other) 108 | return nil unless other.is_a?(Session) 109 | [@server, @name] <=> [other.server, other.name] 110 | end 111 | 112 | # The session name. 113 | # 114 | # Setting this will rename the session. 115 | # 116 | # @todo escape name 117 | # @tmux rename-session 118 | # @return [String] 119 | attr_reader :name 120 | 121 | # @return [Server] 122 | attr_reader :server 123 | 124 | # @return [OptionsList] 125 | attr_reader :options 126 | 127 | # @return [StatusBar] 128 | attr_reader :status_bar 129 | def initialize(server, name) 130 | @server, @name = server, name 131 | @status_bar = StatusBar.new(self) 132 | @options = OptionsList.new(:session, self, false) 133 | end 134 | 135 | # @!attribute name 136 | # 137 | # @return [String] 138 | def name=(new_name) 139 | raise ArgumentError if new_name.to_s.strip.empty? 140 | ret = @server.invoke_command("rename-session -t #{identifier} '#{new_name}'") 141 | 142 | if ret.start_with?("duplicate session:") 143 | raise RuntimeError, ret 144 | end 145 | 146 | @name = new_name 147 | end 148 | 149 | # @!attribute [r] identifier 150 | # 151 | # @return [String] 152 | def identifier 153 | @name 154 | end 155 | 156 | # Locks the session. 157 | # 158 | # @tmux lock-session 159 | # @return [void] 160 | # @tmuxver >=1.1 161 | def lock 162 | @server.check_for_version!("1.1") 163 | 164 | @server.invoke_command "lock-session -t #{identifier}" 165 | end 166 | 167 | # @!attribute [r] num_windows 168 | # 169 | # @return [Integer] 170 | def num_windows 171 | @server.sessions_information[@name][:num_windows] 172 | end 173 | 174 | # @!attribute [r] creation_time 175 | # 176 | # @return [Time] 177 | def creation_time 178 | @server.sessions_information[@name][:creation_time] 179 | end 180 | alias_method :created_at, :creation_time 181 | 182 | # @!attribute [r] width 183 | # 184 | # @return [Integer] 185 | def width 186 | @server.sessions_information[@name][:width] 187 | end 188 | 189 | # @!attribute [r] height 190 | # 191 | # @return [Integer] 192 | def height 193 | @server.sessions_information[@name][:height] 194 | end 195 | 196 | # @!attribute [r] attached 197 | # 198 | # @return [Boolean] 199 | def attached 200 | @server.sessions_information[@name][:attached] 201 | end 202 | alias_method :attached?, :attached 203 | 204 | # @!attribute [r] clients 205 | # 206 | # @return [Array] All {Client clients} 207 | def clients 208 | @server.clients({:session => self}) 209 | end 210 | 211 | # Attach to a session. Replaces the ruby process. 212 | # 213 | # @return [void] 214 | # @tmux attach 215 | def attach 216 | exec "#{Tmux::BINARY} attach -t #{identifier}" 217 | end 218 | 219 | # Kills the session. 220 | # 221 | # @tmux kill-session 222 | # @return [void] 223 | def kill 224 | @server.invoke_command "kill-session -t #{identifier}" 225 | end 226 | 227 | # @tmux list-windows 228 | # @tmuxver >=1.1 229 | # @param [Hash] search Filters the resulting hash using {FilterableHash#filter} 230 | # @return [Hash] A hash with information for all windows 231 | # @return [Hash] 232 | def windows_information(search = {}) 233 | @server.check_for_version!("1.1") 234 | 235 | hash = {} 236 | output = @server.invoke_command "list-windows -t #{identifier}" 237 | output.each_line do |session| 238 | # TODO make use of the layout information 239 | # TODO make use of pane count value in later tmux versions 240 | params = session.match(/^(?\d+): (?.*)( \(\d+ panes\))? \[(?\d+)x(?\d+)\](?: \[layout .+?\])?( @\d+)?(? \(active\))?$/) 241 | next if params.nil? # >=1.3 displays layout information in indented lines 242 | num = params[:num].to_i 243 | name = params[:name] 244 | width = params[:width].to_i 245 | height = params[:height].to_i 246 | active = !params[:active].nil? 247 | 248 | hash[num] = {:num => num, :name => name, :width => width, :height => height, :active => active} 249 | end 250 | hash.extend FilterableHash 251 | hash.filter(search) 252 | end 253 | 254 | # @!attribute [r] windows 255 | # 256 | # @tmux list-windows 257 | # @return [Hash{Number => Window}] All {Window windows} 258 | # @tmuxver >=1.1 259 | def windows 260 | hash = {} 261 | @server.check_for_version!("1.1") 262 | 263 | windows_information.each do |num, information| 264 | hash[num] = Window.new(self, num) 265 | end 266 | hash 267 | end 268 | 269 | # @param [Hash] search Filters the resulting hash using {FilterableHash#filter} 270 | # @return [Hash] A hash with information for all buffers 271 | # @tmux list-buffers 272 | def buffers_information(search = {}) 273 | hash = {} 274 | if @server.version < "1.5" 275 | buffers = @server.invoke_command "list-buffers -t #{identifier}" 276 | else 277 | buffers = @server.invoke_command "list-buffers" 278 | end 279 | 280 | buffers.each_line do |buffer| 281 | num, size = buffer.match(/^(\d+): (\d+) bytes/)[1..2] 282 | hash[num] = {:size => size} 283 | end 284 | hash.extend FilterableHash 285 | hash.filter(search) 286 | end 287 | 288 | # @!attribute [r] buffers 289 | # 290 | # @tmux list-buffers 291 | # @return [Array] All {Buffer buffers} 292 | # @note Beginning with tmux 1.5, {Buffer buffers} are global and 293 | # not tied to a session anymore. That means that for 1.5 and 294 | # higher, this method will return all buffers of the server, 295 | # making it identical to {Server#buffers}. 296 | def buffers 297 | buffers_information.map do |num, information| 298 | Buffer.new(num, self) 299 | end 300 | end 301 | 302 | # @group Selecting 303 | 304 | # Select the last (previously selected) window. 305 | # 306 | # @return [Window] 307 | def select_last_window 308 | @server.invoke_command "last-window -t #{identifier}" 309 | current_window 310 | end 311 | 312 | # Selects the next (higher index) window 313 | # 314 | # @param [Number] num How many windows to move 315 | # @tmuxver >=1.3 316 | # @return [Window] 317 | def select_next_window(num = 1) 318 | @server.invoke_command "select-window -t #{identifier}:+#{num}" 319 | current_window 320 | end 321 | 322 | # Selects the previous (lower index) window 323 | # 324 | # @param [Number] num How many windows to move 325 | # @tmuxver >=1.3 326 | # @return [Window] 327 | def select_previous_window(num = 1) 328 | @server.invoke_command "select-window -t:-#{num}" 329 | current_window 330 | end 331 | 332 | # @endgroup 333 | end 334 | end 335 | -------------------------------------------------------------------------------- /lib/tmux/status_bar.rb: -------------------------------------------------------------------------------- 1 | require "tmux/status_bar/field" 2 | 3 | module Tmux 4 | # Every {Session session} has a status bar. This is where various 5 | # information as well as a list of {Window windows} will be 6 | # displayed. For this purpose, the status bar is divided into three 7 | # parts: the left, center and right part. While the center part 8 | # displays the window list, the left and right part can be set to 9 | # display any text. 10 | # 11 | # This class allows accessing various attributes (e.g. the 12 | # {#background_color background color} of the bar) and the 13 | # editable parts ({#left left} and {#right right}). 14 | # 15 | # Note: You will not have to instantiate this class. Use 16 | # {Session#status_bar} instead. 17 | class StatusBar 18 | # @return [Session] 19 | attr_reader :session 20 | # The left {Field field} which may display custom {Field#text 21 | # text} and {Widget widgets}. 22 | # 23 | # @return [Field] 24 | attr_reader :left 25 | 26 | # The right {Field field} which may display custom {Field#text 27 | # text} and {Widget widgets}. 28 | # 29 | # @return [Field] 30 | attr_reader :right 31 | # @param [Session] session 32 | def initialize(session) 33 | @session = session 34 | @left = Field.new(self, :left) 35 | @right = Field.new(self, :right) 36 | end 37 | 38 | # Hides the status bar. 39 | # 40 | # @return [void] 41 | def hide 42 | @session.options.status = false 43 | end 44 | 45 | # Shows the status bar. 46 | # 47 | # @return [void] 48 | def show 49 | @session.options.status = true 50 | end 51 | 52 | # @!attribute background_color 53 | # 54 | # @return [Symbol] 55 | def background_color 56 | @session.options.status_bg 57 | end 58 | 59 | def background_color=(color) 60 | @session.options.status_bg = color 61 | end 62 | 63 | # @!attribute foreground_color 64 | # 65 | # @return [Symbol] 66 | def foreground_color 67 | @session.options.status_fg 68 | end 69 | 70 | def foreground_color=(color) 71 | @session.options.status_fg = color 72 | end 73 | 74 | # @!attribute interval 75 | # 76 | # @return [Number] The interval in which the status bar will be 77 | # updated. 78 | def interval 79 | @session.options.status_interval 80 | end 81 | 82 | def interval=(value) 83 | @session.options.status_interval = value 84 | end 85 | 86 | # @!attribute justification 87 | # 88 | # The justification of the window list component of the status 89 | # line. 90 | # 91 | # @return [Symbol<:left, :right, :centre>] 92 | def justification 93 | @session.options.status_justify 94 | end 95 | 96 | def justification=(val) 97 | @session.options.status_justify = val 98 | end 99 | 100 | # @!attribute keymap 101 | # 102 | # @return [Symbol<:emacs, :vi>] 103 | def keymap 104 | # TODO keymap class? 105 | @session.options.status_keys 106 | end 107 | 108 | def keymap=(val) 109 | @session.options.status_keys = val 110 | end 111 | 112 | # @!attribute utf8 113 | # 114 | # Instruct tmux to treat top-bit-set characters in 115 | # {StatusBar::Field#text} as UTF-8. Notably, this is important for 116 | # wide characters. This option defaults to false. 117 | # 118 | # @return [Boolean] 119 | def utf8 120 | @session.options.status_utf8 121 | end 122 | alias_method :utf8?, :utf8 123 | 124 | def utf8=(bool) 125 | @session.options.status_utf8 = bool 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/tmux/status_bar/field.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | class StatusBar 3 | # This class represents a field in a {StatusBar status bar}. Every 4 | # {StatusBar status bar} has two fields, one on the left side and 5 | # one on the right side. 6 | # 7 | # A field can either display a simple {#text text}, or display a 8 | # {Widget widget}. While only one {Widget widget} can be displayed 9 | # at a time per field, a field will keep a stack of widgets, to 10 | # and from which new {Widget widgets} can be {#push_widget pushed} 11 | # and {#pop_widget popped}. This is useful for example when 12 | # temporarily displaying a {Widgets::ProgressBar progress bar}. 13 | class Field 14 | def initialize(status_bar, side) 15 | @status_bar = status_bar 16 | @side = side 17 | @widgets = [] 18 | @backups = [] 19 | end 20 | 21 | # Pushes a widget to the stack, making it the currently visible 22 | # one. 23 | # 24 | # @param [Widget] widget the widget to push to the stack 25 | # @return [void] 26 | def push_widget(widget) 27 | @backups << self.text 28 | @widgets.last.field = nil if @widgets.last 29 | @widgets << widget 30 | widget.field = self 31 | end 32 | alias_method :add_widget, :push_widget 33 | 34 | # Removes the current {Widget widget} from the stack. 35 | # 36 | # @param [Widget] pop If not nil, try to remove the specified 37 | # widget instead of popping off the topmost one. 38 | # @return [Widget, nil] the {Widget widget} which has been popped 39 | def pop_widget(pop = nil) 40 | widget = pop || @widgets.first 41 | pos = @widgets.index(widget) 42 | @widgets.delete_at(pos) 43 | backup = @backups.delete_at(pos) 44 | 45 | self.text = backup if backup and pos == 0 46 | @widgets.last.field = self if @widgets.last 47 | widget 48 | end 49 | alias_method :remove_widget, :pop_widget 50 | 51 | # @!attribute widget 52 | # 53 | # The currently displayed {Widget widget}, that is the one on top of the stack. 54 | # 55 | # Setting this overwrites the stack of {Widget widgets} and 56 | # makes `widget` the only {Widget widget}. 57 | # 58 | # @return [Widget] 59 | def widget 60 | @widgets.last 61 | end 62 | 63 | def widget=(widget) 64 | restore 65 | push_widget(widget) 66 | end 67 | 68 | # Removes all {Widget widgets} from the stack, restoring the 69 | # {StatusBar status bar's} original state. 70 | # 71 | # @return [void] 72 | def restore 73 | while pop_widget; end 74 | end 75 | 76 | # @!attribute text 77 | # 78 | # @return [String] 79 | def text 80 | @status_bar.session.options.get "status-#@side" 81 | end 82 | 83 | def text=(val) 84 | meth = "status_#@side=" 85 | @status_bar.session.options.set "status-#@side", val 86 | end 87 | 88 | # @!attribute background_color 89 | # @return [Symbol] 90 | def background_color 91 | @status_bar.session.options.get "status-#@side-bg" 92 | end 93 | 94 | def background_color=(color) 95 | @status_bar.session.options.set "status-#@side-bg", color 96 | end 97 | 98 | # @!attribute foreground_color 99 | # @return [Symbol] 100 | def foreground_color 101 | @status_bar.session.options.get "status-#@side-fg" 102 | end 103 | 104 | def foreground_color=(color) 105 | @status_bar.session.options.set "status-#@side-fg", color 106 | end 107 | 108 | # @!attribute max_length 109 | # 110 | # @return [Number] 111 | def max_length 112 | @status_bar.session.options.get "status-#@side-length" 113 | end 114 | 115 | def max_length=(num) 116 | @status_bar.session.options.set "status-#@side-length", num 117 | end 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/tmux/version.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # The version of this library 3 | VERSION = "0.0.2".freeze 4 | end 5 | -------------------------------------------------------------------------------- /lib/tmux/widget.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | # @abstract Subclass this class, provide a meaningful #display 3 | # method and make sure it is being called. 4 | class Widget 5 | # @return [Field] 6 | attr_reader :field 7 | 8 | # @!attribute field 9 | def field=(new_field) 10 | @field = new_field 11 | if new_field 12 | @max_length = new_field.max_length # Cache this to avoid constantly pulling the option 13 | else 14 | @max_length = 0 15 | end 16 | end 17 | 18 | def initialize 19 | @max_length = 0 20 | @field = nil 21 | end 22 | 23 | # Displays the widget if `@field` is not `nil`. 24 | # 25 | # @api abstract 26 | # @return [void] 27 | def display 28 | end 29 | 30 | # @return [Boolean] True if `@field` is not `nil` and `@max_length` is > 0 31 | def can_display? 32 | true if @field && @max_length > 0 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/tmux/widgets/progress_bar.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | module Tmux 3 | module Widgets 4 | # # Description 5 | # 6 | # Tmux::Widgets::ProgressBar offers an easy way of displaying progress 7 | # bars in the {Tmux::StatusBar status bar}. 8 | # 9 | # Thanks to the widget and stack system of tmux-ruby, a progress 10 | # bar can be temporarily displayed without destroying previous 11 | # contents of the {Tmux::StatusBar status bar}, offering an 12 | # unobtrusive way of displaying the progress of a long running 13 | # system. As soon as the progress bar is not needed anymore, it 14 | # can be removed from the stack, revealing any previous content of 15 | # the {Tmux::StatusBar status bar}. 16 | # 17 | # 18 | # # Features 19 | # 20 | # - a freely definable maximum value 21 | # - automatic conversion of absolute values to their respective 22 | # percentage 23 | # - automatic scaling of the progress bar, adapting to the available width in the status bar 24 | # - labels 25 | # 26 | # 27 | # # Example 28 | # require "tmux" 29 | # require "tmux/widgets/progress_bar" # widgets won't be required by default 30 | # 31 | # server = Tmux::Server.new 32 | # session = server.session # returns the first available session. Actual applications can determine the appropriate session. 33 | # 34 | # pbar = Tmux::Widgets::ProgressBar.new("Download") # initialize a new progress bar with the label "Download" 35 | # session.status_bar.right.add_widget(pbar) # add the progress bar to the right part of the status bar and display it 36 | # 37 | # num_files = 24 # in a real application, we would dynamically determine the amount of files/mails/… to download 38 | # pbar.total = num_files 39 | # 40 | # num_files.times do 41 | # # in a real application, we would be downloading something here 42 | # pbar.value += 1 43 | # sleep 0.1 44 | # end 45 | # 46 | # sleep 1 # give the user a chance to see that the process has finished 47 | # 48 | # # Remove the progress bar again, restoring any old content of the status bar. 49 | # # Note: by passing the progress bar to #pop_widget, we avoid breaking the stack 50 | # # if another application decided to display its own widget on top of ours. 51 | # session.status_bar.right.pop_widget(pbar) 52 | # 53 | # 54 | # # Screenshot 55 | # ![Screenshot of ProgressBar](http://doc.fork-bomb.org/tmux/screenshots/progress_bar.png) 56 | 57 | class ProgressBar < Widget 58 | # @!attribute value 59 | def value=(new_value) 60 | @value = new_value 61 | display 62 | end 63 | 64 | # @return [Number] The absolute value of the progress bar. 65 | attr_reader :value 66 | 67 | # @return [String] 68 | attr_accessor :label 69 | 70 | # @return [Number] 71 | attr_accessor :total 72 | 73 | # @param [String] label Label for the progress bar 74 | # @param [Number] total The maximal value of the progress bar 75 | # @param field (see Widget#initialize) 76 | def initialize(label = "Progress", total = 100) 77 | super() 78 | @label = label 79 | @total = total 80 | @value = 0 81 | end 82 | 83 | # Display the progress bar. {#value=} automatically calls this 84 | # method to update the widget. 85 | # 86 | # @return [void] 87 | def display 88 | return unless can_display? 89 | s = "#{@label}: " 90 | remaining_chars = @max_length - s.size - 3 # 3 = "|" + "|" + ">" 91 | return if remaining_chars <= 0 92 | 93 | bar = "=" * (((@value / @total.to_f) * remaining_chars).ceil - 1) + ">" 94 | bar << " " * (remaining_chars - bar.size) unless (remaining_chars - bar.size) < 0 95 | s << "|#{bar}|" 96 | 97 | @field.text = s 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/tmux/window.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "tmux/window/status" 3 | 4 | module Tmux 5 | # A {Window window} occupies the entire 6 | # screen and may be split into rectangular {Pane panes}, each of 7 | # which is a separate pseudo terminal (the pty(4) manual page 8 | # documents the technical details of pseudo terminals). 9 | # 10 | # @todo Figure out better names for some attributes, e.g. mode_mouse 11 | class Window 12 | include Comparable 13 | 14 | class << self 15 | # @return [OptionsList] 16 | def options(server) 17 | OptionsList.new(:window, server, true) 18 | end 19 | end 20 | 21 | # @return [Number] 22 | attr_reader :number 23 | 24 | # @return [Session] 25 | attr_reader :session 26 | 27 | # @return [OptionsList] 28 | attr_reader :options 29 | 30 | # @return [Status] 31 | attr_reader :status 32 | def initialize(session, number) 33 | @session, @number = session, number 34 | @options = OptionsList.new(:window, self, false) 35 | @status = Status.new(self) 36 | end 37 | 38 | # @!attribute session 39 | # 40 | # Moves the window to another {Session session}. First it tries 41 | # to reuse the current number of the window. If that number is 42 | # already used in the new {Session session}, the first free 43 | # number will be used instead. 44 | # 45 | # @return [Session] 46 | # @raise [Exception::IndexInUse] 47 | # @see #move 48 | # @todo use base-index 49 | def session=(new_session) 50 | i = -1 51 | first_try = true 52 | begin 53 | num = (first_try ? @number : (i += 1)) 54 | move(new_session, num) 55 | rescue IndexInUse 56 | first_try = false 57 | retry 58 | end 59 | end 60 | 61 | # @!attribute number 62 | # @return [Number] 63 | # @raise [Exception::IndexInUse] 64 | # @see #move 65 | def number=(new_number) 66 | move(@session, new_number) 67 | end 68 | 69 | # Moves the window to either a different session, a different 70 | # position or both. 71 | # 72 | # @param [Session] new_session 73 | # @param [Number] new_number 74 | # 75 | # @return [void] 76 | # @raise [Exception::IndexInUse] 77 | # @see #number= 78 | # @see #session= 79 | # 80 | # @tmux move-window 81 | def move(new_session, new_number) 82 | return if @session == new_session && @number == new_number 83 | target = "%s:%s" % [new_session.identifier, new_number] 84 | 85 | res = server.invoke_command("move-window -s #{identifier} -t #{target}") 86 | if res =~ /^can't move window: index in use: \d+/ 87 | raise IndexInUse, [new_session, new_number] 88 | end 89 | @session = new_session 90 | @number = new_number 91 | end 92 | 93 | def <=>(other) 94 | return nil unless other.is_a?(Window) 95 | [@session, @number] <=> [other.session, other.number] 96 | end 97 | 98 | # @return [Boolean] 99 | def ==(other) 100 | self.class == other.class && @session == other.session && @number == other.number 101 | end 102 | 103 | # @return [Number] 104 | def hash 105 | [@session.hash, @number].hash 106 | end 107 | 108 | # @return [Boolean] 109 | def eql?(other) 110 | self == other 111 | end 112 | 113 | # @!attribute [r] server 114 | # 115 | # @return [Server] 116 | def server 117 | @session.server 118 | end 119 | 120 | # @!attribute name 121 | # @return [String] 122 | # @tmuxver >=1.1 123 | # @tmux rename-window 124 | def name 125 | server.check_for_version!("1.1") 126 | 127 | @session.windows_information[@number][:name] 128 | end 129 | 130 | # @return [String] 131 | def name=(value) 132 | # TODO escape name? 133 | server.invoke_command "rename-window -t #{identifier} '#{value}'" 134 | end 135 | 136 | # @!attribute [r] width 137 | # 138 | # @return [Integer] 139 | # @tmuxver >=1.1 140 | def width 141 | server.check_for_version!("1.1") 142 | 143 | @session.windows_information[@number][:width] 144 | end 145 | 146 | # @!attribute [r] height 147 | # 148 | # @return [Integer] 149 | # @tmuxver >=1.1 150 | def height 151 | server.check_for_version!("1.1") 152 | 153 | @session.windows_information[@number][:height] 154 | end 155 | 156 | # @!attribute [r] identifier 157 | # 158 | # @return [String] 159 | def identifier 160 | "%s:%s" % [@session.identifier, @number] 161 | end 162 | 163 | # @!attribute aggressive_resize 164 | # 165 | # Aggressively resize the window. This means that tmux will resize 166 | # the window to the size of the smallest {Session session} for 167 | # which it is the current window, rather than the smallest 168 | # {Session session} to which it is attached. The window may resize 169 | # when the current window is changed on another {Session session}; 170 | # this option is good for full-screen programs which support 171 | # SIGWINCH and poor for interactive programs such as shells. 172 | # 173 | # @return [Boolean] 174 | def aggressive_resize 175 | @options.aggressive_resize 176 | end 177 | alias_method :aggressive_resize?, :aggressive_resize 178 | 179 | # @param [Boolean] bool 180 | # @return [Boolean] 181 | def aggressive_resize=(bool) 182 | @options.aggressive_resize = bool 183 | end 184 | 185 | # @!attribute automatic_rename 186 | # 187 | # Control automatic window renaming. When this setting is enabled, 188 | # tmux will attempt – on supported platforms – to rename the 189 | # window to reflect the command currently running in it. This flag 190 | # is automatically disabled for an individual window when a name 191 | # is specified at creation with {Session#create_window} or 192 | # {Server#create_session}, or later with {#name=}. 193 | # 194 | # @return [Boolean] 195 | def automatic_rename 196 | @options.automatic_rename 197 | end 198 | alias_method :automatic_rename?, :automatic_rename 199 | 200 | # @param [Boolean] bool 201 | # @return [Boolean] 202 | def automatic_rename=(bool) 203 | @options.automatic_rename = bool 204 | end 205 | 206 | # @!attribute synchronize_panes 207 | # 208 | # Duplicate input to any {Pane pane} to all other {Pane panes} in 209 | # the same window (only for {Pane panes} that are not in any 210 | # special mode) 211 | # 212 | # @return [Boolean] 213 | def synchronize_panes 214 | @options.synchronize_panes 215 | end 216 | alias_method :synchronize_panes?, :synchronize_panes 217 | 218 | # @param [Boolean] bool 219 | # @return [Boolean] 220 | def synchronize_panes=(bool) 221 | @options.synchronize_panes = bool 222 | end 223 | 224 | # @!attribute remain_on_exit 225 | # A window with this flag set is not destroyed when the program 226 | # running in it exits. The window may be reactivated with 227 | # {#respawn}. 228 | # 229 | # @return [Boolean] 230 | def remain_on_exit 231 | @options.remain_on_exit 232 | end 233 | alias_method :remain_on_exit?, :remain_on_exit 234 | 235 | # @param [Boolean] bool 236 | # @return [Boolean] 237 | def remain_on_exit=(bool) 238 | @options.remain_on_exit = bool 239 | end 240 | 241 | # @!attribute utf8 242 | # 243 | # Instructs tmux to expect UTF-8 sequences to appear in this 244 | # window. 245 | # 246 | # @return [Boolean] 247 | def utf8 248 | @options.utf8 249 | end 250 | alias_method :utf8?, :utf8 251 | 252 | # @param [Boolean] bool 253 | # @return [Boolean] 254 | def utf8=(bool) 255 | @options.utf8 = bool 256 | end 257 | 258 | # @!attribute monitor_activity 259 | # 260 | # Monitor for activity in the window. Windows with activity are 261 | # highlighted in the {StatusBar status line}. 262 | # 263 | # @return [Boolean] 264 | def monitor_activity 265 | @options.monitor_activity 266 | end 267 | alias_method :monitor_activity?, :monitor_activity 268 | 269 | # @param [Boolean] bool 270 | # @return [Boolean] 271 | def monitor_activity=(bool) 272 | @options.monitor_activity = bool 273 | end 274 | 275 | # @!attribute monitor_content 276 | # 277 | # Monitor content in the window. When the 278 | # {http://linux.die.net/man/3/fnmatch fnmatch(3)} pattern appears 279 | # in the window, it is highlighted in the {StatusBar status line}. 280 | # 281 | # @return [String] 282 | def monitor_content 283 | @options.monitor_content 284 | end 285 | 286 | # @param [String] pattern 287 | # @return [String] 288 | def monitor_content=(pattern) 289 | @options.monitor_content = pattern 290 | end 291 | 292 | # @!attribute max_width 293 | # 294 | # Prevent tmux from resizing the window to greater than 295 | # `max_width`. A value of zero restores the default unlimited 296 | # setting. 297 | # 298 | # @return [Number] 299 | def max_width 300 | @options.force_width 301 | end 302 | 303 | # @param [Number] value 304 | # @return [Number] 305 | def max_width=(value) 306 | @options.force_width = value 307 | end 308 | alias_method :force_width, :max_width 309 | alias_method :force_width=, "max_width=" 310 | 311 | # @!attribute max_height 312 | # 313 | # Prevent tmux from resizing the window to greater than 314 | # `max_height`. A value of zero restores the default unlimited 315 | # setting. 316 | # 317 | # @return [Number] 318 | def max_height 319 | @options.force_height 320 | end 321 | 322 | # @param [Number] value 323 | # @return [Number] 324 | def max_height=(value) 325 | @options.force_height = value 326 | end 327 | alias_method :force_height, :max_height 328 | alias_method :force_height=, "max_height=" 329 | 330 | # @!attribute xterm_keys 331 | # 332 | # If this option is set to true, tmux will generate 333 | # {http://linux.die.net/man/1/xterm xterm(1)}-style function key 334 | # sequences. These have a number included to indicate modifiers 335 | # such as Shift, Alt or Ctrl. The default is false. 336 | # 337 | # @return [Boolean] 338 | def xterm_keys 339 | @options.xterm_keys 340 | end 341 | alias_method :xterm_keys?, :xterm_keys 342 | 343 | # @param [Boolean] bool 344 | # @return [Boolean] 345 | def xterm_keys=(bool) 346 | @options.xterm_keys = bool 347 | end 348 | 349 | # @!attribute word_separators 350 | # 351 | # Sets the window's conception of what characters are considered 352 | # word separators, for the purposes of the next and previous word 353 | # commands in {Pane#copy_mode copy mode}. The default is `[" ", 354 | # "-", "_", "@"]`. 355 | # 356 | # @return [Array] 357 | def word_separators 358 | @options.word_separators 359 | end 360 | 361 | # @param [Array] value 362 | # @return [Array] 363 | def word_separators=(value) 364 | @options.word_separators = value 365 | end 366 | 367 | # @!attribute alternate_screen 368 | # This option configures whether programs running inside tmux may 369 | # use the terminal alternate screen feature, which allows the 370 | # smcup and rmcup {http://linux.die.net/man/5/terminfo 371 | # terminfo(5)} capabilities to be issued to preserve the existing 372 | # window content on start and restore it on exit. 373 | # 374 | # @return [Boolean] 375 | def alternate_screen 376 | @options.alternate_screen 377 | end 378 | alias_method :alternate_screen?, :alternate_screen 379 | 380 | # @param [Boolean] bool 381 | # @return [Boolean] 382 | def alternate_screen=(bool) 383 | @options.alternate_screen = bool 384 | end 385 | 386 | # @!attribute mode_mouse 387 | # 388 | # Mouse state in modes. If true, the mouse may be used to copy a 389 | # selection by dragging in {Pane#copy_mode copy mode}, or to 390 | # select an option in choice mode. 391 | # 392 | # @return [Boolean] 393 | def mode_mouse 394 | @options.mode_mouse 395 | end 396 | alias_method :mode_mouse?, :mode_mouse 397 | 398 | # @param [Boolean] bool 399 | # @return [Boolean] 400 | def mode_mouse=(bool) 401 | @options.mode_mouse = bool 402 | end 403 | 404 | # @!attribute clock_mode_colour 405 | # 406 | # Clock color. 407 | # 408 | # @return [Symbol] 409 | def clock_mode_color 410 | @options.clock_mode_colour 411 | end 412 | alias_method :clock_mode_colour, :clock_mode_color 413 | 414 | # @param [Symbol] color 415 | # @return [Symbol] 416 | def clock_mode_color=(color) 417 | @options.clock_mode_colour = color 418 | end 419 | alias_method :clock_mode_colour=, "clock_mode_color=" 420 | 421 | # @!attribute clock_mode_style 422 | # 423 | # Clock hour format. 424 | # 425 | # @return [Symbol<:twelve, :twenty_four>] 426 | def clock_mode_style 427 | @options.clock_mode_style 428 | end 429 | 430 | # @param [Symbol<:twelve, :twenty_four>] style 431 | # @return [Symbol] 432 | def clock_mode_style=(style) 433 | @options.clock_mode_style = style 434 | end 435 | 436 | # @!attribute main_pane_height 437 | # 438 | # The height of the main (left or top) pane in the 439 | # main-horizontal or main-vertical {#layout= layouts}. 440 | # 441 | # @return [Number] 442 | # @see #layout= 443 | def main_pane_height 444 | @options.main_pane_height 445 | end 446 | 447 | # @param [Number] height 448 | # @return [Number] 449 | def main_pane_height=(height) 450 | @options.main_pane_height = height 451 | end 452 | 453 | # @!attribute main_pane_width 454 | # 455 | # The width of the main (left or top) pane in the 456 | # main-horizontal or main-vertical {#layout= layouts}. 457 | # 458 | # @return [Number] 459 | # @see #layout= 460 | def main_pane_width 461 | @options.main_pane_width 462 | end 463 | 464 | # @param [Number] width 465 | # @return [Number] 466 | def main_pane_width=(width) 467 | @options.main_pane_width = width 468 | end 469 | 470 | # @!attribute mode_attr 471 | # 472 | # @return [Symbol] 473 | def mode_attr 474 | @options.mode_attr 475 | end 476 | 477 | # @param [Symbol] attr 478 | # @return [Symbol] 479 | def mode_attr=(attr) 480 | @options.mode_attr = attr 481 | end 482 | 483 | # @!attribute mode_bg 484 | # @return [Symbol] 485 | def mode_bg 486 | @options.mode_bg 487 | end 488 | 489 | # @param [Symbol] bg 490 | # @return [Symbol] 491 | def mode_bg=(bg) 492 | @options.mode_bg = bg 493 | end 494 | 495 | # @!attribute mode_fg 496 | # @return [Symbol] 497 | def mode_fg 498 | @options.mode_fg 499 | end 500 | 501 | # @param [Symbol] fg 502 | # @return [Symbol] 503 | def mode_fg=(fg) 504 | @options.mode_fg = fg 505 | end 506 | 507 | # @!attribute mode_keys 508 | # 509 | # @return [Symbol] 510 | def mode_keys 511 | @options.mode_keys 512 | end 513 | 514 | # @param [Symbol] keymap 515 | # @return [Symbol] 516 | def mode_keys=(keymap) 517 | @options.mode_keys = keymap 518 | end 519 | 520 | # Kills the window. 521 | # @tmux kill-window 522 | # @return [void] 523 | def kill 524 | server.invoke_command "kill-window -t #{identifier}" 525 | end 526 | 527 | # Rotates the positions of the {Pane panes} within a window. 528 | # 529 | # @tmux rotate-window 530 | # @return [void] 531 | def rotate(direction = :upward) 532 | flag = case direction 533 | when :upward 534 | "U" 535 | when :downward 536 | "D" 537 | else 538 | raise ArgumentError 539 | end 540 | server.invoke_command "rotate-window -#{flag} -t #{identifier}" 541 | end 542 | 543 | # @!attribute [w] layout 544 | # 545 | # @todo attr_reader 546 | # @return [Symbol<:even_horizontal, :even_vertical, :main_horizontal, :main_vertical>] 547 | # @tmux select-layout 548 | # @tmuxver >=1.3 for :tiled layout 549 | # @tmuxver >=1.0 for all other layouts 550 | def layout=(layout) 551 | server.check_for_version!("1.0") 552 | raise Exception::UnsupportedVersion, "1.3" if layout == :tiled && server.version < "1.3" 553 | 554 | valid_layouts = [:even_horizontal, :even_vertical, :main_horizontal, :main_vertical, :tiled] 555 | raise ArgumentError unless valid_layouts.include?(layout) 556 | layout = layout.to_s.tr("_", "-") 557 | server.invoke_command "select-layout -t #{identifier} #{layout}" 558 | end 559 | 560 | # @param [Hash] search Filters the resulting hash using {FilterableHash#filter} 561 | # @return [Hash] A hash with information for all panes 562 | # @tmux list-panes 563 | # @tmuxver >=1.1 564 | def panes_information(search={}) 565 | server.check_for_version!("1.1") 566 | 567 | hash = {} 568 | output = server.invoke_command "list-panes -t #{identifier}" 569 | output.each_line do |pane| 570 | params = pane.match(/^(?\d+): \[(?\d+)x(?\d+)\] \[history (?\d+)\/(?\d+), (?\d+) bytes\]( %\d+)?(? \(active\))?$/) 571 | num = params[:num].to_i 572 | width = params[:width].to_i 573 | height = params[:height].to_i 574 | cur_history = params[:cur_history].to_i 575 | max_history = params[:max_history].to_i 576 | memory = Filesize.new(params[:memory].to_i) 577 | 578 | # this flag requires tmux >=1.4 579 | active = !params[:active].nil? 580 | 581 | hash[num] = { 582 | :num => num, 583 | :width => width, 584 | :height => height, 585 | :cur_history => cur_history, 586 | :max_history => max_history, 587 | :memory => memory, 588 | :active => active, 589 | } 590 | end 591 | hash.extend FilterableHash 592 | hash.filter(search) 593 | end 594 | 595 | # @!attribute [r] panes 596 | # 597 | # @return [Array] All {Pane panes} 598 | # @tmuxver >=1.1 599 | def panes 600 | server.check_for_version!("1.1") 601 | 602 | panes_information.map do |num, information| 603 | Pane.new(self, num) 604 | end 605 | end 606 | 607 | # Pastes a {Buffer buffer} into the window. 608 | # 609 | # @param [Buffer] buffer The {Buffer buffer} to paste 610 | # @param pop (see Buffer#paste) 611 | # @param translate (see Buffer#paste) 612 | # @param separator (see Buffer#paste) 613 | # @return [void] 614 | # @tmux paste-buffer 615 | # @see Buffer#paste 616 | # @see Pane#paste 617 | def paste(buffer, pop = false, translate = true, separator = nil) 618 | buffer.paste(self, pop, translate, separator) 619 | end 620 | 621 | # Select the window. 622 | # 623 | # @return [void] 624 | # @tmux select-window 625 | def select 626 | server.invoke_command "select-window -t #{identifier}" 627 | end 628 | 629 | # Swap the window with another one. 630 | # 631 | # @param [Window] window The window to swap with 632 | # @return [void] 633 | # @tmux swap-window 634 | def swap_with(window) 635 | server.invoke_command "swap-window -s #{identifier} -t #{window.identifier}" 636 | end 637 | 638 | # @param [Symbol<:never, :if_same_window, :always>] return_if When 639 | # to return the current pane. 640 | # 641 | # Note: In tmux versions prior to 1.4, :always can lead to flickering 642 | # Note: Since tmux version 1.4, :always is forced 643 | # @return [Pane, nil] The current pane 644 | def current_pane(return_if = :always) 645 | if server.version >= "1.4" 646 | self.panes.find(&:active?) 647 | else 648 | # In tmux <1.4, we can only determine the selected pane of the 649 | # current window. 650 | # 651 | # If the user specified return_if = :always, we select this 652 | # window (if it is not already selected), determine the 653 | # current pane and select the lastly selected window again. 654 | cur_window = self.session.any_client.current_window 655 | same_window = cur_window == self 656 | return_if_b = ((return_if == :if_same_window && same_window) || (return_if == :always)) 657 | 658 | self.select if return_if_b && !same_window 659 | 660 | new_pane = nil 661 | if return_if_b 662 | num = self.session.any_client.message_stdout("#P") 663 | new_pane = Pane.new(self, num.to_i) 664 | end 665 | 666 | if return_if == :always && !same_window 667 | self.session.select_previous_window 668 | end 669 | 670 | return new_pane if new_pane 671 | end 672 | end 673 | 674 | # Select the previously selected pane. 675 | # 676 | # @param return_if (see Window#current_pane) 677 | # @return (see Window#current_pane) 678 | # @tmux last-pane 679 | # @tmuxver >=1.4 680 | def select_last_pane(return_if = :always) 681 | server.invoke_command("last-pane -t #{identifier}") 682 | current_pane(return_if) 683 | end 684 | 685 | # Reactivates a window in which the command has exited. 686 | # 687 | # @param [String, nil] command The command to use to respawn the 688 | # window. If nil, the command used when the window was created is 689 | # executed. 690 | # @param [Boolean] kill Unless `kill` is true, only inactive windows can be respawned 691 | # @return [void] 692 | # @tmux respawn-window 693 | # @see #remain_on_exit 694 | # @todo escape command 695 | def respawn(command = nil, kill = false) 696 | flags = [] 697 | flags << "-k" if kill 698 | flags << "-t #{identifier}" 699 | flags << "\"#{command}\"" if command 700 | 701 | server.invoke_command "respawn-window #{flags.join(" ")}" 702 | end 703 | end 704 | end 705 | -------------------------------------------------------------------------------- /lib/tmux/window/status.rb: -------------------------------------------------------------------------------- 1 | require "tmux/window/status/state" 2 | 3 | module Tmux 4 | class Window 5 | # the "tab" in the statusbar 6 | class Status 7 | # @return [State] 8 | attr_reader :normal 9 | # @return [State] 10 | attr_reader :current 11 | # @return [State] 12 | attr_reader :alert 13 | def initialize(window) 14 | @window = window 15 | @normal = State.new(@window, :normal) 16 | @current = State.new(@window, :current) 17 | @alert = State.new(@window, :alert) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/tmux/window/status/state.rb: -------------------------------------------------------------------------------- 1 | module Tmux 2 | class Window 3 | class Status 4 | # Each status can be in different states: normal, current and alert 5 | class State 6 | def initialize(window, state) 7 | @window = window 8 | @state = state 9 | end 10 | 11 | # @!attribute background_color 12 | # 13 | # @return [Symbol] 14 | def background_color 15 | get_option "bg" 16 | end 17 | 18 | def background_color=(color) 19 | set_option "fg", color 20 | end 21 | 22 | # @!attribute foreground_color 23 | # 24 | # @return [Symbol] 25 | def foreground_color 26 | get_option "fg" 27 | end 28 | 29 | def foreground_color=(color) 30 | set_option "fg", color 31 | end 32 | 33 | # @!attribute format 34 | # 35 | # The format in which the window is displayed in the status line window list. 36 | # 37 | # @return [String] 38 | def format 39 | get_option "format" 40 | end 41 | 42 | def format=(value) 43 | set_option "format" 44 | end 45 | 46 | # @!attribute attributes 47 | # 48 | # @return [Symbol] 49 | def attributes 50 | get_option "attr" 51 | end 52 | 53 | def attributes=(value) 54 | # FIXME string? array? 55 | set_option "attr", value 56 | end 57 | 58 | def get_option(option) 59 | @window.options.get option_name(option) 60 | end 61 | private :get_option 62 | 63 | def set_option(option, value) 64 | @window.options.set option_name(option), value 65 | end 66 | private :set_option 67 | 68 | def option_name(option) 69 | state = case @state 70 | when :normal 71 | "" 72 | when :current 73 | "current-" 74 | when :alert 75 | "alert-" 76 | end 77 | "window-status-#{state}#{option}" 78 | end 79 | private :option_name 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /screenshots/progress_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominikh/tmux-ruby/edecf7e0514d5dad554a56a7d3d0f6358cabd776/screenshots/progress_bar.png -------------------------------------------------------------------------------- /tmux-ruby.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path("../lib/tmux/version", __FILE__) 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = 'tmux-ruby' 5 | gem.version = String.new Tmux::VERSION 6 | 7 | gem.summary = "Ruby library to control tmux" 8 | gem.description = "Ruby library to control tmux" 9 | 10 | gem.authors = ['Dominik Honnef'] 11 | gem.email = 'dominikh@fork-bomb.org' 12 | gem.homepage = 'https://github.com/dominikh/tmux-ruby' 13 | gem.license = 'MIT' 14 | 15 | gem.add_dependency "filesize" 16 | gem.required_ruby_version = ">=1.9.1" 17 | gem.has_rdoc = "yard" 18 | 19 | gem.files = Dir['{lib}/**/*', 'README.md', 'LICENSE', '.yardopts'] 20 | end 21 | --------------------------------------------------------------------------------