" ingo/buffer/scratch.vim: Functions for creating scratch buffers. " " DEPENDENCIES: " - ingo/compat.vim autoload script " - ingo/escape/file.vim autoload script " - ingo/fs/path.vim autoload script " " Copyright: (C) 2009-2015 Ingo Karkat " The VIM LICENSE applies to this script; see ':help copyright'. " " Maintainer: Ingo Karkat " " REVISION DATE REMARKS " 1.024.022 15-Apr-2015 BUG: ingo#buffer#scratch#Create() with existing " scratch buffer yields "E95: Buffer with this " name already exists" instead of reusing the " buffer. Use new a:isFile flag to " ingo#escape#file#bufnameescape() and set " a:isFullMatch to 1 instead of emulating the " full-match for non-existing scratch buffers. " Keep current cursor position when " ingo#buffer#scratch#Create() removes the first " empty line in the scratch buffer. " 1.012.021 08-Aug-2013 Move escapings.vim into ingo-library. " 1.010.020 08-Jul-2013 Move into ingo-library. " 019 11-Jun-2013 Move ingobuffer#ExecuteIn...() and " ingobuffer#CallIn...() to ingo/buffer/temp.vim " and ingo/buffer/visible.vim. " 018 01-Jun-2013 Move ingofile.vim into ingo-library. " 017 19-Feb-2013 Factor out ingobuffer#SetScratchBuffer(). " 016 18-Feb-2013 Add ingobuffer#GetUnusedBracketedFilename(). " 015 18-May-2012 Move ingobuffer#CombineToFilespec() and " ingobuffer#MakeTempfile() to ingofile.vim " autoload script. " 014 26-Mar-2012 Add ingobuffer#IsEmptyBuffer(), copied from " ingotemplates.vim. " 013 26-Oct-2011 Also switch algorithm for " ingobuffer#ExecuteInVisibleBuffer(), because " :hide may destroy the current buffer when " 'bufhidden' is set. (This happened in the blame " buffer of vcscommand.vim). " 012 03-Oct-2011 Switch algorithm for " ingobuffer#ExecuteInTempBuffer() from switching " buffers to new split buffer, since the former " had a noticable delay when in a long Vimscript " file, due to re-sync of syntax highlighting. " 011 01-Oct-2011 Factor out more generic " ingobuffer#NextBracketedFilename(). " 010 27-Sep-2011 Add ingobuffer#ExecuteInTempBuffer(), and " ingobuffer#CallInTempBuffer(). " Also implement ingobuffer#CallInVisibleBuffer() " in the same style. " 009 09-Jul-2011 Have somehow written ingobuffer#MakeTempfile() " without knowledge of the built-in tempname(). " Now use that as the primary source of a temp " directory, and only use the other locations as " (probably unnecessary) fallbacks. " 008 12-Apr-2011 Add ingobuffer#ExecuteInVisibleBuffer() for " :AutoSave command. " 007 31-Mar-2011 ingobuffer#MakeScratchBuffer() only deletes the " first line in the scratch buffer if it is " actually empty. " FIX: Need to check the buftype also when a " window is visible that shows a buffer with the " scratch filename. Otherwise, a buffer containing " a normal file may be re-used as a scratch " buffer. " Also allow scratch buffer names like " "[Messages]", not just "Messages [Scratch]" in " ingobuffer#NextScratchFilename(). " Minor: 'buftype' can only contain one particular " word, change regexp-match to exact match. " 006 17-Jan-2011 Added $TMPDIR to ingobuffer#MakeTempfile(). " 005 02-Mar-2010 ENH: ingobuffer#CombineToFilespec() allows " multiple filenames and passing in a single list " of filespec fragments. Improved detection of " desired path separator and falling back to " system default based on 'shellslash' setting. " 004 15-Oct-2009 ENH: ingobuffer#MakeScratchBuffer() now allows " to omit (via empty string) the a:scratchCommand " Ex command, and will then keep the scratch " buffer writable. " 003 04-Sep-2009 ENH: If a:scratchIsFile is false and " a:scratchDirspec is empty, there will be only " one scratch buffer with the same " a:scratchFilename, regardless of the scratch " buffer's directory path. This also fixes Vim " errors on the :file command when s:Bufnr() has " determined that there is no existing buffer, " when in fact there is. " Replaced ':normal ...dd' with :delete, and not " clobbering the unnamed register any more. " 002 01-Sep-2009 Added ingobuffer#MakeTempfile(). " 001 05-Jan-2009 file creation function! ingo#buffer#scratch#NextBracketedFilename( filespec, template ) "****************************************************************************** "* PURPOSE: " Based on the current format of a:filespec, return a successor according to " a:template. The sequence is: " 1. name [template] " 2. name [template1] " 3. name [template2] " 4. ... " The "name" part may be omitted. " This does not check for actual occurrences in loaded buffers, etc.; it just " performs text manipulation! "* ASSUMPTIONS / PRECONDITIONS: " None. "* EFFECTS / POSTCONDITIONS: " None. "* INPUTS: " a:filespec Filename on which to base the result. " a:template Identifier to be used inside the bracketed counted addendum. "* RETURN VALUES: " filename "****************************************************************************** let l:templateExpr = '\V\C'. escape(a:template, '\') . '\m' if a:filespec !~# '\%(^\| \)\[' . l:templateExpr . ' \?\d*\]$' return a:filespec . (empty(a:filespec) ? '' : ' ') . '['. a:template . ']' elseif a:filespec !~# '\%(^\| \)\[' . l:templateExpr . ' \?\d\+\]$' return substitute(a:filespec, '\]$', '1]', '') else let l:number = matchstr(a:filespec, '\%(^\| \)\[' . l:templateExpr . ' \?\zs\d\+\ze\]$') return substitute(a:filespec, '\d\+\]$', (l:number + 1) . ']', '') endif endfunction function! ingo#buffer#scratch#NextFilename( filespec ) return ingo#buffer#scratch#NextBracketedFilename(a:filespec, 'Scratch') endfunction function! s:Bufnr( dirspec, filename, isFile ) if empty(a:dirspec) && ! a:isFile " This scratch buffer does not behave like a file and is not tethered to " a particular directory; there should be only one scratch buffer with " this name in the Vim session. " Do a partial search for the buffer name matching any file name in any " directory. return bufnr(ingo#escape#file#bufnameescape(a:filename, 1, 0)) else return bufnr( \ ingo#escape#file#bufnameescape( \ fnamemodify( \ ingo#fs#path#Combine(a:dirspec, a:filename), \ '%:p' \ ) \ ) \) endif endfunction function! ingo#buffer#scratch#GetUnusedBracketedFilename( dirspec, baseFilename, isFile, template ) "****************************************************************************** "* PURPOSE: " Determine the next available bracketed filename that does not exist as a Vim " buffer yet. "* ASSUMPTIONS / PRECONDITIONS: " ? List of any external variable, control, or other element whose state affects this procedure. "* EFFECTS / POSTCONDITIONS: " ? List of the procedure's effect on each external variable, control, or other element. "* INPUTS: " a:dirspec Working directory for the buffer. Pass empty string to maintain " the current CWD as-is. " a:baseFilename Filename to base the bracketed filename on; can be empty if " you don't want any prefix before the brackets. " a:isFile Flag whether the buffer should behave like a file (i.e. adapt to " changes in the global CWD), or not. If false and a:dirspec is " empty, there will be only one buffer with the same filename, " regardless of the buffer's directory path. " a:template Identifier to be used inside the bracketed counted addendum. "* RETURN VALUES: " filename "****************************************************************************** let l:bracketedFilename = a:baseFilename while 1 let l:bracketedFilename = ingo#buffer#scratch#NextBracketedFilename(l:bracketedFilename, a:template) if s:Bufnr(a:dirspec, l:bracketedFilename, a:isFile) == -1 return l:bracketedFilename endif endwhile endfunction function! s:ChangeDir( dirspec ) if empty( a:dirspec ) return endif execute 'lchdir' ingo#compat#fnameescape(a:dirspec) endfunction function! s:BufType( scratchIsFile ) return (a:scratchIsFile ? 'nowrite' : 'nofile') endfunction function! ingo#buffer#scratch#Create( scratchDirspec, scratchFilename, scratchIsFile, scratchCommand, windowOpenCommand ) "******************************************************************************* "* PURPOSE: " Create (or re-use an existing) scratch buffer (i.e. doesn't correspond to a " file on disk, but can be saved as such). " To keep the scratch buffer (and create a new scratch buffer on the next " invocation), rename the current scratch buffer via ':file ', or " make it a normal buffer via ':setl buftype='. " "* ASSUMPTIONS / PRECONDITIONS: " None. "* EFFECTS / POSTCONDITIONS: " Creates or opens scratch buffer and loads it in a window (as specified by " a:windowOpenCommand) and activates that window. "* INPUTS: " a:scratchDirspec Local working directory for the scratch buffer " (important for :! scratch commands). Pass empty string " to maintain the current CWD as-is. Pass '.' to maintain " the CWD but also fix it via :lcd. " (Attention: ':set autochdir' will reset any CWD once the " current window is left!) Pass the getcwd() output if " maintaining the current CWD is important for " a:scratchCommand. " a:scratchFilename The name for the scratch buffer, so it can be saved via " either :w! or :w . " a:scratchIsFile Flag whether the scratch buffer should behave like a " file (i.e. adapt to changes in the global CWD), or not. " If false and a:scratchDirspec is empty, there will be " only one scratch buffer with the same a:scratchFilename, " regardless of the scratch buffer's directory path. " a:scratchCommand Ex command(s) to populate the scratch buffer, e.g. " ":1read myfile". Use :1read so that the first empty line " will be kept (it is deleted automatically), and there " will be no trailing empty line. " Pass empty string if you want to populate the scratch " buffer yourself. " a:windowOpenCommand Ex command to open the scratch window, e.g. :vnew or " :topleft new. "* RETURN VALUES: " Indicator whether the scratch buffer has been opened: " 0 Failed to open scratch buffer. " 1 Already in scratch buffer window. " 2 Jumped to open scratch buffer window. " 3 Loaded existing scratch buffer in new window. " 4 Created scratch buffer in new window. " Note: To handle errors caused by a:scratchCommand, you need to put this " method call into a try..catch block and :close the scratch buffer when an " exception is thrown "******************************************************************************* let l:currentWinNr = winnr() let l:status = 0 let l:scratchBufnr = s:Bufnr(a:scratchDirspec, a:scratchFilename, a:scratchIsFile) let l:scratchWinnr = bufwinnr(l:scratchBufnr) "****D echomsg '**** bufnr=' . l:scratchBufnr 'winnr=' . l:scratchWinnr if l:scratchWinnr == -1 if l:scratchBufnr == -1 execute a:windowOpenCommand " Note: The directory must already be changed here so that the :file " command can set the correct buffer filespec. call s:ChangeDir(a:scratchDirspec) execute 'silent keepalt file' ingo#compat#fnameescape(a:scratchFilename) let l:status = 4 elseif getbufvar(l:scratchBufnr, '&buftype') ==# s:BufType(a:scratchIsFile) execute a:windowOpenCommand execute l:scratchBufnr . 'buffer' let l:status = 3 else " A buffer with the scratch filespec is already loaded, but it " contains an existing file, not a scratch file. As we don't want to " jump to this existing file, try again with the next scratch " filename. return ingo#buffer#scratch#Create(a:scratchDirspec, ingo#buffer#scratch#NextFilename(a:scratchFilename), a:scratchIsFile, a:scratchCommand, a:windowOpenCommand) endif else if getbufvar(l:scratchBufnr, '&buftype') !=# s:BufType(a:scratchIsFile) " A window with the scratch filespec is already visible, but its " buffer contains an existing file, not a scratch file. As we don't " want to jump to this existing file, try again with the next " scratch filename. return ingo#buffer#scratch#Create(a:scratchDirspec, ingo#buffer#scratch#NextFilename(a:scratchFilename), a:scratchIsFile, a:scratchCommand, a:windowOpenCommand) elseif l:scratchWinnr == l:currentWinNr let l:status = 1 else execute l:scratchWinnr . 'wincmd w' let l:status = 2 endif endif call s:ChangeDir(a:scratchDirspec) setlocal noreadonly silent %delete _ " Note: ':silent' to suppress the "--No lines in buffer--" message. if ! empty(a:scratchCommand) execute a:scratchCommand " ^ Keeps the existing line at the top of the buffer, if :1{cmd} is used. " v Deletes it. if empty(getline(1)) let l:save_cursor = getpos('.') silent 1delete _ " Note: ':silent' to suppress deletion message if ':set report=0'. call cursor(l:save_cursor[1] - 1, l:save_cursor[2]) endif setlocal readonly endif call ingo#buffer#scratch#SetLocal(a:scratchIsFile) return l:status endfunction function! ingo#buffer#scratch#SetLocal( isFile ) execute 'setlocal buftype=' . s:BufType(a:isFile) setlocal bufhidden=wipe nobuflisted noswapfile endfunction " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :