jsonc.vim 4.7 KB

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