1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-24 03:00:06 +08:00
SpaceVim/bundle/vim-bookmarks/plugin/bookmark.vim

574 lines
17 KiB
VimL
Vendored

if exists('g:bm_has_any') || !has('signs') || &cp
finish
endif
scriptencoding utf-8
let g:bm_has_any = 0
let g:bm_sign_index = 9500
let g:bm_current_file = ''
" Configuration {{{
function! s:set(var, default)
if !exists(a:var)
if type(a:default)
execute 'let' a:var '=' string(a:default)
else
execute 'let' a:var '=' a:default
endif
endif
endfunction
call s:set('g:bookmark_highlight_lines', 0 )
call s:set('g:bookmark_sign', '⚑')
call s:set('g:bookmark_annotation_sign', '☰')
call s:set('g:bookmark_show_warning', 1 )
call s:set('g:bookmark_show_toggle_warning', 1 )
call s:set('g:bookmark_save_per_working_dir', 0 )
call s:set('g:bookmark_auto_save', 1 )
call s:set('g:bookmark_manage_per_buffer', 0 )
call s:set('g:bookmark_auto_save_file', $HOME .'/.vim-bookmarks')
call s:set('g:bookmark_auto_close', 0 )
call s:set('g:bookmark_center', 0 )
call s:set('g:bookmark_location_list', 0 )
call s:set('g:bookmark_disable_ctrlp', 0 )
function! s:init(file)
if g:bookmark_auto_save ==# 1 || g:bookmark_manage_per_buffer ==# 1
augroup bm_vim_enter
autocmd!
autocmd BufEnter * call s:set_up_auto_save(expand('<afile>:p'))
augroup END
endif
if a:file !=# ''
call s:set_up_auto_save(a:file)
elseif g:bookmark_manage_per_buffer ==# 0 && g:bookmark_save_per_working_dir ==# 0
call BookmarkLoad(s:bookmark_save_file(''), 1, 1)
endif
endfunction
" }}}
" Commands {{{
function! BookmarkToggle()
call s:refresh_line_numbers()
let file = expand("%:p")
if file ==# ""
return
endif
let current_line = line('.')
if bm#has_bookmark_at_line(file, current_line)
if g:bookmark_show_toggle_warning ==# 1 && bm#is_bookmark_has_annotation_by_line(file, current_line)
let delete = confirm("Delete Annotated bookmarks?", "&Yes\n&No", 2)
if (delete !=# 1)
echo "Ignore!"
return
endif
endif
call s:bookmark_remove(file, current_line)
echo "Bookmark removed"
else
call s:bookmark_add(file, current_line)
echo "Bookmark added"
endif
endfunction
command! ToggleBookmark call CallDeprecatedCommand('BookmarkToggle', [])
command! BookmarkToggle call BookmarkToggle()
function! BookmarkAnnotate(...)
call s:refresh_line_numbers()
let file = expand("%:p")
if file ==# ""
return
endif
let current_line = line('.')
let has_bm = bm#has_bookmark_at_line(file, current_line)
let bm = has_bm ? bm#get_bookmark_by_line(file, current_line) : 0
let old_annotation = has_bm ? bm['annotation'] : ""
let new_annotation = a:0 ># 0 ? a:1 : ""
" Get annotation from user input if not passed in
if new_annotation ==# ""
let input_msg = old_annotation !=# "" ? "Edit" : "Enter"
let new_annotation = input(input_msg ." annotation: ", old_annotation)
execute ":redraw!"
endif
" Nothing changed, bail out
if new_annotation ==# "" && old_annotation ==# new_annotation
return
" Update annotation
elseif has_bm
call bm#update_annotation(file, bm['sign_idx'], new_annotation)
let result_msg = (new_annotation ==# "")
\ ? "removed"
\ : old_annotation !=# ""
\ ? "updated: ". new_annotation
\ : "added: ". new_annotation
call bm_sign#update_at(file, bm['sign_idx'], bm['line_nr'], bm['annotation'] !=# "")
echo "Annotation ". result_msg
" Create bookmark with annotation
elseif new_annotation !=# ""
call s:bookmark_add(file, current_line, new_annotation)
echo "Bookmark added with annotation: ". new_annotation
endif
endfunction
command! -nargs=* Annotate call CallDeprecatedCommand('BookmarkAnnotate', [<q-args>, 0])
command! -nargs=* BookmarkAnnotate call BookmarkAnnotate(<q-args>, 0)
function! BookmarkClear()
call s:refresh_line_numbers()
let file = expand("%:p")
let lines = bm#all_lines(file)
for line_nr in lines
call s:bookmark_remove(file, line_nr)
endfor
echo "Bookmarks removed"
endfunction
command! ClearBookmarks call CallDeprecatedCommand('BookmarkClear', [])
command! BookmarkClear call BookmarkClear()
function! BookmarkClearAll(silent)
call s:refresh_line_numbers()
let files = bm#all_files()
let file_count = len(files)
let delete = 1
let in_multiple_files = file_count ># 1
let supports_confirm = has("dialog_con") || has("dialog_gui")
if (in_multiple_files && g:bookmark_show_warning ==# 1 && supports_confirm && !a:silent)
let delete = confirm("Delete ". bm#total_count() ." bookmarks in ". file_count . " buffers?", "&Yes\n&No")
endif
if (delete ==# 1)
call s:remove_all_bookmarks()
if (!a:silent)
execute ":redraw!"
echo "All bookmarks removed"
endif
endif
endfunction
command! ClearAllBookmarks call CallDeprecatedCommand('BookmarkClearAll', [0])
command! BookmarkClearAll call BookmarkClearAll(0)
function! BookmarkNext()
call s:refresh_line_numbers()
call s:jump_to_bookmark('next')
endfunction
command! NextBookmark call CallDeprecatedCommand('BookmarkNext')
command! BookmarkNext call BookmarkNext()
function! BookmarkPrev()
call s:refresh_line_numbers()
call s:jump_to_bookmark('prev')
endfunction
command! PrevBookmark call CallDeprecatedCommand('BookmarkPrev')
command! BookmarkPrev call BookmarkPrev()
command! CtrlPBookmark call ctrlp#init(ctrlp#bookmarks#id())
function! BookmarkShowAll()
if s:is_quickfix_win()
q
else
call s:refresh_line_numbers()
if exists(':Unite')
exec ":Unite vim_bookmarks"
elseif exists(':CtrlP') == 2 && g:bookmark_disable_ctrlp == 0
exec ":CtrlPBookmark"
elseif exists(':Leaderf')
exe ':Leaderf bookmarks'
else
let oldformat = &errorformat " backup original format
let &errorformat = "%f:%l:%m" " custom format for bookmarks
if g:bookmark_location_list
lgetexpr bm#location_list()
call setloclist(0, [], 'r', {'title': 'Bookmarks'})
belowright lopen
else
cgetexpr bm#location_list()
call setqflist([], 'r', {'title': 'Bookmarks'})
belowright copen
endif
augroup BM_AutoCloseCommand
autocmd!
autocmd WinLeave * call s:auto_close()
augroup END
let &errorformat = oldformat " re-apply original format
endif
endif
endfunction
command! ShowAllBookmarks call CallDeprecatedCommand('BookmarkShowAll')
command! BookmarkShowAll call BookmarkShowAll()
function! BookmarkSave(target_file, silent)
call s:refresh_line_numbers()
if (bm#total_count() > 0 || (!g:bookmark_save_per_working_dir && !g:bookmark_manage_per_buffer))
let serialized_bookmarks = bm#serialize()
call writefile(serialized_bookmarks, a:target_file)
if (!a:silent)
echo "All bookmarks saved"
endif
elseif (g:bookmark_save_per_working_dir || g:bookmark_manage_per_buffer)
call delete(a:target_file) " remove file, if no bookmarks
endif
endfunction
command! -nargs=1 SaveBookmarks call CallDeprecatedCommand('BookmarkSave', [<f-args>, 0])
command! -nargs=1 BookmarkSave call BookmarkSave(<f-args>, 0)
function! BookmarkLoad(target_file, startup, silent)
let supports_confirm = has("dialog_con") || has("dialog_gui")
let has_bookmarks = bm#total_count() ># 0
let confirmed = 1
if (supports_confirm && has_bookmarks && !a:silent)
let confirmed = confirm("Do you want to override your ". bm#total_count() ." bookmarks?", "&Yes\n&No")
endif
if (confirmed ==# 1)
call s:remove_all_bookmarks()
try
let data = readfile(a:target_file)
let new_entries = bm#deserialize(data)
if !a:startup
for entry in new_entries
call bm_sign#add_at(entry['file'], entry['sign_idx'], entry['line_nr'], entry['annotation'] !=# "")
endfor
if (!a:silent)
echo "Bookmarks loaded"
endif
return 1
endif
catch
if (!a:startup && !a:silent)
echo "Failed to load/parse file"
endif
return 0
endtry
endif
endfunction
command! -nargs=1 LoadBookmarks call CallDeprecatedCommand('BookmarkLoad', [<f-args>, 0, 0])
command! -nargs=1 BookmarkLoad call BookmarkLoad(<f-args>, 0, 0)
function! CallDeprecatedCommand(fun, args)
echo "Warning: Deprecated command, please use ':". a:fun ."' instead"
let Fn = function(a:fun)
return call(Fn, a:args)
endfunction
function! BookmarkModify(diff)
let current_line = line('.')
let new_line_nr = current_line + a:diff
call BookmarkMoveToLine(new_line_nr)
endfunction
function! BookmarkMoveToLine(target)
call s:refresh_line_numbers()
let file = expand("%:p")
if file ==# ""
return
endif
let current_line = line('.')
if a:target == current_line
return
endif
if bm#has_bookmark_at_line(file, current_line)
"ensure we're trying to move to a valid line nr
let max_line_nr = line('$')
if a:target <= 0 || a:target > max_line_nr
echo "Can't move bookmark beyond file bounds"
return
endif
if bm#has_bookmark_at_line(file, a:target)
echo "Hit another bookmark"
return
endif
let bookmark = bm#get_bookmark_by_line(file, current_line)
call bm_sign#update_at(file, bookmark['sign_idx'], a:target, bookmark['annotation'] !=# "")
let bufnr = bufnr(file)
let line_content = getbufline(bufnr, a:target)
let content = len(line_content) > 0 ? line_content[0] : ' '
call bm#update_bookmark_for_sign(file, bookmark['sign_idx'], a:target, content)
call cursor(a:target, 1)
normal! ^
else
return
endif
endfunction
command! -nargs=? BookmarkMoveUp call s:move_relative(<q-args>, -1)
command! -nargs=? BookmarkMoveDown call s:move_relative(<q-args>, 1)
command! -nargs=? BookmarkMoveToLine call s:move_absolute(<q-args>)
" }}}
" Private {{{
" arg is always a string
" direction is {-1,1}
function! s:move_relative(arg, direction)
if !s:has_bookmark_at_cursor()
echo 'No bookmark at current line'
return
endif
" '' => direction
" '0' => direction
" '\d+' => number * direction
" 'v:count' => v:count * direction
" negative/abc arg naturally rejected
if empty(a:arg)
let delta = a:direction
elseif a:arg =~# '\v^v:count1?$'
let delta = eval(a:arg) * a:direction
elseif a:arg =~# '\v^\d+$'
let delta = str2nr(a:arg) * a:direction
else
echo 'Invalid line count'
return
endif
let delta = (delta == 0) ? a:direction : delta
" at this point we're guaranteed to have integer target != 0
call BookmarkModify(delta)
endfunction
" arg is always a string
function! s:move_absolute(arg)
if !s:has_bookmark_at_cursor()
echo 'No bookmark at current line'
return
endif
" '' => prompt
" 'v:count' == 0 => prompt
" 'v:count' => use
" '\d+' => use
" negative/abc arg naturally rejected
if empty(a:arg)
let target = 0
elseif a:arg =~# '\v^v:count1?$'
let target = eval(a:arg)
elseif a:arg =~# '\v^\d+$'
let target = str2nr(a:arg)
else
echo 'Invalid line number'
return
endif
if target == 0
let target = input("Enter target line number: ")
" clear the line for subsequent messages
echo "\r"
" if 'abc<Esc>' or '<empty><Enter>' (indistinguishable), cancel with no error message
if empty(target)
return
endif
if target !~# '\v^\d+$'
echo "Invalid line number"
return
endif
let target = str2nr(target)
endif
" at this point we're guaranteed to have integer target > 0
call BookmarkMoveToLine(target)
endfunction
function! s:has_bookmark_at_cursor()
let file = expand("%:p")
let current_line = line('.')
if file ==# ""
return
endif
return bm#has_bookmark_at_line(file, current_line)
endfunction
function! s:lazy_init()
if g:bm_has_any ==# 0
augroup bm_refresh
autocmd!
autocmd ColorScheme * call bm_sign#define_highlights()
autocmd BufLeave * call s:refresh_line_numbers()
augroup END
let g:bm_has_any = 1
endif
endfunction
function! s:refresh_line_numbers()
call s:lazy_init()
let file = expand("%:p")
if file ==# "" || !bm#has_bookmarks_in_file(file)
return
endif
let bufnr = bufnr(file)
let sign_line_map = bm_sign#lines_for_signs(file)
for sign_idx in keys(sign_line_map)
let line_nr = sign_line_map[sign_idx]
let line_content = getbufline(bufnr, line_nr)
let content = len(line_content) > 0 ? line_content[0] : ' '
call bm#update_bookmark_for_sign(file, sign_idx, line_nr, content)
endfor
endfunction
function! s:bookmark_add(file, line_nr, ...)
let annotation = (a:0 ==# 1) ? a:1 : ""
let sign_idx = bm_sign#add(a:file, a:line_nr, annotation !=# "")
call bm#add_bookmark(a:file, sign_idx, a:line_nr, getline(a:line_nr), annotation)
endfunction
function! s:bookmark_remove(file, line_nr)
let bookmark = bm#get_bookmark_by_line(a:file, a:line_nr)
call bm_sign#del(a:file, bookmark['sign_idx'])
call bm#del_bookmark_at_line(a:file, a:line_nr)
endfunction
function! s:jump_to_bookmark(type)
let file = expand("%:p")
let line_nr = bm#{a:type}(file, line("."))
if line_nr ==# 0
echo "No bookmarks found"
else
call cursor(line_nr, 1)
normal! ^
if g:bookmark_center ==# 1
normal! zz
endif
let bm = bm#get_bookmark_by_line(file, line_nr)
let annotation = bm['annotation'] !=# "" ? " (". bm['annotation'] . ")" : ""
execute ":redraw!"
echo "Jumped to bookmark". annotation
endif
endfunction
function! s:remove_all_bookmarks()
let files = bm#all_files()
for file in files
let lines = bm#all_lines(file)
for line_nr in lines
call s:bookmark_remove(file, line_nr)
endfor
endfor
endfunction
function! s:startup_load_bookmarks(file)
call BookmarkLoad(s:bookmark_save_file(a:file), 1, 1)
call s:add_missing_signs(a:file)
endfunction
function! s:bookmark_save_file(file)
" Managing bookmarks per buffer implies saving them to a location based on the
" open file (working dir doesn't make much sense unless auto changing the
" working directory based on current file location is turned on - but this is
" a serious dependency to try and require), so the function used to customize
" the bookmarks file location must be based on the current file.
" For backwards compatibility reasons, a new function is used.
if (g:bookmark_manage_per_buffer ==# 1)
return exists("*g:BMBufferFileLocation") ? g:BMBufferFileLocation(a:file) : s:default_file_location()
elseif (g:bookmark_save_per_working_dir)
return exists("*g:BMWorkDirFileLocation") ? g:BMWorkDirFileLocation() : s:default_file_location()
else
return g:bookmark_auto_save_file
endif
endfunction
function! s:default_file_location()
return getcwd(). '/.vim-bookmarks'
endfunction
" should only be called from autocmd!
function! s:add_missing_signs(file)
let bookmarks = values(bm#all_bookmarks_by_line(a:file))
for bookmark in bookmarks
call bm_sign#add_at(a:file, bookmark['sign_idx'], bookmark['line_nr'], bookmark['annotation'] !=# "")
endfor
endfunction
function! s:is_quickfix_win()
return getbufvar(winbufnr('.'), '&buftype') == 'quickfix'
endfunction
function! s:auto_close()
if s:is_quickfix_win()
if (g:bookmark_auto_close)
" Setting 'splitbelow' before closing the quickfix window will ensure
" that its space is given back to the window above rather than to the
" window below.
let l:sb = &sb
set splitbelow
q
let &sb = l:sb
endif
call s:remove_auto_close()
endif
endfunction
function! s:remove_auto_close()
augroup BM_AutoCloseCommand
autocmd!
augroup END
endfunction
function! s:auto_save()
if g:bm_current_file !=# ''
call BookmarkSave(s:bookmark_save_file(g:bm_current_file), 1)
endif
augroup bm_auto_save
autocmd!
augroup END
endfunction
function! s:set_up_auto_save(file)
if g:bookmark_auto_save ==# 1 || g:bookmark_manage_per_buffer ==# 1
call s:startup_load_bookmarks(a:file)
let g:bm_current_file = a:file
augroup bm_auto_save
autocmd!
autocmd BufWinEnter * call s:add_missing_signs(expand('<afile>:p'))
autocmd BufLeave,VimLeave,BufReadPre * call s:auto_save()
augroup END
endif
endfunction
" }}}
" Maps {{{
function! s:register_mapping(command, shortcut, has_count)
if a:has_count
execute "nnoremap <silent> <Plug>". a:command ." :<C-u>". a:command ." v:count<CR>"
else
execute "nnoremap <silent> <Plug>". a:command ." :". a:command ."<CR>"
endif
if !hasmapto("<Plug>". a:command)
\ && !get(g:, 'bookmark_no_default_key_mappings', 0)
\ && maparg(a:shortcut, 'n') ==# ''
execute "nmap ". a:shortcut ." <Plug>". a:command
endif
endfunction
call s:register_mapping('BookmarkShowAll', 'ma', 0)
call s:register_mapping('BookmarkToggle', 'mm', 0)
call s:register_mapping('BookmarkAnnotate', 'mi', 0)
call s:register_mapping('BookmarkNext', 'mn', 0)
call s:register_mapping('BookmarkPrev', 'mp', 0)
call s:register_mapping('BookmarkClear', 'mc', 0)
call s:register_mapping('BookmarkClearAll', 'mx', 0)
call s:register_mapping('BookmarkMoveUp', 'mkk', 1)
call s:register_mapping('BookmarkMoveDown', 'mjj', 1)
call s:register_mapping('BookmarkMoveToLine', 'mg', 1)
" }}}
" Init {{{
if has('vim_starting')
autocmd VimEnter * call s:init(expand('<afile>:p'))
else
call s:init(expand('%:p'))
endif