├── .github └── workflows │ └── lint.yaml ├── .gitignore ├── .mdl.rb ├── .mdlrc ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── autoload ├── stay.vim └── stay │ ├── integrate │ └── fastfold.vim │ ├── shim.vim │ ├── view.vim │ ├── viewdir.vim │ └── win.vim ├── doc └── vim-stay.txt └── plugin └── stay.vim /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint code 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | mdl: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actionshub/markdownlint@main 13 | name: Lint markdown files 14 | 15 | vimlint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: vimlint 20 | uses: tsuyoshicho/action-vimlint@v1 21 | with: 22 | github_token: ${{ secrets.GITHUB_TOKEN }} 23 | target: autoload plugin 24 | fail_on_error: true 25 | reporter: github-check 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore vim-generated doc tags (this will clutter the local copy of this repo being used by vim) 2 | doc/tags 3 | -------------------------------------------------------------------------------- /.mdl.rb: -------------------------------------------------------------------------------- 1 | all 2 | 3 | exclude_tag :line_length 4 | exclude_rule 'MD026' # trailing punctuation in header 5 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | style '.mdl.rb' 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at zach@himsel.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Kopischke, 2018 Zach Himsel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stay at my cursor, boy! 2 | 3 | [![Project status][badge-status]][vimscripts] 4 | [![Current release][badge-release]][releases] 5 | [![Open issues][badge-issues]][issues] 6 | [![License][badge-license]][license] 7 | [![Lint status][badge-lint]][job-lint] 8 | 9 | *vim-stay* adds automated view session creation and restoration whenever editing a buffer, across Vim sessions and window life cycles. It also alleviates Vim's tendency to lose view state when cycling through buffers (via `argdo`, `bufdo` et al.). It is smart about which buffers should be persisted and which should not, making the procedure painless and invisible. 10 | 11 | If you have wished Vim would be smarter about keeping your editing state, *vim-stay* is for you. 12 | 13 | ## Installation 14 | 15 | 1. The old way: download and source the vimball from the [releases page][releases], then run `:helptags {dir}` on your runtimepath/doc directory. Updating the plug-in via `:GetLatestVimScripts` is supported. Or, 16 | 1. The plug-in manager way: using a git-based plug-in manager (Pathogen, Vundle, NeoBundle, Vim-Plug etc.), simply add `zhimsel/vim-stay` to the list of plug-ins, source that and issue your manager's install command. Or, 17 | 1. The Vim package way (requires Vim 7.4 with patch 1384): create a `pack/vim-stay/start/` directory in your `'packagepath'` and clone this repository into it. Run `:helptags {dir}` on the `doc` directory of the created repo. Run `:runtime plugin/stay.vim` to load *vim-stay* (or restart Vim). 18 | 19 | ## Usage 20 | 21 | Recommended: `set viewoptions=cursor,folds,slash,unix` (but at the very least do `set viewoptions-=options`). Edit as usual. See the [documentation][doc] for more. 22 | 23 | ## Rationale 24 | 25 | Keeping editing session state should be a given in an editor; unluckily, Vim's solution for this, *view sessions*, are not easily automated [without encountering painful bumps][mkview-wikia]. As the one plug-in available that aimed to fix this, Zhou Yi Chao’s [*restore_view.vim*][chao-plugin], limited itself to Vim editing sessions, didn’t play well with other position setting plug-ins like [vim-fetch][vim-fetch] and as there were [some issues with its heuristics][heuristics], *vim-stay* was born. 26 | 27 | ## License 28 | 29 | *vim-stay* is licensed under [the terms of the MIT license according to the accompanying license file][license]. 30 | 31 | [badge-status]: https://img.shields.io/badge/status-maintained-green.svg?style=flat-square 32 | [badge-release]: https://img.shields.io/github/release/zhimsel/vim-stay.svg?style=flat-square 33 | [badge-issues]: https://img.shields.io/github/issues/zhimsel/vim-stay.svg?style=flat-square 34 | [badge-license]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square 35 | [badge-lint]: https://img.shields.io/github/actions/workflow/status/zhimsel/vim-stay/lint.yaml?label=linting 36 | [job-lint]: https://github.com/zhimsel/vim-stay/actions/workflows/lint.yaml 37 | [chao-plugin]: http://www.vim.org/scripts/script.php?script_id=4021 38 | [doc]: doc/vim-stay.txt 39 | [heuristics]: https://github.com/zhimsel/vim-stay/issues/2 40 | [issues]: https://github.com/zhimsel/vim-stay/issues 41 | [license]: LICENSE.md 42 | [mkview-wikia]: http://vim.wikia.com/wiki/Make_views_automatic 43 | [releases]: https://github.com/zhimsel/vim-stay/releases 44 | [vim-fetch]: http://www.vim.org/scripts/script.php?script_id=5089 45 | [vimscripts]: http://www.vim.org/scripts/script.php?script_id=5099 46 | -------------------------------------------------------------------------------- /autoload/stay.vim: -------------------------------------------------------------------------------- 1 | " AUTOLOAD FUNCTION LIBRARY FOR VIM-STAY 2 | " Core functions (will be loaded when first autocommand is triggered) 3 | if &compatible || v:version < 700 4 | finish 5 | endif 6 | 7 | let s:cpoptions = &cpoptions 8 | set cpoptions&vim 9 | 10 | " Check if the buffer {bufnr} is persistent: 11 | " @signature: stay#ispersistent({bufnr:Number}, {volatile_ftypes:List}) 12 | " @returns: Boolean 13 | " @notes: the persistence heuristics are 14 | " - buffer name must not be empty 15 | " - buffer must not be marked as ignored 16 | " - buffer must be listed 17 | " - buffer must be of ordinary or "acwrite" 'buftype' 18 | " - buffer's 'bufhidden' must be empty or "hide" 19 | " - buffer must map to a readable file 20 | " - buffer must not be of a volatile file type 21 | " - buffer file must not be located in a known temp dir 22 | function! stay#ispersistent(bufnr, volatile_ftypes) abort 23 | let l:bufpath = expand('#'.a:bufnr.':p') " empty on invalid buffer numbers 24 | return 25 | \ !empty(l:bufpath) && 26 | \ getbufvar(a:bufnr, 'stay_ignore') isnot 1 && 27 | \ getbufvar(a:bufnr, '&buflisted') is 1 && 28 | \ index(['', 'acwrite'], getbufvar(a:bufnr, '&buftype')) isnot -1 && 29 | \ index(['', 'hide'], getbufvar(a:bufnr, '&bufhidden')) isnot -1 && 30 | \ filereadable(l:bufpath) && 31 | \ stay#isftype(a:bufnr, a:volatile_ftypes) isnot 1 && 32 | \ stay#istemp(l:bufpath) isnot 1 33 | endfunction 34 | 35 | " Check if the window with ID {winid} is eligible for view saving: 36 | " @signature: stay#isviewwin({winid:Number}) 37 | " @returns: Boolean 38 | " @notes: a window is considered eligible when 39 | " - it exists 40 | " - it is not a preview window 41 | " - it is not a diff window 42 | function! stay#isviewwin(winid) abort 43 | let [l:tabnr, l:winnr] = stay#win#id2tabwin(a:winid) 44 | return 45 | \ l:tabnr isnot 0 && 46 | \ l:winnr isnot 0 && 47 | \ gettabwinvar(l:tabnr, l:winnr, '&previewwindow') isnot 1 && 48 | \ gettabwinvar(l:tabnr, l:winnr, '&diff') isnot 1 49 | endfunction 50 | 51 | " Check if {fname} is in a 'backupskip' location: 52 | " @signature: stay#istemp({fname:String}) 53 | " @returns: Boolean 54 | if exists('*glob2regpat') " fastest option, Vim 7.4 with patch 668 only 55 | let s:backupskip = {'option': '', 'items': []} 56 | function! stay#istemp(path) abort 57 | " cache List of option-unescaped 'backuspkip' values 58 | if s:backupskip.option isnot &backupskip 59 | let s:backupskip.option = &backupskip 60 | let s:backupskip.items = split(s:backupskip.option, '\v\\@}) 92 | " @returns: Boolean 93 | " @notes: - tests individual parts of composite (dotted) 'filetype's 94 | " - comparison is always case sensitive 95 | function! stay#isftype(bufnr, ftypes) abort 96 | let l:candidates = split(getbufvar(a:bufnr, '&filetype'), '\.') 97 | return !empty(filter(l:candidates, 'index(a:ftypes, v:val) isnot -1')) 98 | endfunction 99 | 100 | " Get the buffer state Dictionary for {bufnr}: 101 | " @signature: stay#getbufstate({bufnr:Expression}) 102 | " @returns: Dictionary 103 | function! stay#getbufstate(bufnr) abort 104 | let l:state = getbufvar(a:bufnr, '_stay') 105 | if l:state is '' 106 | unlet l:state | let l:state = {} 107 | call setbufvar(a:bufnr, '_stay', l:state) 108 | endif 109 | return l:state 110 | endfunction 111 | 112 | let &cpoptions = s:cpoptions 113 | unlet! s:cpoptions 114 | 115 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 116 | -------------------------------------------------------------------------------- /autoload/stay/integrate/fastfold.vim: -------------------------------------------------------------------------------- 1 | " FASTFOLD INTEGRATION MODULE 2 | " https://github.com/Konfekt/FastFold 3 | let s:cpoptions = &cpoptions 4 | set cpoptions&vim 5 | 6 | " - cancel integration if FastFold is not found 7 | if empty(findfile('plugin/fastfold.vim', &rtp)) 8 | let &cpoptions = s:cpoptions 9 | unlet! s:cpoptions 10 | finish 11 | endif 12 | 13 | " - register integration autocommands if FastFold plug-in is found 14 | function! stay#integrate#fastfold#setup() abort 15 | autocmd User BufStaySavePre unsilent call stay#integrate#fastfold#save_pre() 16 | autocmd User BufStaySavePost unsilent call stay#integrate#fastfold#save_post() 17 | autocmd User BufStayLoadPost,BufStaySavePost let b:isPersistent = 1 18 | endfunction 19 | 20 | " - on User event 'BufStaySavePre': restore original 'foldmethod' 21 | function! stay#integrate#fastfold#save_pre() abort 22 | if index(split(&viewoptions, ','), 'folds') isnot -1 23 | let [l:fdmlocal, l:fdmorig] = [&l:foldmethod, get(w:, 'lastfdm', &l:foldmethod)] 24 | if l:fdmlocal is# 'manual' 25 | noautocmd silent let &l:foldmethod = l:fdmorig 26 | endif 27 | endif 28 | endfunction 29 | 30 | " - on User event 'BufStaySavePost': restore FastFold 'foldmethod' 31 | function! stay#integrate#fastfold#save_post() abort 32 | if &foldmethod isnot# 'manual' && exists('w:lastfdm') 33 | noautocmd silent let &l:foldmethod = 'manual' 34 | endif 35 | endfunction 36 | 37 | let &cpoptions = s:cpoptions 38 | unlet! s:cpoptions 39 | 40 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 41 | -------------------------------------------------------------------------------- /autoload/stay/shim.vim: -------------------------------------------------------------------------------- 1 | " STAY EVAL SHIM MODULE 2 | " Are these Vim patch levels, my dear? 3 | if &compatible || v:version < 700 4 | finish 5 | endif 6 | 7 | let s:cpoptions = &cpoptions 8 | set cpoptions&vim 9 | 10 | " Full forward and backward `globpath()` compatibility between Vim 7.0 and Vim 7.4: 11 | " - no {nosuf} argument before 7.2.051 - :h version7.txt 12 | " - no {list} argument before 7.4.279 - http://ftp.vim.org/pub/vim/patches/7.4/README 13 | if v:version < 702 || (v:version is 702 && !has('patch051')) 14 | function! stay#shim#globpath(path, glob, ...) abort 15 | let l:nosuf = get(a:, 1, 0) 16 | let l:list = get(a:, 2, 0) 17 | let l:suffixes = &suffixes 18 | let l:wildignore = &wildignore 19 | try 20 | if l:nosuf isnot 0 21 | set suffixes= 22 | set wildignore= 23 | endif 24 | let l:result = globpath(a:path, a:glob) 25 | return l:list isnot 0 ? s:fnames2list(l:result, 0) : l:result 26 | finally 27 | let &suffixes = l:suffixes 28 | let &wildignore = l:wildignore 29 | endtry 30 | endfunction 31 | 32 | elseif v:version < 704 || (v:version is 704 && !has('patch279')) 33 | function! stay#shim#globpath(path, glob, ...) abort 34 | let l:nosuf = get(a:, 1, 0) 35 | let l:list = get(a:, 2, 0) 36 | let l:result = globpath(a:path, a:glob, l:nosuf) 37 | return l:list isnot 0 ? s:fnames2list(l:result, l:nosuf) : l:result 38 | endfunction 39 | 40 | else 41 | function! stay#shim#globpath(path, glob, ...) abort 42 | return globpath(a:path, a:glob, get(a:, 1, 0), get(a:, 2, 0)) 43 | endfunction 44 | endif 45 | 46 | " Get a List out of {fnames} without mangling file names with NL in them: 47 | " @signature: s:fnames2list({fnames:String[NL-separated]}, {setnosuf:Boolean}) 48 | " @returns: List of file system object paths in {fnames} 49 | function! s:fnames2list(fnames, setnosuf) abort 50 | let l:globcmd = a:setnosuf is 1 ? 'glob(%s, 1)' : 'glob(%s)' 51 | let l:fnames = split(a:fnames, '\n') 52 | let l:fragments = filter(copy(l:fnames), 'empty('.printf(l:globcmd, 'v:val').')') 53 | if empty(l:fragments) 54 | return l:fnames 55 | endif 56 | 57 | let l:fnames = filter(l:fnames, '!empty('.printf(l:globcmd, 'v:val').')') 58 | let l:index = 0 59 | while l:index+1 < len(l:fragments) 60 | let l:composite = get(l:, 'composite', l:fragments[l:index])."\n".l:fragments[l:index+1] 61 | if !empty(eval(printf(l:globcmd, string(l:composite)))) 62 | call add(l:fnames, l:composite) 63 | unlet l:composite 64 | let l:index += 1 65 | endif 66 | let l:index += 1 67 | endwhile 68 | return sort(l:fnames, 'i') 69 | endfunction 70 | 71 | let &cpoptions = s:cpoptions 72 | unlet! s:cpoptions 73 | 74 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 75 | -------------------------------------------------------------------------------- /autoload/stay/view.vim: -------------------------------------------------------------------------------- 1 | " AUTOLOAD FUNCTION LIBRARY FOR VIM-STAY 2 | " View session handling functions 3 | if &compatible || !has('autocmd') || !has('mksession') || v:version < 700 4 | finish 5 | endif 6 | 7 | let s:cpoptions = &cpoptions 8 | set cpoptions&vim 9 | 10 | " Make a persistent view for window ID {winid}: 11 | " @signature: stay#view#make({winid:Number}) 12 | " @returns: Boolean (-1 on error) 13 | " @notes: Exceptions are suppressed, but written to |v:errmsg| 14 | function! stay#view#make(winid) abort 15 | let l:curwinid = stay#win#getid() 16 | if s:sneak2winid(a:winid) isnot 1 17 | let v:errmsg = "vim-stay could not switch to window ID: ".a:winid 18 | return 0 19 | endif 20 | 21 | call s:doautocmd('User', 'BufStaySavePre') 22 | " enforce non-storage of options as that causes odd issues 23 | let l:viewoptions = &viewoptions 24 | try 25 | set viewoptions-=options 26 | set viewoptions-=localoptions 27 | unlet! b:stay_atpos 28 | silent mkview 29 | return 1 30 | catch /\vE%(166|190|212)/ " no write access to existing view file 31 | let v:errmsg = "vim-stay could not write the view session file! " 32 | let v:errmsg .= "Vim error ".s:exception2errmsg(v:exception) 33 | return -1 34 | catch " other errors 35 | let v:errmsg = 'vim-stay error '.s:exception2errmsg(v:exception) 36 | return -1 37 | finally 38 | call s:doautocmd('User', 'BufStaySavePost') 39 | let &viewoptions = l:viewoptions 40 | call s:sneak2winid(l:curwinid) 41 | endtry 42 | endfunction 43 | 44 | " Load a persistent view for window ID {winid}: 45 | " @signature: stay#view#load({winid:Number}) 46 | " @returns: Boolean (-1 on error) 47 | " @notes: Exceptions are suppressed, but written to |v:errmsg| 48 | let s:viewdir = {'option': '', 'path': ''} 49 | function! stay#view#load(winid) abort 50 | let l:curwinid = stay#win#getid() 51 | if s:sneak2winid(a:winid) isnot 1 52 | let v:errmsg = "vim-stay could not switch to window ID: ".a:winid 53 | return 0 54 | endif 55 | 56 | " emit Pre event before we change any setting 57 | call s:doautocmd('User', 'BufStayLoadPre') 58 | 59 | " the `doautoall SessionLoadPost` in view session files significantly 60 | " slows down buffer load, hence we suppress it... 61 | let l:eventignore = &eventignore 62 | try 63 | set eventignore+=SessionLoadPost 64 | set eventignore-=SourceCmd 65 | " ensure we only react to a fresh view load without clobbering 66 | " b:stay_loaded_view (which is part of the API) 67 | if exists('b:stay_loaded_view') 68 | let l:stay_loaded_view = b:stay_loaded_view 69 | unlet b:stay_loaded_view 70 | endif 71 | 72 | " cache 'viewdir' value and normalise its directory path 73 | if s:viewdir.option isnot &viewdir 74 | let s:viewdir.option = &viewdir 75 | " Windows path backslashes will not work in autocommands and need to be replaced by 76 | " slashes; also Windows paths set in Vim options may contain both slashes and backslashes, 77 | " so we need to give them the foward slash treatment too to normalise misformatted options 78 | let l:slashes = exists('+shellslash') ? '[/\\]' : '/' 79 | let s:viewdir.path = substitute(&viewdir, '\v'.l:slashes.'+', '/', 'g') 80 | endif 81 | 82 | " catch sourcing of the view file with a one-off SourcePre autocommand 83 | let l:buftail = fnamemodify(bufname('%'), ':t') 84 | let l:pattern = fnameescape(s:viewdir.path).'*'.fnameescape(l:buftail).'*' 85 | execute 'autocmd SourcePre' l:pattern 86 | \ 'let b:stay_loaded_view = expand('''') | autocmd! SourcePre' l:pattern 87 | silent loadview 88 | let l:did_load_view = exists('b:stay_loaded_view') 89 | 90 | " fire SessionLoadPost in a more targeted way 91 | if l:did_load_view is 1 92 | let &eventignore = l:eventignore 93 | " don't use s:doautocmd(): we need modelines to be evaluated! 94 | execute (exists('#SessionLoadPost') ? '' : 'silent') 'doautocmd SessionLoadPost' 95 | endif 96 | 97 | " respect position set by other scripts / plug-ins 98 | if l:did_load_view is 1 && exists('b:stay_atpos') && getpos('.')[1:2] != b:stay_atpos 99 | call cursor(b:stay_atpos[0], b:stay_atpos[1]) 100 | silent! normal! zOzz 101 | endif 102 | return l:did_load_view 103 | catch /E484/ " failed to open view file 104 | let v:errmsg = 'vim-stay could not open the view session file! ' 105 | let v:errmsg .= 'Vim error '.s:exception2errmsg(v:exception) 106 | if has('nvim') " neovim produces this error every time; don't return it 107 | return 0 108 | else 109 | return -1 110 | endif 111 | catch /E485/ " no read access to existing view file 112 | let v:errmsg = "vim-stay could not read the view session file! " 113 | let v:errmsg .= "Vim error ".s:exception2errmsg(v:exception) 114 | return -1 115 | catch /\vE%(35[0-2]|490)/ " fold errors 116 | let v:errmsg = 'vim-stay error '.s:exception2errmsg(v:exception) 117 | return 0 118 | catch " other errors 119 | let v:errmsg = 'vim-stay error '.s:exception2errmsg(v:exception) 120 | return -1 121 | finally 122 | " restore stale b:stay_loaded_view for API usage 123 | if get(l:, 'did_load_view', 0) isnot 1 && exists('l:stay_loaded_view') 124 | let b:stay_loaded_view = l:stay_loaded_view 125 | endif 126 | let &eventignore = l:eventignore 127 | call s:doautocmd('User', 'BufStayLoadPost') 128 | call s:sneak2winid(l:curwinid) 129 | endtry 130 | endfunction 131 | 132 | " Private helper functions: {{{ 133 | " - apply {event} autocommands, optionally matching pattern {a:1}, 134 | " but only if there are any 135 | " 1. avoids flooding message history with "No matching autocommands" 136 | " 2. avoids re-applying modelines in Vim < 7.3.442, which doesn't honor || 137 | " see https://groups.google.com/forum/#!topic/vim_dev/DidKMDAsppw 138 | function! s:doautocmd(event, ...) abort 139 | let l:event = a:0 ? [a:event, a:1] : [a:event] 140 | if exists('#'.join(l:event, '#')) 141 | execute 'doautocmd ' join(l:event, ' ') 142 | endif 143 | endfunction 144 | 145 | " - activate a window ID without leaving a trail 146 | function! s:sneak2winid(id) abort 147 | silent noautocmd keepalt return stay#win#gotoid(a:id) 148 | endfunction 149 | 150 | " - extract the error message from an {exception} 151 | function! s:exception2errmsg(exception) abort 152 | return substitute(a:exception, '\v^.{-}:\zeE\d.+$', '', '') 153 | endfunction " }}} 154 | 155 | let &cpoptions = s:cpoptions 156 | unlet! s:cpoptions 157 | 158 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 159 | -------------------------------------------------------------------------------- /autoload/stay/viewdir.vim: -------------------------------------------------------------------------------- 1 | " AUTOLOAD FUNCTION LIBRARY FOR VIM-STAY 2 | " 'viewdir' handling functions 3 | if &compatible || v:version < 700 4 | finish 5 | endif 6 | 7 | let s:cpoptions = &cpoptions 8 | set cpoptions&vim 9 | 10 | " Remove view session files from 'viewdir': 11 | " @signature: stay#viewdir#clean({bang:String}, [{keepdays:Number}]) 12 | " @optargs: {keepdays} keep files not older than this in days (default: 0) 13 | " @returns: List tuple of deletion candidates count, deleted files count 14 | function! stay#viewdir#clean(bang, ...) abort 15 | let l:keepsecs = max([get(a:, 1, 0) * 86400, 0]) 16 | let l:candidates = stay#shim#globpath(&viewdir, '*', 1, 1) 17 | call filter(l:candidates, 'localtime() - getftime(v:val) > l:keepsecs') 18 | let l:candcount = len(l:candidates) 19 | let l:delcount = 0 20 | if a:bang is 1 || 21 | \ input("Delete ".l:candcount." view session files? (y/n): ") is? 'y' 22 | for l:file in l:candidates 23 | let l:delcount += (delete(l:file) is 0) 24 | endfor 25 | endif 26 | echo "\nDeleted ".l:delcount." files." 27 | return [l:candcount, l:delcount] 28 | endfunction 29 | 30 | let &cpoptions = s:cpoptions 31 | unlet! s:cpoptions 32 | 33 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 34 | -------------------------------------------------------------------------------- /autoload/stay/win.vim: -------------------------------------------------------------------------------- 1 | " AUTOLOAD FUNCTION LIBRARY FOR VIM-STAY 2 | " Window handling functions 3 | if &compatible || v:version < 700 4 | finish 5 | endif 6 | 7 | let s:cpoptions = &cpoptions 8 | set cpoptions&vim 9 | 10 | " PUBLIC API {{{ 11 | " Shim for versions of Vim without native window ID support 12 | if v:version < 704 || (v:version is 704 && !has('patch1518')) 13 | if has('windows') 14 | let s:needbeam = 1 15 | 16 | " Get a list of window IDs for windows containing buffer {bufnr}: 17 | " @signature: stay#win#findbuf({bufnr:Number) 18 | " @returns: List 19 | " @see: |win_findbuf()| 20 | function! stay#win#findbuf(bufnr) abort 21 | let l:winnids = [] 22 | let l:tabnrs = range(1, tabpagenr('$')) 23 | call filter(l:tabnrs, 'index(tabpagebuflist(v:val), a:bufnr) isnot -1') 24 | if !empty(l:tabnrs) 25 | let l:home = s:home() 26 | try 27 | for l:tab in l:tabnrs 28 | call s:beam('tab', l:tab) 29 | let l:bufwins = filter(range(1, winnr('$')), 'winbufnr(v:val) is a:bufnr') 30 | let l:winnids += map(l:bufwins, 'stay#win#getid(v:val)') 31 | endfor 32 | finally 33 | call s:scotty(l:home) 34 | endtry 35 | endif 36 | return l:winnids 37 | endfunction 38 | 39 | else " Stubs for Vim with no support for multiple windows 40 | function! stay#win#findbuf(bufnr) abort 41 | return bufnr('%') is a:bufnr ? [1] : [] 42 | endfunction 43 | endif 44 | 45 | else " Wrapper around native window ID functions 46 | function! stay#win#findbuf(bufnr) abort 47 | return win_findbuf(a:bufnr) 48 | endfunction 49 | endif 50 | 51 | " Shims for versions of Vim without native window ID support 52 | if v:version < 704 || (v:version is 704 && !has('patch1517')) 53 | if has('windows') 54 | let s:needbeam = 1 55 | let s:idvar = '_winid' " global counter and window ID var name 56 | let s:maxid = 0 " shadow and sanity check for global counter 57 | 58 | " Get the window ID for the specified window: 59 | " @signature: stay#win#getid([{win:Number}[, {tab:Number}]]) 60 | " @returns: Number 61 | " @see: |win_getid()| 62 | " @caveats: Although the Number ID returned is guaranteed to be unique 63 | " for every window this function is called on, the IDs are 64 | " assigned lazily, not internally for every created window 65 | " like with the native functions. They neither match 66 | " - the creation sequence of windows 67 | " - the total number of active or created windows 68 | function! stay#win#getid(...) abort 69 | let l:winnr = a:0 > 0 ? a:1 : winnr() 70 | let l:tabnr = a:0 > 1 ? a:2 : tabpagenr() 71 | 72 | let l:winid = gettabwinvar(l:tabnr, l:winnr, s:idvar) 73 | if !empty(l:winid) | return l:winid | endif 74 | 75 | let l:winid = max([get(g:, s:idvar, 0), s:maxid]) + 1 76 | let s:maxid = l:winid 77 | let g:{s:idvar} = l:winid 78 | call settabwinvar(l:tabnr, l:winnr, s:idvar, l:winid) 79 | return l:winid 80 | endfunction 81 | 82 | " Go to the window with ID {expr}: 83 | " @signature: stay#win#gotoid({expr:Expression}) 84 | " @returns: Boolean (false if the window cannot be found) 85 | " @see: |win_gotoid()| 86 | function! stay#win#gotoid(expr) abort 87 | let l:target = stay#win#id2tabwin(a:expr) 88 | let l:home = s:home() 89 | if !s:beam('tab', l:target[0]) || !s:beam('win', l:target[1]) 90 | call s:scotty(l:home) 91 | return 0 92 | endif 93 | return 1 94 | endfunction 95 | 96 | " Get the tab and window number of the window with ID {expr} 97 | " @signature: stay#win#id2tabwin({expr:Expression}) 98 | " @returns: List 99 | " @see: |win_id2tabwin()| 100 | function! stay#win#id2tabwin(expr) abort 101 | if tabpagenr('$') > 1 102 | let l:home = s:home() 103 | try 104 | for l:tabnr in range(1, tabpagenr('$')) 105 | call s:beam('tab', l:tabnr) 106 | let l:winnr = stay#win#id2win(a:expr) 107 | if l:winnr isnot 0 108 | return [tabpagenr(), l:winnr] 109 | endif 110 | endfor 111 | return [0, 0] 112 | finally " restore active tab page and window 113 | call s:scotty(l:home) 114 | endtry 115 | else 116 | let l:winnr = stay#win#id2win(a:expr) 117 | return l:winnr isnot 0 ? [1, l:winnr] : [0, 0] 118 | endif 119 | endfunction 120 | 121 | " Get the window number of the window with ID {expr} 122 | " @signature: stay#win#id2win({expr:Expression}) 123 | " @returns: Number 124 | " @see: |win_id2win()| 125 | function! stay#win#id2win(expr) abort 126 | let l:wincnt = range(1, winnr('$')) 127 | let l:winnrs = filter(l:wincnt, 'getwinvar(v:val, '.string(s:idvar).') is '.string(a:expr)) 128 | return empty(l:winnrs) ? 0 : l:winnrs[0] 129 | endfunction 130 | 131 | else " Stubs for Vim with no support for multiple windows 132 | function! stay#win#getid(...) abort 133 | return get(a:, 1, 1) is 1 && get(a:, 2, 1) is 1 134 | endfunction 135 | 136 | function! stay#win#gotoid(expr) abort 137 | return a:expr is 1 138 | endfunction 139 | 140 | function! stay#win#id2tabwin(expr) abort 141 | return a:expr is 1 ? [1, 1] : [0, 0] 142 | endfunction 143 | 144 | function! stay#win#id2win(expr) abort 145 | return a:expr is 1 146 | endfunction 147 | endif 148 | 149 | else " Wrappers around native window ID functions 150 | function! stay#win#getid(...) abort 151 | return call('win_getid', a:000) 152 | endfunction 153 | 154 | function! stay#win#gotoid(expr) abort 155 | return win_gotoid(a:expr) 156 | endfunction 157 | 158 | function! stay#win#id2tabwin(expr) abort 159 | return win_id2tabwin(a:expr) 160 | endfunction 161 | 162 | function! stay#win#id2win(expr) abort 163 | return win_id2win(a:expr) 164 | endfunction 165 | endif "}}} 166 | 167 | " PRIVATE API {{{ 168 | if get(s:, 'needbeam', 0) is 1 169 | " - activate a window or tab without leaving a trail 170 | let s:beamdcmds = { 'tab': 'tabnext %i', 'win': '%iwincmd w' } 171 | let s:beamtests = { 'tab': 'tabpagenr', 'win': 'winnr' } 172 | function! s:beam(scope, number) abort 173 | if a:number < 1 || call(s:beamtests[a:scope], []) is a:number 174 | return 1 175 | endif 176 | silent execute 'noautocmd' 'keepalt' printf(s:beamdcmds[a:scope], a:number) 177 | return call(s:beamtests[a:scope], []) is a:number 178 | endfunction 179 | 180 | " - nothing like home in the void 181 | function! s:home() abort 182 | return [tabpagenr(), winnr()] 183 | endfunction 184 | 185 | " - go home. fast. 186 | function! s:scotty(home) abort 187 | return s:beam('tab', a:home[0]) && s:beam('win', a:home[1]) 188 | endfunction 189 | endif "}}} 190 | 191 | let &cpoptions = s:cpoptions 192 | unlet! s:cpoptions s:needbeam 193 | 194 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 195 | -------------------------------------------------------------------------------- /doc/vim-stay.txt: -------------------------------------------------------------------------------- 1 | *vim-stay.txt* For Vim version 7.0 or better 2 | 3 | 4 | VIM REFERENCE for the Stay plug-in 5 | 6 | 7 | Never lose your place in a buffer again *vim-stay* 8 | 9 | 1. Introduction |vim-stay-introduction| 10 | 2. Configuration |vim-stay-configuration| 11 | 3. Commands |vim-stay-commands| 12 | 4. Variables |vim-stay-variables| 13 | 5. Position specifications |vim-stay-integration| 14 | 6. Troubleshooting |vim-stay-troubleshooting| 15 | 7. Credits and license |vim-stay-credits-license| 16 | 17 | {not available when |'compatible'| is set, or when Vim is compiled without 18 | |+autocmd| or without |+mksession|} 19 | 20 | ============================================================================== 21 | 1. Introduction *vim-stay-introduction* 22 | 23 | vim-stay adds automated |View| creation and restoration whenever editing 24 | a buffer, across Vim sessions and window life cycles. It also alleviates Vim's 25 | tendency to lose view state when cycling through buffers (via |argdo|, |bufdo| 26 | et al.). It is smart about which buffers should be persisted and which should 27 | not, making the procedure painless and invisible. 28 | 29 | ============================================================================== 30 | 2. Configuration *vim-stay-configuration* 31 | 32 | 33 | VIEW SESSION CONFIGURATION: *vim-stay-viewoptions* 34 | 35 | The following, non-standard 'viewoptions' settings are recommended: 36 | > 37 | set viewoptions=cursor,folds,slash,unix 38 | < 39 | It is recommended to clear the 'viewdir' contents after changing this option, 40 | as it is only applied when creating a view session file, not when loading it. 41 | vim-stay provides the |:CleanViewdir| command to do that. 42 | 43 | Note that even if you would rather not change the other option flags from Vim‘s 44 | defaults, you absolutely should remove `options` from 'viewoptions', i.e. do 45 | > 46 | set viewoptions-=options 47 | < 48 | as storing local options in view session files causes no end of trouble. 49 | 50 | 51 | IGNORED FILE TYPES: *g:volatile_ftypes* 52 | 53 | vim-stay applies heuristics to detect buffers that should not be persisted, 54 | but in some cases non-persistent buffers slip through. Some of them are 55 | regular files that are not persistent by their very nature (like git commit 56 | messages), a few are buffers created by plug-ins that miss all indication 57 | that they are not files. These can be expressly marked as volatile (meaning 58 | buffers of this type will never be persisted) by adding their 'filetype' to 59 | the `volatile_ftypes` global |List|. 60 | 61 | Note this list is meant as a safety net for the case heuristics fail; it 62 | usually should not be necessary to modify vim-stay's defaults. If you find 63 | you need to add file types to it, make sure the plug-in has loaded, then do 64 | > 65 | let g:volatile_ftypes += ['foo', 'bar'] 66 | < 67 | ERROR MESSAGING VERBOSITY: *g:stay_verbosity* 68 | 69 | Because it is designed to work in the background of frequent Vim operations 70 | (buffer view changes), vim-stay will suppress most errors and only echo 71 | messages about important ones. You can adjust this policy by setting the 72 | value of this variable to 73 | 74 | -1 echoes no messages for errors at all 75 | 0 echoes messages for important errors (default) 76 | 1 echoes messages for all errors 77 | 78 | ============================================================================== 79 | 3. Commands *vim-stay-commands* 80 | 81 | 82 | :CleanViewdir[!] [days] 83 | Remove all saved view sessions in 'viewdir', optionally 84 | keeping view sessions files not older than {days} days. 85 | 86 | Note: this will ask for confirmation before deleting files. 87 | Use the bang variant to bypass the confirmation prompt. 88 | 89 | :StayReload[!] Load new |vim-stay-plugin-api| integrations. 90 | 91 | The bang variant will re-load all integrations (not just new 92 | ones), clear and re-define the core autocommands as well as 93 | all |vim-stay-commands| and reset all global configuration 94 | variables (as listed in |vim-stay-configuration|) to the plug-in 95 | defaults. 96 | 97 | ============================================================================== 98 | 4. Variables *vim-stay-variables* 99 | 100 | 101 | b:stay_loaded_view 102 | Full path to the last view session file loaded for the current 103 | buffer. This value is not vim-stay specific and will only 104 | change when the loaded view file's path changes. 105 | 106 | ============================================================================== 107 | 5. Integration *vim-stay-integration* 108 | 109 | 110 | INTEGRATION WITH 3RD PARTY PLUG-INS: 111 | 112 | Out of the box, vim-stay integrates with the following plug-ins: 113 | 114 | 1. vim-fetch http://www.vim.org/scripts/script.php?script_id=5089 115 | 2. FastFold https://github.com/Konfekt/FastFold 116 | 117 | If you'd like vim-stay to integrate with other position-setting or view 118 | management plug-ins, open an issue or a PR at 119 | 120 | https://github.com/kopischke/vim-stay/issues 121 | 122 | If the plug-in in question is one you own or contribute to, see 123 | |vim-stay-plugin-api| instead. 124 | 125 | 126 | INTEGRATION API: 127 | 128 | 1. Keeping the position set by other scripts *b:stay_atpos* 129 | 130 | To make vim-stay respect a position set by an unsupported script or plug-in, 131 | set the `stay_atpos` buffer-local variable: 132 | > 133 | let b:stay_atpos = [lnum, colnum] 134 | < 135 | This position will be restored after loading the session. 136 | 137 | 2. Ignoring a file on a per-buffer basis *b:stay_ignore* 138 | 139 | To stop vim-stay making and restoring sessions for a specific buffer, do 140 | > 141 | let b:stay_ignore = 1 142 | < 143 | See the |g:volatile_ftypes| user setting for a way to ignore all buffers of 144 | a certain file type. 145 | 146 | 3. Autocommand API *vim-stay-autocommands* 147 | 148 | vim-stay triggers two |User| autocommand events each when loading or saving 149 | state: *BufStayLoadPre* before loading a view session and *BufStayLoadPost* 150 | after loading it, *BufStaySavePre* before saving a view session and 151 | *BufStaySavePost* after. 152 | 153 | 4. Extended plug-in integration API *vim-stay-plugin-api* 154 | 155 | The mechanism vim-stay itself uses to integrate with other plug-ins is open 156 | to 3rd parties. Add a file 157 | > 158 | autoload/stay/integrate/yourplugin.vim 159 | < 160 | containing a `stay#integrate#yourplugin#setup()` |autoload| function to your 161 | plug-in. Any function with that signature found in 'runtimepath' when vim-stay 162 | loads will be executed. You can set up autocommands in there (which will 163 | automatically be added to the `stay_integrate` autocommand group), optionally 164 | leveraging vim-stay's autocommand API (|vim-stay-autocommands|), or add to the 165 | volatile 'filetype' list (|g:volatile_ftypes|). 166 | 167 | The advantage over hard-wiring support for vim-stay in your plug-in is that 168 | - the integration will be set up when your user uses vim-stay regardless of 169 | plug-in load order, but 170 | - the integration code will only be active if your user actually uses vim-stay. 171 | 172 | ============================================================================== 173 | 6. Troubleshooting *vim-stay-troubleshooting* 174 | 175 | 176 | MY CURSOR POSITION IS NOT PERSISTED 177 | 178 | You have removed "cursor" from 'viewoptions'. See the recommended setting 179 | under |vim-stay-viewoptions|. 180 | 181 | 182 | MY FOLD STATE IS NOT PERSISTED / MY CURSOR ENDS UP IN A CLOSED FOLD 183 | 184 | You have removed "folds" from 'viewoptions'. See the recommended setting 185 | under |vim-stay-viewoptions|. 186 | 187 | 188 | MY BUFFER-LOCAL OPTIONS ARE NOT PERSISTED 189 | 190 | This is by design: restoring local options from view session files causes hard 191 | to track issues with both Vim features and other plug-ins. Because of this, 192 | starting from version 1.4, vim-stay will ignore the `options` flag in 193 | 'viewoptions' when creating views. 194 | 195 | 196 | MY STATE IS NOT PERSISTED WHEN SWITCHING BETWEEN WINDOWS AND OTHER OSes 197 | 198 | With the default settings, 'viewoptions' uses platform specific path 199 | separators, which means stored view sessions are not portable. See the 200 | recommended setting under |vim-stay-viewoptions|. 201 | 202 | 203 | MY CURRENT WORKING DIRECTORY / MY ARGLIST CHANGES WHEN OPENING A FILE 204 | 205 | vim-stay uses |mkview| and |loadview|, which persist the local arglist and 206 | local working directory. This can be a bit disorienting at first, but it is by 207 | (Vim's) design. If this really irks you, you may be able to work around it 208 | using vim-stay's autocommand API (see |vim-stay-autocommands|). 209 | 210 | For an example, see 211 | 212 | https://github.com/kopischke/vim-stay/issues/10#issuecomment-83691770. 213 | 214 | 215 | MY MODELINES DO NOT SEEM TO TAKE EFFECT / 216 | THE VIM-AIRLINE STATUSLINE IS WRONG AFTER SWITCHING BUFFERS / 217 | PLUG-INS ACT UP AFTER SWITCHING WINDOWS / 218 | VIM-STAY MESSES UP OPTION X 219 | 220 | Your 'viewoptions' include or did include the default `options` (or 221 | `localoptions`) flag, which means all buffer options get stored in the view 222 | session files created by vim-stay. Loading such a view session file has a lot 223 | of unpleasant side effects, hence the recommended |vim-stay-viewoptions| which 224 | do not include the flag. 225 | 226 | Starting from version 1.4, vim-stay temporarily removes the `options`and 227 | `localoptions` flag from 'viewoptions' when creating views. However, view 228 | session files created with the incorrect settings (either by earlier versions 229 | of vim-stay, or manually via |:mkview|) will only be updated when vim-stay 230 | re-writes them, and thus still cause issues on first load. If you would rather 231 | start with a fresh slate, you can wipe your 'viewdir' with |:CleanViewdir|. 232 | Note that you will lose all view state doing so. 233 | 234 | 235 | VIM-STAY TRIES TO PERSIST STATE FOR TEMPORARY FILES 236 | 237 | - If the files are in a standard system temporary location, you should check 238 | if it listed in 'backupskip' - vim-stay will ignore files in the hierarchy 239 | of directories listed there. 240 | - Files in a temporary or cache directory not listed in 'backupskip' are not 241 | recognized as volatile, unless their 'buftype' is set to a non-file type. 242 | You can alleviate the issue by setting |b:stay_ignore| in affected buffers. 243 | 244 | Note: for performance reasons, 'backupskip' checking is skipped if Vim is 245 | compiled without |+wildignore| and |glob2regpat()| is not available. 246 | 247 | 248 | VIM-STAY TRIES TO PERSIST STATE FOR OTHER VOLATILE FILES 249 | 250 | Check if the 'filetype' of the affected file is listed in |g:volatile_ftypes| 251 | and try adding it if it is not. I'd also be grateful if you reported the file 252 | type by opening a support issue (or even better, a PR) at 253 | 254 | https://github.com/zhimsel/vim-stay/issues 255 | 256 | 257 | VIM-STAY SAYS IT CANNOT READ / WRITE THE VIEW FILE 258 | 259 | You might have opened the file triggering this with elevated privileges before 260 | (e.g. using `sudo` on Unix). This will result in the view session file created 261 | by vim-stay having elevated privileges too, thus not being writable at a lower 262 | privilege level. Try cleaning your 'viewdir' with a suitable OS tool operating 263 | at the necessary privilege level. 264 | 265 | 266 | MY VIEW DIRECTORY IS A FESTERING MESS 267 | 268 | That is a consequence of Vim's view session design. To quote |loadview|: 269 | "You might want to clean up your 'viewdir' directory now and then." 270 | Use the |:CleanViewdir| command to do exactly that. 271 | 272 | 273 | MY PROBLEM ISN'T LISTED HERE 274 | 275 | You might have found a bug. Please open an issue at 276 | 277 | https://github.com/zhimsel/vim-stay/issues 278 | 279 | Please do not forget to list the steps to reproduce the issue as well as your 280 | Vim version and platform. 281 | 282 | ============================================================================== 283 | 7. Credits and License *vim-stay-credits-license* 284 | 285 | vim-stay was originally created by Martin Kopischke, http://martin.kopischke.net, 286 | and is now maintained by Zach Himsel, http://zach.himsel.net. 287 | 288 | It is licensed under the terms of the MIT license according to the accompanying 289 | license file (LICENSE.md). It is inspired by, but not based on, `restore_view.vim` 290 | by Zhou Yi Chao (http://www.vim.org/scripts/script.php?script_id=4021). 291 | 292 | vim:tw=78:ts=8:ft=help:norl:noet:fen:fdl=0:fdm=marker: 293 | -------------------------------------------------------------------------------- /plugin/stay.vim: -------------------------------------------------------------------------------- 1 | " A LESS SIMPLISTIC TAKE ON RESTORE_VIEW.VIM 2 | " Maintainer: Zach Himsel 3 | " License: MIT (see LICENSE.md) 4 | if &compatible || !has('autocmd') || !has('mksession') || v:version < 700 5 | finish 6 | endif 7 | 8 | let s:cpoptions = &cpoptions 9 | set cpoptions&vim 10 | 11 | " PLUG-IN CONFIGURATION {{{ 12 | " Defaults: 13 | let s:defaults = {} 14 | " - bona fide file types that should never be persisted 15 | let s:defaults.volatile_ftypes = [ 16 | \ 'gitcommit', 'gitrebase', 'gitsendmail', 17 | \ 'hgcommit', 'hgcommitmsg', 'hgstatus', 'hglog', 'hglog-changelog', 'hglog-compact', 18 | \ 'svn', 'cvs', 'cvsrc', 'bzr', 19 | \ ] 20 | " - verbosity of echomsg error / status reporting 21 | " -1 no messages 22 | " 0 important error messages (default) 23 | " 1 all status and error messages 24 | let s:defaults.stay_verbosity = 0 25 | 26 | " Loaded 3rd party integrations: 27 | let s:integrations = [] " }}} 28 | 29 | " PLUG-IN MACHINERY {{{ 30 | " Echo v:errmsg if {level} is below g:stay_verbosity. 31 | " Makes sure we do not echo messages that are not vim-stay errors: 32 | function! s:HandleErrMsg(level) abort 33 | if a:level < min([1, g:stay_verbosity]) 34 | echomsg v:errmsg 35 | endif 36 | endfunction 37 | 38 | " Conditionally create a view session file for {bufnr} in {winid}: 39 | function! s:MakeView(stage, bufnr, winid) abort 40 | " do not create a view session if a call with a lower {stage} number 41 | " did so recently (currently hardwired to 1 second or less ago) 42 | let l:state = stay#getbufstate(a:bufnr) 43 | let l:left = get(l:state, 'left', {}) 44 | if a:stage > 1 && !empty(l:left) && localtime() - get(l:left, a:stage-1, 0) <= 1 45 | return 0 46 | endif 47 | 48 | if pumvisible() || 49 | \ !stay#isviewwin(a:winid) || 50 | \ !stay#ispersistent(a:bufnr, g:volatile_ftypes) 51 | return 0 52 | endif 53 | 54 | let l:done = stay#view#make(a:winid) 55 | call s:HandleErrMsg(l:done) 56 | if l:done is 1 57 | let l:state.left = extend(l:left, {string(a:stage): localtime()}) 58 | endif 59 | return l:done 60 | endfunction 61 | 62 | " Conditionally load view session file for {bufnr} in {winid}: 63 | function! s:LoadView(bufnr, winid) abort 64 | if exists('g:SessionLoad') || 65 | \ pumvisible() || 66 | \ !stay#isviewwin(a:winid) || 67 | \ !stay#ispersistent(a:bufnr, g:volatile_ftypes) 68 | return 0 69 | endif 70 | 71 | let l:done = stay#view#load(a:winid) 72 | call s:HandleErrMsg(l:done) 73 | return l:done 74 | endfunction 75 | 76 | " Set up global configuration, autocommands, commands: 77 | function! s:Setup(force) abort 78 | " core functionality (skipped unless {force} is 1) 79 | if a:force is 1 80 | " - make defaults available as individual global variables 81 | for [l:key, l:val] in items(s:defaults) 82 | let g:{l:key} = l:val 83 | unlet! l:val 84 | endfor 85 | 86 | " - autocommands 87 | augroup stay 88 | autocmd! 89 | " ensure a newly visible buffer loads its view 90 | autocmd BufWinEnter ?* nested 91 | \ call s:LoadView(str2nr(expand('')), stay#win#getid(winnr())) 92 | " make sure the view is always in sync with window state 93 | autocmd WinLeave ?* nested 94 | \ call s:MakeView(2, str2nr(expand('')), stay#win#getid(winnr())) 95 | " catch hiding of buffers and quitting 96 | autocmd BufWinLeave ?* nested 97 | \ call s:MakeView(3, str2nr(expand('')), stay#win#getid(winnr())) 98 | " catch saving and renaming of buffers 99 | autocmd BufFilePost,BufWritePost ?* nested 100 | \ call s:MakeView(1, str2nr(expand('')), stay#win#getid(winnr())) 101 | augroup END 102 | 103 | " - ex commands 104 | command! -bar -bang -nargs=? CleanViewdir 105 | \ call stay#viewdir#clean(expand('') is '!', ) 106 | command! -bar -bang -nargs=0 StayReload 107 | \ call Setup(expand('') is '!') 108 | endif 109 | 110 | " load 3rd party integrations (new ones only unless {force} is 1) 111 | augroup stay_integrate 112 | if a:force is 1 113 | autocmd! 114 | let s:integrations = [] 115 | endif 116 | for l:file in stay#shim#globpath(&rtp, 'autoload/stay/integrate/*.vim', 1, 1) 117 | let l:name = fnamemodify(l:file, ':t:r') 118 | if index(s:integrations, l:name) is -1 119 | try 120 | call call('stay#integrate#'.l:name.'#setup', []) 121 | catch /E117/ " no setup function found 122 | let v:errmsg = "No vim-stay integration setup function found for ".l:name 123 | call s:HandleErrMsg(0) 124 | continue 125 | catch " integration setup execution errors 126 | let v:errmsg = "Skipped vim-stay integration for ".l:name." due to error: ".v:errmsg 127 | call s:HandleErrMsg(-1) 128 | continue 129 | endtry 130 | call add(s:integrations, l:name) 131 | endif 132 | endfor 133 | augroup END 134 | endfunction " }}} 135 | 136 | " PLUG-IN SETUP 137 | call s:Setup(1) 138 | 139 | let &cpoptions = s:cpoptions 140 | unlet! s:cpoptions 141 | 142 | " vim:set sw=2 sts=2 ts=2 et fdm=marker fmr={{{,}}}: 143 | --------------------------------------------------------------------------------