├── README.md ├── autoload └── jfmt.vim └── plugin └── jfmt.vim /README.md: -------------------------------------------------------------------------------- 1 | # vim-jfmt 2 | 3 | `jfmt` is a simple plugin for vim to automatically validate and format/pretty-print JSON files. 4 | 5 |

vim-jfmt

6 | 7 | ## Installing 8 | 9 | Ensure you have [jq](https://stedolan.github.io/jq/) installed and available in your local `$PATH` 10 | 11 | Then, add to your `.vimrc` using your plugin manager of choice; e.g. vundle: 12 | ```vim 13 | Plugin 'bcicen/vim-jfmt' 14 | ``` 15 | 16 | ## Usage 17 | 18 | By default, `jfmt` will only validate JSON files on save, opening a location list with any parse errors encountered. 19 | 20 | To manually format/pretty-print the open file, use the `:Jfmt` command. To automatically run this on save as well, simply add the below to your `.vimrc`: 21 | ```vim 22 | let g:jfmt_autofmt = 1 23 | ``` 24 | 25 | ## Options 26 | 27 | Additional options may be provided to jq by setting `g:jfmt_jq_options`: 28 | 29 | ```vim 30 | " use tabs instead of spaces for indentation 31 | let g:jfmt_jq_options = '--tab' 32 | ``` 33 | 34 | Likewise, the default filter(`.`) can be changed by setting `g:jfmt_jq_filter` 35 | -------------------------------------------------------------------------------- /autoload/jfmt.vim: -------------------------------------------------------------------------------- 1 | " jfmt.vim: format json files with jq 2 | 3 | if !exists('g:jfmt_jq_options') 4 | let g:jfmt_jq_options = '' 5 | endif 6 | 7 | if !exists('g:jfmt_jq_filter') 8 | let g:jfmt_jq_filter = '.' 9 | endif 10 | 11 | 12 | function! jfmt#GetLines() 13 | let buf = getline(1, '$') 14 | if &encoding != 'utf-8' 15 | let buf = map(buf, 'iconv(v:val, &encoding, "utf-8")') 16 | endif 17 | return buf 18 | endfunction 19 | 20 | function! jfmt#Sh(str) abort 21 | " Preserve original shell and shellredir values 22 | let l:shell = &shell 23 | let l:shellredir = &shellredir 24 | set shell=/bin/sh shellredir=>%s\ 2>&1 25 | 26 | try 27 | let l:output = call('system', [a:str] + a:000) 28 | return l:output 29 | finally 30 | " Restore original values 31 | let &shell = l:shell 32 | let &shellredir = l:shellredir 33 | endtry 34 | endfunction 35 | 36 | function! jfmt#Run(autofmt) abort 37 | " Save cursor position 38 | let l:curw = winsaveview() 39 | 40 | " Write current unsaved buffer to a temp file 41 | let l:tmpsrc = tempname() 42 | let l:tmptgt = tempname() 43 | call writefile(jfmt#GetLines(), l:tmpsrc) 44 | 45 | let cmd = ["jq"] 46 | call extend(cmd, split(g:jfmt_jq_options, " ")) 47 | call add(cmd, g:jfmt_jq_filter) 48 | call add(cmd, l:tmpsrc) 49 | call add(cmd, "1>") 50 | call add(cmd, l:tmptgt) 51 | 52 | let out = jfmt#Sh(join(cmd, " ")) 53 | 54 | if v:shell_error == 0 55 | let errors = [] 56 | if a:autofmt == 1 57 | call jfmt#update_file(l:tmptgt, expand('%')) 58 | endif 59 | else 60 | let errors = jfmt#parse_error(expand('%'), out) 61 | endif 62 | call s:show_errors(errors) 63 | 64 | call delete(l:tmpsrc) 65 | 66 | " Restore cursor/windows positions 67 | call winrestview(l:curw) 68 | endfunction 69 | 70 | " update_file updates the target file with the given formatted source 71 | function! jfmt#update_file(source, target) 72 | " remove undo point caused via BufWritePre 73 | try | silent undojoin | catch | endtry 74 | 75 | let old_fileformat = &fileformat 76 | if exists("*getfperm") 77 | " save file permissions 78 | let original_fperm = getfperm(a:target) 79 | endif 80 | 81 | call rename(a:source, a:target) 82 | 83 | " restore file permissions 84 | if exists("*setfperm") && original_fperm != '' 85 | call setfperm(a:target , original_fperm) 86 | endif 87 | 88 | " reload buffer to reflect latest changes 89 | silent! edit! 90 | 91 | let &fileformat = old_fileformat 92 | let &syntax = &syntax 93 | endfunction 94 | 95 | function! jfmt#parse_error(filename, content) abort 96 | let errors = [] 97 | let line = matchstr(a:content, '.*line \zs.*\ze,') 98 | let col = matchstr(a:content, '.*column \zs.*\ze$') 99 | let text = matchstr(a:content, '.*: \zs.*\ze at line') 100 | call add(errors,{ 101 | \"filename": a:filename, 102 | \"lnum": line, 103 | \"col": col, 104 | \"text": text, 105 | \ }) 106 | return errors 107 | endfunction 108 | 109 | function! s:show_errors(errors) abort 110 | let title = "Format" 111 | if !empty(a:errors) 112 | call setloclist(0, a:errors, 'r') 113 | " The last argument ({what}) is introduced with 7.4.2200: 114 | " https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640 115 | if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': title}) | endif 116 | echohl Error | echomsg "jq returned error" | echohl None 117 | lopen 118 | else 119 | lclose 120 | endif 121 | endfunction 122 | 123 | " vim: sw=2 ts=2 et 124 | -------------------------------------------------------------------------------- /plugin/jfmt.vim: -------------------------------------------------------------------------------- 1 | if !exists('g:jfmt_autofmt') 2 | let g:jfmt_autofmt = 0 3 | endif 4 | 5 | command! Jfmt call jfmt#Run(1) 6 | 7 | augroup jfmt 8 | autocmd! 9 | autocmd BufWritePre *.json call jfmt#Run(g:jfmt_autofmt) 10 | augroup END 11 | 12 | " vim: sw=2 ts=2 et 13 | --------------------------------------------------------------------------------