diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim index 558fe233..b4bf3794 100644 --- a/autoload/ale/command.vim +++ b/autoload/ale/command.vim @@ -20,7 +20,7 @@ endfunction " %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, command, pipe_file_if_needed) abort +function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_needed) abort let l:temporary_file = '' let l:command = a:command @@ -28,6 +28,11 @@ function! ale#command#FormatCommand(buffer, command, pipe_file_if_needed) abort " with an ugly string. let l:command = substitute(l:command, '%%', '<>', 'g') + " Replace %e with the escaped executable, if available. + if !empty(a:executable) && l:command =~# '%e' + let l:command = substitute(l:command, '%e', '\=ale#Escape(a:executable)', 'g') + endif + " Replace all %s occurrences in the string with the name of the current " file. if l:command =~# '%s' diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index d97b6937..a64d8f9f 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -189,6 +189,7 @@ function! s:HandleExit(job_id, exit_code) abort let l:linter = l:job_info.linter let l:output = l:job_info.output let l:buffer = l:job_info.buffer + let l:executable = l:job_info.executable let l:next_chain_index = l:job_info.next_chain_index if g:ale_history_enabled @@ -212,7 +213,7 @@ function! s:HandleExit(job_id, exit_code) abort endif if l:next_chain_index < len(get(l:linter, 'command_chain', [])) - call s:InvokeChain(l:buffer, l:linter, l:next_chain_index, l:output) + call s:InvokeChain(l:buffer, l:executable, l:linter, l:next_chain_index, l:output) return endif @@ -440,6 +441,12 @@ endfunction " Returns 1 when the job was started successfully. function! s:RunJob(options) abort let l:command = a:options.command + + if empty(l:command) + return 0 + endif + + let l:executable = a:options.executable let l:buffer = a:options.buffer let l:linter = a:options.linter let l:output_stream = a:options.output_stream @@ -447,11 +454,12 @@ function! s:RunJob(options) abort let l:read_buffer = a:options.read_buffer let l:info = g:ale_buffer_info[l:buffer] - if empty(l:command) - return 0 - endif - - let [l:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, l:read_buffer) + let [l:temporary_file, l:command] = ale#command#FormatCommand( + \ l:buffer, + \ l:executable, + \ l:command, + \ l:read_buffer, + \) if s:CreateTemporaryFileForJob(l:buffer, l:temporary_file) " If a temporary filename has been formatted in to the command, then @@ -512,6 +520,7 @@ function! s:RunJob(options) abort let s:job_info_map[l:job_id] = { \ 'linter': l:linter, \ 'buffer': l:buffer, + \ 'executable': l:executable, \ 'output': [], \ 'next_chain_index': l:next_chain_index, \} @@ -604,8 +613,9 @@ function! ale#engine#ProcessChain(buffer, linter, chain_index, input) abort \} endfunction -function! s:InvokeChain(buffer, linter, chain_index, input) abort +function! s:InvokeChain(buffer, executable, linter, chain_index, input) abort let l:options = ale#engine#ProcessChain(a:buffer, a:linter, a:chain_index, a:input) + let l:options.executable = a:executable return s:RunJob(l:options) endfunction @@ -699,7 +709,7 @@ function! s:RunLinter(buffer, linter) abort let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) if ale#engine#IsExecutable(a:buffer, l:executable) - return s:InvokeChain(a:buffer, a:linter, 0, []) + return s:InvokeChain(a:buffer, l:executable, a:linter, 0, []) endif endif diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 6e5875f6..62674b87 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -212,6 +212,7 @@ function! s:RunJob(options) abort let [l:temporary_file, l:command] = ale#command#FormatCommand( \ l:buffer, + \ '', \ l:command, \ l:read_buffer, \) diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 9e72362b..4527c74e 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -151,6 +151,8 @@ function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort return {} endif + " Format the command, so %e can be formatted into it. + let l:command = ale#command#FormatCommand(a:buffer, l:executable, l:command, 0)[1] let l:command = ale#job#PrepareCommand( \ a:buffer, \ ale#linter#GetCommand(a:buffer, a:linter), diff --git a/doc/ale.txt b/doc/ale.txt index b500d5d4..d325fb22 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -2494,15 +2494,23 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* For example: > 'command': 'ghc -fno-code -v0 %t', +< + Any substring `%e` will be replaced with the escaped executable supplied + with `executable` or `executable_callback`. This provides a convenient way + to define a command string which needs to include a dynamic executable name, + but which is otherwise static. + + For example: > + 'command': '%e --some-argument', < The character sequence `%%` can be used to emit a literal `%` into a command, so literal character sequences `%s` and `%t` can be escaped by using `%%s` and `%%t` instead, etc. If a callback for a command generates part of a command string which might - possibly contain `%%`, `%s`, or `%t` where the special formatting behaviour - is not desired, the |ale#engine#EscapeCommandPart()| function can be used to - replace those characters to avoid formatting issues. + possibly contain `%%`, `%s`, `%t`, or `%e`, where the special formatting + behavior is not desired, the |ale#engine#EscapeCommandPart()| function can + be used to replace those characters to avoid formatting issues. *ale-linter-loading-behavior* *ale-linter-loading-behaviour* diff --git a/test/test_format_command.vader b/test/test_format_command.vader index f6143a5a..71285efa 100644 --- a/test/test_format_command.vader +++ b/test/test_format_command.vader @@ -15,10 +15,10 @@ After: delfunction CheckTempFile Execute(FormatCommand should do nothing to basic command strings): - AssertEqual ['', 'awesome-linter do something'], ale#command#FormatCommand(bufnr('%'), 'awesome-linter do something', 0) + AssertEqual ['', 'awesome-linter do something'], ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0) Execute(FormatCommand should handle %%, and ignore other percents): - AssertEqual ['', '% %%d %%f %x %'], ale#command#FormatCommand(bufnr('%'), '%% %%%d %%%f %x %', 0) + AssertEqual ['', '% %%d %%f %x %'], ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0) Execute(FormatCommand should convert %s to the current filename): AssertEqual @@ -26,10 +26,10 @@ Execute(FormatCommand should convert %s to the current filename): \ '', \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')) \ ], - \ ale#command#FormatCommand(bufnr('%'), 'foo %s bar %s', 0) + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0) Execute(FormatCommand should convert %t to a new temporary filename): - let g:result = ale#command#FormatCommand(bufnr('%'), 'foo %t bar %t', 0) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0) call CheckTempFile(g:result[0]) @@ -43,7 +43,7 @@ Execute(FormatCommand should convert %t to a new temporary filename): AssertEqual g:match[1], g:match[2] Execute(FormatCommand should let you combine %s and %t): - let g:result = ale#command#FormatCommand(bufnr('%'), 'foo %t bar %s', 0) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0) call CheckTempFile(g:result[0]) @@ -56,11 +56,34 @@ Execute(FormatCommand should let you combine %s and %t): " The second item should be equal to the original filename. AssertEqual ale#Escape(expand('%:p')), g:match[2] +Execute(FormatCommand should replace %e with the escaped executable): + if has('win32') + AssertEqual + \ ['', 'foo foo'], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0) + AssertEqual + \ ['', '"foo bar"'], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0) + AssertEqual + \ ['', '%e %e'], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0) + else + AssertEqual + \ ['', '''foo'' ''foo'''], + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0) + AssertEqual + \ ['', '''foo bar'''], + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0) + AssertEqual + \ ['', '%e %e'], + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0) + endif + Execute(EscapeCommandPart should escape all percent signs): AssertEqual '%%s %%t %%%% %%s %%t %%%%', ale#engine#EscapeCommandPart('%s %t %% %s %t %%') Execute(EscapeCommandPart should pipe in temporary files appropriately): - let g:result = ale#command#FormatCommand(bufnr('%'), 'foo bar', 1) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar', 1) call CheckTempFile(g:result[0]) @@ -68,7 +91,7 @@ Execute(EscapeCommandPart should pipe in temporary files appropriately): Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] AssertEqual ale#Escape(g:result[0]), g:match[1] - let g:result = ale#command#FormatCommand(bufnr('%'), 'foo bar %t', 1) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar %t', 1) call CheckTempFile(g:result[0])