Commits

Audrius Kažukauskas committed 8c39cd2

Upgrade to session.vim 2.0.

Comments (0)

Files changed (4)

autoload/xolox/session.vim

 " Vim script
 " Author: Peter Odding
-" Last Change: April 21, 2013
+" Last Change: May 6, 2013
 " URL: http://peterodding.com/code/vim/session/
 
-let g:xolox#session#version = '1.5.10'
+let g:xolox#session#version = '2.0'
 
-call xolox#misc#compat#check('session', 1)
+call xolox#misc#compat#check('session', 2)
 
 " Public API for session persistence. {{{1
 
 " argument:
 
 function! xolox#session#save_session(commands, filename) " {{{2
-  call add(a:commands, '" ' . a:filename . ': Vim session script.')
+  let is_all_tabs = xolox#session#include_tabs()
+  call add(a:commands, '" ' . a:filename . ':')
+  call add(a:commands, '" Vim session script' . (is_all_tabs ? '' : ' for a single tab page') . '.')
   call add(a:commands, '" Created by session.vim ' . g:xolox#session#version . ' on ' . strftime('%d %B %Y at %H:%M:%S.'))
   call add(a:commands, '" Open this file in Vim and run :source % to restore your session.')
   call add(a:commands, '')
-  call add(a:commands, 'set guioptions=' . escape(&go, ' "\'))
-  call add(a:commands, 'silent! set guifont=' . escape(&gfn, ' "\'))
+  if is_all_tabs
+    call add(a:commands, 'set guioptions=' . escape(&go, ' "\'))
+    call add(a:commands, 'silent! set guifont=' . escape(&gfn, ' "\'))
+  endif
   call xolox#session#save_globals(a:commands)
-  call xolox#session#save_features(a:commands)
-  call xolox#session#save_colors(a:commands)
+  if is_all_tabs
+    call xolox#session#save_features(a:commands)
+    call xolox#session#save_colors(a:commands)
+  endif
   call xolox#session#save_qflist(a:commands)
   call xolox#session#save_state(a:commands)
-  call xolox#session#save_fullscreen(a:commands)
-  call add(a:commands, 'doautoall SessionLoadPost')
+  if is_all_tabs
+    call xolox#session#save_fullscreen(a:commands)
+  endif
+  if is_all_tabs
+    call add(a:commands, 'doautoall SessionLoadPost')
+  else
+    call add(a:commands, 'windo doautocmd SessionLoadPost')
+    call s:jump_to_window(a:commands, tabpagenr(), winnr())
+  endif
   call add(a:commands, 'unlet SessionLoad')
   call add(a:commands, '" vim: ft=vim ro nowrap smc=128')
 endfunction
     call s:eat_trailing_line(lines, 'unlet SessionLoad')
     call s:eat_trailing_line(lines, 'doautoall SessionLoadPost')
     call xolox#session#save_special_windows(lines)
+    if !xolox#session#include_tabs()
+      " Customize the output of :mksession for tab scoped sessions.
+      let buffers = tabpagebuflist()
+      call map(lines, 's:tabpage_filter(buffers, v:val)')
+    endif
     call extend(a:commands, map(lines, 's:state_filter(v:val)'))
     return 1
   finally
   endtry
 endfunction
 
-function! s:eat_trailing_line(session, line)
+function! s:eat_trailing_line(session, line) " {{{3
+  " Remove matching, trailing strings from a list of strings.
   if a:session[-1] == a:line
     call remove(a:session, -1)
   endif
 endfunction
 
-function! s:state_filter(line)
+function! s:tabpage_filter(buffers, line) " {{{3
+  " Change output of :mksession if for single tab page.
+  if a:line =~ '^badd +\d\+ '
+    " The :mksession command adds all open buffers to a session even for tab
+    " scoped sessions. That makes sense, but we want only the buffers in the
+    " tab page to be included. That's why we filter out any references to the
+    " rest of the buffers from the script generated by :mksession.
+    let pathname = matchstr(a:line, '^badd +\d\+ \zs.*')
+    let bufnr = bufnr('^' . pathname . '$')
+    if index(a:buffers, bufnr) == -1
+      return '" ' . a:line
+    endif
+  elseif a:line =~ '^let v:this_session\s*='
+    " The :mksession command unconditionally adds the global v:this_session
+    " variable definition to the session script, but we want a differently
+    " scoped variable for tab scoped sessions.
+    return substitute(a:line, 'v:this_session', 't:this_session', 'g')
+  endif
+  " Preserve all other lines.
+  return a:line
+endfunction
+
+function! s:state_filter(line) " {{{3
+  " Various changes to the output of :mksession.
   if a:line =~ '^normal!\? zo$'
     " Silence "E490: No fold found" errors.
     return 'silent! ' . a:line
   let window = winnr()
   let s:nerdtrees = {}
   try
-    if &sessionoptions =~ '\<tabpages\>'
+    if xolox#session#include_tabs()
       tabdo call s:check_special_tabpage(a:session)
     else
       call s:check_special_tabpage(a:session)
       call add(a:session, command)
     else
       let argument = fnamemodify(argument, ':~')
-      if &sessionoptions =~ '\<slash\>'
+      if xolox#session#options_include('slash')
         let argument = substitute(argument, '\', '/', 'g')
       endif
       call add(a:session, command . ' ' . fnameescape(argument))
 endfunction
 
 function! s:jump_to_window(session, tabpage, window)
-  if &sessionoptions =~ '\<tabpages\>'
+  if xolox#session#include_tabs()
     call add(a:session, 'tabnext ' . a:tabpage)
   endif
   call add(a:session, a:window . 'wincmd w')
 " Automatic commands to manage the default session. {{{1
 
 function! xolox#session#auto_load() " {{{2
+  " Automatically load the default / last used session when Vim starts.
   if g:session_autoload == 'no'
     return
   endif
   " Check that the user has started Vim without editing any files.
-  if bufnr('$') == 1 && bufname('%') == '' && !&mod && getline(1, '$') == ['']
+  let current_buffer_is_empty = (&modified == 0 && getline(1, '$') == [''])
+  let buffer_list_is_empty = (bufnr('$') == 1 && bufname('%') == '')
+  let buffer_list_is_persistent = (index(xolox#misc#option#split(&viminfo), '%') >= 0)
+  if current_buffer_is_empty && (buffer_list_is_empty || buffer_list_is_persistent)
     " Check whether a session matching the user-specified server name exists.
     if v:servername !~ '^\cgvim\d*$'
       for session in xolox#session#get_names()
         if v:servername ==? session
-          call xolox#session#open_cmd(session, '')
+          call xolox#session#open_cmd(session, '', 'OpenSession')
           return
         endif
       endfor
     endif
-    " Default to the last used session or the session named `default'?
-    let session = s:last_session_recall()
+    " Default to the last used session or the default session?
+    let [has_last_session, session] = s:get_last_or_default_session()
     let path = xolox#session#name_to_path(session)
-    if filereadable(path) && !s:session_is_locked(path)
-      let msg = "Do you want to restore your %s editing session?"
-      let label = session != 'default' ? 'last used' : 'default'
-      if s:prompt(printf(msg, label), 'g:session_autoload')
-        call xolox#session#open_cmd(session, '')
+    if (g:session_default_to_last == 0 || has_last_session) && filereadable(path) && !s:session_is_locked(path)
+      " Compose the message for the prompt.
+      let is_default_session = (session == g:session_default_name)
+      let msg = printf("Do you want to restore your %s editing session%s?",
+            \ is_default_session ? 'default' : 'last used',
+            \ is_default_session ? '' : printf(' (%s)', session))
+      " Prepare the list of choices.
+      let choices = ['&Yes', '&No']
+      if !is_default_session
+        call add(choices, '&Forget')
+      endif
+      " Prompt the user (if not configured otherwise).
+      let choice = s:prompt(msg, choices, 'g:session_autoload')
+      if choice == 1
+        call xolox#session#open_cmd(session, '', 'OpenSession')
+      elseif choice == 3
+        call s:last_session_forget()
       endif
     endif
   endif
 endfunction
 
 function! xolox#session#auto_save() " {{{2
-  if !v:dying && g:session_autosave != 'no'
-    let name = s:get_name('', 0)
-    if name != ''
-      let msg = "Do you want to save your editing session before quitting Vim?"
-      if s:prompt(msg, 'g:session_autosave')
-        call xolox#session#save_cmd(name, '')
+  " We won't save the session if Vim is not terminating normally.
+  if v:dying
+    return
+  endif
+  " We won't save the session if auto-save is explicitly disabled.
+  if g:session_autosave == 'no'
+    return
+  endif
+  " Get the name of the active session (if any).
+  let name = s:get_name('', 0)
+  " If no session is active and the user doesn't have any sessions yet, help
+  " them get started by suggesting to create the default session.
+  if empty(name) && empty(xolox#session#get_names())
+    let name = g:session_default_name
+  endif
+  " Prompt the user to save the active or first session?
+  if !empty(name)
+    let is_tab_scoped = xolox#session#is_tab_scoped()
+    let msg = "Do you want to save your %s before quitting Vim?"
+    if s:prompt(printf(msg, xolox#session#get_label(name)), ['&Yes', '&No'], 'g:session_autosave') == 1
+      if is_tab_scoped
+        call xolox#session#save_tab_cmd(name, '', 'SaveTabSession')
+      else
+        call xolox#session#save_cmd(name, '', 'SaveSession')
       endif
     endif
   endif
 endfunction
 
+function! xolox#session#auto_save_periodic() " {{{2
+  " Automatically save the session every few minutes?
+  let interval = g:session_autosave_periodic * 60
+  let next_save = s:session_last_flushed + interval
+  if next_save < localtime()
+    call xolox#misc#msg#debug("session.vim %s: Skipping this beat of 'updatetime' (it's not our time yet).", g:xolox#session#version)
+  else
+    call xolox#misc#msg#debug("session.vim %s: This is our beat of 'updatetime'!", g:xolox#session#version)
+    let name = s:get_name('', 0)
+    if !empty(name)
+      if xolox#session#is_tab_scoped()
+        call xolox#session#save_tab_cmd(name, '', 'SaveTabSession')
+      else
+        call xolox#session#save_cmd(name, '', 'SaveSession')
+      endif
+    endif
+  endif    
+endfunction
+
+function! s:flush_session()
+  let s:session_last_flushed = localtime()
+endfunction
+
+if !exists('s:session_last_flushed')
+  call s:flush_session()
+endif
+
 function! xolox#session#auto_unlock() " {{{2
+  " Automatically unlock all sessions when Vim quits.
   let i = 0
   while i < len(s:lock_files)
     let lock_file = s:lock_files[i]
 
 " Commands that enable users to manage multiple sessions. {{{1
 
-function! s:prompt(msg, var)
-  let value = eval(a:var)
-  if value == 'yes' || (type(value) != type('') && value)
+function! s:prompt(msg, choices, option_name)
+  let option_value = eval(a:option_name)
+  if option_value == 'yes'
     return 1
+  elseif option_value == 'no'
+    return 0
   else
-    let format = "%s Note that you can permanently disable this dialog by adding the following line to your %s script:\n\n\t:let %s = 'no'"
-    let vimrc = xolox#misc#os#is_win() ? '~\_vimrc' : '~/.vimrc'
-    let prompt = printf(format, a:msg, vimrc, a:var)
-    return confirm(prompt, "&Yes\n&No", 1, 'Question') == 1
+    if g:session_verbose_messages
+      let format = "%s Note that you can permanently disable this dialog by adding the following line to your %s script:\n\n\t:let %s = 'no'"
+      let prompt = printf(format, a:msg, xolox#misc#os#is_win() ? '~\_vimrc' : '~/.vimrc', a:option_name)
+    else
+      let prompt = a:msg
+    endif
+    return confirm(prompt, join(a:choices, "\n"), 1, 'Question')
   endif
 endfunction
 
-function! xolox#session#open_cmd(name, bang) abort " {{{2
+function! xolox#session#open_cmd(name, bang, command) abort " {{{2
   let name = s:select_name(s:unescape(a:name), 'restore')
   if name != ''
     let starttime = xolox#misc#timer#start()
     if !filereadable(path)
       let msg = "session.vim %s: The %s session at %s doesn't exist!"
       call xolox#misc#msg#warn(msg, g:xolox#session#version, string(name), fnamemodify(path, ':~'))
-    elseif a:bang == '!' || !s:session_is_locked(path, 'OpenSession')
+    elseif a:bang == '!' || !s:session_is_locked(path, a:command)
       let oldcwd = s:nerdtree_persist()
-      call xolox#session#close_cmd(a:bang, 1, name != s:get_name('', 0))
-      let s:oldcwd = oldcwd
+      call xolox#session#close_cmd(a:bang, 1, name != s:get_name('', 0), a:command)
+      if xolox#session#include_tabs()
+        let g:session_old_cwd = oldcwd
+      else
+        let t:session_old_cwd = oldcwd
+      endif
       call s:lock_session(path)
       execute 'source' fnameescape(path)
       call s:last_session_persist(name)
-      call xolox#misc#timer#stop("session.vim %s: Opened %s session in %s.", g:xolox#session#version, string(name), starttime)
-      call xolox#misc#msg#info("session.vim %s: Opened %s session from %s.", g:xolox#session#version, string(name), fnamemodify(path, ':~'))
+      call s:flush_session()
+      let session_type = xolox#session#include_tabs() ? 'global' : 'tab scoped'
+      call xolox#misc#timer#stop("session.vim %s: Opened %s %s session in %s.", g:xolox#session#version, session_type, string(name), starttime)
+      call xolox#misc#msg#info("session.vim %s: Opened %s %s session from %s.", g:xolox#session#version, session_type, string(name), fnamemodify(path, ':~'))
     endif
   endif
 endfunction
   endif
 endfunction
 
-function! xolox#session#save_cmd(name, bang) abort " {{{2
+function! xolox#session#save_cmd(name, bang, command) abort " {{{2
   let starttime = xolox#misc#timer#start()
   let name = s:get_name(s:unescape(a:name), 1)
   let path = xolox#session#name_to_path(name)
   let friendly_path = fnamemodify(path, ':~')
-  if a:bang == '!' || !s:session_is_locked(path, 'SaveSession')
+  if a:bang == '!' || !s:session_is_locked(path, a:command)
     let lines = []
     call xolox#session#save_session(lines, friendly_path)
-    if xolox#misc#os#is_win() && &ssop !~ '\<unix\>'
+    if xolox#misc#os#is_win() && !xolox#session#options_include('unix')
       call map(lines, 'v:val . "\r"')
     endif
     if writefile(lines, path) != 0
       call xolox#misc#msg#warn(msg, g:xolox#session#version, string(name), friendly_path)
     else
       call s:last_session_persist(name)
-      call xolox#misc#timer#stop("session.vim %s: Saved %s session in %s.", g:xolox#session#version, string(name), starttime)
-      call xolox#misc#msg#info("session.vim %s: Saved %s session to %s.", g:xolox#session#version, string(name), friendly_path)
-      let v:this_session = path
+      call s:flush_session()
+      let label = xolox#session#get_label(name)
+      call xolox#misc#timer#stop("session.vim %s: Saved %s in %s.", g:xolox#session#version, label, starttime)
+      call xolox#misc#msg#info("session.vim %s: Saved %s to %s.", g:xolox#session#version, label, friendly_path)
+      if xolox#session#include_tabs()
+        let v:this_session = path
+      else
+        let t:this_session = path
+      endif
       call s:lock_session(path)
     endif
   endif
   endif
 endfunction
 
-function! xolox#session#close_cmd(bang, silent, save_allowed) abort " {{{2
+function! xolox#session#close_cmd(bang, silent, save_allowed, command) abort " {{{2
+  let is_all_tabs = xolox#session#include_tabs()
   let name = s:get_name('', 0)
   if name != ''
     if a:save_allowed
-      let msg = "Do you want to save your current editing session before closing it?"
-      if s:prompt(msg, 'g:session_autosave')
-        call xolox#session#save_cmd(name, a:bang)
+      let msg = "Do you want to save your current %s before closing it?"
+      let label = xolox#session#get_label(name)
+      if s:prompt(printf(msg, label), ['&Yes', '&No'], 'g:session_autosave') == 1
+        call xolox#session#save_cmd(name, a:bang, a:command)
       endif
     else
       call xolox#misc#msg#debug("session.vim %s: Session reset requested, not saving changes to session ..", g:xolox#session#version)
     endif
     call s:unlock_session(xolox#session#name_to_path(name))
   endif
-  " Close al but the current tab page.
-  if tabpagenr('$') > 1
+  " Close al but the current tab page?
+  if is_all_tabs && tabpagenr('$') > 1
     execute 'tabonly' . a:bang
   endif
   " Close all but the current window.
   " Start editing a new, empty buffer.
   execute 'enew' . a:bang
   " Close all but the current buffer.
-  let bufnr_keep = bufnr('%')
-  for bufnr in range(1, bufnr('$'))
-    if buflisted(bufnr) && bufnr != bufnr_keep
+  let bufnr_save = bufnr('%')
+  let all_buffers = is_all_tabs ? range(1, bufnr('$')) : tabpagebuflist()
+  for bufnr in all_buffers
+    if buflisted(bufnr) && bufnr != bufnr_save
       execute 'silent bdelete' bufnr
     endif
   endfor
   " Restore working directory (and NERDTree?) from before :OpenSession.
-  if exists('s:oldcwd')
-    execute s:oldcwd
-    unlet s:oldcwd
+  if is_all_tabs && exists('g:session_old_cwd')
+    execute g:session_old_cwd
+    unlet g:session_old_cwd
+  elseif !is_all_tabs && exists('t:session_old_cwd')
+    execute t:session_old_cwd
+    unlet t:session_old_cwd
   endif
-  if v:this_session == ''
-    if !a:silent
-      let msg = "session.vim %s: Closed session."
-      call xolox#misc#msg#info(msg, g:xolox#session#version)
-    endif
+  call s:flush_session()
+  if !a:silent
+    let msg = "session.vim %s: Closed %s."
+    let label = xolox#session#get_label(xolox#session#find_current_session())
+    call xolox#misc#msg#info(msg, g:xolox#session#version, label)
+  endif
+  if xolox#session#is_tab_scoped()
+    let t:this_session = ''
   else
-    if !a:silent
-      let msg = "session.vim %s: Closed session %s."
-      call xolox#misc#msg#info(msg, g:xolox#session#version, fnamemodify(v:this_session, ':~'))
-    endif
     let v:this_session = ''
   endif
   return 1
 endfunction
 
+function! xolox#session#open_tab_cmd(name, bang, command) abort " {{{2
+  try
+    call xolox#session#change_tab_options()
+    call xolox#session#open_cmd(a:name, a:bang, a:command)
+  finally
+    call xolox#session#restore_tab_options()
+  endtry
+endfunction
+
+function! xolox#session#save_tab_cmd(name, bang, command) abort " {{{2
+  try
+    call xolox#session#change_tab_options()
+    call xolox#session#save_cmd(a:name, a:bang, a:command)
+  finally
+    call xolox#session#restore_tab_options()
+  endtry
+endfunction
+
+function! xolox#session#append_tab_cmd(name, bang, count, command) abort " {{{2
+  try
+    call xolox#session#change_tab_options()
+    execute printf('%stabnew', a:count == 94919 ? '' : a:count)
+    call xolox#session#open_cmd(a:name, a:bang, a:command)
+  finally
+    call xolox#session#restore_tab_options()
+  endtry
+endfunction
+
+function! xolox#session#close_tab_cmd(bang, command) abort " {{{2
+  let save_allowed = xolox#session#is_tab_scoped()
+  try
+    call xolox#session#change_tab_options()
+    call xolox#session#close_cmd(a:bang, 0, save_allowed, a:command)
+  finally
+    call xolox#session#restore_tab_options()
+  endtry
+endfunction
+
 function! xolox#session#restart_cmd(bang, args) abort " {{{2
   if !has('gui_running')
     " In console Vim we can't start a new Vim and kill the old one...
     let msg = "session.vim %s: The :RestartVim command only works in graphical Vim!"
     call xolox#misc#msg#warn(msg, g:xolox#session#version)
   else
+    " Save the current session (if there is no active
+    " session we will create a session called "restart").
     let name = s:get_name('', 0)
     if name == '' | let name = 'restart' | endif
-    call xolox#session#save_cmd(name, a:bang)
-    let progname = xolox#misc#escape#shell(fnameescape(v:progname))
-    let command = progname . ' -c ' . xolox#misc#escape#shell('OpenSession\! ' . fnameescape(name))
+    call xolox#session#save_cmd(name, a:bang, 'RestartVim')
+    " Generate the Vim command line.
+    let progname = xolox#misc#escape#shell(fnameescape(s:find_executable()))
+    let command = progname . ' -g -c ' . xolox#misc#escape#shell('OpenSession\! ' . fnameescape(name))
     let args = matchstr(a:args, '^\s*|\s*\zs.\+$')
     if !empty(args)
       let command .= ' -c ' . xolox#misc#escape#shell(args)
     endif
+    " Close the session, releasing the session lock.
+    call xolox#session#close_cmd(a:bang, 0, 1, 'RestartVim')
+    " Start the new Vim instance.
     if xolox#misc#os#is_win()
+      " On Microsoft Windows.
       execute '!start' command
     else
+      " On anything other than Windows (UNIX like).
       let cmdline = []
       for variable in g:session_restart_environment
         call add(cmdline, variable . '=' . xolox#misc#escape#shell(fnameescape(eval('$' . variable))))
       call add(cmdline, printf("--cmd ':set enc=%s'", escape(&enc, '\ ')))
       silent execute '!' join(cmdline, ' ') '&'
     endif
-    call xolox#session#close_cmd(a:bang, 0, 1)
+    " Close Vim.
     silent quitall
   endif
 endfunction
 
+function! s:find_executable()
+  let progname = v:progname
+  if has('macunix')
+    " Special handling for Mac OS X where MacVim is usually not on the $PATH.
+    let segments = xolox#misc#path#split($VIMRUNTIME)
+    if segments[-3:] == ['Resources', 'vim', 'runtime']
+      let progname = xolox#misc#path#join(segments[0:-4] + ['MacOS', 'Vim'])
+    endif
+  endif
+  return progname
+endfunction
+
 " Miscellaneous functions. {{{1
 
 function! s:unescape(s) " {{{2
-  return substitute(a:s, '\\\(.\)', '\1', 'g')
+  " Undo escaping of special characters (preceded by a backslash).
+  let s = substitute(a:s, '\\\(.\)', '\1', 'g')
+  " Expand any environment variables in the user input.
+  let s = substitute(s, '\(\$[A-Za-z0-9_]\+\)', '\=expand(submatch(1))', 'g')
+  return s
 endfunction
 
 function! s:select_name(name, action) " {{{2
   endif
   let sessions = sort(xolox#session#get_names())
   if empty(sessions)
-    return 'default'
+    return g:session_default_name
   elseif len(sessions) == 1
     return sessions[0]
   endif
 
 function! s:get_name(name, use_default) " {{{2
   let name = a:name
-  if name == '' && v:this_session != ''
-    let this_session_dir = fnamemodify(v:this_session, ':p:h')
-    if xolox#misc#path#equals(this_session_dir, g:session_directory)
-      let name = xolox#session#path_to_name(v:this_session)
-    endif
+  if name == ''
+    for variable in ['t:this_session', 'v:this_session']
+      if exists(variable)
+        let value = eval(variable)
+        if value != ''
+          if xolox#misc#path#equals(fnamemodify(value, ':p:h'), g:session_directory)
+            let name = xolox#session#path_to_name(value)
+            break
+          endif
+        endif
+      endif
+    endfor
   endif
-  return name != '' ? name : a:use_default ? 'default' : ''
+  return name != '' ? name : a:use_default ? g:session_default_name : ''
 endfunction
 
 function! xolox#session#name_to_path(name) " {{{2
   let directory = xolox#misc#path#absolute(g:session_directory)
-  let filename = xolox#misc#path#encode(a:name) . '.vim'
+  let filename = xolox#misc#path#encode(a:name) . g:session_extension
   return xolox#misc#path#merge(directory, filename)
 endfunction
 
 
 function! xolox#session#get_names() " {{{2
   let directory = xolox#misc#path#absolute(g:session_directory)
-  let filenames = split(glob(xolox#misc#path#merge(directory, '*.vim')), "\n")
+  let filenames = split(glob(xolox#misc#path#merge(directory, '*' . g:session_extension)), "\n")
   return map(filenames, 'xolox#session#path_to_name(v:val)')
 endfunction
 
   return map(names, 'fnameescape(v:val)')
 endfunction
 
+function! xolox#session#is_tab_scoped() " {{{2
+  " Determine whether the current session is tab scoped or global.
+  return exists('t:this_session')
+endfunction
+
+function! xolox#session#find_current_session() " {{{2
+  " Find the name of the current session.
+  let pathname = xolox#session#is_tab_scoped() ? t:this_session : v:this_session
+  return xolox#session#path_to_name(pathname)
+endfunction
+
+function! xolox#session#get_label(name) " {{{2
+  if xolox#session#is_tab_scoped()
+    let name = xolox#session#path_to_name(t:this_session)
+    if a:name == name
+      return printf('tab scoped session %s', string(a:name))
+    endif
+  endif
+  return printf('global session %s', string(a:name))
+endfunction
+
+function! xolox#session#options_include(value) " {{{2
+  return index(xolox#misc#option#split(&sessionoptions), a:value) >= 0
+endfunction
+
+" Tab scoped sessions: {{{2
+
+function! xolox#session#include_tabs() " {{{3
+  return xolox#session#options_include('tabpages')
+endfunction
+
+function! xolox#session#change_tab_options() " {{{3
+  " Save the original value of 'sessionoptions'.
+  let s:ssop_save = &sessionoptions
+  " Only persist the current tab page.
+  set sessionoptions-=tabpages
+  " Don't persist the size and position of the Vim window.
+  set ssop-=winpos ssop-=resize
+endfunction
+
+function! xolox#session#restore_tab_options() " {{{3
+  " Restore the original value of 'sessionoptions'.
+  if exists('s:ssop_save')
+    let &ssop = s:ssop_save
+    unlet s:ssop_save
+  endif
+endfunction
+
 " Default to last used session: {{{2
 
 function! s:last_session_file()
   endif
 endfunction
 
-function! s:last_session_recall()
-  if g:session_default_to_last
-    let fname = s:last_session_file()
-    if filereadable(fname)
-      return readfile(fname)[0]
-    endif
+function! s:last_session_forget()
+  let last_session_file = s:last_session_file()
+  if filereadable(last_session_file) && delete(last_session_file) != 0
+    call xolox#misc#msg#warn("session.vim %s: Failed to delete name of last used session!", g:xolox#session#version)
   endif
-  return 'default'
+endfunction
+
+function! s:get_last_or_default_session()
+  let last_session_file = s:last_session_file()
+  let has_last_session = filereadable(last_session_file)
+  if g:session_default_to_last && has_last_session
+    let lines = readfile(last_session_file)
+    return [has_last_session, lines[0]]
+  else
+    return [has_last_session, g:session_default_name]
+  endif
 endfunction
 
 " Lock file management: {{{2
   let s:lock_files = []
 endif
 
+function! s:vim_instance_id()
+  let id = {'pid': getpid()}
+  if !empty(v:servername)
+    let id['servername'] = v:servername
+  endif
+  if !xolox#session#include_tabs()
+    let id['tabpage'] = tabpagenr()
+  endif
+  return id
+endfunction
+
 function! s:lock_session(session_path)
   let lock_file = a:session_path . '.lock'
-  if writefile([v:servername], lock_file) == 0
+  if writefile([string(s:vim_instance_id())], lock_file) == 0
     if index(s:lock_files, lock_file) == -1
       call add(s:lock_files, lock_file)
     endif
 function! s:session_is_locked(session_path, ...)
   let lock_file = a:session_path . '.lock'
   if filereadable(lock_file)
-    let lines = readfile(lock_file)
-    if lines[0] !=? v:servername
-      if a:0 >= 1
-        let msg = "session.vim %s: The %s session is locked by another Vim instance named %s! Use :%s! to override."
-        let name = string(fnamemodify(a:session_path, ':t:r'))
-        call xolox#misc#msg#warn(msg, g:xolox#session#version, name, string(lines[0]), a:1)
+    let this_instance = s:vim_instance_id()
+    let other_instance = eval(get(readfile(lock_file), 0, '{}'))
+    let name = string(fnamemodify(a:session_path, ':t:r'))
+    let arguments = [g:xolox#session#version, name]
+    if this_instance == other_instance
+      " Session belongs to current Vim instance and tab page.
+      return 0
+    elseif this_instance['pid'] == other_instance['pid']
+      if has_key(other_instance, 'tabpage')
+        let msg = "session.vim %s: The %s session is already loaded in tab page %s."
+        call add(arguments, other_instance['tabpage'])
+      else
+        let msg = "session.vim %s: The %s session is already loaded in this Vim."
       endif
-      return 1
+    else
+      let msg = "session.vim %s: The %s session is locked by another Vim instance %s."
+      if has_key(other_instance, 'servername')
+        call add(arguments, 'named ' . other_instance['servername'])
+      else
+        call add(arguments, 'with PID ' . other_instance['pid'])
+      endif
     endif
+    if exists('a:1')
+      let msg .= " Use :%s! to override."
+      call add(arguments, a:1)
+    endif
+    call call('xolox#misc#msg#warn', [msg] + arguments)
+    return 1
   endif
 endfunction
 
   4. The |:CloseSession| command
   5. The |:DeleteSession| command
   6. The |:ViewSession| command
- 4. Options                                                    |session-options|
+  7. Tab scoped sessions                                   |tab-scoped-sessions|
+   1. The |:OpenTabSession| command
+   2. The |:SaveTabSession| command
+   3. The |:AppendTabSession| command
+   4. The |:CloseTabSession| command
+ 8. Options                                                    |session-options|
   1. The |sessionoptions| setting
   2. The |g:session_directory| option
-  3. The |g:session_autoload| option
-  4. The |g:session_autosave| option
-  5. The |g:session_default_to_last| option
-  6. The |g:session_persist_globals| option
-  7. The |g:session_restart_environment| option
-  8. The |g:session_command_aliases| option
-  9. The |g:loaded_session| option
- 5. Compatibility with other plug-ins |session-compatibility-with-other-plug-ins|
- 6. Known issues                                          |session-known-issues|
- 7. Contact                                                    |session-contact|
- 8. License                                                    |session-license|
- 9. Sample session script                                |sample-session-script|
+  3. The |g:session_default_name| option
+  4. The |g:session_extension| option
+  5. The |g:session_autoload| option
+  6. The |g:session_autosave| option
+  7. The |g:session_autosave_periodic| option
+  8. The |g:session_verbose_messages| option
+  9. The |g:session_default_to_last| option
+  10. The |g:session_persist_globals| option
+  11. The |g:session_restart_environment| option
+  12. The |g:session_command_aliases| option
+  13. The |g:loaded_session| option
+ 9. Compatibility with other plug-ins |session-compatibility-with-other-plug-ins|
+ 10. Known issues                                         |session-known-issues|
+ 11. Contact                                                   |session-contact|
+ 12. License                                                   |session-license|
+ 13. Sample session script                               |sample-session-script|
 
 ===============================================================================
                                                           *session-introduction*
 and the files they contain.
 
 To persist your current editing session you can execute the |:SaveSession|
-command. If you don't provide a name for the session 'default' is used. You're
-free to use whatever characters you like in session names. When you want to
-restore your session simply execute |:OpenSession|. Again the name 'default'
-is used if you don't provide one. When a session is active, has been changed
-and you quit Vim you'll be prompted whether you want to save the open session
-before quitting Vim:
+command. If you don't provide a name for the session 'default' is used (you
+can change this name with an option). You're free to use whatever characters
+you like in session names. When you want to restore your session simply
+execute |:OpenSession|. Again the name 'default' is used if you don't provide
+one. When a session is active, has been changed and you quit Vim you'll be
+prompted whether you want to save the open session before quitting Vim:
 
     Screenshot of auto-save prompt, see reference [1]
 
-When you start Vim without editing any files and the 'default' session exists,
-you'll be prompted whether you want to restore the default session:
+If you want, the plug-in can also automatically save your session every few
+minutes (see the |g:session_autosave_periodic| option). When you start Vim
+without editing any files and the default session exists, you'll be prompted
+whether you want to restore the default session:
 
     Screenshot of auto-open prompt, see reference [2]
 
                                                               *session-commands*
 Commands ~
 
+Note that environment variables inside command arguments are expanded by the
+plug-in.
+
 -------------------------------------------------------------------------------
 The *:SaveSession* command
 
 This command saves your current editing session just like Vim's built-in
 |:mksession| command does. The difference is that you don't pass a full pathname
 as argument but just a name, any name really. Press '<Tab>' to get completion
-of existing session names. If you don't provide an argument the name 'default'
-is used, unless an existing session is open in which case the name of that
-session will be used.
+of existing session names. If you don't provide an argument the default
+session name is used, unless an existing session is open in which case the
+name of that session will be used.
 
 If the session you're trying to save is already active in another Vim instance
 you'll get a warning and nothing happens. You can use a bang (!) as in
 command is useful when you need to review the generated Vim script repeatedly,
 for example while debugging or modifying the 'session.vim' plug-in.
 
+-------------------------------------------------------------------------------
+                                                           *tab-scoped-sessions*
+Tab scoped sessions ~
+
+When |'sessionoptions'| contains 'tabpages' (this is the default) session
+scripts will persist and restore all windows in all tab pages. When you remove
+'tabpages' from |'sessionoptions'| you get a sort of light-weight sessions: They
+are constrained to a single tab page. Vim's |:mksession| command and the
+vim-session plug-in both fully support this.
+
+You can change |'sessionoptions'| in your |vimrc| script but then you can never
+save a session including tab pages. To decide on the spot whether you want a
+global or tab scoped session, the vim-session plug-in defines the three
+commands documented below.
+
+Note that tab scoped sessions are regular session scripts, so when you load a
+tab scoped session using |:OpenSession| instead of |:OpenTabSession| the
+vim-session plug-in assumes it is a global session and will close all active
+tab pages before opening the tab scoped session.
+
+-------------------------------------------------------------------------------
+The *:OpenTabSession* command
+
+Just like |:OpenSession| but applies only to the current tab page.
+
+-------------------------------------------------------------------------------
+The *:SaveTabSession* command
+
+Just like |:SaveSession| but applies only to the current tab page.
+
+-------------------------------------------------------------------------------
+The *:AppendTabSession* command
+
+This command opens a new tab page and loads the given tab scoped session in
+that tab page. You can give this command a count just like |:tabnew|.
+
+-------------------------------------------------------------------------------
+The *:CloseTabSession* command
+
+Just like |:CloseSession| but applies only to the current tab page.
+
 ===============================================================================
                                                                *session-options*
 Options ~
 
+The following Vim options and plug-in options (global variables) can be used
+to configure the plug-in to your preferences.
+
 -------------------------------------------------------------------------------
 The *sessionoptions* setting
 
 directory ('$HOME' on UNIX, '%USERPROFILE%' on Windows).
 
 -------------------------------------------------------------------------------
+The *g:session_default_name* option
+
+The name of the default session without directory or filename extension
+(you'll never guess what the default is).
+
+-------------------------------------------------------------------------------
+The *g:session_extension* option
+
+The filename extension of session scripts. This should include the dot that
+separates the basename from the extension. Defaults to '.vim'.
+
+-------------------------------------------------------------------------------
 The *g:session_autoload* option
 
 By default this option is set to 'prompt'. This means that when you start Vim
-without opening any files and the 'default' session script exists, the session
+without opening any files and the default session script exists, the session
 plug-in will ask whether you want to restore your default session. When you
 set this option to 'yes' and you start Vim without opening any files the
 default session will be restored without a prompt. To completely disable
 can set this option to 'no'.
 
 -------------------------------------------------------------------------------
+The *g:session_autosave_periodic* option
+
+This option sets the interval in minutes for automatic, periodic saving of
+active sessions. The default is zero which disables the feature.
+
+Note that when the plug-in automatically saves a session (because you enabled
+this feature) the plug-in will not prompt for your permission.
+
+-------------------------------------------------------------------------------
+The *g:session_verbose_messages* option
+
+The session load/save prompts are quite verbose by default because they
+explain how to disable the prompts. If you find the additional explanation
+distracting you can lower the verbosity by setting this option to 0 (false) in
+your |vimrc| script.
+
+-------------------------------------------------------------------------------
 The *g:session_default_to_last* option
 
 By default this option is set to false (0). When you set this option to true
 
  - 'SessionClose' is an alias for 'CloseSession'
 
+Then there are the command aliases for tab scoped sessions:
+
+ - 'SessionTabOpen' is an alias for 'OpenTabSession'
+
+ - 'SessionTabSave' is an alias for 'SaveTabSession'
+
+ - 'SessionTabAppend' is an alias for 'AppendTabSession'
+
+ - 'SessionTabClose' is an alias for 'CloseTabSession'
+
 The aliases support tab completion just like the real commands; they're
 exactly the same except for the names.
 
+:AppendTabSession	session.txt	/*:AppendTabSession*
 :CVSEdit	vcscommand.txt	/*:CVSEdit*
 :CVSEditors	vcscommand.txt	/*:CVSEditors*
 :CVSUnedit	vcscommand.txt	/*:CVSUnedit*
 :CVSWatchRemove	vcscommand.txt	/*:CVSWatchRemove*
 :CVSWatchers	vcscommand.txt	/*:CVSWatchers*
 :CloseSession	session.txt	/*:CloseSession*
+:CloseTabSession	session.txt	/*:CloseTabSession*
 :ColorClear	Colorizer.txt	/*:ColorClear*
 :ColorContrast	Colorizer.txt	/*:ColorContrast*
 :ColorHighlight	Colorizer.txt	/*:ColorHighlight*
 :HighlightTags	easytags.txt	/*:HighlightTags*
 :MatchDebug	matchit.txt	/*:MatchDebug*
 :OpenSession	session.txt	/*:OpenSession*
+:OpenTabSession	session.txt	/*:OpenTabSession*
 :RGB2Xterm	Colorizer.txt	/*:RGB2Xterm*
 :RestartVim	session.txt	/*:RestartVim*
 :SaveSession	session.txt	/*:SaveSession*
+:SaveTabSession	session.txt	/*:SaveTabSession*
 :TagbarClose	tagbar.txt	/*:TagbarClose*
 :TagbarCurrentTag	tagbar.txt	/*:TagbarCurrentTag*
 :TagbarDebug	tagbar.txt	/*:TagbarDebug*
 g:loaded_session	session.txt	/*g:loaded_session*
 g:session_autoload	session.txt	/*g:session_autoload*
 g:session_autosave	session.txt	/*g:session_autosave*
+g:session_autosave_periodic	session.txt	/*g:session_autosave_periodic*
 g:session_command_aliases	session.txt	/*g:session_command_aliases*
+g:session_default_name	session.txt	/*g:session_default_name*
 g:session_default_to_last	session.txt	/*g:session_default_to_last*
 g:session_directory	session.txt	/*g:session_directory*
+g:session_extension	session.txt	/*g:session_extension*
 g:session_persist_globals	session.txt	/*g:session_persist_globals*
 g:session_restart_environment	session.txt	/*g:session_restart_environment*
+g:session_verbose_messages	session.txt	/*g:session_verbose_messages*
 g:tagbar_autoclose	tagbar.txt	/*g:tagbar_autoclose*
 g:tagbar_autofocus	tagbar.txt	/*g:tagbar_autofocus*
 g:tagbar_autoshowtag	tagbar.txt	/*g:tagbar_autoshowtag*
 surround-replacements	surround.txt	/*surround-replacements*
 surround-targets	surround.txt	/*surround-targets*
 surround.txt	surround.txt	/*surround.txt*
+tab-scoped-sessions	session.txt	/*tab-scoped-sessions*
 tabular	Tabular.txt	/*tabular*
 tabular-intro	Tabular.txt	/*tabular-intro*
 tabular-scripting	Tabular.txt	/*tabular-scripting*

plugin/session.vim

 " Vim script
 " Author: Peter Odding
-" Last Change: April 20, 2013
+" Last Change: May 6, 2013
 " URL: http://peterodding.com/code/vim/session/
 
 " Support for automatic update using the GLVS plug-in.
   finish
 endif
 
+" The name of the default session (without directory or filename extension).
+if !exists('g:session_default_name')
+  let g:session_default_name = 'default'
+endif
+
+" The file extension of session scripts.
+if !exists('g:session_extension')
+  let g:session_extension = '.vim'
+endif
+
 " When you start Vim without opening any files the plug-in will prompt you
 " whether you want to load the default session. Other supported values for
 " this option are 'yes' (to load the default session without prompting) and
   let g:session_autosave = 'prompt'
 endif
 
+" Periodically save the active session automatically? Set this to the
+" auto-save interval in minutes. The value zero disables the feature
+" (this is the default).
+if !exists('g:session_autosave_periodic')
+  let g:session_autosave_periodic = 0
+endif
+
+" Define the verbosity of messages.
+if !exists('g:session_verbose_messages')
+  let g:session_verbose_messages = 1
+endif
+
 " The session plug-in can automatically open sessions in three ways: based on
 " Vim's server name, by remembering the last used session or by opening the
-" session named `default'. Enable this option to use the second approach.
+" default session. Enable this option to use the second approach.
 if !exists('g:session_default_to_last')
   let g:session_default_to_last = 0
 endif
 augroup PluginSession
   autocmd!
   au VimEnter * nested call xolox#session#auto_load()
+  au CursorHold,CursorHoldI * call xolox#session#auto_save_periodic()
   au VimLeavePre * call xolox#session#auto_save()
   au VimLeavePre * call xolox#session#auto_unlock()
 augroup END
 
-" Define commands that enable users to manage multiple named sessions.
-command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names OpenSession call xolox#session#open_cmd(<q-args>, <q-bang>)
+" Define commands that enable users to manage multiple named, heavy-weight
+" sessions (used to persist/restore a complete Vim editing session including
+" one or more tab pages).
+command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names OpenSession call xolox#session#open_cmd(<q-args>, <q-bang>, 'OpenSession')
 command! -bar -nargs=? -complete=customlist,xolox#session#complete_names ViewSession call xolox#session#view_cmd(<q-args>)
-command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SaveSession call xolox#session#save_cmd(<q-args>, <q-bang>)
+command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SaveSession call xolox#session#save_cmd(<q-args>, <q-bang>, 'SaveSession')
 command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names DeleteSession call xolox#session#delete_cmd(<q-args>, <q-bang>)
-command! -bar -bang CloseSession call xolox#session#close_cmd(<q-bang>, 0, 1)
+command! -bar -bang CloseSession call xolox#session#close_cmd(<q-bang>, 0, 1, 'CloseSession')
+
+" Define commands that enable users to manage multiple named, light-weight
+" sessions (used to persist/restore the window layout of a single tab page).
+command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names OpenTabSession call xolox#session#open_tab_cmd(<q-args>, <q-bang>, 'OpenTabSession')
+command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SaveTabSession call xolox#session#save_tab_cmd(<q-args>, <q-bang>, 'SaveTabSession')
+command! -bar -bang -count=94919 -nargs=? -complete=customlist,xolox#session#complete_names AppendTabSession call xolox#session#append_tab_cmd(<q-args>, <q-bang>, <count>, 'AppendTabSession')
+command! -bar -bang CloseTabSession call xolox#session#close_tab_cmd(<q-bang>, 'CloseTabSession')
+
+" Define a command to restart Vim editing sessions.
 command! -bang -nargs=* -complete=command RestartVim call xolox#session#restart_cmd(<q-bang>, <q-args>)
 
 if g:session_command_aliases
   " Define command aliases of the form "Session" + Action in addition to
   " the real command names which are of the form Action + "Session" (above).
-  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionOpen call xolox#session#open_cmd(<q-args>, <q-bang>)
+  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionOpen call xolox#session#open_cmd(<q-args>, <q-bang>, 'SessionOpen')
   command! -bar -nargs=? -complete=customlist,xolox#session#complete_names SessionView call xolox#session#view_cmd(<q-args>)
-  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionSave call xolox#session#save_cmd(<q-args>, <q-bang>)
+  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionSave call xolox#session#save_cmd(<q-args>, <q-bang>, 'SessionSave')
   command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionDelete call xolox#session#delete_cmd(<q-args>, <q-bang>)
-  command! -bar -bang SessionClose call xolox#session#close_cmd(<q-bang>, 0, 1)
+  command! -bar -bang SessionClose call xolox#session#close_cmd(<q-bang>, 0, 1, 'SessionClose')
+  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionTabOpen call xolox#session#open_tab_cmd(<q-args>, <q-bang>, 'SessionTabOpen')
+  command! -bar -bang -nargs=? -complete=customlist,xolox#session#complete_names SessionTabSave call xolox#session#save_tab_cmd(<q-args>, <q-bang>, 'SessionTabSave')
+  command! -bar -bang -count=94919 -nargs=? -complete=customlist,xolox#session#complete_names SessionTabAppend call xolox#session#append_tab_cmd(<q-args>, <q-bang>, <count>, 'SessionTabAppend')
+  command! -bar -bang SessionTabClose call xolox#session#close_tab_cmd(<q-bang>, 'SessionTabClose')
 endif
 
 " Don't reload the plug-in once it has loaded successfully.