summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorw0rp <devw0rp@gmail.com>2017-11-07 23:20:14 +0000
committerw0rp <devw0rp@gmail.com>2017-11-07 23:20:14 +0000
commit1bf894f48c2169e18e5978c9347e40f186e425ab (patch)
tree9f61a99476233e80b70ab4f4efaecdb60f887d10
parentd97924b6986216aea3eae68a3fdc27b9bde341bb (diff)
downloadale-1bf894f48c2169e18e5978c9347e40f186e425ab.zip
Fix #1086 - Implement command chaining for fixers
-rw-r--r--autoload/ale/fix.vim59
-rw-r--r--doc/ale.txt20
-rw-r--r--test/test_ale_fix.vader89
3 files changed, 160 insertions, 8 deletions
diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim
index 677fb3da..a57ad195 100644
--- a/autoload/ale/fix.vim
+++ b/autoload/ale/fix.vim
@@ -108,17 +108,27 @@ function! s:HandleExit(job_id, exit_code) abort
let l:job_info.output = readfile(l:job_info.file_to_read)
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,
" 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.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({
\ 'buffer': l:buffer,
\ 'input': l:input,
+ \ 'output': l:job_info.output,
\ '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
@@ -172,6 +182,26 @@ function! s:RunJob(options) abort
let l:input = a:options.input
let l:output_stream = a:options.output_stream
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)
call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input)
@@ -186,8 +216,9 @@ function! s:RunJob(options) abort
\ 'buffer': l:buffer,
\ 'input': l:input,
\ 'output': [],
- \ 'callback_list': a:options.callback_list,
+ \ 'chain_with': l:chain_with,
\ 'callback_index': a:options.callback_index,
+ \ 'callback_list': a:options.callback_list,
\}
if l:read_temporary_file
@@ -250,13 +281,24 @@ function! s:RunFixer(options) abort
let l:buffer = a:options.buffer
let l:input = a:options.input
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
- let l:Function = a:options.callback_list[l:index]
-
- let l:result = ale#util#FunctionArgCount(l:Function) == 1
- \ ? call(l:Function, [l:buffer])
- \ : call(l:Function, [l:buffer, copy(l:input)])
+ let l:Function = l:chain_callback isnot v:null
+ \ ? ale#util#GetFunction(l:chain_callback)
+ \ : a:options.callback_list[l:index]
+
+ if l:chain_callback isnot v:null
+ " Chained commands accept (buffer, output, [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
" When `0` is returned, skip this item.
@@ -271,6 +313,7 @@ function! s:RunFixer(options) abort
\ 'input': l:input,
\ 'output_stream': get(l:result, 'output_stream', 'stdout'),
\ '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_index': l:index,
\})
diff --git a/doc/ale.txt b/doc/ale.txt
index 67a5a130..d3efcc13 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -443,6 +443,26 @@ are supported for running the commands.
for commands which need to modify some file on disk in
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*
Synchronous functions and asynchronous jobs will be run in a sequence for
diff --git a/test/test_ale_fix.vader b/test/test_ale_fix.vader
index fbf4755a..ffe3d93b 100644
--- a/test/test_ale_fix.vader
+++ b/test/test_ale_fix.vader
@@ -62,6 +62,49 @@ Before:
return [{'lnum': 1, 'col': 1, 'text': 'xxx'}]
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()
call ale#linter#Define('testft', {
\ 'name': 'testlinter',
@@ -97,6 +140,13 @@ After:
delfunction RemoveLastLine
delfunction RemoveLastLineOneArg
delfunction TestCallback
+ delfunction FirstChainCallback
+ delfunction FirstChainCallbackSkipped
+ delfunction FirstChainCallbackSecondSkipped
+ delfunction SecondChainCallback
+ delfunction ThirdChainCallback
+ delfunction ChainWhereLastIsSkipped
+ delfunction ChainEndSkipped
delfunction SetUpLinters
delfunction GetLastMessage
@@ -470,3 +520,42 @@ Execute(ALE should print a message telling you something isn't a valid fixer whe
ALEFix
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