Commits

Steve Losh committed 254f29d

Initial commit.

  • Participants

Comments (0)

Files changed (5)

File LICENSE.markdown

+Copyright (C) 2012 Steve Losh and contributors
+
+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.**
+

File README.markdown

+Vitality
+========
+
+(Vit)ality is a plugin that makes (V)im play nicely with (i)Term 2 and
+(t)mux.
+
+Features
+--------
+
+Vitality restores the `FocusLost` and `FocusGained` autocommand functionality.
+Now Vim can save when iTerm 2 loses focus, even if it's inside tmux!
+
+It also handles switching the cursor to a bar shaped one when in insert mode,
+and restoring it when not.
+
+Pull requests for other helpful behavior are welcome.
+
+Installation and Usage
+----------------------
+
+Use Pathogen to install.
+
+You shouldn't need to do anything else, but you can read `:help vitality` if
+you're curious.
+
+License
+-------
+
+MIT/X11

File doc/vitality.txt

+*vitality.txt*   make Vim play nicely with iTerm2 and tmux
+
+    █████ █      ██                       ███
+ ██████  █    █████ █       █              ███     █       █
+██   █  █       ██████     ██               ██    ███     ██
+    █  ██       █ ███      ██               ██     █      ██
+   █  ███      █         ████████           ██          ████████
+  ██   ██      █  ███   ████████   ████     ██   ███   ████████ ██   ████
+  ██   ██      █   ███     ██     █ ███  █  ██    ███     ██     ██    ███  █
+  ██   ██     █     ██     ██    █   ████   ██     ██     ██     ██     ████
+  ██   ██     █     ██     ██   ██    ██    ██     ██     ██     ██      ██
+  ██   ██     █     ██     ██   ██    ██    ██     ██     ██     ██      ██
+   ██  ██    █      ██     ██   ██    ██    ██     ██     ██     ██      ██
+    ██ █     █      ██     ██   ██    ██    ██     ██     ██     ██      ██
+     ███     █      ██     ██   ██    ██    ██     ██     ██     ██      ██
+      ███████       ███ █   ██   █████ ██   ███ █  ███ █   ██     █████████
+        ███          ███          ███   ██   ███    ███             ████ ███
+                                                                          ███
+    Vitality makes [V]im play nicely with                          █████   ███
+                   [i]Term2 and                                  ████████  ██
+                   [t]mux.                                      █      ████
+
+==============================================================================
+CONTENTS                                                       *clam-contents*
+
+    1. Overview and Usage .............. |VitalityOverview|
+    2. How it Works .................... |VitalityHow|
+    3. License ......................... |VitalityLicense|
+    4. Bugs ............................ |VitalityBugs|
+    5. Contributing .................... |VitalityContributing|
+    6. Changelog ....................... |VitalityChangelog|
+
+==============================================================================
+1. Overview and Usage                         *VitalityOverview* *VitalityUsage*
+
+Vitality is a Vim plugin designed to restore some sanity to the fairly common
+combination of Vim, iTerm2 and (optionally) tmux.
+
+It currently performs two main functions.  First, it makes the |FocusGained|
+and |FocusLost| autocommands fire when iTerm2 gains or loses focus.  Yes, even
+through tmux.
+
+It also handles setting the cursor to and from a bar when entering and exiting
+insert mode.
+
+You shouldn't have to do anything other than install the plugin for all this
+to work.
+
+If you encounter bugs, please report them (|VitalityBugs|).  Terminal codes
+are a fiddly little rabbit hole, so it's entirely possible that things will
+sometimes break.
+==============================================================================
+2. How it Works                                                  *VitalityHow*
+
+You don't need to know this.
+
+You don't WANT to know this.
+
+Turn back now or skip to the |VitalityLicense|.
+
+You've been warned.
+
+------------------------------------------------------------------------------
+2.1  I WILL BE YOUR HANDS AND EYES
+
+DISCLAIMER: I am not a terminal expert by any means.  Some of this may not be
+            entirely correct.  However, it should at least give you a general
+            understanding about what's going on.
+
+DISCLAIMER 2: Any specific examples given apply only to Vim, iTerm 2, and
+              tmux, because that's what Vitality targets.  Other terminals
+              do things differently.
+
+First: some background.  Vim and your terminal program (iTerm2) communicate by
+sending and receiving special strings that you'd never want to type or read in
+real life.  For the sake of brevity we'll call these "escape sequences" in
+this document.
+
+For example: if a program outputs the text: >
+
+    <Esc>]50;CursorShape=1<C-G>
+
+iTerm will change the cursor to a bar shape.
+
+This particular sequence is 19 bytes long.  The <Esc> and <C-G> characters are
+actual single-byte characters: ASCII points 27 and 7 (033 and 07 in octal)
+respectively.
+
+You can try this out yourself.  Open a new iTerm2 window (WITHOUT tmux) and
+run bash, then try running something that will output that magic string: >
+
+    echo -e "hello \033]50;CursorShape=1\007 world"
+
+Your cursor will now be a bar.  Run the command again with a 0 instead of
+a 1 to turn it into a block, or 2 to turn it into an underline.
+
+iTerm2 supports a number of escape sequences.  Here are the ones Vitality
+uses to accomplish its goals (don't worry about what they mean yet):
+
+Set cursor to a bar shape (19 bytes): >
+    <Esc>]50;CursorShape=1<C-G>
+
+Set cursor to a block shape (19 bytes): >
+    <Esc>]50;CursorShape=0<C-G>
+
+Enable focus reporting (8 bytes): >
+    <Esc>[?1004h
+
+Disable focus reporting (8 bytes): >
+    <Esc>[?1004l
+
+Save screen (kind of) (8 bytes): >
+    <Esc>[?1049h
+
+Restore screen (kind of) (8 bytes): >
+    <Esc>[?1049h
+
+You can play with these in bash with echo -e "..." if you want.  Just remember
+to replace <Esc> with \033 and <C-G> with \007.
+
+------------------------------------------------------------------------------
+2.2  YOU ARE IN A MAZE OF TWISTY LITTLE TERMINAL CODES, NONE ALIKE
+
+Before we get to how Vitality uses these codes, there's one other wrinkle to
+take care of: tmux.
+
+When you run Vim inside of tmux it's no longer talking directly to your
+terminal.  Vim talks to tmux, and tmux talks to your terminal.  Tmux will
+intercept the escape sequences and deal with them in its own way.
+
+Usually this is what you want, but in Vitality's case it needs to get some of
+the escape codes all the way through to iTerm.
+
+Recent versions of tmux allow you to wrap escape sequences in two more escape
+sequences, kind of like an envelope.  When tmux sees this special wrapper it
+will strip it off and send the contents directly to your terminal.
+
+The wrapping sequences are: >
+
+    <Esc>Ptmux;
+    <Esc>\
+
+The <Esc> is still a literal escape character.
+
+You also need to double any escape characters in your original sequence.  tmux
+will collapse them into single characters before sending them along.
+
+You can try this out by opening up an iTerm2 window, starting tmux, running
+bash, and then trying to change the cursor shape like before: >
+
+    echo -e "hello \033]50;CursorShape=1\007 world"
+
+It won't work, because tmux intercepts and discards the sequences.  Try
+wrapping it in the tmux wrapping sequences: >
+
+    echo -e "hello \033Ptmux;\033\033]50;CursorShape=1\007\033\\ world"
+                   wwwwwwwwww----DDDD---------------------wwwwww
+    w = wrapper
+    - = original sequence
+    D = doubled escape character
+
+This time your cursor should change!
+
+The cursor-changing and focus reporting (we'll get to those soon) sequences
+need to go to iTerm 2 and so need to be escaped like this.  The screen
+restoring ones, however, need to go to tmux, because tmux is handling the
+drawing of the screen!
+
+------------------------------------------------------------------------------
+2.3  IT IS PITCH BLACK
+
+The trickest part of Vitality is how it restores the |FocusGained| and
+|FocusLost| autocommands.
+
+The first thing to know is that iTerm 2 supports two escape sequences: one
+that turn on "focus reporting" and one that turns it off.  You send these
+sequences as described in the previous section.
+
+When "focus reporting" is turned on, whenever an iTerm 2 window gains focus it
+will send the following string over standard in to whatever program is
+currently running, as if you had typed it yourself: >
+
+    <Esc>[I
+
+This sequence is three bytes long.  Once again, the <Esc> is a literal ASCII
+escape.  A similar sequence is sent when the window loses focus: >
+
+    <Esc>[O
+
+That's an "oh", not a "zero".
+
+Play around with this by opening up a new iTerm 2 window without tmux, and
+running the following command: >
+
+    echo -e "hello \033[?1004h world" && read
+
+Focus and unfocus the window a few times and notice that something like this
+will be appearing on your screen: >
+
+    $ echo -e "hello \033[?1004h world" && read
+    ^[[O^[[I^[[O^[[I
+
+The "^[" stands for an escape character.
+
+At this point you might think we could just add mappings that watch for
+<Esc>[O and <Esc>[I to Vim and be done.  That will work, but isn't ideal.
+
+Imagine adding the following mapping: >
+
+    inoremap <Esc>[O <c-o>:doautocmd FocusLost %<cr>
+
+This will work, but not perfectly.
+
+When you're in insert mode and press <Esc> Vim will wait to see what you type
+next instead of exiting.  Vim doesn't know if [O is coming (and therefore the
+mapping should happen) or if it's you pressing the escape key and wanting it
+to happen right away.
+
+We can get around this by using a combination of Vim features.  But first:
+more background.
+
+------------------------------------------------------------------------------
+2.4  YOU ARE LIKELY TO BE EATEN BY A GRUE
+
+When you press your right arrow key, what happens?  There's no "right" ASCII
+code, so how can it tell Vim that you pressed the arrow?
+
+The answer is the iTerm 2 (or any terminal) will send a series of codes to
+represent the key.  Vim looks for these series, and when it sees them it
+treats it as the appropriate key.  For example, when you press the right arrow
+key Vim will see: >
+
+    <Esc>OC
+
+Three bytes: an escape, an O, then a C.  You can test this by mapping that
+sequence in Vim: >
+
+    nnoremap <Esc>OC :echom "Right was pressed!"<cr>
+
+Press the right arrow key in normal mode and you'll see the message.
+
+You can find out what bytes Vim expects to see for various keys by using the
+|set| command on the keycode: >
+
+    :set <left>?
+
+This will show you what Vim considers your left arrow key to be.
+
+You can try this with other keys too.  Check out the |termcap| help if you
+want to know more about the format of the settings.
+
+Open a new instance of Vim and try typing out the <Esc>, O, and C characters
+manually.  Why doesn't it act like the right arrow key?
+
+To fully understand you'll want to read the |ttimeout|, |timeout|, and
+|ttimeout| helps, but in a nutshell: Vim only treats a sequence as a keycode
+if it happens really, really fast -- far faster than you could possibly type,
+but well within your terminal's ability to send.
+
+So now we know how Vim receives non-ASCII key codes.  Why does that matter?
+Because we're going two more little facts to fix the <Esc>-hanging property of
+our focus-related mappings.
+
+The first fact: although there are usually only 12 or 15 function keys (like
+F1, F2, etc) on a physical keyboard, Vim include support for up to thirty
+seven of them!
+
+Try the following commands in Vim: >
+
+    :set <f37>?
+    :set <f38>?
+
+The second command will fail saying "Unknown option", but the first fails only
+because Vim doesn't know what to expect to receive for F37.
+
+The other fact: you can change these key codes with :set!  If you run this: >
+
+    :set <f37>=<Esc>A
+
+    (you need to enter an actual escape character by pressing Ctrl-V and then
+    the escape key)
+
+Whenever Vim sees an <Esc> followed very quickly by an A it will treat it as
+a press of the F37 key.  You can then map that key like any other: >
+
+    nnoremap <f37> :echo "HOW BIG IS YOUR KEYBOARD?!"<cr>
+
+This avoids the hanging-<Esc> problem that simply mapping the sequence
+manually has.  Well, technically it doesn't, but it only hangs for
+|ttimeoutlen| milliseconds which is usually set to something on the order of
+10, which is far too quick for humans to notice.
+
+You might be able to see where we're going from here.  Vitality maps iTerm 2's
+focus-reporting sequences to two of these higher F keys (F24 and F25 by
+default) and then maps those to the "fire the autocommand" functionality.
+
+------------------------------------------------------------------------------
+2.4  WITH WHAT?  YOUR BARE HANDS?
+
+Now that we know how all this stuff works we can put it all together.
+
+Vitality works by changing four Vim settings:
+
+    |t_ti|  Sent to the terminal from Vim when Vim starts up.
+    |t_te|  Sent to the terminal from Vim when Vim shuts down.
+    |t_SI|  Sent to the terminal from Vim when Vim enters insert mode.
+    |t_EI|  Sent to the terminal from Vim when Vim leaves insert mode.
+
+These settings are strings of the bytes we want to send.
+
+It builds the proper escape codes by checking to make sure we're in iTerm
+2 (and possibly tmux) by looking at some environment variables.  Then it sets
+the four variables above to do the following:
+
+    |t_ti|  Vim startup: enable focus reporting, save the screen.
+    |t_te|  Vim shutdown: disable focus reporting, restore the screen.
+    |t_SI|  Enter insert mode: change cursor to bar.
+    |t_EI|  Leave insert mode: change cursor to block.
+
+All that's left is to map the focus-reporting keys as described in the last
+section and it's done!
+
+You now have a friendlier Vim/tmux/iTerm 2 environment!
+
+==============================================================================
+3. License                                                   *VitalityLicense*
+
+Vitality is MIT/X11 licensed.
+
+==============================================================================
+4. Bugs                                                         *VitalityBugs*
+
+If you find a bug please post it on the issue tracker:
+http://github.com/sjl/vitality.vim/issues/
+
+==============================================================================
+5. Contributing                                         *VitalityContributing*
+
+Think you can make this plugin better?  Awesome!  Fork it on BitBucket or
+GitHub and send a pull request.
+
+BitBucket: http://bitbucket.org/sjl/vitality.vim/
+GitHub: http://github.com/sjl/vitality.vim/
+
+==============================================================================
+6. Changelog                                               *VitalityChangelog*
+
+v1.0.0
+    * Initial stable release.
+
+==============================================================================
+#!/usr/bin/env bash
+
+hg archive ~/Desktop/vitality.zip -I 'doc' -I 'plugin' -I README.markdown -I LICENSE.markdown

File plugin/vitality.vim

+" ============================================================================
+" File:        vitality.vim
+" Description: Make Vim play nicely with iTerm2 and tmux.
+" Maintainer:  Steve Losh <steve@stevelosh.com>
+" License:     MIT/X11
+" ============================================================================
+
+" Init {{{
+
+if has('gui_running')
+    finish
+endif
+
+if !exists('g:vitality_debug') && (exists('loaded_vitality') || &cp)
+    finish
+endif
+
+let loaded_vitality = 1
+
+if !exists('g:vitality_fix_cursor') " {{{
+    let g:vitality_fix_cursor = 1
+endif " }}}
+if !exists('g:vitality_fix_focus') " {{{
+    let g:vitality_fix_focus = 1
+endif " }}}
+
+let s:inside_iterm = exists('$ITERM_PROFILE')
+let s:inside_tmux = exists('$TMUX')
+
+" }}}
+
+function! s:WrapForTmux(s) " {{{
+    " To escape a sequence through tmux:
+    "
+    " * Wrap it in these sequences.
+    " * Any <Esc> characters inside it must be doubled.
+    let tmux_start = "\<Esc>Ptmux;"
+    let tmux_end   = "\<Esc>\\"
+
+    return tmux_start . substitute(a:s, "\<Esc>", "\<Esc>\<Esc>", 'g') . tmux_end
+endfunction " }}}
+
+function! s:Vitality() " {{{
+    " Escape sequences {{{
+
+    " iTerm2 allows you to turn "focus reporting" on and off with these
+    " sequences.
+    "
+    " When reporting is on, iTerm2 will send <Esc>[O when the window loses focus
+    " and <Esc>[I when it gains focus.
+    "
+    " TODO: Look into how this works with iTerm tabs.  Seems a bit wonky.
+    let enable_focus_reporting  = "\<Esc>[?1004h"
+    let disable_focus_reporting = "\<Esc>[?1004l"
+
+    " These sequences save/restore the screen.
+    " They should NOT be wrapped in tmux escape sequences for some reason!
+    let save_screen    = "\<Esc>[?1049h"
+    let restore_screen = "\<Esc>[?1049l"
+
+    " These sequences tell iTerm2 to change the cursor shape to a bar or block.
+    let cursor_to_bar   = "\<Esc>]50;CursorShape=1\x7"
+    let cursor_to_block = "\<Esc>]50;CursorShape=0\x7"
+
+    if s:inside_tmux
+        " Some escape sequences (but not all, lol) need to be properly escaped
+        " to get them through tmux without being eaten.
+
+        let enable_focus_reporting = s:WrapForTmux(enable_focus_reporting)
+        let disable_focus_reporting = s:WrapForTmux(disable_focus_reporting)
+
+        let cursor_to_bar = s:WrapForTmux(cursor_to_bar)
+        let cursor_to_block = s:WrapForTmux(cursor_to_block)
+    endif
+
+    " }}}
+    " Startup/shutdown escapes {{{
+
+    " When starting Vim, enable focus reporting and save the screen.
+    " When exiting Vim, disable focus reporting and save the screen.
+    "
+    " The "focus/save" and "nofocus/restore" each have to be in this order.
+    " Trust me, you don't want to go down this rabbit hole.  Just keep them in
+    " this order and no one gets hurt.
+    let &t_ti = enable_focus_reporting . save_screen
+    let &t_te = disable_focus_reporting . restore_screen
+
+    " }}}
+    " Insert enter/leave escapes {{{
+
+    " When entering insert mode, change the cursor to a bar.
+    let &t_SI = cursor_to_bar
+
+    " When exiting insert mode, change it back to a block.
+    let &t_EI = cursor_to_block
+
+    " }}}
+    " Focus reporting keys/mappings {{{
+
+    " Map some of Vim's unused keycodes to the sequences iTerm2 is going to send
+    " on focus lost/gained.
+    "
+    " If you're already using f24 or f25, change them to something else.  Vim
+    " supports up to f37.
+    "
+    " Doing things this way is nicer than just mapping the raw sequences
+    " directly, because Vim won't hang after a bare <Esc> waiting for the rest
+    " of the mapping.
+    execute "set <f24>=\<Esc>[O"
+    execute "set <f25>=\<Esc>[I"
+
+    " Handle the focus gained/lost signals in each mode separately.
+    "
+    " The goal is to fire the autocmd and restore the state as cleanly as
+    " possible.  This is easy for some modes and hard/impossible for others.
+    "
+    " EXAMPLES:
+    nnoremap <silent> <f24> :doautocmd FocusLost %<cr>
+    nnoremap <silent> <f25> :doautocmd FocusGained %<cr>
+
+    onoremap <silent> <f24> <esc>:doautocmd FocusLost %<cr>
+    onoremap <silent> <f25> <esc>:doautocmd FocusGained %<cr>
+
+    vnoremap <silent> <f24> <esc>:doautocmd FocusLost %<cr>gv
+    vnoremap <silent> <f25> <esc>:doautocmd FocusGained %<cr>gv
+
+    inoremap <silent> <f24> <c-o>:doautocmd FocusLost %<cr>
+    inoremap <silent> <f25> <c-o>:doautocmd FocusGained %<cr>
+
+    " }}}
+endfunction " }}}
+
+if s:inside_iterm
+    call s:Vitality()
+endif