You cannot. From `:h 'number'` (emphasis mine): > Print the line number in front of ***each line*** You can only enable `number` or `relativenumber` for the whole window. See: :help 'number' :help 'relativenumber' **Edit:** In Vim, this isn't feasible because `'signcolumn'` has a max width of 2 characters. However, if you're using NeoVim, it can be feasibly done because the `'signcolumn'` options supports a width of up to 9, so unless you're editing files with > 1 billion lines, there is enough space for line numbers. This might be easier done in Lua (I don't know, I've never written a Lua plugin for Nvim), but here is a solution in Vimscript. No guarantees about performance or "correctness" (it's kind of just thrown together), but it appears to work, let me know if I've missed some bug: let g:custom_number_delimiter='##' let s:sign_id=500 " script level variable of current sign ID function! number#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_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_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_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_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_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_signs(number#get_section_lines()) augroup END And here is what that looks like in Vim: ![example picture](https://i.sstatic.net/qRKYz.png) See: :h 'signcolumn' :h :sign :h :sign-define :h :sign-place :h :sign-unplace