1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 05:20:04 +08:00
SpaceVim/bundle/neoformat/autoload/neoformat.vim

355 lines
12 KiB
VimL

" Set global flag to allow checking in custom user config
let g:neoformat = 1
function! neoformat#Neoformat(bang, user_input, start_line, end_line) abort
let view = winsaveview()
let search = @/
let original_filetype = &filetype
call s:neoformat(a:bang, a:user_input, a:start_line, a:end_line)
" Setting &filetype might destroy existing folds, so only do that
" if the filetype got changed (which can only be possible when
" invoking with a bang)
if a:bang && &filetype != original_filetype
let &filetype = original_filetype
endif
let @/ = search
call winrestview(view)
endfunction
function! s:neoformat(bang, user_input, start_line, end_line) abort
if !&modifiable
return neoformat#utils#warn('buffer not modifiable')
endif
let using_visual_selection = a:start_line != 1 || a:end_line != line('$')
let inputs = split(a:user_input)
if a:bang
let &filetype = len(inputs) > 1 ? inputs[0] : a:user_input
endif
let filetype = s:split_filetypes(&filetype)
let using_user_passed_formatter = (!empty(a:user_input) && !a:bang)
\ || (len(inputs) > 1 && a:bang)
if using_user_passed_formatter
let formatters = len(inputs) > 1 ? [inputs[1]] : [a:user_input]
else
let formatters = s:get_enabled_formatters(filetype)
if formatters == []
call neoformat#utils#msg('formatter not defined for ' . filetype . ' filetype')
return s:basic_format()
endif
endif
let formatters_failed = []
let formatters_changed = []
for formatter in formatters
if &formatprg != '' && split(&formatprg)[0] ==# formatter
\ && neoformat#utils#var('neoformat_try_formatprg')
call neoformat#utils#log('using formatprg')
let fmt_prg_def = split(&formatprg)
let definition = {
\ 'exe': fmt_prg_def[0],
\ 'args': fmt_prg_def[1:],
\ 'stdin': 1,
\ }
elseif exists('b:neoformat_' . filetype . '_' . formatter)
let definition = b:neoformat_{filetype}_{formatter}
elseif exists('g:neoformat_' . filetype . '_' . formatter)
let definition = g:neoformat_{filetype}_{formatter}
elseif s:autoload_func_exists('neoformat#formatters#' . filetype . '#' . formatter)
let definition = neoformat#formatters#{filetype}#{formatter}()
else
call neoformat#utils#log('definition not found for formatter: ' . formatter)
if using_user_passed_formatter
call neoformat#utils#msg('formatter definition for ' . a:user_input . ' not found')
return s:basic_format()
endif
continue
endif
let cmd = s:generate_cmd(definition, filetype)
if cmd == {}
if using_user_passed_formatter
return neoformat#utils#warn('formatter ' . a:user_input . ' failed')
endif
continue
endif
let stdin = getbufline(bufnr('%'), a:start_line, a:end_line)
let original_buffer = getbufline(bufnr('%'), 1, '$')
call neoformat#utils#log(stdin)
call neoformat#utils#log(cmd.exe)
if cmd.stdin
call neoformat#utils#log('using stdin')
let stdin_str = join(stdin, "\n")
let stdout = split(system(cmd.exe, stdin_str), '\n')
else
call neoformat#utils#log('using tmp file')
call writefile(stdin, cmd.tmp_file_path)
let stdout = split(system(cmd.exe), '\n')
endif
" read from /tmp file if formatter replaces file on format
if cmd.replace
let stdout = readfile(cmd.tmp_file_path)
endif
call neoformat#utils#log(stdout)
call neoformat#utils#log(cmd.valid_exit_codes)
call neoformat#utils#log(v:shell_error)
let process_ran_succesfully = index(cmd.valid_exit_codes, v:shell_error) != -1
if cmd.stderr_log != ''
call neoformat#utils#log('stderr output redirected to file' . cmd.stderr_log)
call neoformat#utils#log_file_content(cmd.stderr_log)
endif
if process_ran_succesfully
" 1. append the lines that are before and after the formatterd content
let lines_after = getbufline(bufnr('%'), a:end_line + 1, '$')
let lines_before = getbufline(bufnr('%'), 1, a:start_line - 1)
let new_buffer = lines_before + stdout + lines_after
if new_buffer !=# original_buffer
call s:deletelines(len(new_buffer), line('$'))
call setline(1, new_buffer)
call add(formatters_changed, cmd.name)
let endmsg = cmd.name . ' formatted buffer'
else
let endmsg = 'no change necessary with ' . cmd.name
endif
if !neoformat#utils#var('neoformat_run_all_formatters')
return neoformat#utils#msg(endmsg)
endif
call neoformat#utils#log('running next formatter')
else
call add(formatters_failed, cmd.name)
call neoformat#utils#log(v:shell_error)
call neoformat#utils#log('trying next formatter')
endif
endfor
if len(formatters_failed) > 0
call neoformat#utils#msg('formatters ' . join(formatters_failed, ", ") . ' failed to run')
endif
if len(formatters_changed) > 0
call neoformat#utils#msg(join(formatters_changed, ", ") . ' formatted buffer')
elseif len(formatters_failed) == 0
call neoformat#utils#msg('no change necessary')
endif
endfunction
function! s:get_enabled_formatters(filetype) abort
if &formatprg != '' && neoformat#utils#var('neoformat_try_formatprg')
call neoformat#utils#log('adding formatprg to enabled formatters')
let format_prg_exe = [split(&formatprg)[0]]
else
let format_prg_exe = []
endif
" Note: we append format_prg_exe to ever return as it will either be
" [], or it will be a formatter that we want to try first
if exists('b:neoformat_enabled_' . a:filetype)
return format_prg_exe + b:neoformat_enabled_{a:filetype}
elseif exists('g:neoformat_enabled_' . a:filetype)
return format_prg_exe + g:neoformat_enabled_{a:filetype}
elseif s:autoload_func_exists('neoformat#formatters#' . a:filetype . '#enabled')
return format_prg_exe + neoformat#formatters#{a:filetype}#enabled()
endif
return format_prg_exe
endfunction
function! s:deletelines(start, end) abort
silent! execute a:start . ',' . a:end . 'delete _'
endfunction
function! neoformat#CompleteFormatters(ArgLead, CmdLine, CursorPos) abort
if a:CmdLine =~ '!'
" 1. user inputting formatter :Neoformat! css <here>
if a:CmdLine =~# 'Neoformat! \S*\s'
let filetype = split(a:CmdLine)[1]
return filter(s:get_enabled_formatters(filetype),
\ "v:val =~? '^" . a:ArgLead ."'")
endif
" 2. user inputting filetype :Neoformat! <here>
" https://github.com/junegunn/fzf.vim/pull/110
" 1. globpath (find) all filetype files in neoformat
" 2. split at new lines
" 3. map ~/.config/nvim/plugged/neoformat/autoload/neoformat/formatters/xml.vim --> xml
" 4. sort & uniq to eliminate dupes
" 5. filter for input
return filter(uniq(sort(map(split(globpath(&runtimepath,
\ 'plugged/neoformat/autoload/neoformat/formatters/*.vim'), '\n'),
\ "fnamemodify(v:val, ':t:r')"))),
\ "v:val =~? '^" . a:ArgLead . "'")
endif
if a:ArgLead =~ '[^A-Za-z0-9]'
return []
endif
let filetype = s:split_filetypes(&filetype)
return filter(s:get_enabled_formatters(filetype),
\ "v:val =~? '^" . a:ArgLead ."'")
endfunction
function! s:autoload_func_exists(func_name) abort
try
call eval(a:func_name . '()')
catch /^Vim\%((\a\+)\)\=:E/
return 0
endtry
return 1
endfunction
function! s:split_filetypes(filetype) abort
if a:filetype == ''
return ''
endif
return split(a:filetype, '\.')[0]
endfunction
function! s:get_node_exe(exe) abort
let node_exe = findfile('node_modules/.bin/' . a:exe, getcwd() . ';')
if !empty(node_exe) && executable(node_exe)
return node_exe
endif
return a:exe
endfunction
function! s:generate_cmd(definition, filetype) abort
let executable = get(a:definition, 'exe', '')
if executable == ''
call neoformat#utils#log('no exe field in definition')
return {}
endif
if exists('g:neoformat_try_node_exe')
\ && g:neoformat_try_node_exe
\ && get(a:definition, 'try_node_exe', 0)
let executable = s:get_node_exe(executable)
endif
if &shell =~ '\v%(powershell|pwsh)'
if system('[bool](Get-Command ' . executable . ' -ErrorAction SilentlyContinue)') !~ 'True'
call neoformat#utils#log('executable: ' . executable . ' is not a cmdlet, function, script file, or an executable program')
return {}
endif
elseif !executable(executable)
call neoformat#utils#log('executable: ' . executable . ' is not an executable')
return {}
endif
let args = get(a:definition, 'args', [])
let args_expanded = []
for a in args
let args_expanded = add(args_expanded, s:expand_fully(a))
endfor
let no_append = get(a:definition, 'no_append', 0)
let using_stdin = get(a:definition, 'stdin', 0)
let using_stderr = get(a:definition, 'stderr', 0)
let stderr_log = ''
let filename = expand('%:t')
let tmp_dir = has('win32') ? expand('$TEMP/neoformat') :
\ exists('$TMPDIR') ? expand('$TMPDIR/neoformat') :
\ '/tmp/neoformat'
if !isdirectory(tmp_dir)
call mkdir(tmp_dir, 'p')
endif
if get(a:definition, 'replace', 0)
let path = !using_stdin ? expand(tmp_dir . '/' . fnameescape(filename)) : ''
else
let path = !using_stdin ? tempname() : ''
endif
let inline_environment = get(a:definition, 'env', [])
let _fullcmd = join(inline_environment, ' ') . ' ' . executable . ' ' . join(args_expanded) . ' ' . (no_append ? '' : path)
" make sure there aren't any double spaces in the cmd
let fullcmd = join(split(_fullcmd))
if !using_stderr
if neoformat#utils#should_be_verbose()
let stderr_log = expand(tmp_dir . '/stderr.log')
let fullcmd = fullcmd . ' 2> ' . stderr_log
else
if (has('win32') || has('win64'))
let stderr_log = ''
let fullcmd = fullcmd . ' 2> ' . 'NUL'
else
let stderr_log = ''
let fullcmd = fullcmd . ' 2> ' . '/dev/null'
endif
endif
endif
return {
\ 'exe': fullcmd,
\ 'stdin': using_stdin,
\ 'stderr_log': stderr_log,
\ 'name': a:definition.exe,
\ 'replace': get(a:definition, 'replace', 0),
\ 'tmp_file_path': path,
\ 'valid_exit_codes': get(a:definition, 'valid_exit_codes', [0]),
\ }
endfunction
function! s:expand_fully(string) abort
return substitute(a:string, '%\(:[a-z]\)*', '\=expand(submatch(0))', 'g')
endfunction
function! s:basic_format() abort
call neoformat#utils#log('running basic format')
if !exists('g:neoformat_basic_format_align')
let g:neoformat_basic_format_align = 0
endif
if !exists('g:neoformat_basic_format_retab')
let g:neoformat_basic_format_retab = 0
endif
if !exists('g:neoformat_basic_format_trim')
let g:neoformat_basic_format_trim = 0
endif
if neoformat#utils#var('neoformat_basic_format_align')
call neoformat#utils#log('aligning with basic formatter')
let v = winsaveview()
silent! execute 'normal gg=G'
call winrestview(v)
endif
if neoformat#utils#var('neoformat_basic_format_retab')
call neoformat#utils#log('converting tabs with basic formatter')
retab
endif
if neoformat#utils#var('neoformat_basic_format_trim')
call neoformat#utils#log('trimming whitespace with basic formatter')
" http://stackoverflow.com/q/356126
let search = @/
let view = winsaveview()
" vint: -ProhibitCommandRelyOnUser -ProhibitCommandWithUnintendedSideEffect
silent! %s/\s\+$//e
" vint: +ProhibitCommandRelyOnUser +ProhibitCommandWithUnintendedSideEffect
let @/=search
call winrestview(view)
endif
endfunction