Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Created January 29, 2026 13:43
Show Gist options
  • Select an option

  • Save Konfekt/68ff9fb95af48502c119f16a02f09fd3 to your computer and use it in GitHub Desktop.

Select an option

Save Konfekt/68ff9fb95af48502c119f16a02f09fd3 to your computer and use it in GitHub Desktop.
Drop-in Vim(9) Markdown text objects
if !has("vim9script") | finish | endif
vim9script
# Adapted from https://github.com/habamax/.vim/blob/7e2bac788a3a6de59356374ea60d90b5dec68b2e/after/ftplugin/markdown.vim
# 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
exe $":{lnum_end}"
normal! V
exe $":{lnum_start}"
endif
enddef
# Markdown Unit/header textobject
onoremap <buffer><silent> iu <scriptcmd>HeaderTextObj(true)<CR>
onoremap <buffer><silent> au <scriptcmd>HeaderTextObj(false)<CR>
xnoremap <buffer><silent> iu <esc><scriptcmd>HeaderTextObj(true)<CR>
xnoremap <buffer><silent> au <esc><scriptcmd>HeaderTextObj(false)<CR>
def FindSection(dir: string = '')
search('\%(^#\{1,5\}\s\+\S\|^\S.*\n^[=-]\+$\)', $'{dir}sW')
var mdsyn = synstack(line('.'), 1)->map('synIDattr(v:val, "name")')
while mdsyn[0] =~ '\v^mkdCode%(Block)?|pandoc%(DelimitedCodeBlock|NoFormatted)'
search('\%(^#\{1,5\}\s\+\S\|^\S.*\n^[=-]\+$\)', $'{dir}sW')
mdsyn = synstack(line('.'), 1)->map('synIDattr(v:val, "name")')
endwhile
normal! zz
enddef
nnoremap <buffer> ]] <scriptcmd>FindSection()<CR>
nnoremap <buffer> [[ <scriptcmd>FindSection('b')<CR>
# code block text object
def ObjCode(inner: bool)
cursor(line('.'), 1)
def IsCode(): bool
var stx = map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')->join()
return stx =~? 'mkdCode\|pandocDelimitedCodeBlock'
enddef
def IsCodeDelimiter(): bool
var stx = map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')->join()
return stx =~? '\vmkdCodeDelimiter|pandocDelimitedCodeBlock%(Start|End)'
enddef
if exists("g:syntax_on") && index(['markdown', 'pandoc'], &syntax) >= 0 &&
\ !IsCode() && !IsCodeDelimiter()
if search('^\s*```', 'cW', line(".") + 500, 100) <= 0
return
endif
elseif !IsCodeDelimiter() || (!IsCode() && IsCodeDelimiter())
if search('^\s*```', 'bW') <= 0
return
endif
endif
var pos_start = line('.') + (inner ? 1 : 0)
# Search for the code end.
if search('^\s*```\s*$', 'W') <= 0 | return | endif
var pos_end = line('.') - (inner ? 1 : 0)
exe $":{pos_start}"
normal! V
exe $":{pos_end}"
enddef
onoremap <buffer> <silent>il <scriptcmd>ObjCode(true)<CR>
onoremap <buffer> <silent>al <scriptcmd>ObjCode(false)<CR>
xnoremap <buffer> <silent>il <esc><scriptcmd>ObjCode(true)<CR>
xnoremap <buffer> <silent>al <esc><scriptcmd>ObjCode(false)<CR>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment