diff --git a/autoload/magit/sign.vim b/autoload/magit/sign.vim new file mode 100644 index 0000000000000000000000000000000000000000..abf488dd65dab1ed70b9f109aa1ebec33f2f7656 --- /dev/null +++ b/autoload/magit/sign.vim @@ -0,0 +1,88 @@ +" Got lot of stuf from vim-gitgutter +" https://github.com/airblade/vim-gitgutter + +" Vim doesn't namespace sign ids so every plugin shares the same +" namespace. Sign ids are simply integers so to avoid clashes with other +" signs we guess at a clear run. +" +" Note also we currently never reset s:next_sign_id. +let s:first_sign_id = 42000 +let s:next_sign_id = s:first_sign_id +let s:dummy_sign_id = s:first_sign_id - 1 +" Remove-all-signs optimisation requires Vim 7.3.596+. +let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596")) + +let s:bufnr = bufnr(g:magit_buffer_name) + +" magit#sign#remove_signs: unplace a list of signs +" param[in] sign_ids: list of signs ids +function! magit#sign#remove_signs(sign_ids) + let bufnr = magit#utils#bufnr() + for id in a:sign_ids + execute "sign unplace" id + endfor +endfunction + +" s:get_next_sign_id: helper function to increment sign ids +function! s:get_next_sign_id() + let next_id = s:next_sign_id + let s:next_sign_id += 1 + return next_id +endfunction + +" magit#sign#find_signs: this function returns signs matching a pattern in a +" range of lines +" param[in] pattern: regex pattern to match +" param[in] startline,endline: range of lines +" FIXME: find since which version "sign place" is sorted +function! magit#sign#find_signs(pattern, startline, endline) + let bufnr = magit#utils#bufnr() + " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>} + let found_signs = {} + + redir => signs + silent execute "sign place buffer=" . bufnr + redir END + + for sign_line in filter(split(signs, '\n'), 'v:val =~# "="') + " Typical sign line: line=88 id=1234 name=GitGutterLineAdded + " We assume splitting is faster than a regexp. + let components = split(sign_line) + let name = split(components[2], '=')[1] + let line_number = str2nr(split(components[0], '=')[1]) + if ( name =~# a:pattern && + \ line_number >= a:startline && + \ line_number <= a:endline ) + let id = str2nr(split(components[1], '=')[1]) + let found_signs[line_number] = {'id': id, 'name': name} + endif + endfor + return found_signs +endfunction + +" s:magit_mark_sign: string of the sign for lines to be staged +let s:magit_mark_sign='MagitMark' + +" magit#sign#init: initializer function for signs +function! magit#sign#init() + execute "sign define " . s:magit_mark_sign . " text=S> linehl=Visual" +endfunction + +" magit#sign#toggle_signs: toggle marks for range of lines +" marked lines are unmarked, non marked are marked +" param[in] startline,endline: range of lines +function! magit#sign#toggle_signs(startline, endline) + let bufnr = magit#utils#bufnr() + let current_signs = magit#sign#find_signs(s:magit_mark_sign, a:startline, a:endline) + let line = a:startline + while ( line <= a:endline ) + if ( has_key(current_signs, line) == 0 ) + execute ":sign place " . <SID>get_next_sign_id() . + \ " line=" . line . " name=" . s:magit_mark_sign . + \ " buffer=" . bufnr + else + execute ":sign unplace " . current_signs[line].id + endif + let line += 1 + endwhile +endfunction diff --git a/autoload/magit/utils.vim b/autoload/magit/utils.vim index 670294453680223135cafbea361f47d45ae3323a..6b8ca64f6d186c6fbb44c9096b74cb063f0349da 100644 --- a/autoload/magit/utils.vim +++ b/autoload/magit/utils.vim @@ -162,3 +162,16 @@ function! magit#utils#append_file(file, lines) call writefile(fcontents+a:lines, a:file, 'b') endfunction +" s:bufnr: local variable to store current magit buffer id +let s:bufnr = 0 +" magit#utils#setbufnr: function to set current magit buffer id +" param[in] bufnr: current magit buffer id +function! magit#utils#setbufnr(bufnr) + let s:bufnr = a:bufnr +endfunction + +" magit#utils#bufnr: function to get current magit buffer id +" return: current magit buffer id +function! magit#utils#bufnr() + return s:bufnr +endfunction diff --git a/plugin/magit.vim b/plugin/magit.vim index 78d99baf85adf0ff5b8b7c3b8630819b2ea73c6d..99068abc03d0234007291c449eafbaba4942faae 100644 --- a/plugin/magit.vim +++ b/plugin/magit.vim @@ -41,6 +41,7 @@ call s:set('g:magit_show_magit_mapping', '<leader>M' ) call s:set('g:magit_stage_file_mapping', 'F' ) call s:set('g:magit_stage_hunk_mapping', 'S' ) call s:set('g:magit_stage_line_mapping', 'L' ) +call s:set('g:magit_mark_line_mapping', 'M' ) call s:set('g:magit_discard_hunk_mapping', 'DDD' ) call s:set('g:magit_commit_mapping_command', 'w<cr>' ) call s:set('g:magit_commit_mapping', 'CC' ) @@ -456,6 +457,19 @@ function! s:mg_create_diff_from_select(start_select_line, end_select_line) return selection endfunction +" s:mg_mark_lines_in_hunk: this function toggle marks for selected lines in a +" hunk. +" if a hunk contains marked lines, only these lines will be (un)staged on next +" (un)stage command +" param[in] start_select_line,end_select_line: limits of the selection +function! s:mg_mark_lines_in_hunk(start_select_line, end_select_line) + let [starthunk,endhunk] = <SID>mg_select_hunk_block() + if ( a:start_select_line < starthunk || a:end_select_line > endhunk ) + throw 'out of hunk selection' + endif + return magit#sign#toggle_signs(a:start_select_line, a:end_select_line) +endfunction + " s:mg_get_section: helper function to get the current section, according to " cursor position " return: section id, empty string if no section found @@ -476,6 +490,13 @@ function! s:mg_get_filename() return substitute(getline(search(g:magit_file_re, "cbnW")), g:magit_file_re, '\2', '') endfunction +" s:mg_get_hunkheader: helper function to get the current hunk header, +" according to cursor position +" return: hunk header +function! s:mg_get_hunkheader() + return getline(search(g:magit_hunk_re, "cbnW")) +endfunction + " }}} " {{{ User functions and commands @@ -592,6 +613,9 @@ function! magit#show_magit(display) silent! execute "bdelete " . g:magit_buffer_name execute "file " . g:magit_buffer_name + call magit#utils#setbufnr(bufnr(g:magit_buffer_name)) + call magit#sign#init() + execute "nnoremap <buffer> <silent> " . g:magit_stage_file_mapping . " :call magit#stage_file()<cr>" execute "nnoremap <buffer> <silent> " . g:magit_stage_hunk_mapping . " :call magit#stage_hunk(0)<cr>" execute "nnoremap <buffer> <silent> " . g:magit_discard_hunk_mapping . " :call magit#stage_hunk(1)<cr>" @@ -607,6 +631,9 @@ function! magit#show_magit(display) execute "nnoremap <buffer> <silent> " . g:magit_stage_line_mapping . " :call magit#stage_vselect()<cr>" execute "xnoremap <buffer> <silent> " . g:magit_stage_hunk_mapping . " :call magit#stage_vselect()<cr>" + execute "nnoremap <buffer> <silent> " . g:magit_mark_line_mapping . " :call magit#mark_vselect()<cr>" + execute "xnoremap <buffer> <silent> " . g:magit_mark_line_mapping . " :call magit#mark_vselect()<cr>" + for mapping in g:magit_folding_toggle_mapping " trick to pass '<cr>' in a mapping command without being interpreted let func_arg = ( mapping ==? "<cr>" ) ? '+' : mapping @@ -739,6 +766,12 @@ function! magit#stage_vselect() range return magit#stage_block(selection, 0) endfunction +" magit#mark_vselect: wrapper function to mark selected lines (see +" mg_mark_lines_in_hunk) +function! magit#mark_vselect() range + return <SID>mg_mark_lines_in_hunk(a:firstline, a:lastline) +endfunction + " magit#ignore_file: this function add the file under cursor to .gitignore " FIXME: git diff adds some strange characters to end of line function! magit#ignore_file() abort