Add support for managing temporary files/directories

This commit is contained in:
w0rp 2017-02-11 15:16:08 +00:00
parent 8ad85858b8
commit 88192e8662
5 changed files with 169 additions and 3 deletions

View File

@ -3,6 +3,8 @@
function! ale#cleanup#Buffer(buffer) abort function! ale#cleanup#Buffer(buffer) abort
if has_key(g:ale_buffer_info, a:buffer) if has_key(g:ale_buffer_info, a:buffer)
call ale#engine#RemoveManagedFiles(a:buffer)
" When buffers are removed, clear all of the jobs. " When buffers are removed, clear all of the jobs.
for l:job in get(g:ale_buffer_info[a:buffer], 'job_list', []) for l:job in get(g:ale_buffer_info[a:buffer], 'job_list', [])
call ale#engine#ClearJob(l:job) call ale#engine#ClearJob(l:job)

View File

@ -30,10 +30,14 @@ function! ale#engine#InitBufferInfo(buffer) abort
" job_list will hold the list of jobs " job_list will hold the list of jobs
" loclist holds the loclist items after all jobs have completed. " loclist holds the loclist items after all jobs have completed.
" new_loclist holds loclist items while jobs are being run. " new_loclist holds loclist items while jobs are being run.
" temporary_file_list holds temporary files to be cleaned up
" temporary_directory_list holds temporary directories to be cleaned up
let g:ale_buffer_info[a:buffer] = { let g:ale_buffer_info[a:buffer] = {
\ 'job_list': [], \ 'job_list': [],
\ 'loclist': [], \ 'loclist': [],
\ 'new_loclist': [], \ 'new_loclist': [],
\ 'temporary_file_list': [],
\ 'temporary_directory_list': [],
\} \}
endif endif
endfunction endfunction
@ -134,6 +138,40 @@ function! ale#engine#JoinNeovimOutput(output, data) abort
endif endif
endfunction endfunction
" Register a temporary file to be managed with the ALE engine for
" a current job run.
function! ale#engine#ManageFile(buffer, filename) abort
call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename)
endfunction
" Same as the above, but manage an entire directory.
function! ale#engine#ManageDirectory(buffer, directory) abort
call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory)
endfunction
function! ale#engine#RemoveManagedFiles(buffer) abort
if !has_key(g:ale_buffer_info, a:buffer)
return
endif
" Delete files with a call akin to a plan `rm` command.
for l:filename in g:ale_buffer_info[a:buffer].temporary_file_list
call delete(l:filename)
endfor
let g:ale_buffer_info[a:buffer].temporary_file_list = []
" Delete directories like `rm -rf`.
" Directories are handled differently from files, so paths that are
" intended to be single files can be set up for automatic deletion without
" accidentally deleting entire directories.
for l:directory in g:ale_buffer_info[a:buffer].temporary_directory_list
call delete(l:directory, 'rf')
endfor
let g:ale_buffer_info[a:buffer].temporary_directory_list = []
endfunction
function! s:HandleExit(job) abort function! s:HandleExit(job) abort
if a:job ==# 'no process' if a:job ==# 'no process'
" Stop right away when the job is not valid in Vim 8. " Stop right away when the job is not valid in Vim 8.
@ -178,6 +216,10 @@ function! s:HandleExit(job) abort
return return
endif endif
" Automatically remove all managed temporary files and directories
" now that all jobs have completed.
call ale#engine#RemoveManagedFiles(l:buffer)
" Sort the loclist again. " Sort the loclist again.
" We need a sorted list so we can run a binary search against it " We need a sorted list so we can run a binary search against it
" for efficient lookup of the messages in the cursor handler. " for efficient lookup of the messages in the cursor handler.
@ -424,6 +466,10 @@ function! s:InvokeChain(buffer, linter, chain_index, input) abort
if !empty(l:options) if !empty(l:options)
call s:RunJob(l:options) call s:RunJob(l:options)
elseif empty(g:ale_buffer_info[a:buffer].job_list)
" If we cancelled running a command, and we have no jobs in progress,
" then delete the managed temporary files now.
call ale#engine#RemoveManagedFiles(a:buffer)
endif endif
endfunction endfunction

View File

@ -1037,6 +1037,35 @@ ale#engine#GetLoclist(buffer) *ale#engine#GetLoclist()*
|setqflist()|. |setqflist()|.
ale#engine#ManageFile(buffer, filename) *ale#engine#ManageFile()*
Given a buffer number for a buffer currently running some linting tasks
and a filename, register a filename with ALE for automatic deletion after
linting is complete, or when Vim exits.
If Vim exits suddenly, ALE will try its best to remove temporary files, but
ALE cannot guarantee with absolute certainty that the files will be removed.
It is advised to create temporary files in the operating system's managed
temporary file directory, such as with |tempname()|.
Directory names should not be given to this function. ALE will only delete
files and symlinks given to this function. This is to prevent entire
directories from being accidentally deleted, say in cases of writing
`dir . '/' . filename` where `filename` is actually `''`, etc. ALE instead
manages directories separetly with the |ale#engine#ManageDirectory| function.
ale#engine#ManageDirectory(buffer, directory) *ale#engine#ManageDirectory()*
Like |ale#engine#ManageFile()|, but directories and all of their contents
will be deleted, akin to `rm -rf directory`, which could lead to loss of
data if mistakes are made. This command will also delete any temporary
filenames given to it.
It is advised to use |ale#engine#ManageFile()| instead for deleting single
files.
ale#linter#Define(filetype, linter) *ale#linter#Define()* ale#linter#Define(filetype, linter) *ale#linter#Define()*
Given a |String| for a filetype and a |Dictionary| Describing a linter Given a |String| for a filetype and a |Dictionary| Describing a linter
configuration, add a linter for the given filetype. The dictionaries each configuration, add a linter for the given filetype. The dictionaries each
@ -1151,6 +1180,12 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
`command_chain` is recommended where any system calls need to be made to `command_chain` is recommended where any system calls need to be made to
retrieve some kind of information before running the final command. retrieve some kind of information before running the final command.
If temporary files or directories are created for commands run with
`command_callback` or `command_chain`, then these tempoary files or
directories can be managed by ALE, for automatic deletion.
See |ale#engine#ManageFile()| and |ale#engine#ManageDirectory| for more
information.
Some programs for checking for errors are not capable of receiving input Some programs for checking for errors are not capable of receiving input
from stdin, as is required by ALE. To remedy this, a wrapper script is from stdin, as is required by ALE. To remedy this, a wrapper script is
provided named in the variable |g:ale#util#stdin_wrapper|. This variable provided named in the variable |g:ale#util#stdin_wrapper|. This variable

View File

@ -2,8 +2,8 @@ Before:
let g:buffer = bufnr('%') let g:buffer = bufnr('%')
let g:ale_buffer_info = { let g:ale_buffer_info = {
\ g:buffer : {}, \ g:buffer : {'temporary_file_list': [], 'temporary_directory_list': []},
\ 10347: {}, \ 10347: {'temporary_file_list': [], 'temporary_directory_list': []},
\} \}
After: After:
@ -12,4 +12,4 @@ After:
Execute('ALE globals should be cleared when the buffer is closed.'): Execute('ALE globals should be cleared when the buffer is closed.'):
:q! :q!
AssertEqual {10347: {}}, g:ale_buffer_info AssertEqual {10347: {'temporary_file_list': [], 'temporary_directory_list': []}}, g:ale_buffer_info

View File

@ -0,0 +1,83 @@
Before:
let g:command = 'echo test'
let g:filename = tempname()
let g:directory = tempname()
let g:preserved_directory = tempname()
function! TestCommandCallback(buffer) abort
" We are registering a temporary file, so we should delete it.
call writefile(['foo'], g:filename)
call ale#engine#ManageFile(a:buffer, g:filename)
" We are registering this directory appropriately, so we should delete
" the whole thing.
call mkdir(g:directory)
call writefile(['foo'], g:directory . '/bar')
call ale#engine#ManageDirectory(a:buffer, g:directory)
" We are registering this directory as temporary file, so we
" shouldn't delete it.
call mkdir(g:preserved_directory)
call writefile(['foo'], g:preserved_directory . '/bar')
call ale#engine#ManageFile(a:buffer, g:preserved_directory)
return g:command
endfunction
function! TestCallback(buffer, output) abort
return []
endfunction
call ale#linter#Define('foobar', {
\ 'name': 'testlinter',
\ 'executable': 'echo',
\ 'callback': 'TestCallback',
\ 'command_callback': 'TestCommandCallback',
\})
After:
call delete(g:preserved_directory, 'rf')
unlet! g:command
unlet! g:filename
unlet! g:directory
unlet! g:preserved_directory
delfunction TestCommandCallback
delfunction TestCallback
call ale#linter#Reset()
Given foobar (Some imaginary filetype):
foo
bar
baz
Execute(ALE should delete managed files/directories appropriately after linting):
AssertEqual 'foobar', &filetype
call ale#Lint()
call ale#engine#WaitForJobs(2000)
Assert !filereadable(g:filename), 'The tempoary file was not deleted'
Assert !isdirectory(g:directory), 'The tempoary directory was not deleted'
Assert isdirectory(g:preserved_directory), 'The tempoary directory was not kept'
Execute(ALE should delete managed files even if no command is run):
AssertEqual 'foobar', &filetype
let g:command = ''
call ale#Lint()
call ale#engine#WaitForJobs(2000)
Assert !filereadable(g:filename), 'The tempoary file was not deleted'
Assert !isdirectory(g:directory), 'The tempoary directory was not deleted'
Assert isdirectory(g:preserved_directory), 'The tempoary directory was not kept'
Execute(ALE should delete managed files when the buffer is removed):
call ale#engine#InitBufferInfo(bufnr('%'))
call TestCommandCallback(bufnr('%'))
call ale#cleanup#Buffer(bufnr('%'))
Assert !filereadable(g:filename), 'The tempoary file was not deleted'
Assert !isdirectory(g:directory), 'The tempoary directory was not deleted'
Assert isdirectory(g:preserved_directory), 'The tempoary directory was not kept'