diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..42ef9ba --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,42 @@ +name: Check +on: [push, pull_request] +jobs: + check: + strategy: + fail-fast: false + matrix: + vimFlavor: [neovim, vim] + vimVersion: [stable, unstable] + exclude: + - vimFlavor: vim + vimVersion: unstable + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Enable Universe package repository + run: | + sudo add-apt-repository ${{ matrix.vimVersion == 'stable' && 'universe' || 'ppa:neovim-ppa/unstable' }} + sudo apt-get update + - name: Install tmux and ${{ matrix.vimFlavor }} + run: | + sudo apt-get install tmux ${{ matrix.vimFlavor }} + - name: Review versions + run: | + tmux -V + ${{ matrix.vimFlavor == 'neovim' && 'nvim' || 'vim' }} --version + # This tests looks for two thigs: + # * That VIM doesn't hang. If it succedes it will quit quickly. If 5 + # seconds later the tmux session is still running either the runner pane + # didn't get closed or (more likely) we threw some error and VIM is + # sitting there expecting us to acknowledge the message(s). + # * That VIM exited normally. This check isn't very useful since :qa + # never bubbles up an error, but if someday we use :cq for a test being + # ready to check the exit code seems like a good thing. + - name: "Try Vimux" + run: | + ec="$(mktemp)" + tmux new -s ci -d "${{ matrix.vimFlavor == 'neovim' && 'nvim -u /dev/null --headless' || 'vim' }} -i NONE \"+so plugin/vimux.vim\" \"+VimuxRunCommand('date')\" \"+VimuxCloseRunner | qa\"; echo \$? > '$ec'" + sleep 5 + tmux kill-session -t ci && exit 1 + exit "$(cat $ec)" diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml new file mode 100644 index 0000000..6427211 --- /dev/null +++ b/.github/workflows/reviewdog.yml @@ -0,0 +1,13 @@ +name: Reviewdog +on: [pull_request] +jobs: + vint: + name: vint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: vint + uses: reviewdog/action-vint@v1 + with: + github_token: ${{ secrets.github_token }} + reporter: github-pr-review diff --git a/.github/workflows/vint.yml b/.github/workflows/vint.yml new file mode 100644 index 0000000..8529ed9 --- /dev/null +++ b/.github/workflows/vint.yml @@ -0,0 +1,15 @@ +name: Vint +on: [push] +jobs: + vint: + name: vint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + - name: Setup dependencies + run: pip install vim-vint + - name: Lint Vimscript + run: vint . diff --git a/.vintrc.yaml b/.vintrc.yaml new file mode 100644 index 0000000..c44b6ab --- /dev/null +++ b/.vintrc.yaml @@ -0,0 +1,5 @@ +cmdargs: + severity: style_problem + color: true + env: + neovim: false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..82308e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2021 Benjamin Mills + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e20e037 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Vimux: easily interact with tmux from vim + +[![Vint](https://github.com/preservim/vimux/workflows/Vint/badge.svg)](https://github.com/preservim/vimux/actions?workflow=Vint) +[![Check](https://github.com/preservim/vimux/workflows/Check/badge.svg)](https://github.com/preservim/vimux/actions?workflow=Check) + +![vimux](https://www.braintreepayments.com/blog/content/images/blog/vimux3.png) + +Vimux was originally inspired by [tslime.vim](https://github.com/jgdavey/tslime.vim/network), a plugin that lets you send input to tmux. While tslime.vim works well, it wasn't optimized for the use case of having a smaller tmux pane used to run tests or play with a REPL. The goal of 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 the focus on vim. Once that pane exists, whenever you call `VimuxRunCommand` again the command will be executed in that pane. A frequent use case is wanting to rerun commands over and over. An example of this is running the current file through rspec. Rather than typing that over and over `VimuxRunLastCommand` will execute the last command called with `VimuxRunCommand`. + +## Installation + +With **[vim-bundle](https://github.com/preservim/vim-bundle)**: `vim-bundle install preservim/vimux` +With **[Vundle](https://github.com/gmarik/Vundle.vim)**: `Plugin 'preservim/vimux'` in your .vimrc + +Otherwise download the latest [tarball](https://github.com/preservim/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`. + +_Notes:_ + +* Vimux assumes a reasonably new version of tmux. Some older versions might work but it is recommended to use the latest stable release. + +## Usage + +The full documentation is available [online](https://raw.github.com/preservim/vimux/master/doc/vimux.txt) and accessible inside vim via `:help vimux` + +## Platform-specific Plugins + +* [vim-vroom](https://github.com/skalnik/vim-vroom) runner for rspec, cucumber and test/unit; vimux support via `g:vroom_use_vimux` +* [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 +* [vim-turbux](https://github.com/jgdavey/vim-turbux) Turbo Ruby testing with tmux +* [vimux-pyutils](https://github.com/julienr/vimux-pyutils) A set of functions for vimux that allow to run code blocks in ipython +* [vimux-nose-test](https://github.com/pitluga/vimux-nose-test) Run nose tests in vimux +* [vimux-golang](https://github.com/benmills/vimux-golang) Run go tests in vimux +* [vimux-zeus](https://github.com/jingweno/vimux-zeus) Run zeus commands in vimux +* [vimix](https://github.com/spiegela/vimix) Run Elixir mix commands in vimux +* [vimux-cargo](https://github.com/jtdowney/vimux-cargo) run rust tests and projects using cargo and vimux +* [vimux-bazel-test](https://github.com/pgr0ss/vimux-bazel-test) Run bazel tests in vimux +* [vimux-jest-test](https://github.com/tyewang/vimux-jest-test) Run jest tests in vimux diff --git a/README.mkd b/README.mkd deleted file mode 100644 index abe5471..0000000 --- a/README.mkd +++ /dev/null @@ -1,37 +0,0 @@ -# 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`. - -_Notes:_ - -* Vimux requires vim with compiled ruby support. You can confirm you have ruby support by running `vim --version | grep +ruby`. -* Vimux assumes a tmux version >= 1.5. Some older versions might work but it is recommeded to use at least version 1.5. - -## Platform-specific Plugins - -* [vim-vroom](https://github.com/skalnik/vim-vroom) runner for rspec, cucumber and test/unit; vimux support via `g:vroom_use_vimux` -* [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 -* [vim-turbux](https://github.com/jgdavey/vim-turbux) Turbo Ruby testing with tmux -* [vimux-pyutils](https://github.com/julienr/vimux-pyutils) A set of functions for vimux that allow to run code blocks in ipython -* [vimux-nose-test](https://github.com/pitluga/vimux-nose-test) Run nose tests in vimux -* [vimux-golang](https://github.com/benmills/vimux-golang) Run go tests in vimux -* [vimux-zeus](https://github.com/jingweno/vimux-zeus) Run zeus commands in 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 index df6d305..efc3898 100644 --- a/doc/vimux.txt +++ b/doc/vimux.txt @@ -12,9 +12,11 @@ CONTENTS *vimux-contents* 2.2 .............................. |VimuxRunLastCommand| 2.3 .............................. |VimuxInspectRunner| 2.4 .............................. |VimuxCloseRunner| - 2.5 .............................. |VimuxClosePanes| - 2.6 .............................. |VimuxInterruptRunner| + 2.5 .............................. |VimuxInterruptRunner| + 2.6 .............................. |VimuxClearTerminalScreen| 2.7 .............................. |VimuxClearRunnerHistory| + 2.8 .............................. |VimuxZoomRunner| + 2.9 .............................. |VimuxRunCommandInDir| 3. Misc ............................ |VimuxMisc| 3.1 Example Keybinding............ |VimuxExampleKeybinding| 3.2 Tslime Replacement............ |VimuxTslimeReplacement| @@ -26,25 +28,24 @@ 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. +Vimux was originally inspired by tslime.vim [1], a plugin that lets you send +input to tmux. While tslime.vim works well, it wasn't optimized for the use +case of having a smaller tmux pane used to run tests or play with a REPL. The +goal of Vimux is to make interacting with tmux from vim effortless. -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 +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`. +without losing the focus on vim. Once that pane exists, whenever you call +`VimuxRunCommand` again the command will be executed in that pane. A frequent +use case is wanting to rerun commands over and over. An example of this is +running the current file through rspec. Rather than typing that over and over +`VimuxRunLastCommand` will execute the last command 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. +Other auxiliary functions and the ones talked about above can be found bellow +with full descriptions and example key bindings for your vimrc. -[1] https://github.com/kikijump/tslime.vim +[1] https://github.com/jgdavey/tslime.vim/network ============================================================================== @@ -63,15 +64,16 @@ Furthermore there are several handy commands all starting with 'Vimux': - |VimuxRunCommand| - |VimuxSendText| - |VimuxSendKeys| - - |VimuxOpenPane| + - |VimuxOpenRunner| - |VimuxRunLastCommand| - |VimuxCloseRunner| - - |VimuxClosePanes| - - |VimuxCloseWindows| - |VimuxInspectRunner| - |VimuxInterruptRunner| - |VimuxPromptCommand| + - |VimuxClearTerminalScreen| - |VimuxClearRunnerHistory| + - |VimuxZoomRunner| + - |VimuxRunCommandInDir| ------------------------------------------------------------------------------ *VimuxRunCommand* @@ -91,8 +93,8 @@ vimux from automatically sending a return after the command. *VimuxSendText* VimuxSendText~ -Send raw text to the runer pane. This command will not open a new pane if one -does not already exist. You will need to use VimuxOpenPane to do this. This +Send raw text to the runner pane. This command will not open a new pane if one +does not already exist. You will need to use |VimuxOpenRunner| to do this. This command can be used to interact with REPLs or other interactive terminal programs that are not shells. @@ -102,26 +104,28 @@ programs that are not shells. VimuxSendKeys~ Send keys to the runner pane. This command will not open a new pane if one -does not already exist. You will need to use VimuxOpenPane to do this. You can -use this command to send keys such as "Enter" or "C-c" to the runner pane. +does not already exist. You will need to use |VimuxOpenRunner| to do this. You +can use this command to send keys such as "Enter" or "C-c" to the runner pane. ------------------------------------------------------------------------------ - *VimuxOpenPane* -VimuxOpenPane~ + *VimuxOpenRunner* +VimuxOpenRunner~ -This will either opne a new pane or use the nearest pane and set it as the +This will either open a new pane or use the nearest pane and set it as the vimux runner pane for the other vimux commands. You can control if this command -uses the nearest pane or always creates a new one with g:VimuxUseNearestPane +uses the nearest pane or always creates a new one with g:|VimuxUseNearest| ------------------------------------------------------------------------------ *VimuxPromptCommand* VimuxPromptCommand~ Prompt for a command and run it in a small horizontal split bellow the current -pane. +pane. A parameter can be supplied to predefine a command or a part of the +command which can be edited in the prompt. > " Prompt for a command to run map - vp :VimuxPromptCommand + map vp :VimuxPromptCommand + map vm :VimuxPromptCommand("make ") < ------------------------------------------------------------------------------ @@ -142,7 +146,7 @@ Move into the tmux runner pane created by `VimuxRunCommand` and enter copy pmode (scroll mode). > " Inspect runner pane map - vi :VimuxInspectRunner + map vi :VimuxInspectRunner < ------------------------------------------------------------------------------ @@ -163,22 +167,60 @@ Interrupt any command that is running inside the runner pane. > " Interrupt any command running in the runner pane map - vs :VimuxInterruptRunner + map vs :VimuxInterruptRunner < +------------------------------------------------------------------------------ + *VimuxClearTerminalScreen* +VimuxClearTerminalScreen~ + +Clear the terminal screen of the runner pane. +> + " Clear the terminal screen of the runner pane. + map v :VimuxClearTerminalScreen +< ------------------------------------------------------------------------------ *VimuxClearRunnerHistory* VimuxClearRunnerHistory~ -Clear ths tmux history of the runner pane for when +Clear the 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 + map vc :VimuxClearRunnerHistory +< + +------------------------------------------------------------------------------ + *VimuxZoomRunner* +VimuxZoomRunner~ + +Zoom the runner pane. Once its zoomed, you will need +to use tmux " z" to restore the runner pane. +Zoom requires tmux version >= 1.8 +> + + " Zoom the tmux runner page + map vz :VimuxZoomRunner < +------------------------------------------------------------------------------ + *VimuxRunCommandInDir* +VimuxRunCommandInDir~ + +Runs the specified command inside the directory of +the currently opened file. Takes two arguments. command and inFile + +command: The command to run +inFile: If 1 the filename will be appended to the command +> + + " Compile currently opened latex file to pdf + autocmd Filetype tex nnoremap rr :update:call VimuxRunCommandInDir('latexmk -pdf', 1) + " Push the repository of the currently opened file + nnoremap gp :call VimuxRunCommandInDir("git push", 0) +< ============================================================================== MISC (3) *VimuxMisc* @@ -193,18 +235,24 @@ Full Keybind Example~ " Prompt for a command to run map vp :VimuxPromptCommand - + " Run last command executed by VimuxRunCommand map vl :VimuxRunLastCommand - + " Inspect runner pane map vi :VimuxInspectRunner - + " Close vim tmux runner opened by VimuxRunCommand map vq :VimuxCloseRunner - + " Interrupt any command running in the runner pane map vx :VimuxInterruptRunner + + " Zoom the runner pane (use z to restore runner pane) + map vz :call VimuxZoomRunner() + + " Clear the terminal screen of the runner pane. + map v :VimuxClearTerminalScreen > ------------------------------------------------------------------------------ @@ -216,8 +264,7 @@ First, add some helpful mappings. > function! VimuxSlime() - call VimuxSendText(@v) - call VimuxSendKeys("Enter") + call VimuxRunCommand(@v, 0) endfunction " If text is selected, save it in the v buffer and send that buffer it to tmux @@ -239,28 +286,31 @@ extra return. Thanks to @trptcolin for discovering this issue. ============================================================================== CONFIGURATION (4) *VimuxConfiguration* -You can configure Vimux like this: +You can configure Vimux as follows. Note that all occurances of global +variables `g:Vimux...` may also be set using buffer variables `b:Vimux...` to +change the behavior of Vimux in just the current buffer. ------------------------------------------------------------------------------ *VimuxConfiguration_height* -2.1 g:VimuxHeight~ - -The percent of the screen the split pane Vimux will spawn should take up. +4.1 g:VimuxHeight~ +The part of the screen the split pane Vimux will spawn should take up. This +option accepts both a number of lines/columns or a percentage. +> let g:VimuxHeight = "40" - -Default: "20" +< +Default: "20%" ------------------------------------------------------------------------------ *VimuxConfiguration_orientation* -2.2 g:VimuxOrientation~ +4.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 @@ -268,37 +318,173 @@ Options: Default: "v" ------------------------------------------------------------------------------ - *VimuxConfiguration_use_nearest_pane* -2.3 g:VimuxUseNearestPane~ - -Use exising pane (not used by vim) if found instead of running split-window. + *VimuxConfiguration_use_nearest* +4.3 g:VimuxUseNearest~ - let VimuxUseNearestPane = 1 - -Default: 0 +Use existing pane or window (not used by vim) if found instead of running +split-window. +> + let g:VimuxUseNearest = 1 +< +Default: 1 ------------------------------------------------------------------------------ *VimuxConfiguration_reset_sequence* -2.4 g:VimuxResetSequence~ +4.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. +The keys sent to the runner pane before running a command. - let VimuxResetSequence = "" +When vimux runs a tmux command, it first makes sure that the runner is not in +copy mode by running `copy-mode -q` on the runner. This sequence is then sent +to make sure that the runner is ready to receive input. -Default: "q C-u" +The default sends `C-u` to clear the line. +> + let g:VimuxResetSequence = "" +< +Default: "C-u" ------------------------------------------------------------------------------ *VimuxPromptString* -2.5 g:VimuxPromptString~ +4.5 g:VimuxPromptString~ The string presented in the vim command line when Vimux is invoked. Be sure to put a space at the end of the string to allow for distinction between the prompt and your input. - +> let g:VimuxPromptString = "" - +< Default: "Command? " +------------------------------------------------------------------------------ + *VimuxRunnerType* +4.6 g:VimuxRunnerType~ + +The type of view object Vimux should use for the runner. For reference, a +tmux session is a group of windows, and a window is a layout of panes. +> + let g:VimuxRunnerType = "window" +< +Options: + "pane": for panes + "window": for windows + +Default: "pane" + +------------------------------------------------------------------------------ + *VimuxRunnerName* +4.7 g:VimuxRunnerName~ + +Setting the name for the runner. Works for panes and windows. This makes the +VimuxRunner reusable between sessions. Caveat is, all your instances (in the +same session/window) use the same window. + +Caution: It is probably best not to mix this with |VimuxCloseOnExit|. +> + let g:VimuxRunnerName = "vimuxout" +< +Default: "" + +------------------------------------------------------------------------------ + *VimuxTmuxCommand* +4.8 g:VimuxTmuxCommand~ + +The command that Vimux runs when it calls out to tmux. It may be useful to +redefine this if you're using something like tmate. +> + let g:VimuxTmuxCommand = "tmate" +< +Default: "tmux" + +------------------------------------------------------------------------------ + *VimuxOpenExtraArgs* +4.9 g:VimuxOpenExtraArgs~ + +Allows addtional arguments to be passed to the tmux command that opens the +runner. Make sure that the arguments specified are valid depending on whether +you're using panes or windows, and your version of tmux. +> + let g:VimuxOpenExtraArgs = "-c #{pane_current_path}" +< +Default: "" + +------------------------------------------------------------------------------ + *VimuxExpandCommand* +4.10 g:VimuxExpandCommand~ + +Should the command given at the prompt via VimuxPromptCommand be expanded +using expand(). 1 to expand the string. + +Unfortunately expand() only expands % (etc.) if the string starts with that +character. So the command is split at spaces and then rejoined after +expansion. With this simple approach things like "%:h/test.xml" are not +possible. +> + let g:VimuxExpandCommand = 1 +< +Default: 0 + +------------------------------------------------------------------------------ + *VimuxCloseOnExit* +4.11 g:VimuxCloseOnExit~ + +Set this option to `1` or `v:true` to tell vimux to close the runner when you quit +vim. + +Caution: It is probably best not to mix this with |VimuxRunnerName|. +> + let g:VimuxCloseOnExit = 1 +< +Default: 0 + +------------------------------------------------------------------------------ + *VimuxCommandShell* +4.12 g:VimuxCommandShell~ + +Set this option to `1` or `v:true` to enable shell completion in +VimuxPromptCommand +Set this option to `0` or `v:false` to enable vim command editing in +VimuxPromptCommand + +Enabling shell completion blocks the ability to use up-arrow to cycle through +previously-run commands in VimuxPromptCommand. +> + let g:VimuxCommandShell = 0 +< +Default: 1 + +------------------------------------------------------------------------------ + *VimuxRunnerQuery* +4.13 g:VimuxRunnerQuery~ + +Set this option to define a query to use for looking up an existing runner +pane or window when initiating Vimux. Uses the tmux syntax for the target-pane +and target-window command arguments. (See the man page for tmux). It must be a +dictionary containing up to two keys, "pane" and "window", defining the query +to use for the respective runner types. + +If no key exists for the current runner type, the search for an existing +runner falls back to the `VimuxUseNearest` option (and the related +`VimuxRunnerName`). If that option is false or either command fails, a new +runner is created instead, positioned according to `VimuxOrientation`. +> + let g:VimuxRunnerQuery = { + \ 'pane': '{down-of}', + \ 'window': 'vimux', + \} +< +Default: {} + +------------------------------------------------------------------------------ + *VimuxDebug* +4.13 g:VimuxDebug~ + +If you're having trouble with vimux, set this option to get vimux to pass each +tmux command to |echomsg| before running it. +> + let g:VimuxDebug = v:true +< +Default: v:false + ============================================================================== vim:tw=78:ts=2:sw=2:expandtab:ft=help:norl: diff --git a/plugin/vimux.vim b/plugin/vimux.vim index 6bcdde4..4f571da 100644 --- a/plugin/vimux.vim +++ b/plugin/vimux.vim @@ -1,147 +1,380 @@ -if exists("g:loaded_vimux") || &cp +if exists('g:loaded_vimux') || &compatible finish endif let g:loaded_vimux = 1 -command VimuxRunLastCommand :call VimuxRunLastCommand() -command VimuxCloseRunner :call VimuxCloseRunner() -command VimuxInspectRunner :call VimuxInspectRunner() -command VimuxScrollUpInspect :call VimuxScrollUpInspect() -command VimuxScrollDownInspect :call VimuxScrollDownInspect() -command VimuxInterruptRunner :call VimuxInterruptRunner() -command VimuxPromptCommand :call VimuxPromptCommand() -command VimuxClearRunnerHistory :call VimuxClearRunnerHistory() - -function! VimuxRunLastCommand() - if exists("g:VimuxRunnerPaneIndex") - call VimuxRunCommand(g:VimuxLastCommand) - else - echo "No last vimux command." +" Set up all global options with defaults right away, in one place +let g:VimuxDebug = get(g:, 'VimuxDebug', v:false) +let g:VimuxHeight = get(g:, 'VimuxHeight', '20%') +let g:VimuxOpenExtraArgs = get(g:, 'VimuxOpenExtraArgs', '') +let g:VimuxOrientation = get(g:, 'VimuxOrientation', 'v') +let g:VimuxPromptString = get(g:, 'VimuxPromptString', 'Command? ') +let g:VimuxResetSequence = get(g:, 'VimuxResetSequence', 'C-u') +let g:VimuxRunnerName = get(g:, 'VimuxRunnerName', '') +let g:VimuxRunnerType = get(g:, 'VimuxRunnerType', 'pane') +let g:VimuxRunnerQuery = get(g:, 'VimuxRunnerQuery', {}) +let g:VimuxTmuxCommand = get(g:, 'VimuxTmuxCommand', 'tmux') +let g:VimuxUseNearest = get(g:, 'VimuxUseNearest', v:true) +let g:VimuxExpandCommand = get(g:, 'VimuxExpandCommand', v:false) +let g:VimuxCloseOnExit = get(g:, 'VimuxCloseOnExit', v:false) +let g:VimuxCommandShell = get(g:, 'VimuxCommandShell', v:true) + +function! VimuxOption(name) abort + return get(b:, a:name, get(g:, a:name)) +endfunction + +if !executable(VimuxOption('VimuxTmuxCommand')) + echohl ErrorMsg | echomsg 'Failed to find executable '.VimuxOption('VimuxTmuxCommand') | echohl None + finish +endif + +command -nargs=* VimuxRunCommand :call VimuxRunCommand() +command -bar VimuxRunLastCommand :call VimuxRunLastCommand() +command -bar VimuxOpenRunner :call VimuxOpenRunner() +command -bar VimuxCloseRunner :call VimuxCloseRunner() +command -bar VimuxZoomRunner :call VimuxZoomRunner() +command -bar VimuxInspectRunner :call VimuxInspectRunner() +command -bar VimuxScrollUpInspect :call VimuxScrollUpInspect() +command -bar VimuxScrollDownInspect :call VimuxScrollDownInspect() +command -bar VimuxInterruptRunner :call VimuxInterruptRunner() +command -nargs=? VimuxPromptCommand :call VimuxPromptCommand() +command -bar VimuxClearTerminalScreen :call VimuxClearTerminalScreen() +command -bar VimuxClearRunnerHistory :call VimuxClearRunnerHistory() +command -bar VimuxTogglePane :call VimuxTogglePane() + +augroup VimuxAutocmds + au! + autocmd VimLeave * call s:autoclose() +augroup END + +function! VimuxRunCommandInDir(command, useFile) abort + let l:file = '' + if a:useFile ==# 1 + let l:file = shellescape(expand('%:t'), 1) endif + call VimuxRunCommand('(cd '.shellescape(expand('%:p:h'), 1).' && '.a:command.' '.l:file.')') endfunction -function! VimuxRunCommand(command, ...) - if !exists("g:VimuxRunnerPaneIndex") || _VimuxHasPane(g:VimuxRunnerPaneIndex) == -1 - call VimuxOpenPane() +function! VimuxRunLastCommand() abort + if exists('g:VimuxLastCommand') + call VimuxRunCommand(g:VimuxLastCommand) + else + echo 'No last vimux command.' endif +endfunction +function! VimuxRunCommand(command, ...) abort + call VimuxOpenRunner() let l:autoreturn = 1 - if exists("a:1") + if exists('a:1') let l:autoreturn = a:1 endif - - let resetSequence = _VimuxOption("g:VimuxResetSequence", "q C-u") + let l:resetSequence = VimuxOption('VimuxResetSequence') let g:VimuxLastCommand = a:command - call VimuxSendKeys(resetSequence) - call VimuxSendText(a:command) + call s:exitCopyMode() + call s:sendKeys(l:resetSequence) + call s:sendText(a:command) + if l:autoreturn ==# 1 + call s:sendKeys('Enter') + endif +endfunction - if l:autoreturn == 1 - call VimuxSendKeys("Enter") +function! VimuxSendText(text) abort + if s:hasRunner() + call s:sendText(a:text) + else + call s:echoNoRunner() endif endfunction -function! VimuxSendText(text) - call VimuxSendKeys('"'.escape(a:text, '"').'"') +function! VimuxSendKeys(keys) abort + if s:hasRunner() + call s:sendKeys(a:keys) + else + call s:echoNoRunner() + endif endfunction -function! VimuxSendKeys(keys) - if exists("g:VimuxRunnerPaneIndex") - call system("tmux send-keys -t ".g:VimuxRunnerPaneIndex." ".a:keys) +function! VimuxOpenRunner() abort + if s:hasRunner() + return + endif + let existingId = s:existingRunnerId() + if existingId !=# '' + let g:VimuxRunnerIndex = existingId else - echo "No vimux runner pane. Create one with VimuxOpenPane" + let extraArguments = VimuxOption('VimuxOpenExtraArgs') + if VimuxOption('VimuxRunnerType') ==# 'pane' + call VimuxTmux('split-window '.s:vimuxPaneOptions().' '.extraArguments) + elseif VimuxOption('VimuxRunnerType') ==# 'window' + call VimuxTmux('new-window '.extraArguments) + endif + let g:VimuxRunnerIndex = s:tmuxIndex() + call s:setRunnerName() + call VimuxTmux('last-'.VimuxOption('VimuxRunnerType')) + endif +endfunction + +function! VimuxCloseRunner() abort + if s:hasRunner() + call VimuxTmux('kill-'.VimuxOption('VimuxRunnerType').' -t '.g:VimuxRunnerIndex) endif + unlet! g:VimuxRunnerIndex endfunction -function! VimuxOpenPane() - let height = _VimuxOption("g:VimuxHeight", 20) - let orientation = _VimuxOption("g:VimuxOrientation", "v") - let nearestIndex = _VimuxNearestPaneIndex() +function! VimuxTogglePane() abort + if s:hasRunner() + if VimuxOption('VimuxRunnerType') ==# 'window' + call VimuxTmux('join-pane -s '.g:VimuxRunnerIndex.' '.s:vimuxPaneOptions()) + let g:VimuxRunnerType = 'pane' + let g:VimuxRunnerIndex = s:tmuxIndex() + call VimuxTmux('last-'.VimuxOption('VimuxRunnerType')) + elseif VimuxOption('VimuxRunnerType') ==# 'pane' + let g:VimuxRunnerIndex=substitute( + \ VimuxTmux('break-pane -d -s '.g:VimuxRunnerIndex." -P -F '#{window_id}'"), + \ '\n', + \ '', + \ '' + \) + let g:VimuxRunnerType = 'window' + endif + else + call s:echoNoRunner() + endif +endfunction - if _VimuxOption("g:VimuxUseNearestPane", 1) == 1 && nearestIndex != -1 - let g:VimuxRunnerPaneIndex = nearestIndex +function! VimuxZoomRunner() abort + if s:hasRunner() + if VimuxOption('VimuxRunnerType') ==# 'pane' + call VimuxTmux('resize-pane -Z -t '.g:VimuxRunnerIndex) + elseif VimuxOption('VimuxRunnerType') ==# 'window' + call VimuxTmux('select-window -t '.g:VimuxRunnerIndex) + endif else - call system("tmux split-window -p ".height." -".orientation) - let g:VimuxRunnerPaneIndex = _VimuxTmuxPaneIndex() - call system("tmux last-pane") + call s:echoNoRunner() endif endfunction -function! VimuxCloseRunner() - if exists("g:VimuxRunnerPaneIndex") - call system("tmux kill-pane -t ".g:VimuxRunnerPaneIndex) - unlet g:VimuxRunnerPaneIndex +function! VimuxInspectRunner() abort + if s:hasRunner() + call VimuxTmux('select-'.VimuxOption('VimuxRunnerType').' -t '.g:VimuxRunnerIndex) + call VimuxTmux('copy-mode') + return v:true endif + call s:echoNoRunner() + return v:false endfunction -function! VimuxInspectRunner() - call system("tmux select-pane -t ".g:VimuxRunnerPaneIndex) - call system("tmux copy-mode") +function! VimuxScrollUpInspect() abort + if VimuxInspectRunner() + call VimuxTmux('last-'.VimuxOption('VimuxRunnerType')) + call s:sendKeys('C-u') + endif endfunction -function! VimuxScrollUpInspect() - call VimuxInspectRunner() - call system("tmux last-pane") - call VimuxSendKeys("C-u") +function! VimuxScrollDownInspect() abort + if VimuxInspectRunner() + call VimuxTmux('last-'.VimuxOption('VimuxRunnerType')) + call s:sendKeys('C-d') + endif endfunction -function! VimuxScrollDownInspect() - call VimuxInspectRunner() - call system("tmux last-pane") - call VimuxSendKeys("C-d") +function! VimuxInterruptRunner() abort + if s:hasRunner() + call s:sendKeys('^c') + else + call s:echoNoRunner() + endif endfunction -function! VimuxInterruptRunner() - call VimuxSendKeys("^c") +function! VimuxClearTerminalScreen() abort + if s:hasRunner() + call s:exitCopyMode() + call s:sendKeys('C-l') + else + call s:echoNoRunner() + endif endfunction -function! VimuxClearRunnerHistory() - if exists("g:VimuxRunnerPaneIndex") - call system("tmux clear-history -t ".g:VimuxRunnerPaneIndex) +function! VimuxClearRunnerHistory() abort + if s:hasRunner() + call VimuxTmux('clear-history -t '.g:VimuxRunnerIndex) + else + call s:echoNoRunner() endif endfunction -function! VimuxPromptCommand() - let l:command = input(_VimuxOption("g:VimuxPromptString", "Command? ")) +function! VimuxPromptCommand(...) abort + let command = a:0 ==# 1 ? a:1 : '' + if VimuxOption('VimuxCommandShell') + let l:command = input(VimuxOption('VimuxPromptString'), command, 'shellcmd') + else + let l:command = input(VimuxOption('VimuxPromptString'), command) + endif + if VimuxOption('VimuxExpandCommand') + let l:command = join(map(split(l:command, ' '), 'expand(v:val)'), ' ') + endif call VimuxRunCommand(l:command) endfunction -function! _VimuxTmuxSession() - return _VimuxTmuxProperty("S") +function! VimuxTmux(arguments) abort + let l:tmuxCommand = VimuxOption('VimuxTmuxCommand').' '.a:arguments + if VimuxOption('VimuxDebug') + echom l:tmuxCommand + endif + if has_key(environ(), 'TMUX') + let l:output = system(l:tmuxCommand) + if v:shell_error + throw 'Tmux command failed with message:' . l:output + endif + return l:output + else + throw 'Aborting, because not inside tmux session.' + endif +endfunction + +function! s:exitCopyMode() abort + try + call VimuxTmux('copy-mode -q -t '.g:VimuxRunnerIndex) + catch + let l:versionString = s:tmuxProperty('#{version}') + if str2float(l:versionString) < 3.2 + call s:sendKeys('q') + endif + endtry +endfunction + +function! s:tmuxSession() abort + return s:tmuxProperty('#S') +endfunction + +function! s:tmuxIndex() abort + if VimuxOption('VimuxRunnerType') ==# 'pane' + return s:tmuxPaneId() + else + return s:tmuxWindowId() + end endfunction -function! _VimuxTmuxPaneIndex() - return _VimuxTmuxProperty("P") +function! s:tmuxPaneId() abort + return s:tmuxProperty('#{pane_id}') endfunction -function! _VimuxTmuxWindowIndex() - return _VimuxTmuxProperty("I") +function! s:tmuxWindowId() abort + return s:tmuxProperty('#{window_id}') endfunction -function! _VimuxNearestPaneIndex() - let panes = split(system("tmux list-panes"), "\n") +function! s:vimuxPaneOptions() abort + let height = VimuxOption('VimuxHeight') + let orientation = VimuxOption('VimuxOrientation') + return '-l '.height.' -'.orientation +endfunction + +"" +" @return a string of the form '%4', the ID of the pane or window to use, +" or '' if no nearest pane or window is found. +function! s:existingRunnerId() abort + let runnerType = VimuxOption('VimuxRunnerType') + let query = get(VimuxOption('VimuxRunnerQuery'), runnerType, '') + if empty(query) + if empty(VimuxOption('VimuxUseNearest')) + return '' + else + return s:nearestRunnerId() + endif + endif + " Try finding the runner using the provided query + let currentId = s:tmuxIndex() + let message = VimuxTmux('select-'.runnerType.' -t '.query.'') + if message ==# '' + " A match was found. Make sure it isn't the current vim pane/window + " though! + let runnerId = s:tmuxIndex() + if runnerId !=# currentId + " Success! + call VimuxTmux('last-'.runnerType) + return runnerId + endif + endif + return '' +endfunction - for i in panes - if match(panes[i], "(active)") == -1 - return split(panes[i], ":")[0] +function! s:nearestRunnerId() abort + " Try finding the runner in the current window/session, optionally using a + " name/title filter + let runnerType = VimuxOption('VimuxRunnerType') + let filter = s:getTargetFilter() + let views = split( + \ VimuxTmux( + \ 'list-'.runnerType.'s' + \ ." -F '#{".runnerType.'_active}:#{'.runnerType."_id}'" + \ .filter), + \ '\n') + " '1:' is the current active pane (the one with vim). + " Find the first non-active pane. + for view in views + if match(view, '1:') ==# -1 + return split(view, ':')[1] endif endfor + return '' +endfunction - return -1 +function! s:getTargetFilter() abort + let targetName = VimuxOption('VimuxRunnerName') + if targetName ==# '' + return '' + endif + let runnerType = VimuxOption('VimuxRunnerType') + if runnerType ==# 'window' + return " -f '#{==:#{window_name},".targetName."}'" + elseif runnerType ==# 'pane' + return " -f '#{==:#{pane_title},".targetName."}'" + endif endfunction -function! _VimuxOption(option, default) - if exists(a:option) - return eval(a:option) - else - return a:default +function! s:setRunnerName() abort + let targetName = VimuxOption('VimuxRunnerName') + if targetName ==# '' + return + endif + let runnerType = VimuxOption('VimuxRunnerType') + if runnerType ==# 'window' + call VimuxTmux('rename-window '.targetName) + elseif runnerType ==# 'pane' + call VimuxTmux('select-pane -T '.targetName) + endif +endfunction + +function! s:tmuxProperty(property) abort + return substitute(VimuxTmux("display -p '".a:property."'"), '\n$', '', '') +endfunction + +function! s:hasRunner() abort + if get(g:, 'VimuxRunnerIndex', '') == '' + return v:false endif + let l:runnerType = VimuxOption('VimuxRunnerType') + let l:command = 'list-'.runnerType."s -F '#{".runnerType."_id}'" + let l:found = match(VimuxTmux(l:command), g:VimuxRunnerIndex) + return l:found != -1 +endfunction + +function! s:autoclose() abort + if VimuxOption('VimuxCloseOnExit') + call VimuxCloseRunner() + endif +endfunction + +function! s:sendKeys(keys) abort + call VimuxTmux('send-keys -t '.g:VimuxRunnerIndex.' '.a:keys) endfunction -function! _VimuxTmuxProperty(property) - return substitute(system("tmux display -p '#".a:property."'"), '\n$', '', '') +function! s:sendText(text) abort + call s:sendKeys(shellescape(substitute(a:text, '\n$', ' ', ''))) endfunction -function! _VimuxHasPane(index) - return match(system("tmux list-panes"), a:index.":") +function! s:echoNoRunner() abort + echo 'No vimux runner pane/window. Create one with VimuxOpenRunner' endfunction