blob: dad95a54adf1c2a0aad3bd3fb9ee12a3abb74850 [file] [log] [blame]
Maxim Kimca7c9b12023-12-11 01:57:41 +11001vim9script
2
3# Maintainer: Maxim Kim <habamax@gmail.com>
4# Last update: 2023-12-10
5#
6# Set of functions to format/beautify JSON data structures.
7#
8# Could be used to reformat a minified json in a buffer (put it into ~/.vim/ftplugin/json.vim):
9# import autoload 'dist/json.vim'
10# setl formatexpr=json.FormatExpr()
11#
12# Or to get a formatted string out of vim's dict/list/string:
13# vim9script
14# import autoload 'dist/json.vim'
15# echo json.Format({
16# "widget": { "debug": "on", "window": { "title": "Sample \"Konfabulator\" Widget",
17# "name": "main_window", "width": 500, "height": 500
18# },
19# "image": { "src": "Images/Sun.png", "name": "sun1", "hOffset": 250,
20# "vOffset": 250, "alignment": "center" },
21# "text": { "data": "Click Here", "size": 36, "style": "bold", "name": "text1",
22# "hOffset": 250, "vOffset": 100, "alignment": "center",
23# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" } }
24# })
25#
26# Should output:
27# {
28# "widget": {
29# "debug": "on",
30# "window": {
31# "title": "Sample \"Konfabulator\" Widget",
32# "name": "main_window",
33# "width": 500,
34# "height": 500
35# },
36# "image": {
37# "src": "Images/Sun.png",
38# "name": "sun1",
39# "hOffset": 250,
40# "vOffset": 250,
41# "alignment": "center"
42# },
43# "text": {
44# "data": "Click Here",
45# "size": 36,
46# "style": "bold",
47# "name": "text1",
48# "hOffset": 250,
49# "vOffset": 100,
50# "alignment": "center",
51# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
52# }
53# }
54# }
55#
56# NOTE: order of `key: value` pairs is not kept.
57#
58# You can also use a JSON string instead of vim's dict/list to maintain order:
59# echo json.Format('{"hello": 1, "world": 2}')
60# {
61# "hello": 1,
62# "world": 2
63# }
64
65
66# To be able to reformat with `gq` add following to `~/.vim/ftplugin/json.vim`:
67# import autoload 'dist/json.vim'
68# setl formatexpr=json.FormatExpr()
69export def FormatExpr(): number
70 FormatRange(v:lnum, v:lnum + v:count - 1)
71 return 0
72enddef
73
74
75# import autoload 'dist/json.vim'
76# command -range=% JSONFormat json.FormatRange(<line1>, <line2>)
77export def FormatRange(line1: number, line2: number)
78 var indent_base = matchstr(getline(line1), '^\s*')
79 var indent = &expandtab ? repeat(' ', &shiftwidth) : "\t"
80
81 var [l1, l2] = line1 > line2 ? [line2, line1] : [line1, line2]
82
83 var json_src = getline(l1, l2)->join()
84 var json_fmt = Format(json_src, {use_tabs: !&et, indent: &sw, indent_base: indent_base})->split("\n")
85
86 exe $":{l1},{l2}d"
87
88 if line('$') == 1 && getline(1) == ''
89 setline(l1, json_fmt[0])
90 append(l1, json_fmt[1 : ])
91 else
92 append(l1 - 1, json_fmt)
93 endif
94enddef
95
96
97# Format JSON string or dict/list as JSON
98# import autoload 'dist/json.vim'
99# echo json.FormatStr('{"hello": "world"}', {use_tabs: false, indent: 2, indent_base: 0})
100
101# {
102# "hello": "world"
103# }
104
105# echo json.FormatStr({'hello': 'world'}, {use_tabs: false, indent: 2, indent_base: 0})
106# {
107# "hello": "world"
108# }
109#
110# Note, when `obj` is dict, order of the `key: value` pairs might be different:
111# echo json.FormatStr({'hello': 1, 'world': 2})
112# {
113# "world": 2,
114# "hello": 1
115# }
116export def Format(obj: any, params: dict<any> = {}): string
117 var obj_str = ''
118 if type(obj) == v:t_string
119 obj_str = obj
120 else
121 obj_str = json_encode(obj)
122 endif
123
124 var indent_lvl = 0
125 var indent_base = get(params, "indent_base", "")
126 var indent = get(params, "use_tabs", false) ? "\t" : repeat(' ', get(params, "indent", 2))
127 var json_line = indent_base
128 var json = ""
129 var state = ""
130 for char in obj_str
131 if state == ""
132 if char =~ '[{\[]'
133 json_line ..= char
134 json ..= json_line .. "\n"
135 indent_lvl += 1
136 json_line = indent_base .. repeat(indent, indent_lvl)
137 elseif char =~ '[}\]]'
138 if json_line !~ '^\s*$'
139 json ..= json_line .. "\n"
140 indent_lvl -= 1
141 if indent_lvl < 0
142 json_line = strpart(indent_base, -indent_lvl * len(indent))
143 else
144 json_line = indent_base .. repeat(indent, indent_lvl)
145 endif
146 elseif json =~ '[{\[]\n$'
147 json = json[ : -2]
148 json_line = substitute(json_line, '^\s*', '', '')
149 indent_lvl -= 1
150 endif
151 json_line ..= char
152 elseif char == ':'
153 json_line ..= char .. ' '
154 elseif char == '"'
155 json_line ..= char
156 state = 'QUOTE'
157 elseif char == ','
158 json_line ..= char
159 json ..= json_line .. "\n"
160 json_line = indent_base .. repeat(indent, indent_lvl)
161 elseif char !~ '\s'
162 json_line ..= char
163 endif
164 elseif state == "QUOTE"
165 json_line ..= char
166 if char == '\'
167 state = "ESCAPE"
168 elseif char == '"'
169 state = ""
170 endif
171 elseif state == "ESCAPE"
172 state = "QUOTE"
173 json_line ..= char
174 else
175 json_line ..= char
176 endif
177 endfor
178 if json_line !~ '^\s*$'
179 json ..= json_line .. "\n"
180 endif
181 return json
182enddef