vim9script setl commentstring= compiler markdown_html # header textobject onoremap iP HeaderTextObj(true) onoremap aP HeaderTextObj(false) xnoremap iP HeaderTextObj(true) xnoremap aP HeaderTextObj(false) import autoload 'popup.vim' def Toc() var toc = [] var toc_num: list = [] var plvl = 0 var lvl = 1 for nr in range(1, line('$')) var line = getline(nr) var pline = getline(nr - 1) var mdsyn = synstack(nr, 1)->map('synIDattr(v:val, "name")') if line =~ '^#\+\s\S\+' && mdsyn[0] !~ '^markdown\(CodeBlock\|Highlight\)' lvl = line->matchstr('^#\+')->len() - 1 if lvl >= len(toc_num) for _ in range(lvl - len(toc_num) + 1) toc_num->add(1) endfor else if lvl > plvl toc_num[lvl] = 1 else toc_num[lvl] += 1 endif endif toc->add({lvl: lvl, toc_num: toc_num[: lvl], text: $'{line->trim(" #")} ({nr})', linenr: nr}) elseif line =~ '^=\+$' && pline =~ '^\S\+' lvl = 1 if len(toc_num) < 1 toc_num->add(1) else if lvl > plvl toc_num[0] = 1 else toc_num[0] += 1 endif endif toc->add({lvl: 0, toc_num: toc_num[: 0], text: $'{pline} ({nr - 1})', linenr: nr - 1}) elseif line =~ '^-\+$' && pline =~ '^\S\+' lvl = 2 if len(toc_num) < 2 toc_num->add(1) else if lvl > plvl toc_num[1] = 1 else toc_num[1] += 1 endif endif var toc_num_str = toc_num[: 1]->join('.') toc->add({lvl: 1, toc_num: toc_num[: 1], text: $'{pline} ({nr - 1})', linenr: nr - 1}) endif plvl = lvl endfor var title = toc->reduce((acc, v) => v.lvl == 0 ? acc + 1 : acc, 0) == 1 ? 1 : 0 for t in toc var toc_num_str = t.toc_num[title : ]->join('.') t.text = repeat(" ", t.lvl - title) .. $"{toc_num_str} {t.text}" endfor popup.FilterMenu("TOC", toc, (res, key) => { exe $":{res.linenr}" normal! zz }, (winid) => { win_execute(winid, 'syn match FilterMenuLineNr "(\d\+)$"') win_execute(winid, 'syn match FilterMenuSecNum "^\s*\(\d\+\.\)*\(\d\+\)"') hi def link FilterMenuLineNr Comment hi def link FilterMenuSecNum Title }) enddef nnoremap z Toc() # Markdown header text object # * inner object is the text between prev section header(excluded) and the next # section of the same level(excluded) or end of file. # * an object is the text between prev section header(included) and the next section of the same # level(excluded) or end of file. def HeaderTextObj(inner: bool) var lnum_start = search('^#\+\s\+[^[:space:]=]', "ncbW") if lnum_start > 0 var lvlheader = matchstr(getline(lnum_start), '^#\+') var lnum_end = search('^#\{1,' .. len(lvlheader) .. '}\s', "nW") if lnum_end == 0 lnum_end = search('\%$', 'cnW') else lnum_end -= 1 endif if inner && getline(lnum_start + 1) !~ '^#\+\s\+[^[:space:]=]' lnum_start += 1 endif echom lnum_start lnum_end exe $":{lnum_end}" normal! V exe $":{lnum_start}" endif enddef