376 lines
15 KiB
VimL
376 lines
15 KiB
VimL
|
|
" vim: fdm=marker:et:ts=4:sw=2:sts=2
|
||
|
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||
|
|
|
||
|
|
function! signature#sign#Place(sign, lnum) "{{{1
|
||
|
|
" Description: Place signs for marks/markers on the specified line number
|
||
|
|
" Arguments:
|
||
|
|
" sign : The mark/marker whose sign is to be placed
|
||
|
|
" lnum : Line number on/from which the sign is to be placed/removed
|
||
|
|
|
||
|
|
"echom "DEBUG: sign = " . a:sign . ", lnum = " . a:lnum
|
||
|
|
|
||
|
|
" If Signature is not enabled, return
|
||
|
|
if !b:sig_enabled | return | endif
|
||
|
|
|
||
|
|
" FIXME: Highly inefficient. Needs work
|
||
|
|
" Place sign only if there are no signs from other plugins (eg. syntastic)
|
||
|
|
"let l:present_signs = s:GetInfo(1)
|
||
|
|
"if ( b:SignatureDeferPlacement
|
||
|
|
" \ && has_key(l:present_signs, a:lnum)
|
||
|
|
" \ && (l:present_signs[a:lnum]['name'] !~# '^sig_Sign_')
|
||
|
|
" \ )
|
||
|
|
" return
|
||
|
|
"endif
|
||
|
|
|
||
|
|
if signature#utils#IsValidMarker(a:sign)
|
||
|
|
let b:sig_markers[a:lnum] = a:sign . get(b:sig_markers, a:lnum, "")
|
||
|
|
elseif signature#utils#IsValidMark(a:sign)
|
||
|
|
let b:sig_marks[a:lnum] = a:sign . get(b:sig_marks, a:lnum, "")
|
||
|
|
else
|
||
|
|
echoerr "Unexpected sign found: " . a:sign
|
||
|
|
endif
|
||
|
|
"}}}3
|
||
|
|
|
||
|
|
call s:RefreshLine(a:lnum)
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#Remove(sign, lnum) "{{{1
|
||
|
|
" Description: Remove signs for marks/markers from the specified line number
|
||
|
|
" Arguments:
|
||
|
|
" sign : The mark/marker whose sign is to be placed/removed/toggled
|
||
|
|
" lnum : Line number from which the sign is to be removed
|
||
|
|
" If sign is a marker and lnum is 0, the sign will be removed from all lines
|
||
|
|
" If sign is a mark and lnum is 0, the lnum will be found and the sign will be removed from that line
|
||
|
|
|
||
|
|
"echom "DEBUG: sign = " . a:sign . ", lnum = " . a:lnum
|
||
|
|
|
||
|
|
" If Signature is not enabled, return
|
||
|
|
if !b:sig_enabled | return | endif
|
||
|
|
|
||
|
|
" Remove sign for markers
|
||
|
|
if signature#utils#IsValidMarker(a:sign)
|
||
|
|
let b:sig_markers[a:lnum] = substitute(b:sig_markers[a:lnum], "\\C" . escape( a:sign, '$^' ), "", "")
|
||
|
|
|
||
|
|
" If there are no markers on the line, delete signs on that line
|
||
|
|
if b:sig_markers[a:lnum] == ""
|
||
|
|
call remove(b:sig_markers, a:lnum)
|
||
|
|
endif
|
||
|
|
call s:RefreshLine(a:lnum)
|
||
|
|
|
||
|
|
" Remove sign for marks
|
||
|
|
else
|
||
|
|
" For marks, if a:lnum == 0, find out the line where the mark was placed
|
||
|
|
if a:lnum == 0
|
||
|
|
let l:arr = keys(filter(copy(b:sig_marks), 'v:val =~# a:sign'))
|
||
|
|
if empty(l:arr) | return | endif
|
||
|
|
else
|
||
|
|
let l:arr = [a:lnum]
|
||
|
|
endif
|
||
|
|
if (v:version >= 800)
|
||
|
|
call assert_true(len(l:arr) == 1, "Multiple marks found where one was expected")
|
||
|
|
elseif (len(l:arr) != 1)
|
||
|
|
echoerr "Multiple marks found where one was expected"
|
||
|
|
endif
|
||
|
|
|
||
|
|
for l:lnum in l:arr
|
||
|
|
" FIXME: Placed guard to avoid triggering issue #53
|
||
|
|
if has_key(b:sig_marks, l:lnum)
|
||
|
|
let b:sig_marks[l:lnum] = substitute(b:sig_marks[l:lnum], "\\C" . a:sign, "", "")
|
||
|
|
" If there are no marks on the line, delete signs on that line
|
||
|
|
if b:sig_marks[l:lnum] == ""
|
||
|
|
call remove(b:sig_marks, l:lnum)
|
||
|
|
endif
|
||
|
|
endif
|
||
|
|
call s:RefreshLine(l:lnum)
|
||
|
|
endfor
|
||
|
|
endif
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! s:EvaluateHL(expr, lnum, ...) "{{{1
|
||
|
|
" Description: If expr points to a function, call it and use its output as the highlight group.
|
||
|
|
" If it is a string, use it directly.
|
||
|
|
" If the optional argument is specified, use it as a fallback. If not, return an empty string
|
||
|
|
|
||
|
|
if type(a:expr) == type("")
|
||
|
|
return a:expr
|
||
|
|
elseif type(a:expr) == type(function("tr"))
|
||
|
|
let l:retval = a:expr(a:lnum)
|
||
|
|
if (l:retval != "")
|
||
|
|
return l:retval
|
||
|
|
endif
|
||
|
|
endif
|
||
|
|
|
||
|
|
return (a:0 > 0 ? a:1 : "")
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! s:RefreshLine(lnum) "{{{1
|
||
|
|
" Description: Decides what the sign string should be based on if there are any marks or markers (using b:sig_marks
|
||
|
|
" and b:sig_markers) on the current line and the value of b:SignaturePrioritizeMarks.
|
||
|
|
" Arguments:
|
||
|
|
" lnum : Line number for which the sign string is to be modified
|
||
|
|
|
||
|
|
let l:id = abs(a:lnum * 1000 + bufnr('%'))
|
||
|
|
let l:str = ""
|
||
|
|
|
||
|
|
" Place the sign
|
||
|
|
if ( has_key(b:sig_marks, a:lnum)
|
||
|
|
\ && ( b:SignaturePrioritizeMarks
|
||
|
|
\ || !has_key(b:sig_markers, a:lnum)
|
||
|
|
\ )
|
||
|
|
\ )
|
||
|
|
let l:SignatureMarkTextHL = s:EvaluateHL(g:SignatureMarkTextHL, a:lnum, "SignatureMarkText")
|
||
|
|
let l:SignatureMarkLineHL = s:EvaluateHL(g:SignatureMarkLineHL, a:lnum, "SignatureMarkLine")
|
||
|
|
let l:str = substitute(b:SignatureMarkOrder, "\m", signature#utils#GetChar(b:sig_marks[a:lnum], 0), '')
|
||
|
|
let l:str = substitute(l:str, "\p", signature#utils#GetChar(b:sig_marks[a:lnum], 1), '')
|
||
|
|
|
||
|
|
execute 'sign define Signature_' . l:str . ' text=' . l:str . ' texthl=' . l:SignatureMarkTextHL . ' linehl=' . l:SignatureMarkLineHL
|
||
|
|
|
||
|
|
elseif has_key(b:sig_markers, a:lnum)
|
||
|
|
let l:SignatureMarkerTextHL = s:EvaluateHL(g:SignatureMarkerTextHL, a:lnum, "SignatureMarkerText")
|
||
|
|
let l:SignatureMarkerLineHL = s:EvaluateHL(g:SignatureMarkerLineHL, a:lnum, "SignatureMarkerLine")
|
||
|
|
|
||
|
|
" Since the same marker can be placed on multiple lines, we can't use the same sign for all of them.
|
||
|
|
" This is because if dynamic highlighting of markers is enabled then the sign placed on eg. a modified line should
|
||
|
|
" be highlighted differently than the one placed on an unchanged line.
|
||
|
|
" In order to support this, I append the name of the TextHL and LineHL group to the name of the sign.
|
||
|
|
let l:txt = signature#utils#GetChar(b:sig_markers[a:lnum], 0)
|
||
|
|
let l:str = l:txt . '_' . l:SignatureMarkerTextHL . '_' . l:SignatureMarkerLineHL
|
||
|
|
|
||
|
|
execute 'sign define Signature_' . l:str . ' text=' . l:txt . ' texthl=' . l:SignatureMarkerTextHL . ' linehl=' . l:SignatureMarkerLineHL
|
||
|
|
else
|
||
|
|
call signature#sign#Unplace(a:lnum)
|
||
|
|
endif
|
||
|
|
|
||
|
|
if (l:str != "")
|
||
|
|
execute 'sign place ' . l:id . ' line=' . a:lnum . ' name=Signature_' . l:str . ' buffer=' . bufnr('%')
|
||
|
|
endif
|
||
|
|
|
||
|
|
" If there is only 1 mark/marker in the file, place a dummy to prevent flickering of the gutter when it is moved
|
||
|
|
" If there are no signs left, remove the dummy
|
||
|
|
call signature#sign#ToggleDummy()
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#Refresh(...) "{{{1
|
||
|
|
" Description: Add signs for new marks/markers and remove signs for deleted marks/markers
|
||
|
|
" Arguments: Specify an argument to force a sign refresh
|
||
|
|
|
||
|
|
call s:InitializeVars(a:0 && a:1)
|
||
|
|
" If Signature is not enabled, return
|
||
|
|
if !b:sig_enabled | return | endif
|
||
|
|
|
||
|
|
for i in signature#mark#GetList('free', 'buf_curr')
|
||
|
|
" ... remove it
|
||
|
|
call signature#sign#Remove(i, 0)
|
||
|
|
endfor
|
||
|
|
|
||
|
|
" Add signs for marks ...
|
||
|
|
for [l:mark, l:lnum, _] in signature#mark#GetList('used', 'buf_curr')
|
||
|
|
" ... if mark is not present in our b:sig_marks list or if it is present but at the wrong line,
|
||
|
|
" remove the old sign and add a new one
|
||
|
|
if ( !has_key(b:sig_marks, l:lnum)
|
||
|
|
\ || (b:sig_marks[l:lnum] !~# l:mark)
|
||
|
|
\ || a:0
|
||
|
|
\ )
|
||
|
|
call signature#sign#Remove(l:mark, 0)
|
||
|
|
call signature#sign#Place (l:mark, l:lnum)
|
||
|
|
endif
|
||
|
|
endfor
|
||
|
|
|
||
|
|
call signature#sign#ToggleDummy()
|
||
|
|
|
||
|
|
" We do not add signs for markers as SignRefresh is executed periodically and we don't have a way to determine if the
|
||
|
|
" marker already has a sign or not
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#Unplace(lnum) "{{{1
|
||
|
|
" Description: Remove the sign from the specified line number
|
||
|
|
" FIXME: Clean-up. Undefine the sign
|
||
|
|
let l:id = abs(a:lnum * 1000 + bufnr('%'))
|
||
|
|
silent! execute 'sign unplace ' . l:id
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#ToggleDummy(...) "{{{1
|
||
|
|
" Description: Places a dummy sign to prevent flickering of the gutter when the mark is moved or the line containing
|
||
|
|
" a mark/marker is deleted and then the delete is undone
|
||
|
|
" Arguments: (optional) 0 : force remove
|
||
|
|
" 1 : force place
|
||
|
|
|
||
|
|
let l:place = a:0 ? a:1 : (len(b:sig_marks) + len(b:sig_markers) == 1) && !b:sig_DummyExists
|
||
|
|
let l:remove = a:0 ? !a:1 : (len(b:sig_marks) + len(b:sig_markers) == 0) && b:sig_DummyExists
|
||
|
|
|
||
|
|
if (l:place)
|
||
|
|
sign define Signature_Dummy
|
||
|
|
execute 'sign place 666 line=1 name=Signature_Dummy buffer=' . bufnr('%')
|
||
|
|
let b:sig_DummyExists = 1
|
||
|
|
elseif (l:remove)
|
||
|
|
silent! execute 'sign unplace 666 buffer=' . bufnr('%')
|
||
|
|
let b:sig_DummyExists = 0
|
||
|
|
endif
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! s:GetInfo(...) "{{{1
|
||
|
|
" Description: Returns a dic of filenames, each of which is a dic of line numbers on which signs are placed
|
||
|
|
" Arguments: filename (optional).
|
||
|
|
" If filename is provided, the return value will contain signs only present in the given file
|
||
|
|
" Eg. {
|
||
|
|
" 'vimrc': {
|
||
|
|
" '711': {
|
||
|
|
" 'id': '1422',
|
||
|
|
" 'name': 'sig_Sign_1422'
|
||
|
|
" },
|
||
|
|
" '676': {
|
||
|
|
" 'id': '1352',
|
||
|
|
" 'name': 'sig_Sign_1352'
|
||
|
|
" }
|
||
|
|
" }
|
||
|
|
" }
|
||
|
|
|
||
|
|
" Redirect the input to a variable
|
||
|
|
redir => l:sign_str
|
||
|
|
silent! sign place
|
||
|
|
redir END
|
||
|
|
|
||
|
|
" Create a Hash of files to store the info.
|
||
|
|
let l:signs_dic = {}
|
||
|
|
" The file that is currently being processed is stored into l:file
|
||
|
|
let l:match_file = ""
|
||
|
|
let l:file_found = 0
|
||
|
|
|
||
|
|
" Split the string into an array of sentences and filter out empty lines
|
||
|
|
for i in filter( split( l:sign_str, '\n' ), 'v:val =~ "^[S ]"' )
|
||
|
|
let l:temp_file = matchstr( i, '\v(Signs for )@<=\S+:@=' )
|
||
|
|
|
||
|
|
if l:temp_file != ""
|
||
|
|
let l:match_file = l:temp_file
|
||
|
|
let l:signs_dic[l:match_file] = {}
|
||
|
|
elseif l:match_file != ""
|
||
|
|
" Get sign info
|
||
|
|
let l:info_match = matchlist( i, '\vline\=(\d+)\s*id\=(\S+)\s*name\=(\S+)' )
|
||
|
|
if !empty( l:info_match )
|
||
|
|
let l:signs_dic[l:match_file][l:info_match[1]] = {
|
||
|
|
\ 'id' : l:info_match[2],
|
||
|
|
\ 'name' : l:info_match[3],
|
||
|
|
\ }
|
||
|
|
endif
|
||
|
|
endif
|
||
|
|
endfor
|
||
|
|
|
||
|
|
if a:0
|
||
|
|
"" Search for the full path first in the hash ...
|
||
|
|
"let l:curr_filepath = expand('%:p')
|
||
|
|
"if has_key( l:signs_dic, l:curr_filepath )
|
||
|
|
" return filter( l:signs_dic, 'v:key ==# l:curr_filepath' )[l:curr_filepath]
|
||
|
|
"else
|
||
|
|
" ... if no entry is found for the full path, search for the filename in the hash ...
|
||
|
|
" Since we're searching for the current file, if present in the hash, it'll be as a filename and not the full path
|
||
|
|
let l:curr_filename = expand('%:t')
|
||
|
|
if has_key( l:signs_dic, l:curr_filename )
|
||
|
|
return filter( l:signs_dic, 'v:key ==# l:curr_filename' )[l:curr_filename]
|
||
|
|
endif
|
||
|
|
|
||
|
|
" ... if nothing is found, then return an empty hash to indicate that no signs are present in the current file
|
||
|
|
return {}
|
||
|
|
endif
|
||
|
|
|
||
|
|
return l:signs_dic
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#GetGitGutterHLGroup(lnum) "{{{1
|
||
|
|
" Description: This returns the highlight group used by vim-gitgutter depending on how the line was edited
|
||
|
|
|
||
|
|
let l:current_bufnr = bufnr('%')
|
||
|
|
let l:line_state = filter(copy(gitgutter#diff#process_hunks(l:current_bufnr, gitgutter#hunk#hunks(l:current_bufnr))), 'v:val[0] == a:lnum')
|
||
|
|
|
||
|
|
if len(l:line_state) == 0
|
||
|
|
return ""
|
||
|
|
endif
|
||
|
|
|
||
|
|
if (l:line_state[0][1]) =~ 'added' | return 'GitGutterAdd'
|
||
|
|
elseif (l:line_state[0][1]) =~ 'modified_removed' | return 'GitGutterChangeDelete'
|
||
|
|
elseif (l:line_state[0][1]) =~ 'modified' | return 'GitGutterChange'
|
||
|
|
elseif (l:line_state[0][1]) =~ 'removed' | return 'GitGutterDelete'
|
||
|
|
endif
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! signature#sign#GetSignifyHLGroup(lnum) "{{{1
|
||
|
|
" Description: This returns the highlight group used by vim-signify depending on how the line was edited
|
||
|
|
" Thanks to @michaelmior
|
||
|
|
|
||
|
|
if !exists('b:sy')
|
||
|
|
return ""
|
||
|
|
endif
|
||
|
|
call sy#sign#get_current_signs(b:sy)
|
||
|
|
|
||
|
|
if has_key(b:sy.internal, a:lnum)
|
||
|
|
let l:line_state = b:sy.internal[a:lnum]['type']
|
||
|
|
if l:line_state =~ 'SignifyAdd' | return 'SignifySignAdd'
|
||
|
|
elseif l:line_state =~ 'SignifyChange' | return 'SignifySignChange'
|
||
|
|
elseif l:line_state =~ 'SignifyDelete' | return 'SignifySignDelete'
|
||
|
|
end
|
||
|
|
endif
|
||
|
|
|
||
|
|
return ""
|
||
|
|
endfunction
|
||
|
|
|
||
|
|
|
||
|
|
" function! signature#sign#GetMarkSignLine(mark) "{{{1
|
||
|
|
" if !signature#utils#IsValidMark(a:mark)
|
||
|
|
" echoe "Signature: Invalid mark " . a:mark
|
||
|
|
" return
|
||
|
|
" endif
|
||
|
|
|
||
|
|
" let l:sign_info=filter(split(execute('sign place'), '\n'),
|
||
|
|
" \ 'v:val =~ "\\vSignature_(.?' . a:mark . '|' . a:mark . '.?)$"')
|
||
|
|
|
||
|
|
" if (len(l:sign_info) != 1)
|
||
|
|
" echoe "Signature: Expected single match, found " . len(l:sign_info)
|
||
|
|
" return
|
||
|
|
" endif
|
||
|
|
|
||
|
|
" return matchstr(l:sign_info[0], '\v(line\=)@<=\d+')
|
||
|
|
" endfunction
|
||
|
|
|
||
|
|
|
||
|
|
function! s:InitializeVars(...) "{{{1
|
||
|
|
" Description: Initialize variables
|
||
|
|
" Arguments: Specify an argument to re-init
|
||
|
|
|
||
|
|
if !exists('b:sig_marks')
|
||
|
|
" b:sig_marks = { lnum => signs_str }
|
||
|
|
let b:sig_marks = {}
|
||
|
|
else
|
||
|
|
" Lines can be removed using an external tool. Hence, we need to filter out marks placed on line numbers that are
|
||
|
|
" now greater than the total number of lines in the file.
|
||
|
|
let l:line_tot = line('$')
|
||
|
|
call filter( b:sig_marks, 'v:key <= l:line_tot' )
|
||
|
|
endif
|
||
|
|
|
||
|
|
if !exists('b:sig_markers')
|
||
|
|
" b:sig_markers = { lnum => marker }
|
||
|
|
let b:sig_markers = {}
|
||
|
|
else
|
||
|
|
" Lines can be removed using an external tool. Hence, we need to filter out marks placed on line numbers that are
|
||
|
|
" now greater than the total number of lines in the file.
|
||
|
|
let l:line_tot = line('$')
|
||
|
|
call filter( b:sig_markers, 'v:key <= l:line_tot' )
|
||
|
|
endif
|
||
|
|
|
||
|
|
call signature#utils#Set('b:sig_DummyExists' , 0 , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:sig_enabled' , g:SignatureEnabledAtStartup, a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignatureIncludeMarks' , g:SignatureIncludeMarks , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignatureIncludeMarkers' , g:SignatureIncludeMarkers , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignatureMarkOrder' , g:SignatureMarkOrder , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignaturePrioritizeMarks', g:SignaturePrioritizeMarks , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignatureDeferPlacement' , g:SignatureDeferPlacement , a:0 && a:1)
|
||
|
|
call signature#utils#Set('b:SignatureWrapJumps' , g:SignatureWrapJumps , a:0 && a:1)
|
||
|
|
endfunction
|