├── 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 |

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 |
--------------------------------------------------------------------------------