]> git.madduck.net Git - etc/vim.git/blob - plugin/black.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Improve error messages from BlackRunner
[etc/vim.git] / plugin / black.vim
1 " black.vim
2 " Author: Łukasz Langa
3 " Created: Mon Mar 26 23:27:53 2018 -0700
4 " Requires: Vim Ver7.0+
5 " Version:  1.1
6 "
7 " Documentation:
8 "   This plugin formats Python files.
9 "
10 " History:
11 "  1.0:
12 "    - initial version
13 "  1.1:
14 "    - restore cursor/window position after formatting
15
16 if v:version < 700 || !has('python3')
17     func! __BLACK_MISSING()
18         echo "The black.vim plugin requires vim7.0+ with Python 3.6 support."
19     endfunc
20     command! Black :call __BLACK_MISSING()
21     command! BlackUpgrade :call __BLACK_MISSING()
22     command! BlackVersion :call __BLACK_MISSING()
23     finish
24 endif
25
26 if exists("g:load_black")
27    finish
28 endif
29
30 let g:load_black = "py1.0"
31 if !exists("g:black_virtualenv")
32   if has("nvim")
33     let g:black_virtualenv = "~/.local/share/nvim/black"
34   else
35     let g:black_virtualenv = "~/.vim/black"
36   endif
37 endif
38 if !exists("g:black_fast")
39   let g:black_fast = 0
40 endif
41 if !exists("g:black_linelength")
42   let g:black_linelength = 88
43 endif
44 if !exists("g:black_skip_string_normalization")
45   let g:black_skip_string_normalization = 0
46 endif
47
48 python3 << EndPython3
49 import collections
50 import os
51 import sys
52 import vim
53
54
55 class Flag(collections.namedtuple("FlagBase", "name, cast")):
56   @property
57   def var_name(self):
58     return self.name.replace("-", "_")
59
60   @property
61   def vim_rc_name(self):
62     name = self.var_name
63     if name == "line_length":
64       name = name.replace("_", "")
65     if name == "string_normalization":
66       name = "skip_" + name
67     return "g:black_" + name
68
69
70 FLAGS = [
71   Flag(name="line_length", cast=int),
72   Flag(name="fast", cast=bool),
73   Flag(name="string_normalization", cast=bool),
74 ]
75
76
77 def _get_python_binary(exec_prefix):
78   try:
79     default = vim.eval("g:pymode_python").strip()
80   except vim.error:
81     default = ""
82   if default and os.path.exists(default):
83     return default
84   if sys.platform[:3] == "win":
85     return exec_prefix / 'python.exe'
86   return exec_prefix / 'bin' / 'python3'
87
88 def _get_pip(venv_path):
89   if sys.platform[:3] == "win":
90     return venv_path / 'Scripts' / 'pip.exe'
91   return venv_path / 'bin' / 'pip'
92
93 def _get_virtualenv_site_packages(venv_path, pyver):
94   if sys.platform[:3] == "win":
95     return venv_path / 'Lib' / 'site-packages'
96   return venv_path / 'lib' / f'python{pyver[0]}.{pyver[1]}' / 'site-packages'
97
98 def _initialize_black_env(upgrade=False):
99   pyver = sys.version_info[:2]
100   if pyver < (3, 6):
101     print("Sorry, Black requires Python 3.6+ to run.")
102     return False
103
104   from pathlib import Path
105   import subprocess
106   import venv
107   virtualenv_path = Path(vim.eval("g:black_virtualenv")).expanduser()
108   virtualenv_site_packages = str(_get_virtualenv_site_packages(virtualenv_path, pyver))
109   first_install = False
110   if not virtualenv_path.is_dir():
111     print('Please wait, one time setup for Black.')
112     _executable = sys.executable
113     _base_executable = getattr(sys, "_base_executable", _executable)
114     try:
115       executable = str(_get_python_binary(Path(sys.exec_prefix)))
116       sys.executable = executable
117       sys._base_executable = executable
118       print(f'Creating a virtualenv in {virtualenv_path}...')
119       print('(this path can be customized in .vimrc by setting g:black_virtualenv)')
120       venv.create(virtualenv_path, with_pip=True)
121     except Exception:
122       print('Encountered exception while creating virtualenv (see traceback below).')
123       print(f'Removing {virtualenv_path}...')
124       import shutil
125       shutil.rmtree(virtualenv_path)
126       raise
127     finally:
128       sys.executable = _executable
129       sys._base_executable = _base_executable
130     first_install = True
131   if first_install:
132     print('Installing Black with pip...')
133   if upgrade:
134     print('Upgrading Black with pip...')
135   if first_install or upgrade:
136     subprocess.run([str(_get_pip(virtualenv_path)), 'install', '-U', 'black'], stdout=subprocess.PIPE)
137     print('DONE! You are all set, thanks for waiting ✨ 🍰 ✨')
138   if first_install:
139     print('Pro-tip: to upgrade Black in the future, use the :BlackUpgrade command and restart Vim.\n')
140   if virtualenv_site_packages not in sys.path:
141     sys.path.insert(0, virtualenv_site_packages)
142   return True
143
144 if _initialize_black_env():
145   import black
146   import time
147
148 def Black():
149   start = time.time()
150   configs = get_configs()
151   mode = black.FileMode(
152     line_length=configs["line_length"],
153     string_normalization=configs["string_normalization"],
154     is_pyi=vim.current.buffer.name.endswith('.pyi'),
155   )
156
157   buffer_str = '\n'.join(vim.current.buffer) + '\n'
158   try:
159     new_buffer_str = black.format_file_contents(
160       buffer_str,
161       fast=configs["fast"],
162       mode=mode,
163     )
164   except black.NothingChanged:
165     print(f'Already well formatted, good job. (took {time.time() - start:.4f}s)')
166   except Exception as exc:
167     print(exc)
168   else:
169     current_buffer = vim.current.window.buffer
170     cursors = []
171     for i, tabpage in enumerate(vim.tabpages):
172       if tabpage.valid:
173         for j, window in enumerate(tabpage.windows):
174           if window.valid and window.buffer == current_buffer:
175             cursors.append((i, j, window.cursor))
176     vim.current.buffer[:] = new_buffer_str.split('\n')[:-1]
177     for i, j, cursor in cursors:
178       window = vim.tabpages[i].windows[j]
179       try:
180         window.cursor = cursor
181       except vim.error:
182         window.cursor = (len(window.buffer), 0)
183     print(f'Reformatted in {time.time() - start:.4f}s.')
184
185 def get_configs():
186   path_pyproject_toml = black.find_pyproject_toml(vim.eval("fnamemodify(getcwd(), ':t')"))
187   if path_pyproject_toml:
188     toml_config = black.parse_pyproject_toml(path_pyproject_toml)
189   else:
190     toml_config = {}
191
192   return {
193     flag.var_name: toml_config.get(flag.name, flag.cast(vim.eval(flag.vim_rc_name)))
194     for flag in FLAGS
195   }
196
197
198 def BlackUpgrade():
199   _initialize_black_env(upgrade=True)
200
201 def BlackVersion():
202   print(f'Black, version {black.__version__} on Python {sys.version}.')
203
204 EndPython3
205
206 command! Black :py3 Black()
207 command! BlackUpgrade :py3 BlackUpgrade()
208 command! BlackVersion :py3 BlackVersion()