Skip to content

Commit bb1ff7f

Browse files
committed
Add shfmt.
1 parent 18fe8fb commit bb1ff7f

File tree

5 files changed

+188
-0
lines changed

5 files changed

+188
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ helpfiles in the `doc/` directory. The helpfiles are also available via
2424
* Proto (clang-format)
2525
* Python (Autopep8 or YAPF)
2626
* TypeScript (clang-format)
27+
* Shell (shfmt)
2728

2829
# Commands
2930

autoload/codefmt/shfmt.vim

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
" Copyright 2017 Google Inc. All rights reserved.
2+
"
3+
" Licensed under the Apache License, Version 2.0 (the "License");
4+
" you may not use this file except in compliance with the License.
5+
" You may obtain a copy of the License at
6+
"
7+
" http://www.apache.org/licenses/LICENSE-2.0
8+
"
9+
" Unless required by applicable law or agreed to in writing, software
10+
" distributed under the License is distributed on an "AS IS" BASIS,
11+
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
" See the License for the specific language governing permissions and
13+
" limitations under the License.
14+
15+
16+
let s:plugin = maktaba#plugin#Get('codefmt')
17+
18+
19+
""
20+
" @private
21+
" Formatter: shfmt
22+
function! codefmt#shfmt#GetFormatter() abort
23+
let l:formatter = {
24+
\ 'name': 'shfmt',
25+
\ 'setup_instructions': 'Install shfmt and configure the ' .
26+
\ 'shfmt_executable flag'}
27+
28+
function l:formatter.IsAvailable() abort
29+
return executable(s:plugin.Flag('shfmt_executable'))
30+
endfunction
31+
32+
function l:formatter.AppliesToBuffer() abort
33+
return &filetype is# 'sh'
34+
endfunction
35+
36+
""
37+
" Reformat the current buffer with shfmt or the binary named in
38+
" @flag(shfmt_executable), only targeting the range between {startline} and
39+
" {endline}.
40+
function l:formatter.FormatRange(startline, endline) abort
41+
let l:Shfmt_options = s:plugin.Flag('shfmt_options')
42+
if type(l:Shfmt_options) is# type([])
43+
let l:shfmt_options = l:Shfmt_options
44+
elseif maktaba#value#IsCallable(l:Shfmt_options)
45+
let l:shfmt_options = maktaba#function#Call(l:Shfmt_options)
46+
else
47+
throw maktaba#error#WrongType(
48+
\ 'shfmt_options flag must be list or callable. Found %s',
49+
\ string(l:Shfmt_options))
50+
endif
51+
" Hack range formatting by formatting range individually, ignoring context.
52+
let l:cmd = [ s:plugin.Flag('shfmt_executable') ] + l:shfmt_options
53+
call maktaba#ensure#IsNumber(a:startline)
54+
call maktaba#ensure#IsNumber(a:endline)
55+
let l:lines = getline(1, line('$'))
56+
let l:input = join(l:lines[a:startline - 1 : a:endline - 1], "\n")
57+
try
58+
let l:result = maktaba#syscall#Create(l:cmd).WithStdin(l:input).Call()
59+
let l:formatted = split(l:result.stdout, "\n")
60+
" Special case empty slice: neither l:lines[:0] nor l:lines[:-1] is right.
61+
let l:before = a:startline > 1 ? l:lines[ : a:startline - 2] : []
62+
63+
let l:full_formatted = l:before + l:formatted + l:lines[a:endline :]
64+
call maktaba#buffer#Overwrite(1, line('$'), l:full_formatted)
65+
catch /ERROR(ShellError):/
66+
" Parse all the errors and stick them in the quickfix list.
67+
let l:errors = []
68+
for l:line in split(v:exception, "\n")
69+
let l:tokens = matchlist(l:line,
70+
\ '\C\v^\<standard input\>:(\d+):(\d+):\s*(.*)')
71+
if !empty(l:tokens)
72+
call add(l:errors, {
73+
\ 'filename': @%,
74+
\ 'lnum': l:tokens[1] + a:startline - 1,
75+
\ 'col': l:tokens[2],
76+
\ 'text': l:tokens[3]})
77+
endif
78+
endfor
79+
80+
if empty(l:errors)
81+
" Couldn't parse shfmt error format; display it all.
82+
call maktaba#error#Shout('Error formatting file: %s', v:exception)
83+
else
84+
call setqflist(l:errors, 'r')
85+
cc 1
86+
endif
87+
endtry
88+
endfunction
89+
90+
return l:formatter
91+
endfunction

instant/flags.vim

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,14 @@ call s:plugin.Flag('buildifier_executable', 'buildifier')
9393
" form:
9494
" `java -jar /path/to/google-java`
9595
call s:plugin.Flag('google_java_executable', 'google-java-format')
96+
97+
""
98+
" Command line arguments to to feed shfmt. Either a list or callable that
99+
" takes no args and returns a list with command line arguments. By default, uses
100+
" the Google's style.
101+
" See https://github.com/mvdan/sh for details.
102+
call s:plugin.Flag('shfmt_options', ['-i', '2', '-sr', '-ci'])
103+
104+
""
105+
" The path to the shfmt executable.
106+
call s:plugin.Flag('shfmt_executable', 'shfmt')

plugin/register.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ call s:registry.AddExtension(codefmt#autopep8#GetFormatter())
3030
call s:registry.AddExtension(codefmt#gn#GetFormatter())
3131
call s:registry.AddExtension(codefmt#buildifier#GetFormatter())
3232
call s:registry.AddExtension(codefmt#googlejava#GetFormatter())
33+
call s:registry.AddExtension(codefmt#shfmt#GetFormatter())

vroom/shfmt.vroom

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
The built-in shfmt formatter knows how to format shell code (POSIX/Bash/mksh).
2+
If you aren't familiar with basic codefmt usage yet, see main.vroom first.
3+
4+
We'll set up codefmt and configure the vroom environment, then jump into some
5+
examples.
6+
7+
:source $VROOMDIR/setupvroom.vim
8+
9+
:let g:repeat_calls = []
10+
:function FakeRepeat(...)<CR>
11+
| call add(g:repeat_calls, a:000)<CR>
12+
:endfunction
13+
:call maktaba#test#Override('repeat#set', 'FakeRepeat')
14+
15+
:call codefmt#SetWhetherToPerformIsAvailableChecksForTesting(0)
16+
17+
18+
The shfmt formatter expects the shfmt executable to be installed on your system.
19+
20+
% f()
21+
:FormatCode shfmt
22+
! shfmt .*
23+
$ f()
24+
25+
The name or path of the shfmt executable can be configured via the
26+
shfmt_executable flag if the default of "shfmt" doesn't work.
27+
28+
:Glaive codefmt shfmt_executable='myshfmt'
29+
:FormatCode shfmt
30+
! myshfmt .*
31+
$ f()
32+
:Glaive codefmt shfmt_executable='shfmt'
33+
34+
35+
You can format any buffer with shfmt specifying the formatter explicitly.
36+
37+
@clear
38+
% if [ $print_hello_world -eq 1 ]; then echo "hello"; echo "world"; fi
39+
40+
:FormatCode shfmt
41+
! shfmt .*2>.*
42+
$ if [ $print_hello_world -eq 1 ]; then
43+
$ echo "hello"
44+
$ echo "world"
45+
$ fi
46+
if [ $print_hello_world -eq 1 ]; then
47+
echo "hello"
48+
echo "world"
49+
fi
50+
@end
51+
52+
The sh filetype will use the shfmt formatter by default.
53+
54+
@clear
55+
% f()
56+
57+
:set filetype=sh
58+
:FormatCode
59+
! shfmt .*
60+
$ f()
61+
62+
:set filetype=
63+
64+
It can format specific line ranges of code using :FormatLines.
65+
66+
@clear
67+
% foo() { echo "my name is:"; echo "foo"; }<CR>
68+
|bar() { echo "my name is:"; echo "bar"; }
69+
70+
:1,2FormatLines shfmt
71+
! shfmt .*2>.*
72+
$ foo() {
73+
$ echo "my name is:"
74+
$ echo "foo"
75+
$ }
76+
foo() {
77+
echo "my name is:"
78+
echo "foo"
79+
}
80+
bar() { echo "my name is:"; echo "bar"; }
81+
@end
82+
83+
NOTE: the shfmt formatter does not natively support range formatting, so there
84+
are certain limitations like not being able to format misaligned braces.

0 commit comments

Comments
 (0)