let g:custom_number_delimiter='##'
let s:sign_id=500 " script level variable of current sign ID
function! number#get_section_liness:get_section_lines() abort
" save cursor pos to restore
let l:cursor_pos=[line('.'), col('.')]
" Get line number by:
" 1. :g/re/p for start of sections
" 2. split output of :g/re/p into List
" 3. isolate line numbers from output
let l:section_start_lnums=number#isolate_lnumssection_start_lnums=s:isolate_lnums(split(execute('g/' . g:custom_number_delimiter . '.\+' . g:custom_number_delimiter . '/p#'), "\n"))
" no matches
if len(l:section_start_lnums) == 0
return []
endif
" restore cursor pos (:g/re/p moves to last match, be nice and put it back)
call cursor(cursor_pos)
" build list of ranges in the format [[range_start_line, range_end_line], ...]
let l:prev_lnum=-1
let l:curr_lnum=-1
let l:sections=[]
for lnum in l:section_start_lnums
let l:prev_lnum=l:curr_lnum
let l:curr_lnum=lnum
if l:prev_lnum != -1
call add(l:sections, [l:prev_lnum, l:curr_lnum-1])
endif
endfor
" logically, final section extends to end of file
call add(l:sections, [l:curr_lnum, line('$')])
return l:sections
endfunction
" gradually trim line until only the line number remains
function! number#isolate_lnumss:isolate_lnums(section_lines) abort
if a:section_lines[0] =~ "Pattern not found"
return []
endif
let l:lnums=[]
for section_line in a:section_lines
" trim trailing characters
let l:lnum = substitute(section_line, '^\s\+\d\+\zs.*', '', '')
" trim excess whitespace
let l:lnum = substitute(l:lnum, '\s', '', 'g')
call add(l:lnums, str2nr(l:lnum))
endfor
return l:lnums
endfunction
" define more signs, if needed
" note: doesn't abort on error
function! number#define_signss:define_signs(sections)
let l:biggest_diff=-1
for section in a:sections
let l:curr_diff=section[1]-section[0]
let l:biggest_diff=l:curr_diff > l:biggest_diff ? l:curr_diff : l:biggest_diff
endfor
for i in range(l:biggest_diff+1)
" fail silently when lnum is >= 100 in Vim
" In NeoVim use :h 'signcolumn' and :h 'number' accordingly
exec 'silent! sign define Lnum' . i . ' texthl=LineNr text=' . i
endfor
endfunction
" place signs in buffer
" note: doesn't abort on error
function! number#place_signss:place_signs(sections)
" clear all of our signs for current buffer
exec 'sign unplace * group=CustomNumber buffer=' . bufnr()
" no matches
if len(a:sections) == 0
return
endif
" define signs, if needed
call number#define_signss:define_signs(a:sections)
for section in a:sections
let l:custom_lnum=0
for lnum in range(section[0], section[1])
exec 'silent! sign place ' . s:sign_id . ' line=' . lnum . ' name=Lnum' . l:custom_lnum . ' group=CustomNumber buffer=' . bufnr()
let l:custom_lnum+=1
let s:sign_id+=1
endfor
endfor
endfunction
augroup CustomNumber
autocmd!
autocmd BufEnter,BufNew,TextChanged,TextChangedI,TextChangedP * call number#place_signss:place_signs(number#get_section_liness:get_section_lines())
augroup END