vim-autoformat/plugin/autoformat.vim

353 lines
12 KiB
VimL
Raw Normal View History

" Function for finding the formatters for this filetype
" Result is stored in b:formatters
2015-12-14 03:41:01 -05:00
if !exists('g:autoformat_autoindent')
let g:autoformat_autoindent = 1
endif
function! s:find_formatters(...)
" Detect verbosity
2015-12-15 07:15:05 -05:00
let verbose = &verbose || g:autoformat_verbosemode == 1
" Extract filetype to be used
let ftype = a:0 ? a:1 : &filetype
" Support composite filetypes by replacing dots with underscores
let compoundtype = substitute(ftype, "[.]", "_", "g")
2016-02-10 22:49:16 -05:00
if ftype =~? "[.]"
2015-06-06 05:54:30 -04:00
" Try all super filetypes in search for formatters in a sane order
let ftypes = [compoundtype] + split(ftype, "[.]")
else
let ftypes = [compoundtype]
endif
2015-05-30 15:00:16 -04:00
" Warn for backward incompatible configuration
let old_formatprg_var = "g:formatprg_".compoundtype
let old_formatprg_args_var = "g:formatprg_args_".compoundtype
let old_formatprg_args_expr_var = "g:formatprg_args_expr_".compoundtype
2015-05-30 15:00:16 -04:00
if exists(old_formatprg_var) || exists(old_formatprg_args_var) || exists(old_formatprg_args_expr_var)
2015-06-06 05:54:30 -04:00
echohl WarningMsg |
\ echomsg "WARNING: the options g:formatprg_<filetype>, g:formatprg_args_<filetype> and g:formatprg_args_expr_<filetype> are no longer supported as of June 2015, due to major backward-incompatible improvements. Please check the README for help on how to configure your formatters." |
\ echohl None
2015-05-30 15:00:16 -04:00
endif
2015-06-06 05:54:30 -04:00
" Detect configuration for all possible ftypes
let b:formatters = []
2015-06-06 05:54:30 -04:00
for supertype in ftypes
let formatters_var = "b:formatters_".supertype
if !exists(formatters_var)
let formatters_var = "g:formatters_".supertype
endif
if !exists(formatters_var)
" No formatters defined
if verbose
2016-02-10 22:49:16 -05:00
echoerr "No formatters defined for supertype ".supertype
endif
else
let formatters = eval(formatters_var)
2016-02-10 22:49:16 -05:00
if type(formatters) != type([])
echoerr formatters_var." is not a list"
else
let b:formatters = b:formatters + formatters
endif
endif
endfor
2013-03-14 13:50:31 -04:00
if len(b:formatters) == 0
" No formatters defined
if verbose
echoerr "No formatters defined for filetype '".ftype."'."
2013-03-14 13:50:31 -04:00
endif
2013-12-20 14:34:38 -05:00
return 0
2013-03-14 13:50:31 -04:00
endif
return 1
endfunction
" Try all formatters, starting with the currently selected one, until one
" works. If none works, autoindent the buffer.
2015-05-30 05:02:07 -04:00
function! s:TryAllFormatters(...) range
" Detect verbosity
2016-02-18 04:13:07 -05:00
let verbose = &verbose || g:autoformat_verbosemode == 1
" Make sure formatters are defined and detected
if !call('<SID>find_formatters', a:000)
2015-12-15 07:15:05 -05:00
" No formatters defined
if verbose
echomsg "No format definitions are defined for this filetype."
2015-12-14 03:41:01 -05:00
endif
call s:Fallback()
return 0
endif
" Make sure index exist and is valid
if !exists('b:current_formatter_index')
let b:current_formatter_index = 0
endif
if b:current_formatter_index >= len(b:formatters)
let b:current_formatter_index = 0
endif
" Try all formatters, starting with selected one
let s:index = b:current_formatter_index
while 1
" Formatter definition must be existent
let formatdef_var = 'b:formatdef_'.b:formatters[s:index]
if !exists(formatdef_var)
let formatdef_var = 'g:formatdef_'.b:formatters[s:index]
endif
if !exists(formatdef_var)
echoerr "No format definition found in '".formatdef_var."'."
return 0
endif
" Eval twice, once for getting definition content,
" once for getting the final expression
let b:formatprg = eval(eval(formatdef_var))
2015-06-19 17:17:31 -04:00
2017-06-26 03:29:22 -04:00
if verbose
echomsg "Trying definition from ".formatdef_var
echomsg "Evaluated formatprg: ".b:formatprg
endif
2015-06-22 03:18:49 -04:00
" Detect if +python or +python3 is available, and call the corresponding function
2015-06-19 17:17:31 -04:00
if !has("python") && !has("python3")
echohl WarningMsg |
\ echomsg "WARNING: vim has no support for python, but it is required to run the formatter!" |
\ echohl None
return 1
endif
if has("python3")
if verbose
echomsg "Using python 3 code..."
endif
2015-06-19 17:17:31 -04:00
let success = s:TryFormatterPython3()
else
if verbose
echomsg "Using python 2 code..."
endif
let success = s:TryFormatterPython()
2015-06-19 17:17:31 -04:00
endif
if success == 0
if verbose
2016-02-18 04:13:07 -05:00
echomsg "Definition in '".formatdef_var."' was successful."
endif
return 1
else
if verbose
2016-02-18 04:13:07 -05:00
echomsg "Definition in '".formatdef_var."' was unsuccessful."
endif
let s:index = (s:index + 1) % len(b:formatters)
endif
if s:index == b:current_formatter_index
if verbose
2016-02-18 04:13:07 -05:00
echomsg "No format definitions were successful."
2015-12-23 11:01:37 -05:00
endif
2015-12-15 07:15:05 -05:00
" Tried all formatters, none worked
call s:Fallback()
return 0
endif
endwhile
endfunction
function! s:Fallback()
" Detect verbosity
let verbose = &verbose || g:autoformat_verbosemode == 1
if exists('b:autoformat_remove_trailing_spaces') ? b:autoformat_remove_trailing_spaces == 1 : g:autoformat_remove_trailing_spaces == 1
if verbose
echomsg "Removing trailing whitespace..."
endif
call s:RemoveTrailingSpaces()
endif
if exists('b:autoformat_retab') ? b:autoformat_retab == 1 : g:autoformat_retab == 1
if verbose
echomsg "Retabbing..."
endif
retab
endif
if exists('b:autoformat_autoindent') ? b:autoformat_autoindent == 1 : g:autoformat_autoindent == 1
if verbose
echomsg "Autoindenting..."
endif
" Autoindent code
exe "normal gg=G"
endif
2013-03-15 18:12:09 -04:00
endfunction
" Call formatter
" If stderr is empty, apply result, return 0
" Otherwise, return 1
2015-06-22 03:18:49 -04:00
2015-06-19 17:17:31 -04:00
" +python version
function! s:TryFormatterPython()
" Detect verbosity
2015-12-15 07:15:05 -05:00
let verbose = &verbose || g:autoformat_verbosemode == 1
2013-03-14 13:50:31 -04:00
python << EOF
import vim, subprocess, os
from subprocess import Popen, PIPE
text = os.linesep.join(vim.current.buffer[:]) + os.linesep
formatprg = vim.eval('b:formatprg')
verbose = bool(int(vim.eval('verbose')))
env = os.environ.copy()
if int(vim.eval('exists("g:formatterpath")')):
extra_path = vim.eval('g:formatterpath')
env['PATH'] = ':'.join(extra_path) + ':' + env['PATH']
# When an entry is unicode, Popen can't deal with it in Python 2.
# As a pragmatic fix, we'll omit that entry.
2016-09-07 04:49:52 -04:00
newenv = {}
for key,val in env.iteritems():
if type(key) == type(val) == str:
newenv[key] = val
env=newenv
p = subprocess.Popen(formatprg, env=env, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdoutdata, stderrdata = p.communicate(text)
if stderrdata:
if verbose:
formattername = vim.eval('b:formatters[s:index]')
2017-06-26 03:29:22 -04:00
print('Formatter {} has errors: {}'.format(formattername, stderrdata))
vim.command('return 1')
else:
# It is not certain what kind of line endings are being used by the format program.
# Therefore we simply split on all possible eol characters.
possible_eols = ['\r\n', os.linesep, '\r', '\n']
# Often shell commands will append a newline at the end of their output.
# It is not entirely clear when and why that happens.
# However, extra newlines are almost never required, while there are linters that complain
# about superfluous newlines, so we remove one empty newline at the end of the file.
for eol in possible_eols:
if len(stdoutdata) > 0 and stdoutdata[-1] == eol:
stdoutdata = stdoutdata[:-1]
lines = [stdoutdata]
for eol in possible_eols:
lines = [splitline for line in lines for splitline in line.split(eol)]
vim.current.buffer[:] = lines
EOF
return 0
2013-03-14 13:50:31 -04:00
endfunction
2015-06-19 17:17:31 -04:00
" +python3 version
function! s:TryFormatterPython3()
" Detect verbosity
2015-12-15 07:15:05 -05:00
let verbose = &verbose || g:autoformat_verbosemode == 1
2015-06-19 17:17:31 -04:00
python3 << EOF
import vim, subprocess, os
from subprocess import Popen, PIPE
# The return code is `failure`, unless otherwise specified
vim.command('return 1')
text = bytes(os.linesep.join(vim.current.buffer[:]) + os.linesep, 'utf-8')
formatprg = vim.eval('b:formatprg')
2015-06-19 17:17:31 -04:00
verbose = bool(int(vim.eval('verbose')))
env = os.environ.copy()
if int(vim.eval('exists("g:formatterpath")')):
extra_path = vim.eval('g:formatterpath')
env['PATH'] = ':'.join(extra_path) + ':' + env['PATH']
try:
p = subprocess.Popen(formatprg, env=env, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdoutdata, stderrdata = p.communicate(text)
except (BrokenPipeError, IOError):
2015-06-19 17:17:31 -04:00
if verbose:
raise
2015-06-19 17:17:31 -04:00
else:
if stderrdata:
if verbose:
formattername = vim.eval('b:formatters[s:index]')
2017-06-26 03:29:22 -04:00
print('Formatter {} has errors: {}'.format(formattername, stderrdata))
elif not stdoutdata:
if verbose:
2017-06-26 03:29:22 -04:00
print('Formatter {} gives empty result: {}'.format(formattername, stderrdata))
else:
# It is not certain what kind of line endings are being used by the format program.
# Therefore we simply split on all possible eol characters.
possible_eols = ['\r\n', os.linesep, '\r', '\n']
stdoutdata = stdoutdata.decode('utf-8')
# Often shell commands will append a newline at the end of their output.
# It is not entirely clear when and why that happens.
# However, extra newlines are almost never required, while there are linters that complain
# about superfluous newlines, so we remove one empty newline at the end of the file.
for eol in possible_eols:
if len(stdoutdata) > 0 and stdoutdata[-1] == eol:
stdoutdata = stdoutdata[:-1]
lines = [stdoutdata]
for eol in possible_eols:
lines = [splitline for line in lines for splitline in line.split(eol)]
vim.current.buffer[:] = lines
vim.command('return 0')
2015-06-19 17:17:31 -04:00
EOF
endfunction
" Create a command for formatting the entire buffer
" Save and recall window state to prevent vim from jumping to line 1
command! -nargs=? -range=% -complete=filetype -bar Autoformat let winview=winsaveview()|<line1>,<line2>call s:TryAllFormatters(<f-args>)|call winrestview(winview)
" Functions for iterating through list of available formatters
function! s:NextFormatter()
call s:find_formatters()
if !exists('b:current_formatter_index')
let b:current_formatter_index = 0
endif
let b:current_formatter_index = (b:current_formatter_index + 1) % len(b:formatters)
2016-02-18 04:13:07 -05:00
echomsg 'Selected formatter: '.b:formatters[b:current_formatter_index]
endfunction
function! s:PreviousFormatter()
call s:find_formatters()
if !exists('b:current_formatter_index')
let b:current_formatter_index = 0
endif
let l = len(b:formatters)
let b:current_formatter_index = (b:current_formatter_index - 1 + l) % l
2016-02-18 04:13:07 -05:00
echomsg 'Selected formatter: '.b:formatters[b:current_formatter_index]
endfunction
function! s:CurrentFormatter()
call s:find_formatters()
if !exists('b:current_formatter_index')
let b:current_formatter_index = 0
endif
2016-02-18 04:13:07 -05:00
echomsg 'Selected formatter: '.b:formatters[b:current_formatter_index]
endfunction
" Create commands for iterating through formatter list
command! NextFormatter call s:NextFormatter()
command! PreviousFormatter call s:PreviousFormatter()
command! CurrentFormatter call s:CurrentFormatter()
" Other commands
function! s:RemoveTrailingSpaces()
let user_gdefault = &gdefault
try
set nogdefault
silent! %s/\s\+$
finally
let &gdefault = user_gdefault
endtry
endfunction
command! RemoveTrailingSpaces call s:RemoveTrailingSpaces()
" Put the uncopyable messages text into the buffer
command! PutMessages redir @" | messages | redir END | put