From 4c454b3ad30b06fb4978150320ff068fecff4f45 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Mon, 9 Jan 2017 19:00:32 -0500 Subject: [PATCH] Added option to format visual selection Works the same as plain :Neoformat when you are formatter a selection in a buffer When you are formatting a subregion in a markdown file for example, then you must use the following syntax. :Neoformat! python Note: the ! and filetype of the selection are required Added tests for visual selection. Using fstrings so travis config can be simplified. https://github.com/sbdchd/neoformat/issues/30 --- .editorconfig | 8 ++- .travis.yml | 3 +- README.md | 10 ++- autoload/neoformat.vim | 105 +++++++++++++++++-------------- doc/neoformat.txt | 10 ++- plugin/neoformat.vim | 4 +- test/test.py | 32 ++++++---- test/visual_selection_after.txt | 16 +++++ test/visual_selection_before.txt | 14 +++++ 9 files changed, 136 insertions(+), 66 deletions(-) create mode 100644 test/visual_selection_after.txt create mode 100644 test/visual_selection_before.txt diff --git a/.editorconfig b/.editorconfig index beca92b9..e95a293a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,13 @@ -[*.vim] +[*] indent_style = space -indent_size = 4 -tab_width = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[*.vim] +indent_size = 4 +tab_width = 4 + [*.vader] indent_size = 2 diff --git a/.travis.yml b/.travis.yml index 3d1a7096..d04efc99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,8 @@ cache: node_js: - "5.11.1" + python: - - "2.7" - - "3.5" - "3.6" install: diff --git a/README.md b/README.md index 2c5384b1..7a5970f9 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ via stdin. ## Basic Usage -Format the current file using its filetype +Format the entire buffer, or visual selection of the buffer ```viml :Neoformat @@ -29,6 +29,14 @@ Or specify a certain formatter (must be defined for the current filetype) :Neoformat js-beautify ``` +Or format a visual selection of code in a different filetype + +__Note:__ you must use a ! and pass the filetype of the selection + +```viml +:Neoformat! python +``` + Or perhaps run a formatter on save ```viml diff --git a/autoload/neoformat.vim b/autoload/neoformat.vim index 88418914..5aacd484 100644 --- a/autoload/neoformat.vim +++ b/autoload/neoformat.vim @@ -1,12 +1,34 @@ -function! neoformat#Neoformat(user_formatter) abort +function! neoformat#Neoformat(bang, user_input, start_line, end_line) abort + let search = @/ + let view = winsaveview() + let original_filetype = &filetype + + call s:neoformat(a:bang, a:user_input, a:start_line, a:end_line) + + let @/ = search + call winrestview(view) + let &filetype = original_filetype +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('$') + + if a:bang + let &filetype = a:user_input + endif + let filetype = s:split_filetypes(&filetype) - if !empty(a:user_formatter) - let formatters = [a:user_formatter] + let using_user_passed_formatter = !empty(a:user_input) && !a:bang + + if using_user_passed_formatter + " user passed a formatter + let formatters = [a:user_input] else let formatters = s:get_enabled_formatters(filetype) if formatters == [] @@ -23,8 +45,9 @@ function! neoformat#Neoformat(user_formatter) abort let definition = neoformat#formatters#{filetype}#{formatter}() else call neoformat#utils#log('definition not found for formatter: ' . formatter) - if !empty(a:user_formatter) - call neoformat#utils#msg('formatter definition for ' . a:user_formatter . ' not found') + if using_user_passed_formatter + call neoformat#utils#msg('formatter definition for ' . a:user_input . ' not found') + return s:basic_format() endif continue @@ -32,16 +55,22 @@ function! neoformat#Neoformat(user_formatter) abort let cmd = s:generate_cmd(definition, filetype) if cmd == {} - if !empty(a:user_formatter) - return neoformat#utils#warn('formatter ' . a:user_formatter . ' failed') + if using_user_passed_formatter + return neoformat#utils#warn('formatter ' . a:user_input . ' failed') endif continue endif - let stdin = getbufline(bufnr('%'), 1, '$') + let stdin = getbufline(bufnr('%'), a:start_line, a:end_line) + let original_buffer = getbufline(bufnr('%'), 1, '$') + + call neoformat#utils#log(stdin) + if cmd.stdin let stdout = systemlist(cmd.exe, stdin) else + call mkdir('/tmp/neoformat', 'p') + call writefile(stdin, cmd.tmp_file_path) let stdout = systemlist(cmd.exe) endif @@ -50,39 +79,30 @@ function! neoformat#Neoformat(user_formatter) abort let stdout = readfile(cmd.tmp_file_path) endif - call neoformat#utils#log(stdin) call neoformat#utils#log(stdout) if !v:shell_error - if stdout != stdin - " 1. set lines to '' aka \n from end of file when new data < old data - let datalen = len(stdout) - - while datalen <= line('$') - call setline(datalen, '') - let datalen += 1 - endwhile - - " 2. remove extra newlines at the end of the file - let search = @/ - let view = winsaveview() - " http://stackoverflow.com/a/7496112/3720597 - " vint: -ProhibitCommandRelyOnUser -ProhibitCommandWithUnintendedSideEffect - silent! %s#\($\n\)\+\%$## - " vint: +ProhibitCommandRelyOnUser +ProhibitCommandWithUnintendedSideEffect - let @/=search - call winrestview(view) - - " 3. write new data to buffer - call setline(1, stdout) - call neoformat#utils#msg(cmd.name . ' formatted buffer') + " 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) + + return neoformat#utils#msg(cmd.name . ' formatted buffer') else - call neoformat#utils#msg('no change necessary with ' . cmd.name) + + return neoformat#utils#msg('no change necessary with ' . cmd.name) endif else call neoformat#utils#log(v:shell_error) - continue + call neoformat#utils#log('trying next formatter') endif endfor + call neoformat#utils#msg('attempted all formatters for current filetype') endfunction function! s:get_enabled_formatters(filetype) abort @@ -94,6 +114,10 @@ function! s:get_enabled_formatters(filetype) abort return [] endfunction +function! s:deletelines(start, end) abort + silent! execute a:start . ',' . a:end . 'delete' +endfunction + function! neoformat#CompleteFormatters(ArgLead, CmdLine, CursorPos) abort if a:ArgLead =~ '[^A-Za-z0-9]' return [] @@ -139,19 +163,8 @@ function! s:generate_cmd(definition, filetype) abort let no_append = get(a:definition, 'no_append', 0) let using_stdin = get(a:definition, 'stdin', 0) - " Write buffer data to /tmp/ file if formatter doesn't use stdin - if !using_stdin - let base_tmp_path = '/tmp/neoformat/' - call mkdir(base_tmp_path, 'p') - - " get the last path component, the filename - let filename = expand('%:t') - let path = base_tmp_path . fnameescape(filename) - let data = getbufline(bufnr('%'), 1, '$') - call writefile(data, path) - else - let path = '' - endif + let filename = expand('%:t') + let path = !using_stdin ? '/tmp/neoformat/' . fnameescape(filename) : '' let _fullcmd = executable . ' ' . join(args_expanded) . ' ' . (no_append ? '' : path) " make sure there aren't any double spaces in the cmd diff --git a/doc/neoformat.txt b/doc/neoformat.txt index 4983efbf..26f9cc96 100644 --- a/doc/neoformat.txt +++ b/doc/neoformat.txt @@ -35,7 +35,7 @@ Install with [vim-plug](https://github.com/junegunn/vim-plug) ============================================================================== USAGE *neoformat-usage* -Format the current file using its filetype +Format the entire buffer, or visual selection of the buffer > :Neoformat @@ -43,6 +43,14 @@ Format the current file using its filetype > :Neoformat js-beautify +Or format a visual selection of code in a different filetype + +*Note:* you must use a ! and pass the filetype of the selection + +> + :Neoformat! python +> + Or perhaps run a formatter on save > diff --git a/plugin/neoformat.vim b/plugin/neoformat.vim index 4adf9835..a2baae69 100644 --- a/plugin/neoformat.vim +++ b/plugin/neoformat.vim @@ -1,2 +1,2 @@ -command! -nargs=? -bar -complete=customlist,neoformat#CompleteFormatters Neoformat - \ call neoformat#Neoformat() +command! -nargs=? -bar -range=% -bang -complete=customlist,neoformat#CompleteFormatters Neoformat + \ call neoformat#Neoformat(0, , , ) diff --git a/test/test.py b/test/test.py index 61a52ca0..57584490 100644 --- a/test/test.py +++ b/test/test.py @@ -5,26 +5,36 @@ def run_formatter(filename): formatter = filename.split('.')[0] - cmd = 'nvim -u vimrc -c "set verbose=1 | Neoformat {formatter} | wq " --headless ./before/{filename}'.format( - filename=filename, formatter=formatter) - output = subprocess.check_output(cmd, shell=True) - print(output.decode('utf-8')) + cmd = f'nvim -u vimrc -c "set verbose=1 | Neoformat {formatter} | wq " --headless ./before/{filename}' + print(subprocess.check_output(cmd, shell=True).decode('utf-8')) -def compare_files(filename): - with open('./before/' + filename) as f_before: - with open('./after/' + filename) as f_after: - before = f_before.readlines() - after = f_after.readlines() +def run_formatter_with_visual(filename, filetype, start_line, end_line): + cmd = f'nvim -u vimrc -c "set verbose=1 | {start_line},{end_line}Neoformat! {filetype} | wq " --headless {filename}' + print(subprocess.check_output(cmd, shell=True).decode('utf-8')) - assert before == after + +def readlines(filename): + with open(filename) as f: + return f.readlines() def test_formatters(): for f in listdir('before'): run_formatter(f) - compare_files(f) + before = readlines('./before/' + f) + after = readlines('./after/' + f) + assert before == after + + +def test_visualselection(): + filename_before = 'visual_selection_before.txt' + for test in [('python', 4, 7), ('css', 9, 9), ('css', 14, 15)]: + run_formatter_with_visual(filename_before, *test) + before = readlines(filename_before) + after = readlines('visual_selection_after.txt') + assert before == after if __name__ == '__main__': test_formatters() diff --git a/test/visual_selection_after.txt b/test/visual_selection_after.txt new file mode 100644 index 00000000..095304e1 --- /dev/null +++ b/test/visual_selection_after.txt @@ -0,0 +1,16 @@ + + + +def main(): + + pass + + +.body { + color: red; +} + + +.textleft { + text-align: left; +} diff --git a/test/visual_selection_before.txt b/test/visual_selection_before.txt new file mode 100644 index 00000000..b8d715b0 --- /dev/null +++ b/test/visual_selection_before.txt @@ -0,0 +1,14 @@ + + + +def main(): + + + pass + + +.body{color:red;} + + +.textleft{ +text-align:left;}