summaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/ale/assert.vim13
-rw-r--r--autoload/ale/command.vim135
-rw-r--r--autoload/ale/debugging.vim1
-rw-r--r--autoload/ale/engine.vim94
-rw-r--r--autoload/ale/fix.vim70
-rw-r--r--autoload/ale/handlers/sh.vim2
-rw-r--r--autoload/ale/lsp_linter.vim37
7 files changed, 207 insertions, 145 deletions
diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim
index ed08ed09..7139f05c 100644
--- a/autoload/ale/assert.vim
+++ b/autoload/ale/assert.vim
@@ -109,6 +109,14 @@ function! ale#assert#LSPProject(expected_root) abort
AssertEqual a:expected_root, l:root
endfunction
+function! ale#assert#LSPProjectFull(expected_root) abort
+ let l:buffer = bufnr('')
+ let l:linter = s:GetLinter()
+ let l:root = ale#lsp_linter#FindProjectRoot(l:buffer, l:linter)
+
+ AssertEqual a:expected_root, l:root
+endfunction
+
function! ale#assert#LSPAddress(expected_address) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
@@ -158,6 +166,7 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>)
command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>)
command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>)
+ command! -nargs=+ AssertLSPProjectFull :call ale#assert#LSPProjectFull(<args>)
command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>)
endfunction
@@ -193,6 +202,10 @@ function! ale#assert#TearDownLinterTest() abort
delcommand AssertLSPProject
endif
+ if exists(':AssertLSPProjectFull')
+ delcommand AssertLSPProjectFull
+ endif
+
if exists(':AssertLSPAddress')
delcommand AssertLSPAddress
endif
diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim
index 6c56d518..6944f170 100644
--- a/autoload/ale/command.vim
+++ b/autoload/ale/command.vim
@@ -1,6 +1,121 @@
" Author: w0rp <devw0rp@gmail.com>
-" Description: Special command formatting for creating temporary files and
-" passing buffer filenames easily.
+" Description: Functions for formatting command strings, running commands, and
+" managing files during linting and fixing cycles.
+
+" This dictionary holds lists of files and directories to remove later.
+if !exists('s:managed_data')
+ let s:managed_data = {}
+endif
+
+" Used to get the data in tests.
+function! ale#command#GetData() abort
+ return deepcopy(s:managed_data)
+endfunction
+
+function! ale#command#ClearData() abort
+ let s:managed_data = {}
+endfunction
+
+function! ale#command#ManageFile(buffer, file) abort
+ if !has_key(s:managed_data, a:buffer)
+ let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
+ endif
+
+ call add(s:managed_data[a:buffer].file_list, a:file)
+endfunction
+
+function! ale#command#ManageDirectory(buffer, directory) abort
+ if !has_key(s:managed_data, a:buffer)
+ let s:managed_data[a:buffer] = {'file_list': [], 'directory_list': []}
+ endif
+
+ call add(s:managed_data[a:buffer].directory_list, a:directory)
+endfunction
+
+function! ale#command#CreateFile(buffer) abort
+ " This variable can be set to 1 in tests to stub this out.
+ if get(g:, 'ale_create_dummy_temporary_file')
+ return 'TEMP'
+ endif
+
+ let l:temporary_file = ale#util#Tempname()
+ call ale#command#ManageFile(a:buffer, l:temporary_file)
+
+ return l:temporary_file
+endfunction
+
+" Create a new temporary directory and manage it in one go.
+function! ale#command#CreateDirectory(buffer) abort
+ " This variable can be set to 1 in tests to stub this out.
+ if get(g:, 'ale_create_dummy_temporary_file')
+ return 'TEMP_DIR'
+ endif
+
+ let l:temporary_directory = ale#util#Tempname()
+ " Create the temporary directory for the file, unreadable by 'other'
+ " users.
+ call mkdir(l:temporary_directory, '', 0750)
+ call ale#command#ManageDirectory(a:buffer, l:temporary_directory)
+
+ return l:temporary_directory
+endfunction
+
+function! ale#command#RemoveManagedFiles(buffer) abort
+ let l:info = get(s:managed_data, a:buffer, {})
+
+ if !empty(l:info)
+ \&& (
+ \ !exists('*ale#engine#IsCheckingBuffer')
+ \ || !ale#engine#IsCheckingBuffer(a:buffer)
+ \)
+ \&& (
+ \ !has_key(g:ale_fix_buffer_data, a:buffer)
+ \ || g:ale_fix_buffer_data[a:buffer].done
+ \)
+ " We can't delete anything in a sandbox, so wait until we escape from
+ " it to delete temporary files and directories.
+ if ale#util#InSandbox()
+ return
+ endif
+
+ " Delete files with a call akin to a plan `rm` command.
+ for l:filename in l:info.file_list
+ call delete(l:filename)
+ endfor
+
+ " 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 l:info.directory_list
+ call delete(l:directory, 'rf')
+ endfor
+
+ call remove(s:managed_data, a:buffer)
+ endif
+endfunction
+
+function! ale#command#CreateTempFile(buffer, temporary_file, input) abort
+ if empty(a:temporary_file)
+ " There is no file, so we didn't create anything.
+ return 0
+ endif
+
+ " Use an existing list of lines of input if we have it, or get the lines
+ " from the file.
+ let l:lines = a:input isnot v:null ? a:input : getbufline(a:buffer, 1, '$')
+
+ let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
+ " Create the temporary directory for the file, unreadable by 'other'
+ " users.
+ call mkdir(l:temporary_directory, '', 0750)
+ " Automatically delete the directory later.
+ call ale#command#ManageDirectory(a:buffer, l:temporary_directory)
+ " Write the buffer out to a file.
+ call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)
+
+ return 1
+endfunction
function! s:TemporaryFilename(buffer) abort
let l:filename = fnamemodify(bufname(a:buffer), ':t')
@@ -16,11 +131,17 @@ function! s:TemporaryFilename(buffer) abort
return ale#util#Tempname() . (has('win32') ? '\' : '/') . l:filename
endfunction
+" Given part of a command, replace any % with %%, so that no characters in
+" the string will be replaced with filenames, etc.
+function! ale#command#EscapeCommandPart(command_part) abort
+ return substitute(a:command_part, '%', '%%', 'g')
+endfunction
+
" Given a command string, replace every...
" %s -> with the current filename
" %t -> with the name of an unused file in a temporary directory
" %% -> with a literal %
-function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, CreateTemporaryFileForJob) abort
+function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed, input) abort
let l:temporary_file = ''
let l:command = a:command
@@ -40,7 +161,7 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
let l:command = substitute(l:command, '%s', '\=ale#Escape(l:filename)', 'g')
endif
- if l:command =~# '%t'
+ if a:input isnot v:false && l:command =~# '%t'
" Create a temporary filename, <temp_dir>/<original_basename>
" The file itself will not be created by this function.
let l:temporary_file = s:TemporaryFilename(a:buffer)
@@ -58,7 +179,11 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne
let l:command = l:command . ' < ' . ale#Escape(l:temporary_file)
endif
- let l:file_created = a:CreateTemporaryFileForJob(a:buffer, l:temporary_file)
+ let l:file_created = ale#command#CreateTempFile(
+ \ a:buffer,
+ \ l:temporary_file,
+ \ a:input,
+ \)
return [l:temporary_file, l:command, l:file_created]
endfunction
diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim
index 3aed38fe..e4bf5e7e 100644
--- a/autoload/ale/debugging.vim
+++ b/autoload/ale/debugging.vim
@@ -31,6 +31,7 @@ let s:global_variable_list = [
\ 'ale_list_vertical',
\ 'ale_list_window_size',
\ 'ale_loclist_msg_format',
+\ 'ale_lsp_root',
\ 'ale_max_buffer_history_size',
\ 'ale_max_signs',
\ 'ale_maximum_file_size',
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index ace123dc..c3313512 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -74,15 +74,11 @@ function! ale#engine#InitBufferInfo(buffer) abort
" job_list will hold the list of job IDs
" active_linter_list will hold the list of active linter names
" loclist holds the loclist items after all jobs have completed.
- " 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] = {
\ 'job_list': [],
\ 'active_linter_list': [],
\ 'active_other_sources_list': [],
\ 'loclist': [],
- \ 'temporary_file_list': [],
- \ 'temporary_directory_list': [],
\}
return 1
@@ -104,73 +100,25 @@ 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 ale#engine#InitBufferInfo(a:buffer)
- call add(g:ale_buffer_info[a:buffer].temporary_file_list, a:filename)
+ " TODO: Emit deprecation warning here later.
+ call ale#command#ManageFile(a:buffer, a:filename)
endfunction
" Same as the above, but manage an entire directory.
function! ale#engine#ManageDirectory(buffer, directory) abort
- call ale#engine#InitBufferInfo(a:buffer)
- call add(g:ale_buffer_info[a:buffer].temporary_directory_list, a:directory)
+ " TODO: Emit deprecation warning here later.
+ call ale#command#ManageDirectory(a:buffer, a:directory)
endfunction
function! ale#engine#CreateFile(buffer) abort
- " This variable can be set to 1 in tests to stub this out.
- if get(g:, 'ale_create_dummy_temporary_file')
- return 'TEMP'
- endif
-
- let l:temporary_file = ale#util#Tempname()
- call ale#engine#ManageFile(a:buffer, l:temporary_file)
-
- return l:temporary_file
+ " TODO: Emit deprecation warning here later.
+ return ale#command#CreateFile(a:buffer)
endfunction
" Create a new temporary directory and manage it in one go.
function! ale#engine#CreateDirectory(buffer) abort
- " This variable can be set to 1 in tests to stub this out.
- if get(g:, 'ale_create_dummy_temporary_file')
- return 'TEMP_DIR'
- endif
-
- let l:temporary_directory = ale#util#Tempname()
- " Create the temporary directory for the file, unreadable by 'other'
- " users.
- call mkdir(l:temporary_directory, '', 0750)
- call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
-
- return l:temporary_directory
-endfunction
-
-function! ale#engine#RemoveManagedFiles(buffer) abort
- let l:info = get(g:ale_buffer_info, a:buffer, {})
-
- " We can't delete anything in a sandbox, so wait until we escape from
- " it to delete temporary files and directories.
- if ale#util#InSandbox()
- return
- endif
-
- " Delete files with a call akin to a plan `rm` command.
- if has_key(l:info, 'temporary_file_list')
- for l:filename in l:info.temporary_file_list
- call delete(l:filename)
- endfor
-
- let l:info.temporary_file_list = []
- endif
-
- " 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.
- if has_key(l:info, 'temporary_directory_list')
- for l:directory in l:info.temporary_directory_list
- call delete(l:directory, 'rf')
- endfor
-
- let l:info.temporary_directory_list = []
- endif
+ " TODO: Emit deprecation warning here later.
+ return ale#command#CreateDirectory(a:buffer)
endfunction
function! s:GatherOutput(job_id, line) abort
@@ -321,7 +269,7 @@ function! ale#engine#SetResults(buffer, loclist) abort
" Automatically remove all managed temporary files and directories
" now that all jobs have completed.
- call ale#engine#RemoveManagedFiles(a:buffer)
+ call ale#command#RemoveManagedFiles(a:buffer)
" Call user autocommands. This allows users to hook into ALE's lint cycle.
silent doautocmd <nomodeline> User ALELintPost
@@ -472,26 +420,8 @@ endfunction
" Given part of a command, replace any % with %%, so that no characters in
" the string will be replaced with filenames, etc.
function! ale#engine#EscapeCommandPart(command_part) abort
- return substitute(a:command_part, '%', '%%', 'g')
-endfunction
-
-function! s:CreateTemporaryFileForJob(buffer, temporary_file) abort
- if empty(a:temporary_file)
- " There is no file, so we didn't create anything.
- return 0
- endif
-
- let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
- " Create the temporary directory for the file, unreadable by 'other'
- " users.
- call mkdir(l:temporary_directory, '', 0750)
- " Automatically delete the directory later.
- call ale#engine#ManageDirectory(a:buffer, l:temporary_directory)
- " Write the buffer out to a file.
- let l:lines = getbufline(a:buffer, 1, '$')
- call ale#util#Writefile(a:buffer, l:lines, a:temporary_file)
-
- return 1
+ " TODO: Emit deprecation warning here later.
+ return ale#command#EscapeCommandPart(a:command_part)
endfunction
" Run a job.
@@ -517,7 +447,7 @@ function! s:RunJob(options) abort
\ l:executable,
\ l:command,
\ l:read_buffer,
- \ function('s:CreateTemporaryFileForJob'),
+ \ v:null,
\)
if l:file_created
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index 59502933..b13878eb 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -66,11 +66,17 @@ function! ale#fix#ApplyQueuedFixes() abort
endfunction
function! ale#fix#ApplyFixes(buffer, output) abort
- call ale#fix#RemoveManagedFiles(a:buffer)
-
let l:data = g:ale_fix_buffer_data[a:buffer]
let l:data.output = a:output
let l:data.changes_made = l:data.lines_before != l:data.output
+ let l:data.done = 1
+
+ call ale#command#RemoveManagedFiles(a:buffer)
+
+ if !bufexists(a:buffer)
+ " Remove the buffer data when it doesn't exist.
+ call remove(g:ale_fix_buffer_data, a:buffer)
+ endif
if l:data.changes_made && bufexists(a:buffer)
let l:lines = getbufline(a:buffer, 1, '$')
@@ -83,13 +89,6 @@ function! ale#fix#ApplyFixes(buffer, output) abort
endif
endif
- if !bufexists(a:buffer)
- " Remove the buffer data when it doesn't exist.
- call remove(g:ale_fix_buffer_data, a:buffer)
- endif
-
- let l:data.done = 1
-
" We can only change the lines of a buffer which is currently open,
" so try and apply the fixes to the current buffer.
call ale#fix#ApplyQueuedFixes()
@@ -146,50 +145,6 @@ function! s:HandleExit(job_id, exit_code) abort
\})
endfunction
-function! ale#fix#ManageDirectory(buffer, directory) abort
- call add(g:ale_fix_buffer_data[a:buffer].temporary_directory_list, a:directory)
-endfunction
-
-function! ale#fix#RemoveManagedFiles(buffer) abort
- if !has_key(g:ale_fix_buffer_data, a:buffer)
- return
- endif
-
- " We can't delete anything in a sandbox, so wait until we escape from
- " it to delete temporary files and directories.
- if ale#util#InSandbox()
- return
- endif
-
- " 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_fix_buffer_data[a:buffer].temporary_directory_list
- call delete(l:directory, 'rf')
- endfor
-
- let g:ale_fix_buffer_data[a:buffer].temporary_directory_list = []
-endfunction
-
-function! s:CreateTemporaryFileForJob(input, buffer, temporary_file) abort
- if empty(a:temporary_file)
- " There is no file, so we didn't create anything.
- return 0
- endif
-
- let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
- " Create the temporary directory for the file, unreadable by 'other'
- " users.
- call mkdir(l:temporary_directory, '', 0750)
- " Automatically delete the directory later.
- call ale#fix#ManageDirectory(a:buffer, l:temporary_directory)
- " Write the buffer out to a file.
- call ale#util#Writefile(a:buffer, a:input, a:temporary_file)
-
- return 1
-endfunction
-
function! s:RunJob(options) abort
let l:buffer = a:options.buffer
let l:command = a:options.command
@@ -223,7 +178,7 @@ function! s:RunJob(options) abort
\ '',
\ l:command,
\ l:read_buffer,
- \ function('s:CreateTemporaryFileForJob', [l:input]),
+ \ l:input,
\)
let l:command = ale#job#PrepareCommand(l:buffer, l:command)
@@ -469,8 +424,13 @@ function! ale#fix#Fix(buffer, fixing_flag, ...) abort
call ale#job#Stop(l:job_id)
endfor
+ " Mark the buffer as `done` so files can be removed.
+ if has_key(g:ale_fix_buffer_data, a:buffer)
+ let g:ale_fix_buffer_data[a:buffer].done = 1
+ endif
+
" Clean up any files we might have left behind from a previous run.
- call ale#fix#RemoveManagedFiles(a:buffer)
+ call ale#command#RemoveManagedFiles(a:buffer)
call ale#fix#InitBufferData(a:buffer, a:fixing_flag)
silent doautocmd <nomodeline> User ALEFixPre
diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim
index e96dd3ce..75eaf71f 100644
--- a/autoload/ale/handlers/sh.vim
+++ b/autoload/ale/handlers/sh.vim
@@ -9,7 +9,7 @@ function! ale#handlers#sh#GetShellType(buffer) abort
" Remove options like -e, etc.
let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
- for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'sh']
+ for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh']
if l:command =~# l:possible_shell . '\s*$'
return l:possible_shell
endif
diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim
index d92dae7e..b029d833 100644
--- a/autoload/ale/lsp_linter.vim
+++ b/autoload/ale/lsp_linter.vim
@@ -152,12 +152,45 @@ function! ale#lsp_linter#GetConfig(buffer, linter) abort
return l:config
endfunction
+function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort
+ let l:buffer_ale_root = getbufvar(a:buffer, 'ale_lsp_root', {})
+
+ if type(l:buffer_ale_root) is v:t_string
+ return l:buffer_ale_root
+ endif
+
+ " Try to get a buffer-local setting for the root
+ if has_key(l:buffer_ale_root, a:linter.name)
+ let l:Root = l:buffer_ale_root[a:linter.name]
+
+ if type(l:Root) is v:t_func
+ return l:Root(a:buffer)
+ else
+ return l:Root
+ endif
+ endif
+
+ " Try to get a global setting for the root
+ if has_key(g:ale_lsp_root, a:linter.name)
+ let l:Root = g:ale_lsp_root[a:linter.name]
+
+ if type(l:Root) is v:t_func
+ return l:Root(a:buffer)
+ else
+ return l:Root
+ endif
+ endif
+
+ " Fall back to the linter-specific configuration
+ return ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
+endfunction
+
" Given a buffer, an LSP linter, start up an LSP linter and get ready to
" receive messages for the document.
function! ale#lsp_linter#StartLSP(buffer, linter) abort
let l:command = ''
let l:address = ''
- let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
+ let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, a:linter)
if empty(l:root) && a:linter.lsp isnot# 'tsserver'
" If there's no project root, then we can't check files with LSP,
@@ -182,7 +215,7 @@ function! ale#lsp_linter#StartLSP(buffer, linter) abort
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
" Format the command, so %e can be formatted into it.
- let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0, {-> 0})[1]
+ let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0, v:false)[1]
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
let l:ready = ale#lsp#StartProgram(l:conn_id, l:executable, l:command)
endif