Move formatexpr config in-tree, out of plugin
Introduces the `format` Python module which provides `clang_format()` and `yapf()` functions which efficiently (compared to vimscript) invoke `clang-format` or `yapf` respectively then apply the minimal number of changes using `difflib.SequenceMatcher`. Additionally, in order to invoke these Python functions add |autoload| functions `format#clang_format()` and `format#yapf()` which can be directly used by the 'formatexpr' setting. Finally, add |ftplugin| files which set 'formatexpr' when the |autoload| functions are available.
This commit is contained in:
parent
4e00e225d4
commit
46d27c17fd
14
autoload/format.vim
Normal file
14
autoload/format.vim
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
if !has('pythonx')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
" set debug=msg,throw
|
||||||
|
pythonx import format
|
||||||
|
|
||||||
|
function! format#clang_format() abort
|
||||||
|
pythonx format.clang_format()
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! format#yapf() abort
|
||||||
|
pythonx format.yapf()
|
||||||
|
endfunction
|
3
ftplugin/c.vim
Normal file
3
ftplugin/c.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/cpp.vim
Normal file
3
ftplugin/cpp.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/java.vim
Normal file
3
ftplugin/java.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/javascript.vim
Normal file
3
ftplugin/javascript.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/objc.vim
Normal file
3
ftplugin/objc.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/objcpp.vim
Normal file
3
ftplugin/objcpp.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/proto.vim
Normal file
3
ftplugin/proto.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#clang_format()
|
||||||
|
endif
|
3
ftplugin/python.vim
Normal file
3
ftplugin/python.vim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
if has('pythonx')
|
||||||
|
set formatexpr=format#yapf()
|
||||||
|
endif
|
8
plugin/format.vim
Normal file
8
plugin/format.vim
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
if !has('pythonx')
|
||||||
|
finish
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:clang_format_path = get(g:, 'clang_format_path', 'clang-format')
|
||||||
|
let g:clang_format_style = get(g:, 'clang_format_style', 'google')
|
||||||
|
let g:yapf_path = get(g:, 'yapf_path', 'yapf')
|
||||||
|
let g:yapf_style = get(g:, 'yapf_style', 'pep8')
|
117
pythonx/format.py
Normal file
117
pythonx/format.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
"""Python formatexpr for clang-format & yapf"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import difflib
|
||||||
|
|
||||||
|
import vim
|
||||||
|
|
||||||
|
|
||||||
|
class FormatError(Exception):
|
||||||
|
"""A formatting error."""
|
||||||
|
|
||||||
|
|
||||||
|
def clang_format():
|
||||||
|
"""Call clang-format on the current text object."""
|
||||||
|
clang_format = vim.vars['clang_format_path']
|
||||||
|
# TODO: The cursor position before gq is invoked is not preserved in the
|
||||||
|
# jump list, this expression will return the cursor position at the
|
||||||
|
# beginning of the motion or text object.
|
||||||
|
# Is there a way to get the cursor position immediately before gq is
|
||||||
|
# invoked? So that we can pass the position to clang-format in order for
|
||||||
|
# it to return the updated position to continue editing.
|
||||||
|
# Query the current absolute cursor positon.
|
||||||
|
cursor = int(vim.eval('line2byte(".") + col(".")')) - 2
|
||||||
|
if cursor < 0:
|
||||||
|
return
|
||||||
|
# Determine the lines to format.
|
||||||
|
startline = int(vim.eval('v:lnum'))
|
||||||
|
endline = startline + int(vim.eval('v:count')) - 1
|
||||||
|
lines = f'{startline}:{endline}'
|
||||||
|
fallback_style = vim.vars['clang_format_style']
|
||||||
|
# Construct the clang-format command to call.
|
||||||
|
command = [
|
||||||
|
clang_format, '-style', 'file', '-cursor',
|
||||||
|
str(cursor), '-lines', lines, '-fallback-style', fallback_style
|
||||||
|
]
|
||||||
|
if vim.current.buffer.name:
|
||||||
|
command += ['-assume-filename', vim.current.buffer.name]
|
||||||
|
# Call the clang-format command.
|
||||||
|
output = call(command)
|
||||||
|
if not output:
|
||||||
|
return
|
||||||
|
# Read the clang-format json header.
|
||||||
|
header = json.loads(output[0])
|
||||||
|
if header.get('IncompleteFormat'):
|
||||||
|
raise FormatError('clang-format: incomplete (syntax errors).')
|
||||||
|
# Replace the formatted regions.
|
||||||
|
replace_regions(output[1:])
|
||||||
|
# Set the updated cursor position.
|
||||||
|
vim.command('goto %d' % (header['Cursor'] + 1))
|
||||||
|
|
||||||
|
|
||||||
|
def yapf():
|
||||||
|
"""Call yapf on the current text object."""
|
||||||
|
yapf = vim.vars['yapf_path']
|
||||||
|
# Determine the lines to format.
|
||||||
|
start = int(vim.eval('v:lnum'))
|
||||||
|
end = start + int(vim.eval('v:count'))
|
||||||
|
lines = '{0}-{1}'.format(start, end)
|
||||||
|
style = vim.vars['yapf_style']
|
||||||
|
# Construct the clang-format command to call.
|
||||||
|
command = [yapf, '--style', style, '--lines', lines]
|
||||||
|
# TODO: Since yapf is a Python package, we could import the module and
|
||||||
|
# call it directly instead. It would then be possible to output better
|
||||||
|
# error messages and act on the buffer directly.
|
||||||
|
# Call the yapf command.
|
||||||
|
output = call(command)
|
||||||
|
if not output:
|
||||||
|
return
|
||||||
|
# Replace the formatted regions.
|
||||||
|
replace_regions(output[:-1])
|
||||||
|
|
||||||
|
|
||||||
|
def call(command):
|
||||||
|
"""Call the command to format the text.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
command (list): Formatting command to call.
|
||||||
|
text (str): Text to be passed to stdin of command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: The output of the formatter split on new lines.
|
||||||
|
None: If the subprocess failed.
|
||||||
|
"""
|
||||||
|
# Don't open a cmd prompt window on Windows.
|
||||||
|
startupinfo = None
|
||||||
|
if sys.platform.startswith('win32'):
|
||||||
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
|
startupinfo.wShowWindow = subprocess.SW_HIDE
|
||||||
|
# Call the formatting command.
|
||||||
|
process = subprocess.Popen(command,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
startupinfo=startupinfo)
|
||||||
|
stdout, stderr = process.communicate(
|
||||||
|
input='\n'.join(vim.current.buffer).encode('utf-8'))
|
||||||
|
if stderr:
|
||||||
|
raise FormatError(stderr)
|
||||||
|
if not stdout:
|
||||||
|
raise FormatError('No output from {0}.'.format(command[0]))
|
||||||
|
# Split the lines into a list of elements.
|
||||||
|
return stdout.decode('utf-8').split('\n')
|
||||||
|
|
||||||
|
|
||||||
|
def replace_regions(lines):
|
||||||
|
"""Replace formatted regions in the current buffer.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
lines (list): The formatted buffer lines.
|
||||||
|
"""
|
||||||
|
matcher = difflib.SequenceMatcher(None, vim.current.buffer, lines)
|
||||||
|
for tag, i1, i2, j1, j2 in reversed(matcher.get_opcodes()):
|
||||||
|
if tag != 'equal':
|
||||||
|
vim.current.buffer[i1:i2] = lines[j1:j2]
|
3
vimrc
3
vimrc
@ -59,9 +59,6 @@ let g:ale_cmake_cmakelint_options =
|
|||||||
" Version control differences in the sign column
|
" Version control differences in the sign column
|
||||||
Pack 'mhinz/vim-signify'
|
Pack 'mhinz/vim-signify'
|
||||||
|
|
||||||
" format.vim - format with text objects
|
|
||||||
Pack 'git@bitbucket.org:infektor/format.vim.git'
|
|
||||||
|
|
||||||
" vim-textobj-user - library for creating text objects
|
" vim-textobj-user - library for creating text objects
|
||||||
Pack 'kana/vim-textobj-user'
|
Pack 'kana/vim-textobj-user'
|
||||||
" vim-textobj-entire - Entire file text object
|
" vim-textobj-entire - Entire file text object
|
||||||
|
Loading…
x
Reference in New Issue
Block a user