Skip to main content
4 of 6
Add signcolumn solution
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16

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

See:

:h 'signcolumn'
:h :sign
:h :sign-define
:h :sign-place
:h :sign-unplace
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16