File: htmlfold.vim

package info (click to toggle)
vim 2%3A9.1.2103-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 93,456 kB
  • sloc: ansic: 433,730; cpp: 6,399; makefile: 4,597; sh: 2,397; java: 2,312; xml: 2,099; python: 1,595; perl: 1,419; awk: 730; lisp: 501; cs: 458; objc: 369; sed: 8; csh: 6; haskell: 1
file content (192 lines) | stat: -rw-r--r-- 4,585 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
" HTML folding script, :h ft-html-plugin
" Latest Change: 2025 May 10
" Original Author: Aliaksei Budavei <0x000c70@gmail.com>

function! htmlfold#MapBalancedTags() abort
  " Describe only _a capturable-name prefix_ for start and end patterns of
  " a tag so that start tags with attributes spanning across lines can also be
  " matched with a single call of "getline()".
  let tag = '\m\c</\=\([0-9A-Za-z-]\+\)'
  let names = []
  let pairs = []
  let ends = []
  let pos = getpos('.')

  try
    call cursor(1, 1)
    let [lnum, cnum] = searchpos(tag, 'cnW')

    " Pair up nearest non-inlined tags in scope.
    while lnum > 0
      let name_attr = synIDattr(synID(lnum, cnum, 0), 'name')

      if name_attr ==# 'htmlTag' || name_attr ==# 'htmlScriptTag'
	let name = get(matchlist(getline(lnum), tag, (cnum - 1)), 1, '')

	if !empty(name)
	  call insert(names, tolower(name), 0)
	  call insert(pairs, [lnum, -1], 0)
	endif
      elseif name_attr ==# 'htmlEndTag'
	let name = get(matchlist(getline(lnum), tag, (cnum - 1)), 1, '')

	if !empty(name)
	  let idx = index(names, tolower(name))

	  if idx >= 0
	    " Dismiss inlined balanced tags and opened-only tags.
	    if pairs[idx][0] != lnum
	      let pairs[idx][1] = lnum
	      call add(ends, lnum)
	    endif

	    " Claim a pair.
	    let names[: idx] = repeat([''], (idx + 1))
	  endif
	endif
      endif

      " Advance the cursor, at "<", past "</a", "<a>", etc.
      call cursor(lnum, (cnum + 3))
      let [lnum, cnum] = searchpos(tag, 'cnW')
    endwhile
  finally
    call setpos('.', pos)
  endtry

  if empty(ends)
    return {}
  endif

  let folds = {}
  let pending_end = ends[0]
  let level = 0

  while !empty(pairs)
    let [start, end] = remove(pairs, -1)

    if end < 0
      continue
    endif

    if start >= pending_end
      " Mark a sibling tag.
      call remove(ends, 0)

      while start >= ends[0]
	" Mark a parent tag.
	call remove(ends, 0)
	let level -= 1
      endwhile

      let pending_end = ends[0]
    else
      " Mark a child tag.
      let level += 1
    endif

    " Flatten the innermost inlined folds.
    let folds[start] = get(folds, start, ('>' . level))
    let folds[end] = get(folds, end, ('<' . level))
  endwhile

  return folds
endfunction

" See ":help vim9-mix".
if !has("vim9script")
  finish
endif

def! g:htmlfold#MapBalancedTags(): dict<string>
  # Describe only _a capturable-name prefix_ for start and end patterns of
  # a tag so that start tags with attributes spanning across lines can also be
  # matched with a single call of "getline()".
  const tag: string = '\m\c</\=\([0-9A-Za-z-]\+\)'
  var names: list<string> = []
  var pairs: list<list<number>> = []
  var ends: list<number> = []
  const pos: list<number> = getpos('.')

  try
    cursor(1, 1)
    var [lnum: number, cnum: number] = searchpos(tag, 'cnW')

    # Pair up nearest non-inlined tags in scope.
    while lnum > 0
      const name_attr: string = synIDattr(synID(lnum, cnum, 0), 'name')

      if name_attr ==# 'htmlTag' || name_attr ==# 'htmlScriptTag'
	const name: string = get(matchlist(getline(lnum), tag, (cnum - 1)), 1, '')

	if !empty(name)
	  insert(names, tolower(name), 0)
	  insert(pairs, [lnum, -1], 0)
	endif
      elseif name_attr ==# 'htmlEndTag'
	const name: string = get(matchlist(getline(lnum), tag, (cnum - 1)), 1, '')

	if !empty(name)
	  const idx: number = index(names, tolower(name))

	  if idx >= 0
	    # Dismiss inlined balanced tags and opened-only tags.
	    if pairs[idx][0] != lnum
	      pairs[idx][1] = lnum
	      add(ends, lnum)
	    endif

	    # Claim a pair.
	    names[: idx] = repeat([''], (idx + 1))
	  endif
	endif
      endif

      # Advance the cursor, at "<", past "</a", "<a>", etc.
      cursor(lnum, (cnum + 3))
      [lnum, cnum] = searchpos(tag, 'cnW')
    endwhile
  finally
    setpos('.', pos)
  endtry

  if empty(ends)
    return {}
  endif

  var folds: dict<string> = {}
  var pending_end: number = ends[0]
  var level: number = 0

  while !empty(pairs)
    const [start: number, end: number] = remove(pairs, -1)

    if end < 0
      continue
    endif

    if start >= pending_end
      # Mark a sibling tag.
      remove(ends, 0)

      while start >= ends[0]
	# Mark a parent tag.
	remove(ends, 0)
	level -= 1
      endwhile

      pending_end = ends[0]
    else
      # Mark a child tag.
      level += 1
    endif

    # Flatten the innermost inlined folds.
    folds[start] = get(folds, start, ('>' .. level))
    folds[end] = get(folds, end, ('<' .. level))
  endwhile

  return folds
enddef

" vim: fdm=syntax sw=2 ts=8 noet