Fix #1086 - Implement command chaining for fixers
This commit is contained in:
parent
d97924b698
commit
1bf894f48c
@ -108,17 +108,27 @@ function! s:HandleExit(job_id, exit_code) abort
|
|||||||
let l:job_info.output = readfile(l:job_info.file_to_read)
|
let l:job_info.output = readfile(l:job_info.file_to_read)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let l:chain_callback = get(l:job_info, 'chain_with', v:null)
|
||||||
|
|
||||||
" Use the output of the job for changing the file if it isn't empty,
|
" Use the output of the job for changing the file if it isn't empty,
|
||||||
" otherwise skip this job and use the input from before.
|
" otherwise skip this job and use the input from before.
|
||||||
let l:input = !empty(l:job_info.output)
|
"
|
||||||
|
" We'll use the input from before for chained commands.
|
||||||
|
let l:input = l:chain_callback is v:null && !empty(l:job_info.output)
|
||||||
\ ? l:job_info.output
|
\ ? l:job_info.output
|
||||||
\ : l:job_info.input
|
\ : l:job_info.input
|
||||||
|
|
||||||
|
let l:next_index = l:chain_callback is v:null
|
||||||
|
\ ? l:job_info.callback_index + 1
|
||||||
|
\ : l:job_info.callback_index
|
||||||
|
|
||||||
call s:RunFixer({
|
call s:RunFixer({
|
||||||
\ 'buffer': l:buffer,
|
\ 'buffer': l:buffer,
|
||||||
\ 'input': l:input,
|
\ 'input': l:input,
|
||||||
|
\ 'output': l:job_info.output,
|
||||||
\ 'callback_list': l:job_info.callback_list,
|
\ 'callback_list': l:job_info.callback_list,
|
||||||
\ 'callback_index': l:job_info.callback_index + 1,
|
\ 'callback_index': l:next_index,
|
||||||
|
\ 'chain_callback': l:chain_callback,
|
||||||
\})
|
\})
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@ -172,6 +182,26 @@ function! s:RunJob(options) abort
|
|||||||
let l:input = a:options.input
|
let l:input = a:options.input
|
||||||
let l:output_stream = a:options.output_stream
|
let l:output_stream = a:options.output_stream
|
||||||
let l:read_temporary_file = a:options.read_temporary_file
|
let l:read_temporary_file = a:options.read_temporary_file
|
||||||
|
let l:chain_with = a:options.chain_with
|
||||||
|
|
||||||
|
if empty(l:command)
|
||||||
|
" If there's nothing further to chain the command with, stop here.
|
||||||
|
if l:chain_with is v:null
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
|
||||||
|
" If there's another chained callback to run, then run that.
|
||||||
|
call s:RunFixer({
|
||||||
|
\ 'buffer': l:buffer,
|
||||||
|
\ 'input': l:input,
|
||||||
|
\ 'callback_index': a:options.callback_index,
|
||||||
|
\ 'callback_list': a:options.callback_list,
|
||||||
|
\ 'chain_callback': l:chain_with,
|
||||||
|
\ 'output': [],
|
||||||
|
\})
|
||||||
|
|
||||||
|
return 1
|
||||||
|
endif
|
||||||
|
|
||||||
let [l:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, 1)
|
let [l:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, 1)
|
||||||
call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input)
|
call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input)
|
||||||
@ -186,8 +216,9 @@ function! s:RunJob(options) abort
|
|||||||
\ 'buffer': l:buffer,
|
\ 'buffer': l:buffer,
|
||||||
\ 'input': l:input,
|
\ 'input': l:input,
|
||||||
\ 'output': [],
|
\ 'output': [],
|
||||||
\ 'callback_list': a:options.callback_list,
|
\ 'chain_with': l:chain_with,
|
||||||
\ 'callback_index': a:options.callback_index,
|
\ 'callback_index': a:options.callback_index,
|
||||||
|
\ 'callback_list': a:options.callback_list,
|
||||||
\}
|
\}
|
||||||
|
|
||||||
if l:read_temporary_file
|
if l:read_temporary_file
|
||||||
@ -250,13 +281,24 @@ function! s:RunFixer(options) abort
|
|||||||
let l:buffer = a:options.buffer
|
let l:buffer = a:options.buffer
|
||||||
let l:input = a:options.input
|
let l:input = a:options.input
|
||||||
let l:index = a:options.callback_index
|
let l:index = a:options.callback_index
|
||||||
|
let l:chain_callback = get(a:options, 'chain_callback', v:null)
|
||||||
|
|
||||||
while len(a:options.callback_list) > l:index
|
while len(a:options.callback_list) > l:index
|
||||||
let l:Function = a:options.callback_list[l:index]
|
let l:Function = l:chain_callback isnot v:null
|
||||||
|
\ ? ale#util#GetFunction(l:chain_callback)
|
||||||
|
\ : a:options.callback_list[l:index]
|
||||||
|
|
||||||
let l:result = ale#util#FunctionArgCount(l:Function) == 1
|
if l:chain_callback isnot v:null
|
||||||
\ ? call(l:Function, [l:buffer])
|
" Chained commands accept (buffer, output, [input])
|
||||||
\ : call(l:Function, [l:buffer, copy(l:input)])
|
let l:result = ale#util#FunctionArgCount(l:Function) == 2
|
||||||
|
\ ? call(l:Function, [l:buffer, a:options.output])
|
||||||
|
\ : call(l:Function, [l:buffer, a:options.output, copy(l:input)])
|
||||||
|
else
|
||||||
|
" Chained commands accept (buffer, [input])
|
||||||
|
let l:result = ale#util#FunctionArgCount(l:Function) == 1
|
||||||
|
\ ? call(l:Function, [l:buffer])
|
||||||
|
\ : call(l:Function, [l:buffer, copy(l:input)])
|
||||||
|
endif
|
||||||
|
|
||||||
if type(l:result) == type(0) && l:result == 0
|
if type(l:result) == type(0) && l:result == 0
|
||||||
" When `0` is returned, skip this item.
|
" When `0` is returned, skip this item.
|
||||||
@ -271,6 +313,7 @@ function! s:RunFixer(options) abort
|
|||||||
\ 'input': l:input,
|
\ 'input': l:input,
|
||||||
\ 'output_stream': get(l:result, 'output_stream', 'stdout'),
|
\ 'output_stream': get(l:result, 'output_stream', 'stdout'),
|
||||||
\ 'read_temporary_file': get(l:result, 'read_temporary_file', 0),
|
\ 'read_temporary_file': get(l:result, 'read_temporary_file', 0),
|
||||||
|
\ 'chain_with': get(l:result, 'chain_with', v:null),
|
||||||
\ 'callback_list': a:options.callback_list,
|
\ 'callback_list': a:options.callback_list,
|
||||||
\ 'callback_index': l:index,
|
\ 'callback_index': l:index,
|
||||||
\})
|
\})
|
||||||
|
20
doc/ale.txt
20
doc/ale.txt
@ -443,6 +443,26 @@ are supported for running the commands.
|
|||||||
for commands which need to modify some file on disk in
|
for commands which need to modify some file on disk in
|
||||||
order to fix files.
|
order to fix files.
|
||||||
|
|
||||||
|
`chain_with` An optional key for defining a callback to call next.
|
||||||
|
|
||||||
|
The callback must accept two or three arguments,
|
||||||
|
`(buffer, output)` or `(buffer, output, input)` .
|
||||||
|
Functions receiving a variable number of arguments will
|
||||||
|
only receive the first two values. The `output` argument
|
||||||
|
will contain the lines of output from the command run.
|
||||||
|
The `input` argument is the List of lines for the
|
||||||
|
buffer, after applying any previous fixers.
|
||||||
|
|
||||||
|
The callback must return the same values returned for
|
||||||
|
any fixer function. This allows fixer functions to be
|
||||||
|
chained recursively.
|
||||||
|
|
||||||
|
When the command string returned for a fixer is an empty
|
||||||
|
string, the next command in the chain will still be run.
|
||||||
|
This allows commands to be skipped, like version checks
|
||||||
|
that are cached. An empty List will be passed to the
|
||||||
|
next callback in the chain for the `output`.
|
||||||
|
|
||||||
*ale-fix-configuration*
|
*ale-fix-configuration*
|
||||||
|
|
||||||
Synchronous functions and asynchronous jobs will be run in a sequence for
|
Synchronous functions and asynchronous jobs will be run in a sequence for
|
||||||
|
@ -62,6 +62,49 @@ Before:
|
|||||||
return [{'lnum': 1, 'col': 1, 'text': 'xxx'}]
|
return [{'lnum': 1, 'col': 1, 'text': 'xxx'}]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! FirstChainCallback(buffer)
|
||||||
|
return {'command': 'echo echoline', 'chain_with': 'SecondChainCallback'}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! FirstChainCallbackSkipped(buffer)
|
||||||
|
return {'command': '', 'chain_with': 'SecondChainCallback'}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! FirstChainCallbackSecondSkipped(buffer)
|
||||||
|
return {'command': 'echo skipit', 'chain_with': 'SecondChainCallback'}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! SecondChainCallback(buffer, output)
|
||||||
|
let l:previous_line = empty(a:output)
|
||||||
|
\ ? 'emptydefault'
|
||||||
|
\ : join(split(a:output[0]))
|
||||||
|
|
||||||
|
if l:previous_line is# 'skipit'
|
||||||
|
return {'command': '', 'chain_with': 'ThirdChainCallback'}
|
||||||
|
endif
|
||||||
|
|
||||||
|
return {
|
||||||
|
\ 'command': 'echo ' . l:previous_line,
|
||||||
|
\ 'chain_with': 'ThirdChainCallback',
|
||||||
|
\}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ThirdChainCallback(buffer, output, input)
|
||||||
|
let l:previous_line = empty(a:output)
|
||||||
|
\ ? 'thirddefault'
|
||||||
|
\ : join(split(a:output[0]))
|
||||||
|
|
||||||
|
return a:input + [l:previous_line]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ChainWhereLastIsSkipped(buffer)
|
||||||
|
return {'command': 'echo echoline', 'chain_with': 'ChainEndSkipped'}
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ChainEndSkipped(buffer, output)
|
||||||
|
return {'command': ''}
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! SetUpLinters()
|
function! SetUpLinters()
|
||||||
call ale#linter#Define('testft', {
|
call ale#linter#Define('testft', {
|
||||||
\ 'name': 'testlinter',
|
\ 'name': 'testlinter',
|
||||||
@ -97,6 +140,13 @@ After:
|
|||||||
delfunction RemoveLastLine
|
delfunction RemoveLastLine
|
||||||
delfunction RemoveLastLineOneArg
|
delfunction RemoveLastLineOneArg
|
||||||
delfunction TestCallback
|
delfunction TestCallback
|
||||||
|
delfunction FirstChainCallback
|
||||||
|
delfunction FirstChainCallbackSkipped
|
||||||
|
delfunction FirstChainCallbackSecondSkipped
|
||||||
|
delfunction SecondChainCallback
|
||||||
|
delfunction ThirdChainCallback
|
||||||
|
delfunction ChainWhereLastIsSkipped
|
||||||
|
delfunction ChainEndSkipped
|
||||||
delfunction SetUpLinters
|
delfunction SetUpLinters
|
||||||
delfunction GetLastMessage
|
delfunction GetLastMessage
|
||||||
|
|
||||||
@ -470,3 +520,42 @@ Execute(ALE should print a message telling you something isn't a valid fixer whe
|
|||||||
ALEFix
|
ALEFix
|
||||||
|
|
||||||
AssertEqual 'There is no fixer named `invalidname`. Check :ALEFixSuggest', GetLastMessage()
|
AssertEqual 'There is no fixer named `invalidname`. Check :ALEFixSuggest', GetLastMessage()
|
||||||
|
|
||||||
|
Execute(Test fixing with chained callbacks):
|
||||||
|
let g:ale_fixers.testft = ['FirstChainCallback']
|
||||||
|
ALEFix
|
||||||
|
|
||||||
|
Expect(The echoed line should be added):
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
echoline
|
||||||
|
|
||||||
|
Execute(Test fixing with chained callback where the first command is skipped):
|
||||||
|
let g:ale_fixers.testft = ['FirstChainCallbackSkipped']
|
||||||
|
ALEFix
|
||||||
|
|
||||||
|
Expect(The default line should be added):
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
emptydefault
|
||||||
|
|
||||||
|
Execute(Test fixing with chained callback where the second command is skipped):
|
||||||
|
let g:ale_fixers.testft = ['FirstChainCallbackSecondSkipped']
|
||||||
|
ALEFix
|
||||||
|
|
||||||
|
Expect(The default line should be added):
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
thirddefault
|
||||||
|
|
||||||
|
Execute(Test fixing with chained callback where the final callback is skipped):
|
||||||
|
let g:ale_fixers.testft = ['ChainWhereLastIsSkipped']
|
||||||
|
ALEFix
|
||||||
|
|
||||||
|
Expect(The lines should be the same):
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
Loading…
x
Reference in New Issue
Block a user