1
2 Author:
3 Last Change:
4 URL:http://peterodding.com/code/vim/session/
5
6 Now working on:
7 :mksession
8
9
10
11
12
13
14 session#save_session()
15
16 function! session#save_session(commands, filename)
17 call add(a:commands, '" ' . a:filename . ': Vim session script.')
18 call add(a:commands, '" Created by session.vim on ' . strftime('%d %B %Y at %H:%M:%S.'))
19 call add(a:commands, '" Open this file in Vim and run :source % to restore your session.')
20 call add(a:commands, '')
21 call add(a:commands, 'set guioptions=' . escape(&go, ' "\'))
22 call add(a:commands, 'set guifont=' . escape(&gfn, ' "\'))
23 call session#save_features(a:commands)
24 call session#save_colors(a:commands)
25 let s:saved_qflist = 0
26 call session#save_state(a:commands)
27 call session#save_qflist(a:commands)
28 call session#save_fullscreen(a:commands)
29 call add(a:commands, '')
30 call add(a:commands, '" vim: ft=vim ro nowrap smc=128')
31 endfunction
32
33 function! session#save_features(commands)
34 let template = "if exists('%s') != %i | %s %s | endif"
35 for [global, command] in [
36 \ ['g:syntax_on', 'syntax'],
37 \ ['g:did_load_filetypes', 'filetype'],
38 \ ['g:did_load_ftplugin', 'filetype plugin'],
39 \ ['g:did_indent_on', 'filetype indent']]
40 let active = exists(global)
41 let toggle = active ? 'on' : 'off'
42 call add(a:commands, printf(template, global, active, command, toggle))
43 endfor
44 endfunction
45
46 function! session#save_colors(commands)
47 if exists('g:colors_name') && type(g:colors_name) == type('') && g:colors_name != ''
48 let template = "if !exists('g:colors_name') || g:colors_name != %s | colorscheme %s | endif"
49 call add(a:commands, printf(template, string(g:colors_name), fnameescape(g:colors_name)))
50 endif
51 endfunction
52
53 function! session#save_fullscreen(commands)
54 try
55 if xolox#shell#is_fullscreen()
56 call add(a:commands, "if has('gui_running')")
57 call add(a:commands, " try")
58 call add(a:commands, " call xolox#shell#fullscreen()")
59 XXX&lines&columns
60 call add(a:commands, " call feedkeys(\":set lines=" . &lines . " columns=" . &columns . "\\<CR>\")")
61 call add(a:commands, " catch " . '/^Vim\%((\a\+)\)\=:E117/')
62 call add(a:commands, " \" Ignore missing full-screen plug-in.")
63 call add(a:commands, " endtry")
64 call add(a:commands, "endif")
65 endif
66 catch /^Vim\%((\a\+)\)\=:E117/
67
68 endtry
69 endfunction
70
71 function! session#save_qflist(commands)
72 if has('quickfix') && !s:saved_qflist
73 call add(a:commands, 'call setqflist(' . s:save_qflist(getqflist()) . ')')
74 let s:saved_qflist = 1
75 endif
76 endfunction
77
78 function! s:save_qflist(input)
79 let result = []
80 for entry in a:input
81 if has_key(entry, 'bufnr')
82 if !has_key(entry, 'filename')
83 let entry.filename = bufname(entry.bufnr)
84 endif
85 unlet entry.bufnr
86 endif
87 call add(result, entry)
88 endfor
89 return string(result)
90 endfunction
91
92 function! session#save_state(commands)
93 let tempfile = tempname()
94 let ssop_save = &sessionoptions
95 try
96 &sessionoptions"options"
97 :mksession
98
99
100 :runtime
101 &sessionoptions
102 set ssop-=options ssop+=resize
103 execute 'mksession' fnameescape(tempfile)
104 let lines = readfile(tempfile)
105 if lines[-1] == '" vim: set ft=vim :'
106 call remove(lines, -1)
107 endif
108 call s:persist_special_windows(lines)
109 call extend(a:commands, lines)
110 return 1
111 finally
112 let &sessionoptions = ssop_save
113 call delete(tempfile)
114 endtry
115 endfunction
116
117 :mksession:NERDTree:Project
118
119 function! s:persist_special_windows(session)
120 if exists(':NERDTree') == 2 && match(a:session, '\<NERD_tree_\d\+$') >= 0
121 \ || exists(':Project') == 2 && exists('g:proj_running')
122 let original_tabpage = tabpagenr()
123 let original_window = winnr()
124 try
125 if &sessionoptions =~ '\<tabpages\>'
126 tabdo call s:foreach_tabpage(a:session)
127 else
128 call s:foreach_tabpage(a:session)
129 endif
130 finally
131 execute 'tabnext' original_tabpage
132 execute original_window . 'wincmd w'
133 call s:jump_to_window(a:session, original_tabpage, original_window)
134 endtry
135 endif
136 endfunction
137
138 function! s:foreach_tabpage(session)
139 let original_window = winnr()
140 try
141 FIXME
142 let s:window_to_info = {}
143 let s:loclist_to_window = {}
144 windo call s:foreach_window(a:session)
145 if !empty(s:window_to_info)
146 for window in reverse(sort(keys(s:window_to_info)))
147 let [entries, title] = s:window_to_info[window]
148 call s:jump_to_window(a:session, tabpagenr(), window)
149 call add(a:session, 'bwipeout')
150 if entries == '[]'
151 call session#save_qflist(a:session)
152 call add(a:session, 'copen')
153 else
154 let other_window = s:loclist_to_window[entries]
155 call add(a:session, other_window . 'wincmd w')
156 call add(a:session, 'botright lopen')
157 endif
158 call add(a:session, 'let w:quickfix_title = ' . string(title))
159 endfor
160 call add(a:session, winrestcmd())
161 endif
162 finally
163 execute original_window . 'wincmd w'
164 endtry
165 endfunction
166
167 function! s:foreach_window(session)
168 if exists('b:NERDTreeRoot')
169 call s:save_plugin_window(a:session, 'NERDTree', b:NERDTreeRoot.path.str())
170 elseif exists('g:proj_running') && g:proj_running == bufnr('%')
171 call s:save_plugin_window(a:session, 'Project', expand('%:p'))
172 elseif has('quickfix')
173 let loclist = s:save_qflist(getloclist(0))
174 if &bt == 'quickfix' && &ft == 'qf' && &bh == 'wipe' && !&swf
175
176 let s:window_to_info[winnr()] = [loclist, w:quickfix_title]
177 elseif loclist != '[]'
178
179 call s:jump_to_window(a:session, tabpagenr(), winnr())
180 call add(a:session, 'call setloclist(0, ' . loclist . ')')
181 let s:loclist_to_window[loclist] = winnr()
182 endif
183 endif
184 endfunction
185
186 function! s:save_plugin_window(session, command, argument)
187 call s:jump_to_window(a:session, tabpagenr(), winnr())
188 call add(a:session, 'bwipeout')
189 let argument = fnamemodify(a:argument, ':~')
190 if &sessionoptions =~ '\<slash\>'
191 let argument = substitute(argument, '\', '/', 'g')
192 endif
193 call add(a:session, a:command . ' ' . fnameescape(argument))
194 endfunction
195
196 function! s:jump_to_window(session, tabpage, window)
197 if &sessionoptions =~ '\<tabpages\>'
198 call add(a:session, 'tabnext ' . a:tabpage)
199 endif
200 call add(a:session, a:window . 'wincmd w')
201 endfunction
202
203
204
205 function! session#auto_load()
206
207 if bufnr('$') == 1 && bufname('%') == '' && !&mod && getline(1, '$') == ['']
208
209 if v:servername !~ '^\cgvim\d*$'
210 for session in session#get_names()
211 if v:servername ==? session
212 execute 'OpenSession' fnameescape(session)
213 return
214 endif
215 endfor
216 endif
217
218 let path = session#name_to_path('default')
219 if filereadable(path) && !s:session_is_locked(path)
220 let msg = "Do you want to restore your default editing session?"
221 if s:prompt(msg, 'g:session_autoload')
222 OpenSession default
223 endif
224 endif
225 endif
226 endfunction
227
228 function! session#auto_save()
229 if !v:dying
230 let name = s:get_name('', 0)
231 if name != '' && exists('s:session_is_dirty')
232 let msg = "Do you want to save your editing session before quitting Vim?"
233 if s:prompt(msg, 'g:session_autosave')
234 execute 'SaveSession' fnameescape(name)
235 endif
236 endif
237 endif
238 endfunction
239
240 function! session#auto_unlock()
241 let i = 0
242 while i < len(s:lock_files)
243 let lock_file = s:lock_files[i]
244 if delete(lock_file) == 0
245 call remove(s:lock_files, i)
246 else
247 let i += 1
248 endif
249 endwhile
250 endfunction
251
252 function! session#auto_dirty_check()
253
254
255
256 if v:this_session == ''
257
258 return
259 elseif !exists('s:cached_layouts')
260 let s:cached_layouts = {}
261 else
262 s:cached_layouts
263 let last_tabpage = tabpagenr('$')
264 call filter(s:cached_layouts, 'v:key <= last_tabpage')
265 endif
266 let tabpagenr = tabpagenr()
267 let keys = ['tabpage:' . tabpagenr]
268 let buflist = tabpagebuflist()
269 for winnr in range(1, winnr('$'))
270 {winnr}
271 call add(keys, printf('width:%i,height:%i,buffer:%i',
272 \ winwidth(winnr), winheight(winnr), buflist[winnr - 1]))
273 endfor
274 let layout = join(keys, "\n")
275 let cached_layout = get(s:cached_layouts, tabpagenr, '')
276 if cached_layout != '' && cached_layout != layout
277 let s:session_is_dirty = 1
278 endif
279 let s:cached_layouts[tabpagenr] = layout
280 endfunction
281
282 function! s:prompt(msg, var)
283 if eval(a:var)
284 return 1
285 else
286 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 = 1"
287 let vimrc = has('win32') || has('win64') ? '~\_vimrc' : '~/.vimrc'
288 let prompt = printf(format, a:msg, vimrc, a:var)
289 return confirm(prompt, "&Yes\n&No", 1, 'Question') == 1
290 endif
291 endfunction
292
293
294
295 function! session#open_cmd(name, bang) abort
296 let name = s:select_name(s:unescape(a:name), 'restore')
297 if name != ''
298 let path = session#name_to_path(name)
299 if !filereadable(path)
300 let msg = "session.vim: The %s session at %s doesn't exist!"
301 call xolox#warning(msg, string(name), fnamemodify(path, ':~'))
302 elseif a:bang == '!' || !s:session_is_locked(path, 'OpenSession')
303 call session#close_cmd(a:bang, 1)
304 call s:lock_session(path)
305 execute 'source' fnameescape(path)
306 unlet! s:session_is_dirty
307 call xolox#message("session.vim: Opened %s session from %s.", string(name), fnamemodify(path, ':~'))
308 endif
309 endif
310 endfunction
311
312 function! session#view_cmd(name) abort
313 let name = s:select_name(s:get_name(s:unescape(a:name), 0), 'view')
314 if name != ''
315 let path = session#name_to_path(name)
316 if !filereadable(path)
317 let msg = "session.vim: The %s session at %s doesn't exist!"
318 call xolox#warning(msg, string(name), fnamemodify(path, ':~'))
319 else
320 execute 'tab drop' fnameescape(path)
321 call xolox#message("session.vim: Viewing session script %s.", fnamemodify(path, ':~'))
322 endif
323 endif
324 endfunction
325
326 function! session#save_cmd(name, bang) abort
327 let name = s:get_name(s:unescape(a:name), 1)
328 let path = session#name_to_path(name)
329 let friendly_path = fnamemodify(path, ':~')
330 if a:bang == '!' || !s:session_is_locked(path, 'SaveSession')
331 let lines = []
332 call session#save_session(lines, friendly_path)
333 let is_dos = has('dos16') || has('dos32')
334 let is_windows = has('win32') || has('win64')
335 if (is_dos || is_windows) && &ssop !~ '\<unix\>'
336 call map(lines, 'v:val . "\r"')
337 endif
338 if writefile(lines, path) != 0
339 let msg = "session.vim: Failed to save %s session to %s!"
340 call xolox#warning(msg, string(name), friendly_path)
341 else
342 let msg = "session.vim: Saved %s session to %s."
343 call xolox#message(msg, string(name), friendly_path)
344 let v:this_session = path
345 call s:lock_session(path)
346 unlet! s:session_is_dirty
347 endif
348 endif
349 endfunction
350
351 function! session#delete_cmd(name, bang)
352 let name = s:select_name(s:unescape(a:name), 'delete')
353 if name != ''
354 let path = session#name_to_path(name)
355 if !filereadable(path)
356 let msg = "session.vim: The %s session at %s doesn't exist!"
357 call xolox#warning(msg, string(name), fnamemodify(path, ':~'))
358 elseif a:bang == '!' || !s:session_is_locked(path, 'DeleteSession')
359 if delete(path) != 0
360 let msg = "session.vim: Failed to delete %s session at %s!"
361 call xolox#warning(msg, string(name), fnamemodify(path, ':~'))
362 else
363 call s:unlock_session(path)
364 let msg = "session.vim: Deleted %s session at %s."
365 call xolox#message(msg, string(name), fnamemodify(path, ':~'))
366 endif
367 endif
368 endif
369 endfunction
370
371 function! session#close_cmd(bang, silent) abort
372 let name = s:get_name('', 0)
373 if name != '' && exists('s:session_is_dirty')
374 let msg = "Do you want to save your current editing session before closing it?"
375 if s:prompt(msg, 'g:session_autosave')
376 SaveSession
377 endif
378 call s:unlock_session(session#name_to_path(name))
379 endif
380 if tabpagenr('$') > 1
381 execute 'tabonly' . a:bang
382 endif
383 if winnr('$') > 1
384 execute 'only' . a:bang
385 endif
386 execute 'enew' . a:bang
387 unlet! s:session_is_dirty
388 if v:this_session == ''
389 if !a:silent
390 let msg = "session.vim: Closed session."
391 call xolox#message(msg)
392 endif
393 else
394 if !a:silent
395 let msg = "session.vim: Closed session %s."
396 call xolox#message(msg, fnamemodify(v:this_session, ':~'))
397 endif
398 let v:this_session = ''
399 endif
400 return 1
401 endfunction
402
403 function! session#restart_cmd(bang) abort
404 let name = s:get_name('', 0)
405 if name == '' | let name = 'restart' | endif
406 execute 'SaveSession' . a:bang fnameescape(name)
407 let progname = shellescape(fnameescape(v:progname))
408 let servername = shellescape(fnameescape(name))
409 let command = progname . ' --servername ' . servername
410 let command .= ' -c ' . shellescape('OpenSession\! ' . fnameescape(name))
411 if has('win32') || has('win64')
412 execute '!start' command
413 else
414 let term = shellescape(fnameescape($TERM))
415 let encoding = "--cmd ':set enc=" . escape(&enc, '\ ') . "'"
416 silent execute '! TERM=' . term command encoding '&'
417 endif
418 execute 'CloseSession' . a:bang
419 quitall
420 endfunction
421
422
423
424 function! s:unescape(s)
425 return substitute(a:s, '\\\(.\)', '\1', 'g')
426 endfunction
427
428 function! s:select_name(name, action)
429 if a:name != ''
430 return a:name
431 endif
432 let sessions = sort(session#get_names())
433 if empty(sessions)
434 return 'default'
435 elseif len(sessions) == 1
436 return sessions[0]
437 endif
438 let lines = copy(sessions)
439 for i in range(len(sessions))
440 let lines[i] = ' ' . (i + 1) . '. ' . lines[i]
441 endfor
442 redraw
443 sleep 100 m
444 echo "\nPlease select the session to " . a:action . ":"
445 sleep 100 m
446 let i = inputlist([''] + lines + [''])
447 return i >= 1 && i <= len(sessions) ? sessions[i - 1] : ''
448 endfunction
449
450 function! s:get_name(name, use_default)
451 let name = a:name
452 if name == '' && v:this_session != ''
453 let this_session_dir = fnamemodify(v:this_session, ':p:h')
454 if xolox#path#equals(this_session_dir, g:session_directory)
455 let name = session#path_to_name(v:this_session)
456 endif
457 endif
458 return name != '' ? name : a:use_default ? 'default' : ''
459 endfunction
460
461 function! session#name_to_path(name)
462 let directory = xolox#path#absolute(g:session_directory)
463 let filename = xolox#path#encode(a:name) . '.vim'
464 return xolox#path#merge(directory, filename)
465 endfunction
466
467 function! session#path_to_name(path)
468 return xolox#path#decode(fnamemodify(a:path, ':t:r'))
469 endfunction
470
471 function! session#get_names()
472 let directory = xolox#path#absolute(g:session_directory)
473 let filenames = split(glob(xolox#path#merge(directory, '*.vim')), "\n")
474 return map(filenames, 'fnameescape(xolox#path#decode(fnamemodify(v:val, ":t:r")))')
475 endfunction
476
477 function! session#complete_names(arg, line, pos)
478 return filter(session#get_names(), 'v:val =~ a:arg')
479 endfunction
480
481 Lock file management:
482
483 if !exists('s:lock_files')
484 let s:lock_files = []
485 endif
486
487 function! s:lock_session(session_path)
488 let lock_file = a:session_path . '.lock'
489 if writefile([v:servername], lock_file) == 0
490 if index(s:lock_files, lock_file) == -1
491 call add(s:lock_files, lock_file)
492 endif
493 return 1
494 endif
495 endfunction
496
497 function! s:unlock_session(session_path)
498 let lock_file = a:session_path . '.lock'
499 if delete(lock_file) == 0
500 let idx = index(s:lock_files, lock_file)
501 if idx >= 0
502 call remove(s:lock_files, idx)
503 endif
504 return 1
505 endif
506 endfunction
507
508 function! s:session_is_locked(session_path, ...)
509 let lock_file = a:session_path . '.lock'
510 if filereadable(lock_file)
511 let lines = readfile(lock_file)
512 if lines[0] !=? v:servername
513 if a:0 >= 1
514 let msg = "session.vim: The %s session is locked by another Vim instance named %s! Use :%s! to override."
515 call xolox#warning(msg, string(fnamemodify(a:session_path, ':t:r')), string(lines[0]), a:1)
516 endif
517 return 1
518 endif
519 endif
520 endfunction
521
522