# HG changeset patch # User Christian Brabandt # Date 1702220404 -3600 # Node ID c2955068ea1671b5d5658093e6a64c360860fd83 # Parent b196646aa8a8d16ebaba9f284555cf9709c567cc runtime(json): Add json formating plugin (Issue #11426) (#11506) Commit: https://github.com/vim/vim/commit/ca7c9b1b59263938eea8dd5a197ca8312ffa91ac Author: Maxim Kim Date: Mon Dec 11 01:57:41 2023 +1100 runtime(json): Add json formating plugin (Issue https://github.com/vim/vim/issues/11426) (https://github.com/vim/vim/issues/11506) related: https://github.com/vim/vim/issues/11426 Signed-off-by: Maxim Kim Signed-off-by: Christian Brabandt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,6 +23,7 @@ runtime/autoload/tar.vim @cecamp runtime/autoload/vimball.vim @cecamp runtime/autoload/xmlformat.vim @chrisbra runtime/autoload/zip.vim @cecamp +runtime/autoload/dist/json.vim @habamax runtime/colors/blue.vim @habamax @romainl @neutaaaaan runtime/colors/darkblue.vim @habamax @romainl @neutaaaaan runtime/colors/default.vim @habamax @romainl @neutaaaaan diff --git a/runtime/autoload/dist/json.vim b/runtime/autoload/dist/json.vim new file mode 100644 --- /dev/null +++ b/runtime/autoload/dist/json.vim @@ -0,0 +1,182 @@ +vim9script + +# Maintainer: Maxim Kim +# Last update: 2023-12-10 +# +# Set of functions to format/beautify JSON data structures. +# +# Could be used to reformat a minified json in a buffer (put it into ~/.vim/ftplugin/json.vim): +# import autoload 'dist/json.vim' +# setl formatexpr=json.FormatExpr() +# +# Or to get a formatted string out of vim's dict/list/string: +# vim9script +# import autoload 'dist/json.vim' +# echo json.Format({ +# "widget": { "debug": "on", "window": { "title": "Sample \"Konfabulator\" Widget", +# "name": "main_window", "width": 500, "height": 500 +# }, +# "image": { "src": "Images/Sun.png", "name": "sun1", "hOffset": 250, +# "vOffset": 250, "alignment": "center" }, +# "text": { "data": "Click Here", "size": 36, "style": "bold", "name": "text1", +# "hOffset": 250, "vOffset": 100, "alignment": "center", +# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" } } +# }) +# +# Should output: +# { +# "widget": { +# "debug": "on", +# "window": { +# "title": "Sample \"Konfabulator\" Widget", +# "name": "main_window", +# "width": 500, +# "height": 500 +# }, +# "image": { +# "src": "Images/Sun.png", +# "name": "sun1", +# "hOffset": 250, +# "vOffset": 250, +# "alignment": "center" +# }, +# "text": { +# "data": "Click Here", +# "size": 36, +# "style": "bold", +# "name": "text1", +# "hOffset": 250, +# "vOffset": 100, +# "alignment": "center", +# "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" +# } +# } +# } +# +# NOTE: order of `key: value` pairs is not kept. +# +# You can also use a JSON string instead of vim's dict/list to maintain order: +# echo json.Format('{"hello": 1, "world": 2}') +# { +# "hello": 1, +# "world": 2 +# } + + +# To be able to reformat with `gq` add following to `~/.vim/ftplugin/json.vim`: +# import autoload 'dist/json.vim' +# setl formatexpr=json.FormatExpr() +export def FormatExpr(): number + FormatRange(v:lnum, v:lnum + v:count - 1) + return 0 +enddef + + +# import autoload 'dist/json.vim' +# command -range=% JSONFormat json.FormatRange(, ) +export def FormatRange(line1: number, line2: number) + var indent_base = matchstr(getline(line1), '^\s*') + var indent = &expandtab ? repeat(' ', &shiftwidth) : "\t" + + var [l1, l2] = line1 > line2 ? [line2, line1] : [line1, line2] + + var json_src = getline(l1, l2)->join() + var json_fmt = Format(json_src, {use_tabs: !&et, indent: &sw, indent_base: indent_base})->split("\n") + + exe $":{l1},{l2}d" + + if line('$') == 1 && getline(1) == '' + setline(l1, json_fmt[0]) + append(l1, json_fmt[1 : ]) + else + append(l1 - 1, json_fmt) + endif +enddef + + +# Format JSON string or dict/list as JSON +# import autoload 'dist/json.vim' +# echo json.FormatStr('{"hello": "world"}', {use_tabs: false, indent: 2, indent_base: 0}) + +# { +# "hello": "world" +# } + +# echo json.FormatStr({'hello': 'world'}, {use_tabs: false, indent: 2, indent_base: 0}) +# { +# "hello": "world" +# } +# +# Note, when `obj` is dict, order of the `key: value` pairs might be different: +# echo json.FormatStr({'hello': 1, 'world': 2}) +# { +# "world": 2, +# "hello": 1 +# } +export def Format(obj: any, params: dict = {}): string + var obj_str = '' + if type(obj) == v:t_string + obj_str = obj + else + obj_str = json_encode(obj) + endif + + var indent_lvl = 0 + var indent_base = get(params, "indent_base", "") + var indent = get(params, "use_tabs", false) ? "\t" : repeat(' ', get(params, "indent", 2)) + var json_line = indent_base + var json = "" + var state = "" + for char in obj_str + if state == "" + if char =~ '[{\[]' + json_line ..= char + json ..= json_line .. "\n" + indent_lvl += 1 + json_line = indent_base .. repeat(indent, indent_lvl) + elseif char =~ '[}\]]' + if json_line !~ '^\s*$' + json ..= json_line .. "\n" + indent_lvl -= 1 + if indent_lvl < 0 + json_line = strpart(indent_base, -indent_lvl * len(indent)) + else + json_line = indent_base .. repeat(indent, indent_lvl) + endif + elseif json =~ '[{\[]\n$' + json = json[ : -2] + json_line = substitute(json_line, '^\s*', '', '') + indent_lvl -= 1 + endif + json_line ..= char + elseif char == ':' + json_line ..= char .. ' ' + elseif char == '"' + json_line ..= char + state = 'QUOTE' + elseif char == ',' + json_line ..= char + json ..= json_line .. "\n" + json_line = indent_base .. repeat(indent, indent_lvl) + elseif char !~ '\s' + json_line ..= char + endif + elseif state == "QUOTE" + json_line ..= char + if char == '\' + state = "ESCAPE" + elseif char == '"' + state = "" + endif + elseif state == "ESCAPE" + state = "QUOTE" + json_line ..= char + else + json_line ..= char + endif + endfor + if json_line !~ '^\s*$' + json ..= json_line .. "\n" + endif + return json +enddef diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -610,6 +610,18 @@ The mapping can be disabled with: > let g:no_gprof_maps = 1 +JSON-FORMAT *ft-json-plugin* + +JSON filetype can be extended to use 'formatexpr' and "json.FormatExpr()" +function for json formatting (using |gq|). + +Add following lines to $HOME/.vim/ftplugin/json.vim: > + + vim9script + import autoload 'dist/json.vim' + setl formatexpr=json.FormatExpr() + + MAIL *ft-mail-plugin* Options: