For various reasons I don't really like black even if it's very good at what it does.
The readability of a Django model with aligned fields vs after running black is a good example of why I'd never use it by default.
Anyhow…
It would be nice to use black
occasionally on snippets of code from
inside my editor (vim
) but that almost never works as the first line
is already indented.
For example:
# note ꜜ space
$ echo " foo( )" | black -q -
foo()
error: cannot format -: cannot use --safe with this file; failed to parse source file. AST error message: unexpected indent (<unknown>, line 1)
# vs no leading space
$ echo "foo( )" | black -q -
foo()
So what to do? There doesn't appear to be any command line switch to say
“ignore leading spaces” so I guess we need to wrap our call to black
, enter
black_wrap
. I was tempted to call this tupac
or drdre
after some great
rappers but I'm not that cool.
$ echo " foo( )" | black_wrap
foo()
# leading space is preserved
Anyhow… here's the code.
#!/usr/bin/env python
"""
* read from stdin
* if there is leading whitespace then dedent it
* call black
* replace leading whitespace if applicable
"""
import sys
import textwrap
import subprocess
def dedented(text):
indent_chars = ""
_text = textwrap.dedent(text)
for c in text:
if c == _text[0]:
break # no longer whitespace
else:
indent_chars += c
return _text, indent_chars
def black(text):
cmd = ["black", "-q", "-"]
proc = subprocess.run(cmd, input=text.encode(), capture_output=True, check=False)
if proc.returncode != 0:
sys.stderr.write(proc.stderr.decode())
sys.exit(proc.returncode)
return proc.stdout.decode()
def main():
text = sys.stdin.read()
_text, indent_chars = dedented(text)
blacked = black(_text)
blacked = textwrap.indent(blacked, indent_chars)
sys.stdout.write(blacked)
if __name__ == "__main__":
main()
I'm using pappasam/vim-filetype-formatter
as my vim
plugin to call
out to black_wrap
, here's a snippet of my .vimrc
Plugin 'pappasam/vim-filetype-formatter'
let g:vim_filetype_formatter_commands = {
\ 'python': 'black_wrap',
\ 'json': 'python -m json.tool --indent=2 -',
\ }
nnoremap <silent> <f4> :FiletypeFormat<cr>
vnoremap <silent> <f4> :FiletypeFormat<cr>
So I just hit F4
then entire file is formatted via black
or I can
select some text (that might be in the middle of a function and already
indented) and hit F4
and just those few lines get formatted.