diff --git a/autoload/codefmt/autopep8.vim b/autoload/codefmt/autopep8.vim index 53d5a66..6b42b8c 100644 --- a/autoload/codefmt/autopep8.vim +++ b/autoload/codefmt/autopep8.vim @@ -67,29 +67,18 @@ function! codefmt#autopep8#GetFormatter() abort call maktaba#ensure#IsNumber(a:startline) call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) if s:autopep8_supports_range - let l:cmd = [l:executable, '--range', ''.a:startline, ''.a:endline, '-'] - let l:input = join(l:lines, "\n") + call codefmt#formatterhelpers#Format([ + \ l:executable, + \ '--range', string(a:startline), string(a:endline), + \ '-']) else - let l:cmd = [l:executable, '-'] - " Hack range formatting by formatting range individually, ignoring context. - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, + \ a:endline, + \ [l:executable, '-']) endif - - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - - if s:autopep8_supports_range - let l:full_formatted = l:formatted - else - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - endif - - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) endfunction return l:formatter diff --git a/autoload/codefmt/buildifier.vim b/autoload/codefmt/buildifier.vim index 822759a..743df9a 100644 --- a/autoload/codefmt/buildifier.vim +++ b/autoload/codefmt/buildifier.vim @@ -45,11 +45,10 @@ function! codefmt#buildifier#GetFormatter() abort let l:cmd += ['-path', l:fname] endif - let l:input = join(getline(1, line('$')), "\n") try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - call maktaba#buffer#Overwrite(1, line('$'), l:formatted) + " NOTE: Ignores any line ranges given and formats entire buffer. + " buildifier does not support range formatting. + call codefmt#formatterhelpers#Format(l:cmd) catch " Parse all the errors and stick them in the quickfix list. let l:errors = [] diff --git a/autoload/codefmt/dartfmt.vim b/autoload/codefmt/dartfmt.vim index ba965c7..f2c0022 100644 --- a/autoload/codefmt/dartfmt.vim +++ b/autoload/codefmt/dartfmt.vim @@ -38,22 +38,12 @@ function! codefmt#dartfmt#GetFormatter() abort " @flag(dartfmt_executable}, only targetting the range from {startline} to " {endline} function l:formatter.FormatRange(startline, endline) abort - " Hack range formatting by formatting range individually, ignoring context. let l:cmd = [ s:plugin.Flag('dartfmt_executable') ] - " TODO When https://github.com/dart-lang/dart_style/issues/92 is implemented - " use those options. - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + " dartfmt does not support range formatting yet: + " https://github.com/dart-lang/dart_style/issues/92 + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, a:endline, l:cmd) catch /ERROR(ShellError):/ " Parse all the errors and stick them in the quickfix list. let l:errors = [] @@ -71,7 +61,8 @@ function! codefmt#dartfmt#GetFormatter() abort if empty(l:errors) " Couldn't parse dartfmt error format; display it all. - call maktaba#error#Shout('Failed to format range; showing all errors: %s', v:exception) + call maktaba#error#Shout( + \ 'Failed to format range; showing all errors: %s', v:exception) else let l:errorHeaderLines = split(v:exception, "\n")[1 : 5] let l:errorHeader = join(l:errorHeaderLines, "\n") diff --git a/autoload/codefmt/formatterhelpers.vim b/autoload/codefmt/formatterhelpers.vim new file mode 100644 index 0000000..43f06c9 --- /dev/null +++ b/autoload/codefmt/formatterhelpers.vim @@ -0,0 +1,64 @@ +" Copyright 2020 Google Inc. All rights reserved. +" +" Licensed under the Apache License, Version 2.0 (the "License"); +" you may not use this file except in compliance with the License. +" You may obtain a copy of the License at +" +" http://www.apache.org/licenses/LICENSE-2.0 +" +" Unless required by applicable law or agreed to in writing, software +" distributed under the License is distributed on an "AS IS" BASIS, +" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +" See the License for the specific language governing permissions and +" limitations under the License. + + +"" +" @public +" Format lines in the current buffer via a formatter invoked by {cmd}, which +" is a system call represented by either a |maktaba.Syscall| or any argument +" accepted by |maktaba#syscall#Create()|. The command must include any +" arguments for the explicit range line numbers to use, if any. +" +" @throws ShellError if the {cmd} system call fails +function! codefmt#formatterhelpers#Format(cmd) abort + let l:lines = getline(1, line('$')) + let l:input = join(l:lines, "\n") + + let l:result = maktaba#syscall#Create(a:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + + call maktaba#buffer#Overwrite(1, line('$'), l:formatted) +endfunction + +"" +" @public +" Attempt to format a range of lines from {startline} to {endline} in the +" current buffer via a formatter that doesn't natively support range +" formatting, which is invoked via {cmd} (a system call represented by either +" a |maktaba.Syscall| or any argument accepted by |maktaba#syscall#Create()|). +" It uses a hacky strategy of sending those lines to the formatter in +" isolation, which gives bad results if the code on those lines isn't +" a self-contained block of syntax or is part of a larger indent. +" +" If invoking this hack, please make sure to file a feature request against +" the tool for range formatting and post a URL for that feature request above +" code that calls it. +" +" @throws ShellError if the {cmd} system call fails +function! codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ startline, endline, cmd) abort + call maktaba#ensure#IsNumber(a:startline) + call maktaba#ensure#IsNumber(a:endline) + + let l:lines = getline(1, line('$')) + let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + + let l:result = maktaba#syscall#Create(a:cmd).WithStdin(l:input).Call() + let l:formatted = split(l:result.stdout, "\n") + " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. + let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] + let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] + + call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) +endfunction diff --git a/autoload/codefmt/gofmt.vim b/autoload/codefmt/gofmt.vim index bab8317..da4aad6 100644 --- a/autoload/codefmt/gofmt.vim +++ b/autoload/codefmt/gofmt.vim @@ -38,20 +38,12 @@ function! codefmt#gofmt#GetFormatter() abort " @flag(gofmt_executable), only targeting the range between {startline} and " {endline}. function l:formatter.FormatRange(startline, endline) abort - " Hack range formatting by formatting range individually, ignoring context. let l:cmd = [ s:plugin.Flag('gofmt_executable') ] - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + " gofmt does not support range formatting. + " TODO: File a feature request with gofmt and link it here. + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, a:endline, l:cmd) catch /ERROR(ShellError):/ " Parse all the errors and stick them in the quickfix list. let l:errors = [] diff --git a/autoload/codefmt/googlejava.vim b/autoload/codefmt/googlejava.vim index 618f0e7..94fba8e 100644 --- a/autoload/codefmt/googlejava.vim +++ b/autoload/codefmt/googlejava.vim @@ -59,10 +59,7 @@ function! codefmt#googlejava#GetFormatter() abort let l:ranges_str = join(map(copy(a:ranges), 'v:val[0] . ":" . v:val[1]'), ',') let l:cmd += ['--lines', l:ranges_str, '-'] - let l:input = join(getline(1, line('$')), "\n") - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - call maktaba#buffer#Overwrite(1, line('$'), l:formatted) + call codefmt#formatterhelpers#Format(l:cmd) endfunction return l:formatter diff --git a/autoload/codefmt/jsbeautify.vim b/autoload/codefmt/jsbeautify.vim index 0bd6fdc..f7f706c 100644 --- a/autoload/codefmt/jsbeautify.vim +++ b/autoload/codefmt/jsbeautify.vim @@ -52,17 +52,10 @@ function! codefmt#jsbeautify#GetFormatter() abort call maktaba#ensure#IsNumber(a:startline) call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - " Hack range formatting by formatting range individually, ignoring context. - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") - - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + " js-beautify does not support range formatting yet: + " https://github.com/beautify-web/js-beautify/issues/610 + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, a:endline, l:cmd) endfunction return l:formatter diff --git a/autoload/codefmt/rustfmt.vim b/autoload/codefmt/rustfmt.vim index 07a48e8..2c0789f 100644 --- a/autoload/codefmt/rustfmt.vim +++ b/autoload/codefmt/rustfmt.vim @@ -52,13 +52,10 @@ function! codefmt#rustfmt#GetFormatter() abort call extend(l:cmd, l:rustfmt_options) try - let l:lines = getline(1, line('$')) - let l:input = join(l:lines, "\n") - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") + " NOTE: Ignores any line ranges given and formats entire buffer. " Even though rustfmt supports formatting ranges through the --file-lines " flag, it is not still enabled in the stable binaries. - call maktaba#buffer#Overwrite(1, line('$'), l:formatted) + call codefmt#formatterhelpers#Format(l:cmd) catch /ERROR(ShellError):/ " Parse all the errors and stick them in the quickfix list. let l:errors = [] diff --git a/autoload/codefmt/shfmt.vim b/autoload/codefmt/shfmt.vim index 19845c2..c701731 100644 --- a/autoload/codefmt/shfmt.vim +++ b/autoload/codefmt/shfmt.vim @@ -48,22 +48,14 @@ function! codefmt#shfmt#GetFormatter() abort \ 'shfmt_options flag must be list or callable. Found %s', \ string(l:Shfmt_options)) endif - " Hack range formatting by formatting range individually, ignoring context. - " Feature request for range formatting: - " https://github.com/mvdan/sh/issues/333 let l:cmd = [ s:plugin.Flag('shfmt_executable') ] + l:shfmt_options - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") try - let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + " Feature request for range formatting: + " https://github.com/mvdan/sh/issues/333 + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, + \ a:endline, + \ l:cmd) catch /ERROR(ShellError):/ " Parse all the errors and stick them in the quickfix list. let l:errors = [] diff --git a/autoload/codefmt/zprint.vim b/autoload/codefmt/zprint.vim index db0af6d..bc0a287 100644 --- a/autoload/codefmt/zprint.vim +++ b/autoload/codefmt/zprint.vim @@ -63,28 +63,20 @@ function! codefmt#zprint#GetFormatter() abort \ 'zprint_options flag must be list or callable. Found %s', \ string(l:ZprintOptions)) endif - let l:cmd = [s:plugin.Flag('zprint_executable')] - call extend(l:cmd, l:zprint_options) - - call maktaba#ensure#IsNumber(a:startline) - call maktaba#ensure#IsNumber(a:endline) - let l:lines = getline(1, line('$')) - - " zprint doesn't support formatting a range of lines, so format the range - " individually, ignoring context. This works well for top-level forms, although it's - " not ideal for inner forms because it loses the indentation. - let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n") + let l:cmd_args = [s:plugin.Flag('zprint_executable')] + call extend(l:cmd_args, l:zprint_options) " Prepare the syscall, changing to the containing directory in case the user " has configured {:search-config? true} in ~/.zprintrc - let l:result = maktaba#syscall#Create(l:cmd).WithCwd(expand('%:p:h')).WithStdin(l:input).Call() - let l:formatted = split(l:result.stdout, "\n") - - " Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right. - let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : [] - let l:full_formatted = l:before + l:formatted + l:lines[a:endline :] - - call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted) + let l:cmd = maktaba#syscall#Create(l:cmd_args).WithCwd(expand('%:p:h')) + " zprint does not support range formatting yet: + " https://github.com/kkinnear/zprint/issues/122 + " This fake range formatting works well for top-level forms, although it's + " not ideal for inner forms because it loses the indentation. + call codefmt#formatterhelpers#AttemptFakeRangeFormatting( + \ a:startline, + \ a:endline, + \ l:cmd) endfunction return l:formatter diff --git a/doc/codefmt.txt b/doc/codefmt.txt index 3c1b455..4f55739 100644 --- a/doc/codefmt.txt +++ b/doc/codefmt.txt @@ -225,6 +225,30 @@ codefmt#FormatMap({type}) *codefmt#FormatMap()* Suitable for use as 'operatorfunc'; see |g@| for details. The type is ignored since formatting only works on complete lines. +codefmt#formatterhelpers#Format({cmd}) *codefmt#formatterhelpers#Format()* + Format lines in the current buffer via a formatter invoked by {cmd}, which + is a system call represented by either a |maktaba.Syscall| or any argument + accepted by |maktaba#syscall#Create()|. The command must include any + arguments for the explicit range line numbers to use, if any. + + Throws ERROR(ShellError) if the {cmd} system call fails + +codefmt#formatterhelpers#AttemptFakeRangeFormatting({startline}, {endline}, + {cmd}) *codefmt#formatterhelpers#AttemptFakeRangeFormatting()* + Attempt to format a range of lines from {startline} to {endline} in the + current buffer via a formatter that doesn't natively support range + formatting, which is invoked via {cmd} (a system call represented by either + a |maktaba.Syscall| or any argument accepted by |maktaba#syscall#Create()|). + It uses a hacky strategy of sending those lines to the formatter in + isolation, which gives bad results if the code on those lines isn't a + self-contained block of syntax or is part of a larger indent. + + If invoking this hack, please make sure to file a feature request against + the tool for range formatting and post a URL for that feature request above + code that calls it. + + Throws ERROR(ShellError) if the {cmd} system call fails + ============================================================================== MAPPINGS *codefmt-mappings*