From e4da4bb5f80736d18dab9b7910eefbc22902a087 Mon Sep 17 00:00:00 2001 From: Evan Light Date: Tue, 17 Jul 2012 17:18:58 -0400 Subject: [PATCH] Initial rev plus my scroll up and down commands --- .gitignore | 1 + README.mkd | 26 ++++ doc/vimux.txt | 296 ++++++++++++++++++++++++++++++++++++++++ plugin/vimux.vim | 348 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 671 insertions(+) create mode 100644 .gitignore create mode 100644 README.mkd create mode 100644 doc/vimux.txt create mode 100644 plugin/vimux.vim diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..926ccaa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +doc/tags diff --git a/README.mkd b/README.mkd new file mode 100644 index 0000000..2fe0fc7 --- /dev/null +++ b/README.mkd @@ -0,0 +1,26 @@ +# vimux + +Easily interact with tmux from vim. + +![vimux](https://www.braintreepayments.com/assets-faccd47687/assets/images/blog/vimux3.png) + +What inspired me to write vimux was [tslime.vim](https://github.com/kikijump/tslime.vim), a plugin that lets you send input to tmux. While tslime.vim works well, I felt it wasn't optimized for my primary use case which was having a smaller tmux pane that I would use to run tests or play with a REPL. + +My goal with vimux is to make interacting with tmux from vim effortless. By default when you call `RunVimTmuxCommand` vimux will create a 20% tall horizontal pane under your current tmux pane and execute a command in it without losing focus of vim. Once that pane exists whenever you call `RunVimTmuxCommand` again the command will be executed in that pane. As I was using vimux myself I wanted to rerun commands over and over. An example of this was running the current file through rspec. Rather than typing that over and over I wrote `RunLastVimTmuxCommand` that will execute the last command you called with `RunVimTmuxCommand`. + +Other auxiliary functions and the ones I talked about above can be found bellow with a full description and example key binds for your vimrc. + +## Installation + +With **[vim-bundle](https://github.com/benmills/vim-bundle)**: `vim-bundle install benmills/vimux` + +Otherwise download the latest [tarball](https://github.com/benmills/vimux/tarball/master), extract it and move `plugin/vimux.vim` inside `~/.vim/plugin`. If you're using [pathogen](https://github.com/tpope/vim-pathogen), then move the entire folder extracted from the tarball into `~/.vim/bundle`. + +## Platform-specific Plugins + +* [vimux-ruby-test](https://github.com/pgr0ss/vimux-ruby-test) a set of commands to easily run ruby tests +* [vimux-cucumber](https://github.com/cloud8421/vimux-cucumber) run Cucumber Features through Vimux + +## Usage + +The full documentation is available [online](https://raw.github.com/benmills/vimux/master/doc/vimux.txt) and accessible inside vim `:help vimux` diff --git a/doc/vimux.txt b/doc/vimux.txt new file mode 100644 index 0000000..2fe686d --- /dev/null +++ b/doc/vimux.txt @@ -0,0 +1,296 @@ +*vimux.txt* easily interact with tmux + + Vimux + effortless vim and tmux interaction + +============================================================================== +CONTENTS *vimux-contents* + + 1. About............................ |VimuxAbout| + 2. Usage ........................... |VimuxUsage| + 2.1 .............................. |VimuxPromptCommand| + 2.2 .............................. |VimuxRunLastCommand| + 2.3 .............................. |VimuxInspectRunner| + 2.4 .............................. |VimuxCloseRunner| + 2.5 .............................. |VimuxClosePanes| + 2.6 .............................. |VimuxInterruptRunner| + 2.7 .............................. |VimuxClearRunnerHistory| + 3. Misc ............................ |VimuxMisc| + 3.1 Example Keybinding............ |VimuxExampleKeybinding| + 3.2 Tslime Replacement............ |VimuxTslimeReplacement| + 4. Configuration ................... |VimuxConfiguration| + + +============================================================================== +ABOUT (1) *VimuxAbout* + +Vimux -- Easily interact with tmux from vim. + +What inspired me to write vimux was tslime.vim [1], a plugin that lets you +send input to tmux. While tslime.vim works well, I felt it wasn't optimized +for my primary use case which was having a smaller tmux pane that I would use +to run tests or play with a REPL. + +My goal with vimux is to make interacting with tmux from vim effortless. By +default when you call `VimuxRunCommand` vimux will create a 20% tall +horizontal pane under your current tmux pane and execute a command in it +without losing focus of vim. Once that pane exists whenever you call +`VimuxRunCommand` again the command will be executed in that pane. As I was +using vimux myself I wanted to rerun commands over and over. An example of +this was running the current file through rspec. Rather than typing that over +and over I wrote `VimuxRunLastCommand` that will execute the last command +you called with `VimuxRunCommand`. + +Other auxiliary functions and the ones I talked about above can be found +bellow with a full description and example key binds for your vimrc. + +[1] https://github.com/kikijump/tslime.vim + + +============================================================================== +USAGE (2) *VimuxUsage* + +The function VimuxRunCommand(command) is the core of Vimux. It will +create a split pane in the current window and run the passed command in it. +> + :call VimuxRunCommand("ls") +< +This will run the command in a split pane without losing focus of vim. If the +command takes a long time to return you can continue to use vim while the +process finishes and will see the output in the pane when it's finished. + +Furthermore there are several handy commands all starting with 'Vimux': + - |VimuxRunCommand| + - |VimuxRunLastCommand| + - |VimuxCloseRunner| + - |VimuxClosePanes| + - |VimuxCloseWindows| + - |VimuxInspectRunner| + - |VimuxInterruptRunner| + - |VimuxPromptCommand| + - |VimuxClearRunnerHistory| + + +Note: +Earlier the all commands had different names. There are still aliases for +convenience. Please chang your configuration according to the new naming +conventions! + +The DEPRECATED commands: + - |PromptVimTmuxCommand| + - |RunLastVimTmuxCommand| + - |InspectVimTmuxRunner| + - |CloseVimTmuxRunner| + - |CloseVimTmuxPanes| + - |InterruptVimTmuxRunner| + +------------------------------------------------------------------------------ + *RunVimTmuxCommand* + *VimuxRunCommand* +VimuxRunCommand~ + +Run a system command in a small horizontal split bellow +the current pane vim is in. You can optionally pass a second argument to stop +vimux from automatically sending a return after the command. +> + " Run the current file with rspec + map rb :call VimuxRunCommand("clear; rspec " . bufname("%")) + " Run command without sending sending a return + map rq :call VimuxRunCommand("clear; rspec " . bufname("%"), 0) +< + +------------------------------------------------------------------------------ + *PromptVimTmuxCommand* + *VimuxPromptCommand* +VimuxPromptCommand~ + +Prompt for a command and run it in a small horizontal split bellow the current +pane. +> + " Prompt for a command to run map + vp :VimuxPromptCommand +< + +------------------------------------------------------------------------------ + *RunLastVimTmuxCommand* + *VimuxRunLastCommand* +VimuxRunLastCommand~ + +Run the last command executed by `VimuxRunCommand` +> + " Run last command executed by VimuxRunCommand + map vl :VimuxRunLastCommand +< + +------------------------------------------------------------------------------ + *InspectVimTmuxRunner* + *VimuxInspectRunner* +VimuxInspectRunner~ + +Move into the tmux runner pane created by `VimuxRunCommand` and enter copy +pmode (scroll mode). +> + " Inspect runner pane map + vi :VimuxInspectRunner +< + +------------------------------------------------------------------------------ + *CloseVimTmuxRunner* + *VimuxCloseRunner* +VimuxCloseRunner~ + +Close the tmux runner created by `VimuxRunCommand` +> + " Close vim tmux runner opened by VimuxRunCommand + map vq :VimuxCloseRunner +< + +------------------------------------------------------------------------------ + *CloseVimTmuxPanes* + *VimuxClosePanes* +VimuxClosePanes~ + +Close all other tmux panes in the current window. +> + " Close all other tmux panes in current window + map vx :VimuxClosePanes +> + +------------------------------------------------------------------------------ + *InterruptVimTmuxRunner* + *VimuxInterruptRunner* +VimuxInterruptRunner~ + +Interrupt any command that is running inside the +runner pane. +> + " Interrupt any command running in the runner pane map + vs :VimuxInterruptRunner +< + + + +------------------------------------------------------------------------------ + *VimuxClearRunnerHistory* +VimuxClearRunnerHistory~ + +Clear ths tmux history of the runner pane for when +you enter tmux scroll mode inside the runner pane. +> + " Clear the tmux history of the runner pane + vc :VimuxClearRunnerHistory +< + + +============================================================================== +MISC (3) *VimuxMisc* + +------------------------------------------------------------------------------ + *VimuxExampleKeybinding* +Full Keybind Example~ + +> + " Run the current file with rspec + map rb :call VimuxRunCommand("clear; rspec " . bufname("%")) + + " Prompt for a command to run + map rp :VimuxPromptCommand + + " Run last command executed by VimuxRunCommand + map rl :VimuxRunLastCommand + + " Inspect runner pane + map ri :VimuxInspectRunner + + " Close all other tmux panes in current window + map rx :VimuxClosePanes + + " Close vim tmux runner opened by VimuxRunCommand + map rq :VimuxCloseRunner + + " Interrupt any command running in the runner pane + map rs :VimuxInterruptRunner +> + +------------------------------------------------------------------------------ + *VimuxTslimeReplacement* +Vimux as tslime replacement~ + +Here is how to use vimux to send code to a REPL. This is similar to tslime. +First, add some helpful mappings. + +> + " Prompt for a command to run + map vp :VimuxPromptCommand + + " If text is selected, save it in the v buffer and send that buffer it to tmux + vmap vs "vy :call VimuxRunCommand(@v . "\n", 0) + + " Select current paragraph and send it to tmux + nmap vs vipvs +< + +Now, open a clojure file. Let's say your leader is backslash (\). Type \vp, +and then type lein repl at the prompt. This opens a tmux split running a REPL. +Then, select text or put the cursor on a function and type \vs. This will send +it to the REPL and evaluate it. The reason we pass `0` to `VimuxRunCommand` +is to stop the normal return that is sent to the runner pane and use our own +new line so the clojure REPL will evaluate the selected text without adding an +extra return. Thanks to @trptcolin for discovering this issue. + + +============================================================================== +CONFIGURATION (4) *VimuxConfiguration* + +You can configure Vimux like this: + +------------------------------------------------------------------------------ + *VimuxConfiguration_height* +2.1 g:VimuxHeight~ + +The percent of the screen the split pane Vimux will spawn should take up. + + let g:VimuxHeight = "40" + +Default: "20" + +------------------------------------------------------------------------------ + *VimuxConfiguration_orientation* +2.2 g:VimuxOrientation~ + +The default orientation of the split tmux pane. This tells tmux to make the +pane either vertically or horizontally, which is backward from how Vim handles +creating splits. + + let g:VimuxOrientation = "h" + +Options: + "v": vertical + "h": horizontal + +Default: "v" + +------------------------------------------------------------------------------ + *VimuxConfiguration_use_nearest_pane* +2.3 g:VimuxUseNearestPane~ + +Use exising pane (not used by vim) if found instead of running split-window. + + let VimuxUseNearestPane = 1 + +Default: 0 + +------------------------------------------------------------------------------ + *VimuxConfiguration_reset_sequence* +2.4 g:VimuxResetSequence~ + +The keys sent to the runner pane before running a command. By default it sends +`q` to make sure the pane is not in scroll-mode and `C-u` to clear the line. + + let VimuxUseNearestPane = "" + +Default: "q C-u" + + +============================================================================== +vim:tw=78:ts=2:sw=2:expandtab:ft=help:norl: diff --git a/plugin/vimux.vim b/plugin/vimux.vim new file mode 100644 index 0000000..2b8ec15 --- /dev/null +++ b/plugin/vimux.vim @@ -0,0 +1,348 @@ +if exists("g:loaded_vimux") || &cp + finish +endif +let g:loaded_vimux = 1 + +if !has("ruby") + finish +end + + +" New style commands with 'normalized' names +command VimuxRunLastCommand :call VimuxRunLastCommand() +command VimuxCloseRunner :call VimuxCloseRunner() +command VimuxClosePanes :call VimuxClosePanes() +command VimuxCloseWindows :call VimuxCloseWindows() +command VimuxInspectRunner :call VimuxInspectRunner() +command VimuxScrollUpInspect :call VimuxScrollUpInspect() +command VimuxScrollDownInspect :call VimuxScrollDownInspect() +command VimuxInterruptRunner :call VimuxInterruptRunner() +command VimuxPromptCommand :call VimuxPromptCommand() +command VimuxClearRunnerHistory :call VimuxClearRunnerHistory() + +" DEPRECATED +command RunLastVimTmuxCommand :call VimuxRunLastCommand() +command CloseVimTmuxRunner :call VimuxCloseRunner() +command CloseVimTmuxPanes :call VimuxClosePanes() +command CloseVimTmuxWindows :call VimuxCloseWindows() +command InspectVimTmuxRunner :call VimuxInspectRunner() +command InterruptVimTmuxRunner :call VimuxInterruptRunner() +command PromptVimTmuxCommand :call VimuxPromptCommand() + + +" new style functions +function VimuxRunCommand(command, ...) + let l:autoreturn = 1 + + if exists("a:1") + let l:autoreturn = a:1 + endif + + let g:_VimTmuxCmd = a:command + + if l:autoreturn == 1 + ruby CurrentTmuxSession.new.run_shell_command(Vim.evaluate("g:_VimTmuxCmd")) + else + ruby CurrentTmuxSession.new.run_shell_command(Vim.evaluate("g:_VimTmuxCmd"), false) + endif +endfunction + +" deprecated! +function RunVimTmuxCommand(command, ...) + " TODO replace me with the direct function call! + let l:autoreturn = 1 + + if exists("a:1") + let l:autoreturn = a:1 + endif + + let g:_VimTmuxCmd = a:command + + if l:autoreturn == 1 + ruby CurrentTmuxSession.new.run_shell_command(Vim.evaluate("g:_VimTmuxCmd")) + else + ruby CurrentTmuxSession.new.run_shell_command(Vim.evaluate("g:_VimTmuxCmd"), false) + endif +endfunction + + +function VimuxRunLastCommand() + if exists("g:_VimTmuxCmd") + ruby CurrentTmuxSession.new.run_shell_command(Vim.evaluate("g:_VimTmuxCmd")) + else + echo "No last command" + endif +endfunction + +" deprecated! +function RunLastVimTmuxCommand() + call VimuxRunLastCommand() +endfunction + + +function VimuxClearWindow() + if exists("g:_VimTmuxRunnerPane") + unlet g:_VimTmuxRunnerPane + end +endfunction + +" deprecated! +function ClearVimTmuxWindow() + call VimuxClearWindow() +endfunction + + +" deprecated! +function CloseVimTmuxWindows() + call VimuxCloseWindows() +endfunction + + +function VimuxCloseRunner() + ruby CurrentTmuxSession.new.close_runner_pane + call VimuxClearWindow() +endfunction + +" deprecated! +function CloseVimTmuxRunner() + call VimuxCloseRunner() +endfunction + + +function VimuxClosePanes() + ruby CurrentTmuxSession.new.close_other_panes + call VimuxClearWindow() +endfunction + +" deprecated! +function CloseVimTmuxPanes() + call VimuxClosePanes() +endfunction + + +function VimuxInterruptRunner() + ruby CurrentTmuxSession.new.interrupt_runner +endfunction + +" deprecated! +function InterruptVimTmuxRunner() + call VimuxInterruptRunner() +endfunction + +function VimuxScrollDownInspect() + ruby CurrentTmuxSession.new.inspect_scroll_down +endfunction + +function VimuxScrollUpInspect() + ruby CurrentTmuxSession.new.inspect_scroll_up +endfunction + +function VimuxInspectRunner() + ruby CurrentTmuxSession.new.inspect_runner +endfunction + +" deprecated! +function InspectVimTmuxRunner() + call VimuxInspectRunner() +endfunction + + +function VimuxPromptCommand() + let l:command = input("Command? ") + call VimuxRunCommand(l:command) +endfunction + +" deprecated! +function PromptVimTmuxCommand() + call VimuxPromptCommand() +endfunction + + +function VimuxClearRunnerHistory() + ruby CurrentTmuxSession.new.clear_runner_history +endfunction + +ruby << EOF +class TmuxSession + def initialize(session, window, pane) + @session = session + @window = window + @pane = pane + @runner_pane = vim_cached_runner_pane + end + + def vim_cached_runner_pane + if Vim.evaluate('exists("g:_VimTmuxRunnerPane")') != 0 + Vim.evaluate('g:_VimTmuxRunnerPane') + else + nil + end + end + + def vim_cached_runner_pane=(runner_pane) + Vim.command("let g:_VimTmuxRunnerPane = '#{runner_pane}'") + end + + def clear_vim_cached_runner_pane + Vim.command("unlet g:_VimTmuxRunnerPane") + end + + def clear_runner_history + _run("clear-history -t #{target(:pane => runner_pane)}") + end + + def height + if Vim.evaluate('exists("g:VimuxHeight")') != 0 + Vim.evaluate('g:VimuxHeight') + else + 20 + end + end + + def orientation + if Vim.evaluate('exists("g:VimuxOrientation")') != 0 && ["h", "v"].include?(Vim.evaluate('g:VimuxOrientation')) + "-#{Vim.evaluate('g:VimuxOrientation')}" + else + "-v" + end + end + + def reset_sequence + if Vim.evaluate('exists("g:VimuxResetSequence")') != 0 + "#{Vim.evaluate('g:VimuxResetSequence')}" + else + "q C-u" + end + end + + def inspect_runner + _run("select-pane -t #{target(:pane => runner_pane)}") + _run("copy-mode") + _move_up_pane + end + + def inspect_send_command(cmd) + t = target(:pane => runner_pane) + _run("select-pane -t #{t}") + _run("copy-mode") + _send_command(cmd, t, false) + _move_up_pane + end + + def inspect_scroll_up + inspect_send_command("C-u") + end + + def inspect_scroll_down + inspect_send_command("C-d") + end + + def current_panes + _run('list-panes').split("\n").map do |line| + line.split(':').first + end + end + + def active_pane_id + _run('list-panes').split("\n").map do |line| + return line.split[-2] if line =~ /\(active\)/ + end + end + + def target(args={}) + "#{args.fetch(:session, @session)}:#{args.fetch(:window, @window)}.#{args.fetch(:pane, @pane)}" + end + + def runner_pane + if @runner_pane.nil? + use_nearest_pane = Vim.evaluate('exists("g:VimuxUseNearestPane")') != 0 + if use_nearest_pane && nearest_inactive_pane_id + _run("select-pane -t #{target(:pane => nearest_inactive_pane_id)}") + else + _run("split-window -p #{height} #{orientation}") + end + @runner_pane = active_pane_id + _send_command("cd #{`pwd`}", target(:pane => runner_pane)) + Vim.command("let g:_VimTmuxRunnerPane = '#{@runner_pane}'") + end + + _run('list-panes').split("\n").map do |line| + return line.split(':').first if line =~ /#{@runner_pane}/ + end + + @runner_pane = nil + runner_pane + end + + def interrupt_runner + _run("send-keys -t #{target(:pane => runner_pane)} ^c") + end + + def run_shell_command(command, auto_return = true) + reset_shell + _send_command(command, target(:pane => runner_pane), auto_return) + _move_up_pane + end + + def close_runner_pane + _run("kill-pane -t #{target(:pane => runner_pane)}") + end + + def close_other_panes + if _run("list-panes").split("\n").length > 1 + _run("kill-pane -a") + end + end + + def reset_shell + _run("send-keys -t #{target(:pane => runner_pane)} #{reset_sequence}") + end + + def nearest_inactive_pane_id + panes = _run("list-pane").split("\n") + pane = panes.find { |p| p !~ /active/ } + pane ? pane.split(':').first : nil + end + + def _move_up_pane + _run("select-pane -t #{target}") + end + + def _send_command(command, target, auto_return = true) + _run("send-keys -t #{target} \"#{command.gsub('"', '\"')}\"") + _run("send-keys -t #{target} Enter") if auto_return + end + + def _run(command) + `tmux #{command}` + end +end + +class CurrentTmuxSession < TmuxSession + def initialize + if tmux? + session = self.get_session + window = self.get_property(:active, :window) + pane = self.get_property(:active, :pane) + + super(session, window, pane) + else + raise "You are not in a tmux session" + end + end + + def get_property(match, type) + _run("list-#{type.to_s}").split("\n").each do |line| + return line.split(':').first if line =~ /\(#{match.to_s}\)/ + end + end + + def get_session + _run("display -p '#S'").strip + end + + def tmux? + `echo $TMUX` =~ /.+/ ? true : false + end +end +EOF