mirror of
https://github.com/vim/vim.git
synced 2025-07-26 11:04:33 -04:00
193 lines
4.5 KiB
VimL
193 lines
4.5 KiB
VimL
|
" 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
|