diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim index ec264a36..8f497169 100644 --- a/autoload/ale/command.vim +++ b/autoload/ale/command.vim @@ -135,13 +135,19 @@ endfunction " Format a filename, converting it with filename mappings, if non-empty, " and escaping it for putting into a command string. -function! s:FormatFilename(filename, mappings) abort +" +" The filename can be modified. +function! s:FormatFilename(filename, mappings, modifiers) abort let l:filename = a:filename if !empty(a:mappings) let l:filename = ale#filename_mapping#Map(l:filename, a:mappings) endif + if !empty(a:modifiers) + let l:filename = fnamemodify(l:filename, a:modifiers) + endif + return ale#Escape(l:filename) endfunction @@ -155,7 +161,7 @@ function! ale#command#FormatCommand( \ command, \ pipe_file_if_needed, \ input, -\ filename_mappings, +\ mappings, \) abort let l:temporary_file = '' let l:command = a:command @@ -173,14 +179,24 @@ function! ale#command#FormatCommand( " file. if l:command =~# '%s' let l:filename = fnamemodify(bufname(a:buffer), ':p') - let l:command = substitute(l:command, '%s', '\=s:FormatFilename(l:filename, a:filename_mappings)', 'g') + let l:command = substitute( + \ l:command, + \ '\v\%s(%(:h|:t|:r|:e)*)', + \ '\=s:FormatFilename(l:filename, a:mappings, submatch(1))', + \ 'g' + \) endif if a:input isnot v:false && l:command =~# '%t' " Create a temporary filename, / " The file itself will not be created by this function. let l:temporary_file = s:TemporaryFilename(a:buffer) - let l:command = substitute(l:command, '%t', '\=s:FormatFilename(l:temporary_file, a:filename_mappings)', 'g') + let l:command = substitute( + \ l:command, + \ '\v\%t(%(:h|:t|:r|:e)*)', + \ '\=s:FormatFilename(l:temporary_file, a:mappings, submatch(1))', + \ 'g' + \) endif " Finish formatting so %% becomes %. diff --git a/doc/ale.txt b/doc/ale.txt index 7327959b..ac51e466 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -342,6 +342,8 @@ are supported for running the commands. file will be created, containing the lines from the file after previous adjustment have been done. + See |ale-command-format-strings| for formatting options. + `read_temporary_file` When set to `1`, ALE will read the contents of the temporary file created for `%t`. This option can be used for commands which need to modify some file on disk in @@ -3739,6 +3741,16 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* command, so literal character sequences `%s` and `%t` can be escaped by using `%%s` and `%%t` instead, etc. + Some |filename-modifiers| can be applied to `%s` and `%t`. Only `:h`, `:t`, + `:r`, and `:e` may be applied, other modifiers will be ignored. Filename + modifiers can be applied to the format markers by placing them after them. + + For example: > + 'command': '%s:h %s:e %s:h:t', +< + Given a path `/foo/baz/bar.txt`, the above command string will generate + something akin to `'/foo/baz' 'txt' 'baz'` + If a callback for a command generates part of a command string which might possibly contain `%%`, `%s`, `%t`, or `%e`, where the special formatting behavior is not desired, the |ale#command#EscapeCommandPart()| function can diff --git a/test/test_format_command.vader b/test/test_format_command.vader index f8e2a66e..9d730fce 100644 --- a/test/test_format_command.vader +++ b/test/test_format_command.vader @@ -126,16 +126,49 @@ 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] +Execute(FormatCommand should apply filename modifiers to the current file): + AssertEqual + \ ale#Escape(expand('%:p:h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(expand('%:p:h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(expand('%:p:r')), + \ ale#command#FormatCommand(bufnr(''), '', '%s:h %s:t %s:h:t %s:e %s:r', 0, v:null, [])[1] + +Execute(FormatCommand should apply filename modifiers to the temporary file): + let g:result = ale#command#FormatCommand(bufnr(''), '', '%t:h %t:t %t:h:t %t:e %t:r', 0, v:null, []) + + AssertEqual + \ ale#Escape(fnamemodify(g:result[0], ':h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':r')), + \ g:result[1] + Execute(FormatCommand should apply filename mappings the current file): - let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s', 1, v:null, [ + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s', 0, v:null, [ \ [expand('%:p:h'), '/foo/bar'], \]) Assert g:result[1] =~# '/foo/bar' Execute(FormatCommand should apply filename mappings to temporary files): - let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t', 1, v:null, [ + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t', 0, v:null, [ \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] \]) Assert g:result[1] =~# '/foo/bar' + +Execute(FormatCommand should apply filename modifiers to mapped filenames): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s:h', 0, v:null, [ + \ [expand('%:p:h'), '/foo/bar'], + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1] + + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t:h:h:h', 0, v:null, [ + \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1]