blob: ad94c7e65c0d8a6d8aa8972537d4806f8b8986ae [file]
python3 << EndPython3
import collections
import os
import sys
import vim
def strtobool(text):
if text.lower() in ["y", "yes", "t", "true", "on", "1"]:
return True
if text.lower() in ["n", "no", "f", "false", "off", "0"]:
return False
raise ValueError(f"{text} is not convertible to boolean")
class Flag(collections.namedtuple("FlagBase", "name, cast")):
@property
def var_name(self):
return self.name.replace("-", "_")
@property
def vim_rc_name(self):
name = self.var_name
if name == "line_length":
name = name.replace("_", "")
return "g:black_" + name
FLAGS = [
Flag(name="line_length", cast=int),
Flag(name="fast", cast=strtobool),
Flag(name="skip_string_normalization", cast=strtobool),
Flag(name="quiet", cast=strtobool),
Flag(name="skip_magic_trailing_comma", cast=strtobool),
Flag(name="preview", cast=strtobool),
]
def _get_python_binary(exec_prefix, pyver):
try:
default = vim.eval("g:pymode_python").strip()
except vim.error:
default = ""
if default and os.path.exists(default):
return default
if sys.platform[:3] == "win":
return exec_prefix / "python.exe"
bin_path = exec_prefix / "bin"
exec_path = (bin_path / f"python{pyver[0]}.{pyver[1]}").resolve()
if exec_path.exists():
return exec_path
# It is possible that some environments may only have python3
exec_path = (bin_path / "python3").resolve()
if exec_path.exists():
return exec_path
raise ValueError("python executable not found")
def _get_pip(venv_path):
if sys.platform[:3] == "win":
return venv_path / "Scripts" / "pip.exe"
return venv_path / "bin" / "pip"
def _get_virtualenv_site_packages(venv_path, pyver):
if sys.platform[:3] == "win":
return venv_path / "Lib" / "site-packages"
if (
venv_path.exists()
and not (venv_path / "lib" / f"python{pyver[0]}.{pyver[1]}").exists()
):
# The virtualenv already exists but it doesn't seem to have the expected
# Python version, so we disregard the requested `pyver` and
# discover the real Python interpreter from this virtualenv
import subprocess
result = subprocess.run(
[_get_python_binary(venv_path, pyver), "--version"],
stdout=subprocess.PIPE,
text=True,
)
venv_version = result.stdout.split(" ")[1].strip().split(".")
return (
venv_path
/ "lib"
/ f"python{venv_version[0]}.{venv_version[1]}"
/ "site-packages"
)
else:
return venv_path / "lib" / f"python{pyver[0]}.{pyver[1]}" / "site-packages"
def _initialize_black_env(upgrade=False):
if vim.eval("g:black_use_virtualenv ? 'true' : 'false'") == "false":
if upgrade:
print("Upgrade disabled due to g:black_use_virtualenv being disabled.")
print(
"Either use your system package manager (or pip) to upgrade black"
" separately,"
)
print("or modify your vimrc to have 'let g:black_use_virtualenv = 1'.")
return False
else:
# Nothing needed to be done.
return True
pyver = sys.version_info[:3]
if pyver < (3, 10):
print("Sorry, Black requires Python 3.10+ to run.")
return False
from pathlib import Path
import subprocess
import venv
virtualenv_path = Path(vim.eval("g:black_virtualenv")).expanduser()
virtualenv_site_packages = str(
_get_virtualenv_site_packages(virtualenv_path, pyver)
)
first_install = False
if not virtualenv_path.is_dir():
print("Please wait, one time setup for Black.")
_executable = sys.executable
_base_executable = getattr(sys, "_base_executable", _executable)
try:
executable = str(_get_python_binary(Path(sys.exec_prefix), pyver))
sys.executable = executable
sys._base_executable = executable
print(f"Creating a virtualenv in {virtualenv_path}...")
print(
"(this path can be customized in .vimrc by setting g:black_virtualenv)"
)
venv.create(virtualenv_path, with_pip=True)
except Exception:
print(
"Encountered exception while creating virtualenv (see traceback below)."
)
print(f"Removing {virtualenv_path}...")
import shutil
shutil.rmtree(virtualenv_path)
raise
finally:
sys.executable = _executable
sys._base_executable = _base_executable
first_install = True
if first_install:
print("Installing Black with pip...")
if upgrade:
print("Upgrading Black with pip...")
if first_install or upgrade:
subprocess.run(
[str(_get_pip(virtualenv_path)), "install", "-U", "black"],
stdout=subprocess.PIPE,
)
print("DONE! You are all set, thanks for waiting ✨ 🍰 ✨")
if first_install:
print(
"Pro-tip: to upgrade Black in the future, use the :BlackUpgrade command and"
" restart Vim.\n"
)
if virtualenv_site_packages not in sys.path:
sys.path.insert(0, virtualenv_site_packages)
return True
if _initialize_black_env():
try:
import black
except ImportError:
print(f"Could not import black from any of: {', '.join(sys.path)}.")
import time
def get_target_version(tv):
if isinstance(tv, black.TargetVersion):
return tv
ret = None
try:
ret = black.TargetVersion[tv.upper()]
except KeyError:
print(
f"WARNING: Target version {tv!r} not recognized by Black, using default"
" target"
)
return ret
def Black(**kwargs):
"""
kwargs allows you to override ``target_versions`` argument of
``black.FileMode``.
``target_version`` needs to be cleaned because ``black.FileMode``
expects the ``target_versions`` argument to be a set of TargetVersion enums.
Allow kwargs["target_version"] to be a string to allow
to type it more quickly.
Using also target_version instead of target_versions to remain
consistent to Black's documentation of the structure of pyproject.toml.
"""
start = time.time()
configs = get_configs()
black_kwargs = {}
if "target_version" in kwargs:
target_version = kwargs["target_version"]
if not isinstance(target_version, (list, set)):
target_version = [target_version]
target_version = set(
filter(lambda x: x, map(lambda tv: get_target_version(tv), target_version))
)
black_kwargs["target_versions"] = target_version
mode = black.FileMode(
line_length=configs["line_length"],
string_normalization=not configs["skip_string_normalization"],
is_pyi=vim.current.buffer.name.endswith(".pyi"),
magic_trailing_comma=not configs["skip_magic_trailing_comma"],
preview=configs["preview"],
**black_kwargs,
)
quiet = configs["quiet"]
buffer_str = "\n".join(vim.current.buffer) + "\n"
try:
new_buffer_str = black.format_file_contents(
buffer_str,
fast=configs["fast"],
mode=mode,
)
except black.NothingChanged:
if not quiet:
print(
"Black: already well formatted, good job. (took"
f" {time.time() - start:.4f}s)"
)
except Exception as exc:
print(f"Black: {exc}")
else:
current_buffer = vim.current.window.buffer
cursors = []
for i, tabpage in enumerate(vim.tabpages):
if tabpage.valid:
for j, window in enumerate(tabpage.windows):
if window.valid and window.buffer == current_buffer:
cursors.append((i, j, window.cursor))
vim.current.buffer[:] = new_buffer_str.split("\n")[:-1]
for i, j, cursor in cursors:
window = vim.tabpages[i].windows[j]
try:
window.cursor = cursor
except vim.error:
window.cursor = (len(window.buffer), 0)
if not quiet:
print(f"Black: reformatted in {time.time() - start:.4f}s.")
def get_configs():
filename = vim.eval("@%")
path_pyproject_toml = black.find_pyproject_toml((filename,))
if path_pyproject_toml:
toml_config = black.parse_pyproject_toml(path_pyproject_toml)
else:
toml_config = {}
return {
flag.var_name: toml_config.get(flag.name, flag.cast(vim.eval(flag.vim_rc_name)))
for flag in FLAGS
}
def BlackUpgrade():
_initialize_black_env(upgrade=True)
def BlackVersion():
print(f"Black, version {black.__version__} on Python {sys.version}.")
EndPython3
function black#Black(...)
let kwargs = {}
for arg in a:000
let arg_list = split(arg, '=')
let kwargs[arg_list[0]] = arg_list[1]
endfor
python3 << EOF
import vim
kwargs = vim.eval("kwargs")
EOF
:py3 Black(**kwargs)
endfunction
function black#BlackUpgrade()
:py3 BlackUpgrade()
endfunction
function black#BlackVersion()
:py3 BlackVersion()
endfunction