Skip to main content
portability
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16
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
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
let g:custom_number_delimiter='##'
let s:sign_id=500 " script level variable of current sign ID

function! s: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=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! s: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! s: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! s: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 s: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 s:place_signs(s:get_section_lines())
augroup END
deleted 32 characters in body
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16

In Vim, this isn't feasible because 'signcolumn' has a max width of 2 characters. HoweverFor some reason, if you're using NeoVimeven though Neovim supports larger sign columns, it caneach individual sign text must still be feasibly done because1 or 2 characters. If this were not the case, it would be possible in Neovim 'signcolumn' options supports a width of up(Relevant source code). This would have to 9, so unless you're editing files with > 1 billion lines, there is enough space for line numbersbe changed.

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.

In Vim, this isn't feasible because 'signcolumn' has a max width of 2 characters. For some reason, even though Neovim supports larger sign columns, each individual sign text must still be 1 or 2 characters. If this were not the case, it would be possible in Neovim (Relevant source code). This would have to be changed.

Add signcolumn solution
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16

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 (spoiler for brevity):

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 (spoiler for brevity):

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:

Add signcolumn solution
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16
Loading
Fix incorrect terminology
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16
Loading
Source Link
Jake Grossman
  • 1.7k
  • 1
  • 5
  • 16
Loading