jsonc.vim 4.6 KB

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