changeset 33904:c2955068ea16

runtime(json): Add json formating plugin (Issue #11426) (#11506) Commit: https://github.com/vim/vim/commit/ca7c9b1b59263938eea8dd5a197ca8312ffa91ac Author: Maxim Kim <habamax@gmail.com> 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 <habamax@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sun, 10 Dec 2023 16:00:04 +0100
parents b196646aa8a8
children 239b9deeab93
files .github/CODEOWNERS runtime/autoload/dist/json.vim runtime/doc/filetype.txt
diffstat 3 files changed, 195 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- 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
new file mode 100644
--- /dev/null
+++ b/runtime/autoload/dist/json.vim
@@ -0,0 +1,182 @@
+vim9script
+
+# Maintainer: Maxim Kim <habamax@gmail.com>
+# 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(<line1>, <line2>)
+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<any> = {}): 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
--- 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: