json.vim 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. " Vim indent file
  2. " Language: JSON
  3. " Maintainer: Eli Parra <eli@elzr.com> https://github.com/elzr/vim-json
  4. " Last Change: 2020 Aug 30
  5. " https://github.com/jakar/vim-json/commit/20b650e22aa750c4ab6a66aa646bdd95d7cd548a#diff-e81fc111b2052e306d126bd9989f7b7c
  6. " 2022 Sep 07: b:undo_indent added by Doug Kearns
  7. " Original Author: Rogerz Zhang <rogerz.zhang at gmail.com> http://github.com/rogerz/vim-json
  8. " Acknowledgement: Based off of vim-javascript maintained by Darrick Wiebe
  9. " http://www.vim.org/scripts/script.php?script_id=2765
  10. " 0. Initialization {{{1
  11. " =================
  12. " Only load this indent file when no other was loaded.
  13. if exists("b:did_indent")
  14. finish
  15. endif
  16. let b:did_indent = 1
  17. setlocal nosmartindent
  18. " Now, set up our indentation expression and keys that trigger it.
  19. setlocal indentexpr=GetJSONIndent(v:lnum)
  20. setlocal indentkeys=0{,0},0),0[,0],!^F,o,O,e
  21. let b:undo_indent = "setl inde< indk< si<"
  22. " Only define the function once.
  23. if exists("*GetJSONIndent")
  24. finish
  25. endif
  26. let s:cpo_save = &cpo
  27. set cpo&vim
  28. " 1. Variables {{{1
  29. " ============
  30. let s:line_term = '\s*\%(\%(\/\/\).*\)\=$'
  31. " Regex that defines blocks.
  32. let s:block_regex = '\%({\)\s*\%(|\%([*@]\=\h\w*,\=\s*\)\%(,\s*[*@]\=\h\w*\)*|\)\=' . s:line_term
  33. " 2. Auxiliary Functions {{{1
  34. " ======================
  35. " Check if the character at lnum:col is inside a string.
  36. function s:IsInString(lnum, col)
  37. return synIDattr(synID(a:lnum, a:col, 1), 'name') == 'jsonString'
  38. endfunction
  39. " Find line above 'lnum' that isn't empty, or in a string.
  40. function s:PrevNonBlankNonString(lnum)
  41. let lnum = prevnonblank(a:lnum)
  42. while lnum > 0
  43. " If the line isn't empty or in a string, end search.
  44. let line = getline(lnum)
  45. if !(s:IsInString(lnum, 1) && s:IsInString(lnum, strlen(line)))
  46. break
  47. endif
  48. let lnum = prevnonblank(lnum - 1)
  49. endwhile
  50. return lnum
  51. endfunction
  52. " Check if line 'lnum' has more opening brackets than closing ones.
  53. function s:LineHasOpeningBrackets(lnum)
  54. let open_0 = 0
  55. let open_2 = 0
  56. let open_4 = 0
  57. let line = getline(a:lnum)
  58. let pos = match(line, '[][(){}]', 0)
  59. while pos != -1
  60. let idx = stridx('(){}[]', line[pos])
  61. if idx % 2 == 0
  62. let open_{idx} = open_{idx} + 1
  63. else
  64. let open_{idx - 1} = open_{idx - 1} - 1
  65. endif
  66. let pos = match(line, '[][(){}]', pos + 1)
  67. endwhile
  68. return (open_0 > 0) . (open_2 > 0) . (open_4 > 0)
  69. endfunction
  70. function s:Match(lnum, regex)
  71. let col = match(getline(a:lnum), a:regex) + 1
  72. return col > 0 && !s:IsInString(a:lnum, col) ? col : 0
  73. endfunction
  74. " 3. GetJSONIndent Function {{{1
  75. " =========================
  76. function GetJSONIndent(...)
  77. " 3.1. Setup {{{2
  78. " ----------
  79. " For the current line, use the first argument if given, else v:lnum
  80. let clnum = a:0 ? a:1 : v:lnum
  81. " Set up variables for restoring position in file. Could use clnum here.
  82. let vcol = col('.')
  83. " 3.2. Work on the current line {{{2
  84. " -----------------------------
  85. " Get the current line.
  86. let line = getline(clnum)
  87. let ind = -1
  88. " If we got a closing bracket on an empty line, find its match and indent
  89. " according to it.
  90. let col = matchend(line, '^\s*[]}]')
  91. if col > 0 && !s:IsInString(clnum, col)
  92. call cursor(clnum, col)
  93. let bs = strpart('{}[]', stridx('}]', line[col - 1]) * 2, 2)
  94. let pairstart = escape(bs[0], '[')
  95. let pairend = escape(bs[1], ']')
  96. let pairline = searchpair(pairstart, '', pairend, 'bW')
  97. if pairline > 0
  98. let ind = indent(pairline)
  99. else
  100. let ind = virtcol('.') - 1
  101. endif
  102. return ind
  103. endif
  104. " If we are in a multi-line string, don't do anything to it.
  105. if s:IsInString(clnum, matchend(line, '^\s*') + 1)
  106. return indent('.')
  107. endif
  108. " 3.3. Work on the previous line. {{{2
  109. " -------------------------------
  110. let lnum = prevnonblank(clnum - 1)
  111. if lnum == 0
  112. return 0
  113. endif
  114. " Set up variables for current line.
  115. let line = getline(lnum)
  116. let ind = indent(lnum)
  117. " If the previous line ended with a block opening, add a level of indent.
  118. " if s:Match(lnum, s:block_regex)
  119. " return indent(lnum) + shiftwidth()
  120. " endif
  121. " If the previous line contained an opening bracket, and we are still in it,
  122. " add indent depending on the bracket type.
  123. if line =~ '[[({]'
  124. let counts = s:LineHasOpeningBrackets(lnum)
  125. if counts[0] == '1' || counts[1] == '1' || counts[2] == '1'
  126. return ind + shiftwidth()
  127. else
  128. call cursor(clnum, vcol)
  129. end
  130. endif
  131. " }}}2
  132. return ind
  133. endfunction
  134. " }}}1
  135. let &cpo = s:cpo_save
  136. unlet s:cpo_save
  137. " vim:set sw=2 sts=2 ts=8 noet: