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
|
||||
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
|
||||
Pack 'kana/vim-textobj-user'
|
||||
" vim-textobj-entire - Entire file text object
|
||||
|
Loading…
x
Reference in New Issue
Block a user