Files

227 lines
9.3 KiB
VimL

" ingo/buffer/generate.vim: Functions for creating buffers.
"
" DEPENDENCIES:
"
" Copyright: (C) 2009-2022 Ingo Karkat
" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
function! ingo#buffer#generate#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! s:Bufnr( dirspec, filename, isFile )
if empty(a:dirspec) && ! a:isFile
" This buffer does not behave like a file and is not tethered to a
" particular directory; there should be only one 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#generate#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#generate#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! ingo#buffer#generate#BufType( isFile )
return (a:isFile ? 'nowrite' : 'nofile')
endfunction
function! ingo#buffer#generate#Create( dirspec, filename, isFile, ContentsCommand, windowOpenCommand, NextFilenameFuncref )
"*******************************************************************************
"* PURPOSE:
" Create (or re-use an existing) buffer (i.e. doesn't correspond to a file on
" disk, but can be saved as such).
" To keep the buffer (and create a new buffer on the next invocation), rename
" the current buffer via ':file <newname>', or make it a normal buffer via
" ':setl buftype='.
"
"* ASSUMPTIONS / PRECONDITIONS:
" None.
"* EFFECTS / POSTCONDITIONS:
" Creates or opens buffer and loads it in a window (as specified by
" a:windowOpenCommand) and activates that window.
"* INPUTS:
" a:dirspec Local working directory for the buffer (important for :!
" 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:ContentsCommand.
" a:filename The name for the buffer, so it can be saved via either
" :w! or :w <newname>.
" 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 a:filename, regardless of the buffer's
" directory path.
" a:ContentsCommand Ex command(s) to populate the 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 buffer
" yourself.
" Pass a Funcref to build the buffer contents with it.
" Pass a List of lines to set the buffer contents directly
" to the lines.
" a:windowOpenCommand Ex command to open the window, e.g. "vnew" or
" "topleft new". Also supports "pedit".
" a:NextFilenameFuncref Funcref that is invoked (with a:filename) to
" generate file names for the generated buffer should
" the desired one (a:filename) already exist but not
" be a generated buffer.
"* RETURN VALUES:
" Indicator whether the buffer has been opened:
" 0 Failed to open buffer.
" 1 Already in buffer window.
" 2 Jumped to open buffer window.
" 3 Loaded existing buffer in new window.
" 4 Created buffer in new window.
" Note: To handle errors caused by a:ContentsCommand, you need to put this
" method call into a try..catch block and :bwipe the buffer when an exception
" is thrown.
"*******************************************************************************
let l:currentWinNr = winnr()
let l:status = 0
let l:bufnr = s:Bufnr(a:dirspec, a:filename, a:isFile)
let l:winnr = bufwinnr(l:bufnr)
"****D echomsg '**** bufnr=' . l:bufnr 'winnr=' . l:winnr
if l:winnr == -1
if l:bufnr == -1
let l:windowOpenCommand = (a:windowOpenCommand ==# 'pedit' ? 'call ingo#window#preview#OpenNew()' : a:windowOpenCommand)
execute l:windowOpenCommand
" Note: The directory must already be changed here so that the :file
" command can set the correct buffer filespec.
call s:ChangeDir(a:dirspec)
execute 'silent keepalt file' ingo#compat#fnameescape(a:filename)
let l:status = 4
elseif getbufvar(l:bufnr, '&buftype') ==# ingo#buffer#generate#BufType(a:isFile)
if a:windowOpenCommand ==# 'pedit'
call ingo#window#preview#OpenBuffer(l:bufnr)
else
execute a:windowOpenCommand
execute l:bufnr . 'buffer'
endif
let l:status = 3
else
" A buffer with the filespec is already loaded, but it contains an
" existing file, not a generated file. As we don't want to jump to
" this existing file, try again with the next filename.
return ingo#buffer#generate#Create(a:dirspec, call(a:NextFilenameFuncref, [a:filename]), a:isFile, a:ContentsCommand, a:windowOpenCommand, a:NextFilenameFuncref)
endif
else
if getbufvar(l:bufnr, '&buftype') !=# ingo#buffer#generate#BufType(a:isFile)
" A window with the filespec is already visible, but its buffer
" contains an existing file, not a generated file. As we don't want
" to jump to this existing file, try again with the next filename.
return ingo#buffer#generate#Create(a:dirspec, call(a:NextFilenameFuncref, [a:filename]), a:isFile, a:ContentsCommand, a:windowOpenCommand, a:NextFilenameFuncref)
elseif l:winnr == l:currentWinNr
let l:status = 1
else
execute l:winnr . 'wincmd w'
let l:status = 2
endif
endif
call s:ChangeDir(a:dirspec)
setlocal noreadonly
silent %delete _
" Note: ':silent' to suppress the "--No lines in buffer--" message.
if ! empty(a:ContentsCommand)
if type(a:ContentsCommand) == type([])
call setline(1, a:ContentsCommand)
call cursor(1, 1)
call ingo#change#Set([1, 1], [line('$'), 1])
elseif type(a:ContentsCommand) == type(function('tr'))
call call(a:ContentsCommand, [])
else
execute a:ContentsCommand
" ^ 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
endif
endif
return l:status
endfunction
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :