From 95e235ab43f237511a74029a869163dd18a2f780 Mon Sep 17 00:00:00 2001 From: Wang Shidong Date: Wed, 4 Aug 2021 17:05:38 +0800 Subject: [PATCH] add bookmarks (#4346) --- .vim-bookmarks | 3 + autoload/SpaceVim/layers/tools.vim | 12 +- bundle/vim-bookmarks/.gitignore | 5 + bundle/vim-bookmarks/.travis.yml | 4 + bundle/vim-bookmarks/CONTRIBUTING.md | 42 ++ bundle/vim-bookmarks/Gemfile | 7 + bundle/vim-bookmarks/Gemfile.lock | 64 ++ bundle/vim-bookmarks/Guardfile | 5 + bundle/vim-bookmarks/LICENSE | 21 + bundle/vim-bookmarks/README.md | 317 ++++++++++ bundle/vim-bookmarks/Rakefile | 198 ++++++ bundle/vim-bookmarks/autoload/bm.vim | 208 +++++++ bundle/vim-bookmarks/autoload/bm_sign.vim | 86 +++ .../autoload/ctrlp/bookmarks.vim | 73 +++ .../filters/converter_vim_bookmarks_long.vim | 34 ++ .../filters/converter_vim_bookmarks_short.vim | 35 ++ .../autoload/unite/kinds/vim_bookmarks.vim | 82 +++ .../autoload/unite/sources/vim_bookmarks.vim | 64 ++ bundle/vim-bookmarks/autoload/util.vim | 6 + bundle/vim-bookmarks/doc/bookmarks.txt | 356 +++++++++++ .../examples/bm-autoload-example.vim | 47 ++ bundle/vim-bookmarks/plugin/bookmark.vim | 569 ++++++++++++++++++ bundle/vim-bookmarks/preview.gif | Bin 0 -> 975672 bytes .../release/vim-bookmarks-0.1.0.zip | Bin 0 -> 4945 bytes .../release/vim-bookmarks-0.2.0.zip | Bin 0 -> 6957 bytes .../release/vim-bookmarks-1.0.0.zip | Bin 0 -> 8727 bytes .../release/vim-bookmarks-1.1.0.zip | Bin 0 -> 9135 bytes .../release/vim-bookmarks-1.2.0.zip | Bin 0 -> 9903 bytes .../vim-bookmarks/screenshot-bright-small.png | Bin 0 -> 14828 bytes bundle/vim-bookmarks/screenshot-bright.png | Bin 0 -> 32062 bytes .../screenshot-unite-interface-actions.png | Bin 0 -> 37405 bytes .../screenshot-unite-interface.png | Bin 0 -> 19550 bytes bundle/vim-bookmarks/t/bm_move_spec.vim | 176 ++++++ bundle/vim-bookmarks/t/bm_sign_spec.vim | 123 ++++ bundle/vim-bookmarks/t/bm_spec.vim | 206 +++++++ bundle/vim-bookmarks/t/util_spec.vim | 7 + 36 files changed, 2740 insertions(+), 10 deletions(-) create mode 100644 .vim-bookmarks create mode 100644 bundle/vim-bookmarks/.gitignore create mode 100644 bundle/vim-bookmarks/.travis.yml create mode 100644 bundle/vim-bookmarks/CONTRIBUTING.md create mode 100644 bundle/vim-bookmarks/Gemfile create mode 100644 bundle/vim-bookmarks/Gemfile.lock create mode 100644 bundle/vim-bookmarks/Guardfile create mode 100644 bundle/vim-bookmarks/LICENSE create mode 100644 bundle/vim-bookmarks/README.md create mode 100644 bundle/vim-bookmarks/Rakefile create mode 100644 bundle/vim-bookmarks/autoload/bm.vim create mode 100644 bundle/vim-bookmarks/autoload/bm_sign.vim create mode 100644 bundle/vim-bookmarks/autoload/ctrlp/bookmarks.vim create mode 100644 bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_long.vim create mode 100644 bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_short.vim create mode 100644 bundle/vim-bookmarks/autoload/unite/kinds/vim_bookmarks.vim create mode 100644 bundle/vim-bookmarks/autoload/unite/sources/vim_bookmarks.vim create mode 100644 bundle/vim-bookmarks/autoload/util.vim create mode 100644 bundle/vim-bookmarks/doc/bookmarks.txt create mode 100644 bundle/vim-bookmarks/examples/bm-autoload-example.vim create mode 100644 bundle/vim-bookmarks/plugin/bookmark.vim create mode 100644 bundle/vim-bookmarks/preview.gif create mode 100644 bundle/vim-bookmarks/release/vim-bookmarks-0.1.0.zip create mode 100644 bundle/vim-bookmarks/release/vim-bookmarks-0.2.0.zip create mode 100644 bundle/vim-bookmarks/release/vim-bookmarks-1.0.0.zip create mode 100644 bundle/vim-bookmarks/release/vim-bookmarks-1.1.0.zip create mode 100644 bundle/vim-bookmarks/release/vim-bookmarks-1.2.0.zip create mode 100644 bundle/vim-bookmarks/screenshot-bright-small.png create mode 100644 bundle/vim-bookmarks/screenshot-bright.png create mode 100644 bundle/vim-bookmarks/screenshot-unite-interface-actions.png create mode 100644 bundle/vim-bookmarks/screenshot-unite-interface.png create mode 100644 bundle/vim-bookmarks/t/bm_move_spec.vim create mode 100644 bundle/vim-bookmarks/t/bm_sign_spec.vim create mode 100644 bundle/vim-bookmarks/t/bm_spec.vim create mode 100644 bundle/vim-bookmarks/t/util_spec.vim diff --git a/.vim-bookmarks b/.vim-bookmarks new file mode 100644 index 000000000..5a89976b9 --- /dev/null +++ b/.vim-bookmarks @@ -0,0 +1,3 @@ +let l:bm_file_version = 1 +let l:bm_sessions = {'default': {'C:\Users\wsdjeg\DotFiles\work.md': [{'sign_idx': 9502, 'line_nr': 2043, 'content': '#### 南京市妇幼保健院'},{'sign_idx': 9501, 'line_nr': 2016, 'content': '#### 南京儿童医院'},{'sign_idx': 9500, 'line_nr': 2032, 'content': '#### 南京玄武医院'},],}} +let l:bm_current_session = 'default' diff --git a/autoload/SpaceVim/layers/tools.vim b/autoload/SpaceVim/layers/tools.vim index 4d8131e88..89fe4de39 100644 --- a/autoload/SpaceVim/layers/tools.vim +++ b/autoload/SpaceVim/layers/tools.vim @@ -25,16 +25,8 @@ function! SpaceVim#layers#tools#plugins() abort call add(plugins, ['itchyny/calendar.vim', { 'on_cmd' : 'Calendar'}]) call add(plugins, ['junegunn/limelight.vim', { 'on_cmd' : 'Limelight'}]) call add(plugins, ['junegunn/goyo.vim', { 'on_cmd' : 'Goyo', 'loadconf' : 1}]) - call add(plugins, ['MattesGroeger/vim-bookmarks', { 'on_cmd' : - \ [ - \ 'BookmarkShowAll', - \ 'BookmarkToggle', - \ 'BookmarkAnnotate', - \ 'BookmarkNext', - \ 'BookmarkPrev', - \ 'BookmarkClear', - \ 'BookmarkClearAll' - \ ], + call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-bookmarks', + \ {'merged': 0, \ 'loadconf_before' : 1}]) if s:CMP.has('python') call add(plugins, ['gregsexton/VimCalc', {'on_cmd' : 'Calc'}]) diff --git a/bundle/vim-bookmarks/.gitignore b/bundle/vim-bookmarks/.gitignore new file mode 100644 index 000000000..57f1985e8 --- /dev/null +++ b/bundle/vim-bookmarks/.gitignore @@ -0,0 +1,5 @@ +*/tags +VimFlavor.lock +.vim-flavor +tags +.*.swp diff --git a/bundle/vim-bookmarks/.travis.yml b/bundle/vim-bookmarks/.travis.yml new file mode 100644 index 000000000..a7bc0f779 --- /dev/null +++ b/bundle/vim-bookmarks/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +cache: bundler +rvm: 2.0.0 +script: bundle exec rake ci diff --git a/bundle/vim-bookmarks/CONTRIBUTING.md b/bundle/vim-bookmarks/CONTRIBUTING.md new file mode 100644 index 000000000..1c77043c5 --- /dev/null +++ b/bundle/vim-bookmarks/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +If you'd like to contribute to the project, you can use the usual github pull-request flow: + +1. Fork the project +2. Make your change/addition, preferably in a separate branch +3. Test the new behaviour and make sure all existing tests pass +4. Issue a pull request with a description of your feature/bugfix + +## Github Issues + +When reporting a bug make sure you search the existing github issues for the same/similar issues. If you find one, feel free to add a `+1` comment with any additional information that may help us solve the issue. + +When creating a new issue be sure to state the following: + +* Steps to reproduce the bug +* The version of vim you are using +* The version of vim-bookmarks you are using + +## Coding Conventions + +* Use 2 space indents +* Don't use abbreviated keywords - e.g. use `endfunction`, not `endfun` (there's always room for more fun!) +* Don't use `l:` prefixes for variables unless actually required (i.e. almost never) +* Code for maintainability + +## Testing + +This project uses [vim-flavor](https://github.com/kana/vim-flavor) and [vim-vspec](https://github.com/kana/vim-vspec) to test its behaviour. All logic is extracted into autoload files which are fully tested. The tests can be found in the `t` folder. The tests are written in vim script as well. Tests are also executed on Travis-CI to make sure there are no regressions. + +Install bundler first: + +``` +$ gem install bundler +``` + +If you already have the `bundle` command (check it out with `which bundle`), you don't need this step. Afterwards, it should be as simple as: + +``` +$ bundle install +$ bundle exec vim-flavor test +``` diff --git a/bundle/vim-bookmarks/Gemfile b/bundle/vim-bookmarks/Gemfile new file mode 100644 index 000000000..8c7228f18 --- /dev/null +++ b/bundle/vim-bookmarks/Gemfile @@ -0,0 +1,7 @@ +source 'https://rubygems.org' + +gem 'rake' +gem 'octokit' +gem 'netrc' +gem 'guard-shell' +gem 'vim-flavor' diff --git a/bundle/vim-bookmarks/Gemfile.lock b/bundle/vim-bookmarks/Gemfile.lock new file mode 100644 index 000000000..407003714 --- /dev/null +++ b/bundle/vim-bookmarks/Gemfile.lock @@ -0,0 +1,64 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.3.5) + blankslate (2.1.2.4) + celluloid (0.15.2) + timers (~> 1.1.0) + celluloid-io (0.15.0) + celluloid (>= 0.15.0) + nio4r (>= 0.5.0) + coderay (1.1.0) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) + ffi (1.9.3) + formatador (0.2.4) + guard (2.5.1) + formatador (>= 0.2.4) + listen (~> 2.6) + lumberjack (~> 1.0) + pry (>= 0.9.12) + thor (>= 0.18.1) + guard-shell (0.6.1) + guard (>= 1.1.0) + listen (2.7.1) + celluloid (>= 0.15.2) + celluloid-io (>= 0.15.0) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + lumberjack (1.0.5) + method_source (0.8.2) + multipart-post (2.0.0) + netrc (0.7.7) + nio4r (1.0.0) + octokit (2.7.2) + sawyer (~> 0.5.2) + parslet (1.5.0) + blankslate (~> 2.0) + pry (0.9.12.6) + coderay (~> 1.0) + method_source (~> 0.8) + slop (~> 3.4) + rake (0.9.2.2) + rb-fsevent (0.9.4) + rb-inotify (0.9.3) + ffi (>= 0.5.0) + sawyer (0.5.3) + addressable (~> 2.3.5) + faraday (~> 0.8, < 0.10) + slop (3.5.0) + thor (0.19.0) + timers (1.1.0) + vim-flavor (2.0.0) + parslet (~> 1.0) + thor (~> 0.14) + +PLATFORMS + ruby + +DEPENDENCIES + guard-shell + netrc + octokit + rake + vim-flavor diff --git a/bundle/vim-bookmarks/Guardfile b/bundle/vim-bookmarks/Guardfile new file mode 100644 index 000000000..06c21530d --- /dev/null +++ b/bundle/vim-bookmarks/Guardfile @@ -0,0 +1,5 @@ +guard :shell do + watch(/(autoload|plugin|t)\/.+\.vim$/) do |m| + `rake test` + end +end diff --git a/bundle/vim-bookmarks/LICENSE b/bundle/vim-bookmarks/LICENSE new file mode 100644 index 000000000..6d15d358d --- /dev/null +++ b/bundle/vim-bookmarks/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2014 Mattes Groeger + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bundle/vim-bookmarks/README.md b/bundle/vim-bookmarks/README.md new file mode 100644 index 000000000..b6efa6fed --- /dev/null +++ b/bundle/vim-bookmarks/README.md @@ -0,0 +1,317 @@ +## vim-bookmarks [![Build Status](https://travis-ci.org/MattesGroeger/vim-bookmarks.svg)](https://travis-ci.org/MattesGroeger/vim-bookmarks) [![Release](http://img.shields.io/github/release/MattesGroeger/vim-bookmarks.svg)](https://github.com/MattesGroeger/vim-bookmarks/releases) [![Release](http://img.shields.io/github/issues/MattesGroeger/vim-bookmarks.svg)](https://github.com/MattesGroeger/vim-bookmarks/issues) + +This vim plugin allows toggling bookmarks per line. A quickfix window gives access to all bookmarks. Annotations can be added as well. These are special bookmarks with a comment attached. They are useful for preparing code reviews. All bookmarks will be restored on the next startup. + +![Preview](https://raw.github.com/MattesGroeger/vim-bookmarks/master/preview.gif) + +#### Bright Colors Example + +[![Screenshot Bright Colors](https://raw.github.com/MattesGroeger/vim-bookmarks/master/screenshot-bright-small.png)](https://raw.github.com/MattesGroeger/vim-bookmarks/master/screenshot-bright.png) + +```viml +highlight BookmarkSign ctermbg=NONE ctermfg=160 +highlight BookmarkLine ctermbg=194 ctermfg=NONE +let g:bookmark_sign = '♥' +let g:bookmark_highlight_lines = 1 +``` + +### Features + +* Toggle bookmarks per line ⚑ +* Add annotations per line ☰ +* Navigate all bookmarks with quickfix window +* Bookmarks will be restored on next startup +* [Bookmarks per working directory](https://github.com/MattesGroeger/vim-bookmarks#bookmarks-per-working-directory) (optional) +* Fully customisable (signs, sign column, highlights, mappings) +* Integrates with [Unite's](https://github.com/Shougo/unite.vim) quickfix source if installed +* Integrates with [ctrlp.vim](https://github.com/ctrlpvim/ctrlp.vim) if installed +* Works independently from [vim marks](http://vim.wikia.com/wiki/Using_marks) + +## Installation + +Before installation, please check your Vim supports signs by running `:echo has('signs')`. `1` means you're all set; `0` means you need to install a Vim with signs support. If you're compiling Vim yourself you need the 'big' or 'huge' feature set. [MacVim][] supports signs. + +Use your favorite plugin manager: + +* [Pathogen][] + * `git clone https://github.com/MattesGroeger/vim-bookmarks.git ~/.vim/bundle/vim-bookmarks` +* [Vundle][] + 1. Add `Plugin 'MattesGroeger/vim-bookmarks'` to .vimrc + 2. Run `:PluginInstall` +* [NeoBundle][] + 1. Add `NeoBundle 'MattesGroeger/vim-bookmarks'` to .vimrc + 2. Run `:NeoBundleInstall` +* [vim-plug][vimplug] + 1. Add `Plug 'MattesGroeger/vim-bookmarks'` to .vimrc + 2. Run `:PlugInstall` + +## Usage + +After installation you can directly start using it. You can do this by either using the default shortcuts or the commands: + +| Action | Shortcut | Command | +|-------------------------------------------------|-------------|------------------------------| +| Add/remove bookmark at current line | `mm` | `:BookmarkToggle` | +| Add/edit/remove annotation at current line | `mi` | `:BookmarkAnnotate ` | +| Jump to next bookmark in buffer | `mn` | `:BookmarkNext` | +| Jump to previous bookmark in buffer | `mp` | `:BookmarkPrev` | +| Show all bookmarks (toggle) | `ma` | `:BookmarkShowAll` | +| Clear bookmarks in current buffer only | `mc` | `:BookmarkClear` | +| Clear bookmarks in all buffers | `mx` | `:BookmarkClearAll` | +| Move up bookmark at current line | `[count]mkk`| `:BookmarkMoveUp []` | +| Move down bookmark at current line | `[count]mjj`| `:BookmarkMoveDown []`| +| Move bookmark at current line to another line | `[count]mg` | `:BookmarkMoveToLine ` | +| Save all bookmarks to a file | | `:BookmarkSave ` | +| Load bookmarks from a file | | `:BookmarkLoad ` | + +You can change the shortcuts as you like, just read on... + +## Customization + +### Key Bindings + +You can overwrite any of the default mappings. Just put the following into your `~/.vimrc` and adjust as you like: + +```viml +nmap BookmarkToggle +nmap i BookmarkAnnotate +nmap a BookmarkShowAll +nmap j BookmarkNext +nmap k BookmarkPrev +nmap c BookmarkClear +nmap x BookmarkClearAll +nmap kk BookmarkMoveUp +nmap jj BookmarkMoveDown +nmap g BookmarkMoveToLine +``` +You can disable all default key bindings by setting the following in your `~/.vimrc`: + +```viml +let g:bookmark_no_default_key_mappings = 1 +``` + +### Colors + +Overwrite the default hightlight groups by adding this to your colorscheme or `.vimrc`: + +```viml +highlight BookmarkSign ctermbg=whatever ctermfg=whatever +highlight BookmarkAnnotationSign ctermbg=whatever ctermfg=whatever +highlight BookmarkLine ctermbg=whatever ctermfg=whatever +highlight BookmarkAnnotationLine ctermbg=whatever ctermfg=whatever +``` + +### Options + +Put any of the following options into your `~/.vimrc` in order to overwrite the default behaviour. + +| Option | Default | Description | +|------------------------------------------------|--------------------------|---------------------------------------------------------| +| `let g:bookmark_sign = '>>'` | ⚑ | Sets bookmark icon for sign column | +| `let g:bookmark_annotation_sign = '##'` | ☰ | Sets bookmark annotation icon for sign column | +| `let g:bookmark_save_per_working_dir = 1` | 0 | Save bookmarks per working dir, the folder you opened vim from | +| `let g:bookmark_auto_save = 0` | 1 | Enables/disables automatic saving for bookmarks | +| `let g:bookmark_manage_per_buffer = 1` | 0 | Save bookmarks when leaving a buffer, load when entering one | +| `let g:bookmark_auto_save_file = '/bookmarks'` | $HOME .'/.vim-bookmarks' | Sets file for auto saving (ignored when `bookmark_save_per_working_dir` is enabled) | +| `let g:bookmark_auto_close = 1` | 0 | Automatically close bookmarks split when jumping to a bookmark | +| `let g:bookmark_highlight_lines = 1` | 0 | Enables/disables line highlighting | +| `let g:bookmark_show_warning = 0` | 1 | Enables/disables warning when clearing all bookmarks | +| `let g:bookmark_show_toggle_warning = 0` | 1 | Enables/disables warning when toggling to clear a bookmark with annotation | +| `let g:bookmark_center = 1` | 0 | Enables/disables line centering when jumping to bookmark| +| `let g:bookmark_no_default_key_mappings = 1` | 0 | Prevent any default key mapping from being created| +| `let g:bookmark_location_list = 1` | 0 | Use the location list to show all bookmarks | +| `let g:bookmark_disable_ctrlp = 1` | 0 | Disable ctrlp interface when show all bookmarks | + +### Bookmarks per working directory + +This feature allows the grouping of bookmarks per root directory. This way bookmarks from other projects are not interfering. This is done by saving a file called `.vim-bookmarks` into the current working directory (the folder you opened vim from). + +Whenever a vim instance is opened from the same root folder the bookmarks from that folder will be loaded. Note, that you can place your bookmarks in any file underneath the working directory, though. + +In order to use this feature, put this into your `.vimrc`: + +```viml +let g:bookmark_save_per_working_dir = 1 +let g:bookmark_auto_save = 1 +``` + +You should add the filename `.vim-bookmarks` to your (global) `.gitignore` file so it doesn't get checked into version control. + +If you want to customize the location or filename you can define the following function in your `.vimrc`. The return value will be used to retrieve and save the bookmark positions. This way you can implement you custom strategy for determining the work dir location (e.g. inside the `.git` directory): + +```viml +" Finds the Git super-project directory. +function! g:BMWorkDirFileLocation() + let filename = 'bookmarks' + let location = '' + if isdirectory('.git') + " Current work dir is git's work tree + let location = getcwd().'/.git' + else + " Look upwards (at parents) for a directory named '.git' + let location = finddir('.git', '.;') + endif + if len(location) > 0 + return location.'/'.filename + else + return getcwd().'/.'.filename + endif +endfunction +``` + +### Bookmarks per buffer + +This feature implies `bookmark_auto_save`. When configured bookmarks will be +loaded and saved on each buffer change. This allows working with different +buffers/tabs and keeping a different bookmark file for each one based on the +file open in the buffer. I.e., using the following function and having files +from different Git repositories open in different tabs will use a different +bookmarks file per Git repository. + +This is different from how saving per working directory works because it allows +for having different bookmarks for different buffers/tabs open in the same +window without having the working directory change automatically when switching +between them. + +The following function is similar to the one shown above (finds the .git folder +location, defaults to current file's directory) : +```viml +" Finds the Git super-project directory based on the file passed as an argument. +function! g:BMBufferFileLocation(file) + let filename = 'vim-bookmarks' + let location = '' + if isdirectory(fnamemodify(a:file, ":p:h").'/.git') + " Current work dir is git's work tree + let location = fnamemodify(a:file, ":p:h").'/.git' + else + " Look upwards (at parents) for a directory named '.git' + let location = finddir('.git', fnamemodify(a:file, ":p:h").'/.;') + endif + if len(location) > 0 + return simplify(location.'/.'.filename) + else + return simplify(fnamemodify(a:file, ":p:h").'/.'.filename) + endif +endfunction +``` + +### Silent saving and loading + +Call functions BookmarkSave, BookmarkLoad and BookmarkClearAll with the last argument set to 0 to perform these operations silently. You may use this to manage your bookmark list transparently from within your custom script. + +## Unite Integration +![A screenshot of vim-bookmarks' Unite interface](./screenshot-unite-interface.png) + +[Unite](https://github.com/Shougo/unite.vim) is a multi-purpose user-interface plugin platform. Vim-bookmarks provides a Unite source called `vim_bookmarks` so users who use Unite will handle bookmarks with the Unite interface. + +Additionally, when showing all your bookmarks, Unite is detected and the plugin will open `:Unite vim_bookmarks` instead of Vim's quickfix window. Note that `g:bookmark_auto_close` is no longer applied. Once opened, the window is managed by Unite. + +To set a global per-source context setting, that will apply to Unite's vim_bookmarks source everytime it's opened, you can add this to your `vimrc`: + +```viml +call unite#custom#profile('source/vim_bookmarks', 'context', { + \ 'winheight': 13, + \ 'direction': 'botright', + \ 'start_insert': 0, + \ 'keep_focus': 1, + \ 'no_quit': 1, + \ }) +``` + +With the Unite interface, when you select bookmarks, you can perform the following actions: + +* Open the selected bookmarks in various ways (open to the right, open above, open in new tab, etc.) +* Yank the informations of selected bookmarks (path and line number, the line content, annotation, etc.) +* Highlight the lines of the selected bookmarks +* Replace the contents of selected bookmarks with [vim-qfreplace](https://github.com/thinca/vim-qfreplace) interface +* Delete the selected bookmarks +* And more... + +See the screenshot below to get an idea of what you can do with the interface (the picture shows only a fraction, that means you can select even more actions): + +![A screenshot of action list of vim-bookmarks' Unite interface](./screenshot-unite-interface-actions.png) + +For more information about Unite, start reading `:help Unite`. + +## CtrlP Integration + +[ctrlp.vim](https://github.com/ctrlpvim/ctrlp.vim) is a Full path fuzzy file, buffer, mru, tag, ... finder for Vim. + +Additionally, when showing all your bookmarks, CtrlP is detected and the plugin will open `:CtrlPBookmark` instead of Vim's quickfix window. Note that `g:bookmark_auto_close` is no longer applied. Once opened, the window is managed by CtrlP. + +With the CtrlP interface, when you select bookmarks, you can perform the following actions: +* Open the selected bookmarks in various ways (open to the right, open above, open in new tab, etc.) +* And more... + +## FAQ + +> Why are the colours in the sign column weird? + +Your colorscheme is configuring the `SignColumn` highlight group weirdly. To change that add this to your `.vimrc`: `highlight SignColumn ctermbg=whatever`. + +> What happens if I also use another plugin which uses signs (e.g. Syntastic)? + +Vim only allows one sign per line. Therefore bookmarks will override any existing sign. When removing the bookmark the original sign will show up again. In other words vim-bookmarks won't remove another plugin's signs. + +> Why aren't any signs showing at all? + +Make sure your vim supports signs: `:echo has('signs')` should give `1` + +> How do I avoid keybinding conflicts with the Nerdtree plugin? + +You could unbind the keys whenever Nerdtree becomes active (thanks to [@Nitive](https://github.com/Nitive)). + +```viml +let g:bookmark_no_default_key_mappings = 1 +function! BookmarkMapKeys() + nmap mm :BookmarkToggle + nmap mi :BookmarkAnnotate + nmap mn :BookmarkNext + nmap mp :BookmarkPrev + nmap ma :BookmarkShowAll + nmap mc :BookmarkClear + nmap mx :BookmarkClearAll + nmap mkk :BookmarkMoveUp + nmap mjj :BookmarkMoveDown +endfunction +function! BookmarkUnmapKeys() + unmap mm + unmap mi + unmap mn + unmap mp + unmap ma + unmap mc + unmap mx + unmap mkk + unmap mjj +endfunction +autocmd BufEnter * :call BookmarkMapKeys() +autocmd BufEnter NERD_tree_* :call BookmarkUnmapKeys() +``` + +> Why do my bookmarks disappear when running the `:make` command? + +By default, the bookmark list is shown using the quickfix window, which can sometimes conflict with other commands. The location list may be used to show the bookmark list instead by setting the `g:bookmark_location_list` option documented above. + +## Changelog + +See the [release page](https://github.com/MattesGroeger/vim-bookmarks/releases) for all changes. + +## Credits & Contribution + +This plugin was developed by [Mattes Groeger][blog] under the [MIT License][license]. Pull requests are very welcome. + +The following plugins were a great inspiration to me: +* [vmark.vim][vmark] by Michael Zhou +* [vim-gitgutter][gitgutter] by Andrew Stewart + + + [pathogen]: https://github.com/tpope/vim-pathogen + [vundle]: https://github.com/gmarik/vundle + [neobundle]: https://github.com/Shougo/neobundle.vim + [vimplug]: https://github.com/MattesGroeger/vim-plug + [macvim]: http://code.google.com/p/macvim/ + [license]: https://github.com/MattesGroeger/vim-bookmarks/blob/master/LICENSE + [blog]: http://blog.mattes-groeger.de + [vmark]: http://www.vim.org/scripts/script.php?script_id=4076 + [gitgutter]: https://github.com/airblade/vim-gitgutter diff --git a/bundle/vim-bookmarks/Rakefile b/bundle/vim-bookmarks/Rakefile new file mode 100644 index 000000000..44f75b4d8 --- /dev/null +++ b/bundle/vim-bookmarks/Rakefile @@ -0,0 +1,198 @@ +require 'rake/packagetask' +require 'octokit' + +GIT_REPO = "MattesGroeger/vim-bookmarks" +VIM_SCRIPT_URL = "http://www.vim.org/scripts/add_script_version.php?script_id=4893" +LABELS = ["feature", "enhancement", "bug"] + +task :default => [:release] +task :ci => [:dump, :test] + +desc "Dump version numbers" +task :dump do + sh 'vim --version' +end + +desc "Run tests" +task :test do + sh "bundle exec vim-flavor test" +end + +desc "Create release archive" +task :release do + version = request_user_input("Which version do you want to release (0.1.0)?") + file_path = create_zip(version) + if request_user_input("Create git release now? (y/n)") == 'y' + upload_release(version, file_path) + end +end + +desc "See the changelog for a specific version" +task :changelog do + version = request_user_input("For which version (0.1.0)?") + show_changelog(version) +end + +def create_zip(version) + file_path = "release/vim-bookmarks-#{version}.zip" + `mkdir -p release` + `zip -r #{file_path} . -i "doc/*" -i "plugin/*" -i "autoload/*" -i LICENSE` + file_path +end + +def upload_release(version, asset_path) + asset_name = asset_path.split("/")[-1] + + # login to github + @client = Octokit::Client.new(netrc: true) + @client.login + + # get all milestones + milestones = @client.milestones(GIT_REPO) + milestone = milestones.select { |m| + m.title == version + }.first + return puts "Unable to find milestone for version #{version}. Aborted!" if !milestone + + # abort if there are still open issues + return puts "Found #{milestone.open_issues} open issues for milestone #{version}. Close them first!" if milestone.open_issues > 0 + + # get change log via issues + issues = @client.issues(GIT_REPO, milestone: milestone.number, state: :closed) + changes = build_changelog(issues) + + # get contributor sentence + thanks = contributor_sentence_since_last_release() + + # show change log, get it confirmed by user (y/n) + changelog = changes.join + changelog << "\n#{thanks}" unless thanks.nil? + puts "> Changelog:\n#{changelog}\n\n" + return puts "Aborted!" if request_user_input("Do you want to create release #{version} with the above changelog? (y/n)", "n").downcase != "y" + + # create release + release = @client.create_release(GIT_REPO, version, name: "vim-bookmarks-#{version}", body: changelog) + puts "> Created release #{release.name} (id: #{release.id})\n\n" + + # if release exists already: + # releases = @client.releases(GIT_REPO) + # release = releases.first + + # upload asset + release_url = "https://api.github.com/repos/#{GIT_REPO}/releases/#{release.id}" + asset = @client.upload_asset(release_url, asset_path, content_type: 'application/zip', name: asset_name) + puts "> Uploaded asset #{asset.name} (id: #{asset.id})\n\n" + + # close milestone + @client.update_milestone(GIT_REPO, milestone.number, state: :closed) + puts "> Closed milestone #{version}. Done!" + + if request_user_input("Update script on vim.org now? (y/n)", "n").downcase == "y" + `open "#{VIM_SCRIPT_URL}"` + end +end + +def show_changelog(version) + # login to github + @client = Octokit::Client.new(netrc: true) + @client.login + + # get all milestones + milestones = @client.milestones(GIT_REPO) + @client.milestones(GIT_REPO, state: :closed) + milestone = milestones.select { |m| + m.title == version + }.first + return puts "Unable to find milestone for version #{version}. Aborted!" if !milestone + + # get change log via issues + issues = @client.issues(GIT_REPO, milestone: milestone.number, state: :closed) + + # show change log + puts "\nChangelog:\n\n#{build_changelog(issues).join}\n" +end + +def build_changelog(issues) + issues.map { |i| + label = (LABELS & i.labels.map { |l| l.name }).first + label_txt = label == "bug" ? "bugfix" : label + line = " * [#{label_txt}] #{i.title} ##{i.number}\n" if label + {label: label, issue: i.number, line: line} + }.select { |f| + f[:label] != nil + }.sort { |a, b| + [LABELS.find_index(a[:label]),b[:issue]] <=> [LABELS.find_index(b[:label]),a[:issue]] + }.map { |e| + e[:line] + } +end + +def contributor_sentence_since_last_release() + releases = @client.releases(GIT_REPO) + since = releases.size > 0 ? releases.first.created_at : nil + contributors = contributors(since) + contributor_sentence(contributors) +end + +def contributors(from = nil, to = nil) + + # Get all by default + from = Time.new(1).utc.iso8601 if from.nil? + to = Time.now.utc.iso8601 if to.nil? + + # Get all commits since last release + commits = @client.commits(GIT_REPO, sha: :master, since: from, until: to) + + # Get all collaborators + collaborators = @client.collaborators(GIT_REPO).map { |c| + c.login + } + + # Find all authors that are not site_admin + contributor_map = commits.inject({}) { |hash, commit| + if commit.author.nil? + user = "[#{commit.commit.author.name}](mailto:#{commit.commit.author.email})" + hash[user] = 1 + else + login = commit.author.login + user = "@#{login}" + if collaborators.index(login).nil? # exclude repo maintainer + change_count = @client.commit(GIT_REPO, commit.sha).stats.total + hash[user] = hash[user].nil? ? change_count : hash[user] + change_count + end + end + hash + } + + # Sort them by number of changes (importance) + contributor_map.keys.sort { |a, b| + contributor_map[b] <=> contributor_map[a] + }.map { |c| + "#{c}" + } +end + +def contributor_sentence(contributors) + if contributors.size == 0 + return nil + elsif contributors.size == 1 + authors = "#{contributors.first} for the contribution" + else + last = contributors.pop + authors = "#{contributors.join(', ')} and #{last} who contributed" + end + "Thanks #{authors} to this release!" +end + +def request_user_input(message, fallback = "") + STDOUT.puts message + input = STDIN.gets.strip.to_s + if input.empty? + if fallback.empty? + request_user_input(message) # try again + else + fallback.to_s + end + else + input.to_s + end +end diff --git a/bundle/vim-bookmarks/autoload/bm.vim b/bundle/vim-bookmarks/autoload/bm.vim new file mode 100644 index 000000000..3a6d83d58 --- /dev/null +++ b/bundle/vim-bookmarks/autoload/bm.vim @@ -0,0 +1,208 @@ +if !exists("g:line_map") + let g:line_map = {} " 'line_nr' => 'bookmark' + let g:sign_map = {} " 'sign_idx' => 'line_nr' +endif + +" Bookmark Model {{{ + +function! bm#has_bookmarks_in_file(file) + if !has_key(g:line_map, a:file) + return 0 + endif + return len(keys(g:line_map[a:file])) > 0 +endfunction + +function! bm#has_bookmark_at_line(file, line_nr) + if !has_key(g:line_map, a:file) + return 0 + endif + return has_key(g:line_map[a:file], a:line_nr) +endfunction + +function! bm#get_bookmark_by_line(file, line_nr) + return g:line_map[a:file][a:line_nr] +endfunction + +function! bm#is_bookmark_has_annotation_by_line(file, line_nr) + return g:line_map[a:file][a:line_nr]['annotation'] !=# "" +endfunction + +function! bm#get_bookmark_by_sign(file, sign_idx) + let line_nr = g:sign_map[a:file][a:sign_idx] + return bm#get_bookmark_by_line(a:file, line_nr) +endfunction + +function! bm#add_bookmark(file, sign_idx, line_nr, content, ...) + if !has_key(g:line_map, a:file) + let g:line_map[a:file] = {} + let g:sign_map[a:file] = {} + endif + let annotation = a:0 ==# 1 ? a:1 : "" + let entry = {'sign_idx': a:sign_idx, 'line_nr': a:line_nr, 'content': a:content, 'annotation': annotation} + let g:line_map[a:file][a:line_nr] = entry + let g:sign_map[a:file][a:sign_idx] = a:line_nr + return entry +endfunction + +function! bm#update_bookmark_for_sign(file, sign_idx, new_line_nr, new_content) + let bookmark = bm#get_bookmark_by_sign(a:file, a:sign_idx) + call bm#del_bookmark_at_line(a:file, bookmark['line_nr']) + call bm#add_bookmark(a:file, a:sign_idx, a:new_line_nr, a:new_content, bookmark['annotation']) +endfunction + +function! bm#update_annotation(file, sign_idx, annotation) + let bookmark = bm#get_bookmark_by_sign(a:file, a:sign_idx) + let bookmark['annotation'] = a:annotation +endfunction + +function! bm#next(file, current_line_nr) + let line_nrs = sort(bm#all_lines(a:file), "bm#compare_lines") + if empty(line_nrs) + return 0 + endif + if a:current_line_nr >=# line_nrs[-1] || a:current_line_nr <# line_nrs[0] + return line_nrs[0] + 0 + else + let idx = 0 + let lines_count = len(line_nrs) + while idx <# lines_count-1 + let cur_bookmark = line_nrs[idx] + let next_bookmark = line_nrs[idx+1] + if a:current_line_nr >=# cur_bookmark && a:current_line_nr <# next_bookmark + return next_bookmark + 0 + endif + let idx = idx+1 + endwhile + endif + return 0 +endfunction + +function! bm#prev(file, current_line_nr) + let line_nrs = sort(bm#all_lines(a:file), "bm#compare_lines") + if empty(line_nrs) + return 0 + endif + let lines_count = len(line_nrs) + let idx = lines_count-1 + if a:current_line_nr <=# line_nrs[0] || a:current_line_nr ># line_nrs[-1] + return line_nrs[idx] + 0 + else + while idx >=# 0 + let cur_bookmark = line_nrs[idx] + let next_bookmark = line_nrs[idx-1] + if a:current_line_nr <=# cur_bookmark && a:current_line_nr ># next_bookmark + return next_bookmark + 0 + endif + let idx = idx-1 + endwhile + endif + return 0 +endfunction + +function! bm#del_bookmark_at_line(file, line_nr) + let bookmark = bm#get_bookmark_by_line(a:file, a:line_nr) + unlet g:line_map[a:file][a:line_nr] + unlet g:sign_map[a:file][bookmark['sign_idx']] + if empty(g:line_map[a:file]) + unlet g:line_map[a:file] + unlet g:sign_map[a:file] + endif +endfunction + +function! bm#total_count() + return len(bm#location_list()) +endfunction + +function! bm#all_bookmarks_by_line(file) + if !has_key(g:line_map, a:file) + return {} + endif + return g:line_map[a:file] +endfunction + +function! bm#all_lines(file) + if !has_key(g:line_map, a:file) + return [] + endif + return keys(g:line_map[a:file]) +endfunction + +function! bm#location_list() + let files = sort(bm#all_files()) + let locations = [] + for file in files + let line_nrs = sort(bm#all_lines(file), "bm#compare_lines") + for line_nr in line_nrs + let bookmark = bm#get_bookmark_by_line(file, line_nr) + let content = bookmark['annotation'] !=# '' + \ ? "Annotation: ". bookmark['annotation'] + \ : (bookmark['content'] !=# "" + \ ? bookmark['content'] + \ : "empty line") + call add(locations, file .":". line_nr .":". content) + endfor + endfor + return locations +endfunction + +function! bm#all_files() + return keys(g:line_map) +endfunction + +function! bm#del_all() + for file in bm#all_files() + for line_nr in bm#all_lines(file) + call bm#del_bookmark_at_line(file, line_nr) + endfor + endfor +endfunction + +function! bm#serialize() + let file_version = "let l:bm_file_version = 1" + let sessions = "let l:bm_sessions = {'default': {" + for file in bm#all_files() + let sessions .= "'". file ."': [" + for bm in values(bm#all_bookmarks_by_line(file)) + let escaped_content = substitute(bm['content'], "'", "''", "g") + let escaped_annotation = substitute(bm['annotation'], "'", "''", "g") + let annotation = bm['annotation'] !=# "" ? ", 'annotation': '". escaped_annotation ."'" : "" + let sessions .= "{'sign_idx': ". bm['sign_idx'] .", 'line_nr': ". bm['line_nr'] .", 'content': '". escaped_content ."'". annotation ."}," + endfor + let sessions .= "]," + endfor + let sessions .= "}}" + let current_session = "let l:bm_current_session = 'default'" + return [file_version, sessions, current_session] +endfunction + +function! bm#deserialize(data) + exec join(a:data, " | ") + let ses = l:bm_sessions["default"] + let result = [] + for file in keys(ses) + for bm in ses[file] + let annotation = has_key(bm, 'annotation') ? bm['annotation'] : '' + call add(result, + \ extend( + \ copy( + \ bm#add_bookmark(file, bm['sign_idx'], bm['line_nr'], bm['content'], annotation) + \ ), + \ {'file': file} + \ )) + endfor + endfor + return result +endfunction + +" }}} + + +" Private {{{ + +function! bm#compare_lines(line_str1, line_str2) + let line1 = str2nr(a:line_str1) + let line2 = str2nr(a:line_str2) + return line1 ==# line2 ? 0 : line1 > line2 ? 1 : -1 +endfunc + +" }}} diff --git a/bundle/vim-bookmarks/autoload/bm_sign.vim b/bundle/vim-bookmarks/autoload/bm_sign.vim new file mode 100644 index 000000000..b670c5d96 --- /dev/null +++ b/bundle/vim-bookmarks/autoload/bm_sign.vim @@ -0,0 +1,86 @@ +if !exists("g:bm_sign_init") + let g:bm_sign_init = 0 +endif + +" Sign {{{ + +function! bm_sign#lazy_init() + if g:bm_sign_init ==# 0 + call bm_sign#init() + let g:bm_sign_init = 1 + endif +endfunction + +function! bm_sign#init() + call bm_sign#define_highlights() + sign define Bookmark texthl=BookmarkSign + sign define BookmarkAnnotation texthl=BookmarkAnnotationSign + execute "sign define Bookmark text=". g:bookmark_sign + execute "sign define BookmarkAnnotation text=". g:bookmark_annotation_sign + if g:bookmark_highlight_lines + sign define Bookmark linehl=BookmarkLine + sign define BookmarkAnnotation linehl=BookmarkAnnotationLine + else + sign define Bookmark linehl= + sign define BookmarkAnnotation linehl= + endif +endfunction + +function! bm_sign#define_highlights() + highlight BookmarkSignDefault ctermfg=33 ctermbg=NONE + highlight BookmarkAnnotationSignDefault ctermfg=28 ctermbg=NONE + highlight BookmarkLineDefault ctermfg=232 ctermbg=33 + highlight BookmarkAnnotationLineDefault ctermfg=232 ctermbg=28 + highlight default link BookmarkSign BookmarkSignDefault + highlight default link BookmarkAnnotationSign BookmarkAnnotationSignDefault + highlight default link BookmarkLine BookmarkLineDefault + highlight default link BookmarkAnnotationLine BookmarkAnnotationLineDefault +endfunction + +function! bm_sign#add(file, line_nr, is_annotation) + call bm_sign#lazy_init() + let sign_idx = g:bm_sign_index + call bm_sign#add_at(a:file, sign_idx, a:line_nr, a:is_annotation) + return sign_idx +endfunction + +" add sign with certain index +function! bm_sign#add_at(file, sign_idx, line_nr, is_annotation) + call bm_sign#lazy_init() + let name = a:is_annotation ==# 1 ? "BookmarkAnnotation" : "Bookmark" + execute "sign place ". a:sign_idx ." line=" . a:line_nr ." name=". name ." file=". a:file + if (a:sign_idx >=# g:bm_sign_index) + let g:bm_sign_index = a:sign_idx + 1 + endif +endfunction + +function! bm_sign#update_at(file, sign_idx, line_nr, is_annotation) + call bm_sign#del(a:file, a:sign_idx) + call bm_sign#add_at(a:file, a:sign_idx, a:line_nr, a:is_annotation) +endfunction + +function! bm_sign#del(file, sign_idx) + call bm_sign#lazy_init() + try + execute "sign unplace ". a:sign_idx ." file=". a:file + catch + endtry +endfunction + +" Returns dict with {'sign_idx': 'line_nr'} +function! bm_sign#lines_for_signs(file) + call bm_sign#lazy_init() + let bufnr = bufnr(a:file) + let signs_raw = util#redir_execute(":sign place file=". a:file) + let lines = split(signs_raw, "\n") + let result = {} + for line in lines + let results = matchlist(line, 'line=\(\d\+\)\W\+id=\(\d\+\)\W\+name=bookmark\c') + if len(results) ># 0 + let result[results[2]] = results[1] + endif + endfor + return result +endfunction + +" }}} diff --git a/bundle/vim-bookmarks/autoload/ctrlp/bookmarks.vim b/bundle/vim-bookmarks/autoload/ctrlp/bookmarks.vim new file mode 100644 index 000000000..ec80abb72 --- /dev/null +++ b/bundle/vim-bookmarks/autoload/ctrlp/bookmarks.vim @@ -0,0 +1,73 @@ +if v:version < 700 || &cp + finish +endif + +call add(g:ctrlp_ext_vars, { + \ 'init': 'ctrlp#bookmarks#init()', + \ 'accept': 'ctrlp#bookmarks#accept', + \ 'lname': 'vim-bookmarks', + \ 'sname': '', + \ 'type': 'line', + \ 'sort': 0, + \ 'specinput': 0, + \ }) + + +function! ctrlp#bookmarks#init() abort + let l:text=[] + let l:files = sort(bm#all_files()) + for l:file in l:files + let l:line_nrs = sort(bm#all_lines(l:file), "bm#compare_lines") + for l:line_nr in l:line_nrs + let l:bookmark = bm#get_bookmark_by_line(l:file, l:line_nr) + let l:detail = printf("%s", l:bookmark.annotation !~ '^\s*$' ? + \ l:bookmark.annotation + \ : l:bookmark.content !~ '^\s*$' ? + \ l:bookmark.content + \ : "EMPTY LINE") + call add(l:text, l:detail) + endfor + endfor + return l:text +endfunction + +function! ctrlp#bookmarks#accept(mode, str) abort + if a:mode ==# 'e' + let l:HowToOpen='e' + elseif a:mode ==# 't' + let l:HowToOpen='tabnew' + elseif a:mode ==# 'v' + let l:HowToOpen='vsplit' + elseif a:mode ==# 'h' + let l:HowToOpen='sp' + endif + call ctrlp#exit() + let l:text=[] + let l:files = sort(bm#all_files()) + for l:file in l:files + let l:line_nrs = sort(bm#all_lines(l:file), "bm#compare_lines") + for l:line_nr in l:line_nrs + let l:bookmark = bm#get_bookmark_by_line(l:file, l:line_nr) + if a:str ==# l:bookmark.annotation + execute l:HowToOpen." ".l:file + execute ":".l:line_nr + break + elseif a:str ==# l:bookmark.content + execute l:HowToOpen." ".l:file + execute ":".l:line_nr + break + elseif a:str ==# "EMPTY LINE" + execute l:HowToOpen." ".l:file + execute ":".l:line_nr + break + endif + endfor + endfor +endfunction + +let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars) +function! ctrlp#bookmarks#id() abort + return s:id +endfunction + +" vim:nofen:fdl=0:ts=2:sw=2:sts=2 diff --git a/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_long.vim b/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_long.vim new file mode 100644 index 000000000..d0bce3d80 --- /dev/null +++ b/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_long.vim @@ -0,0 +1,34 @@ +let s:save_cpo = &cpo +set cpo&vim + + +function! s:format_bookmark(candidate) " {{{ + let file = a:candidate.action__path + let line_nr = a:candidate.action__line + let bookmark = a:candidate.action__bookmark + return printf("%s:%d | %s", file, line_nr, + \ bookmark.annotation !=# '' + \ ? "Annotation: " . bookmark.annotation + \ : (bookmark.content !=# "" ? bookmark.content + \ : "empty line") + \ ) +endfunction " }}} +let s:converter = { + \ 'name': 'converter_vim_bookmarks_short', + \ 'description': 'vim-bookmarks converter which show short informations', + \} +function! s:converter.filter(candidates, context) " {{{ + for candidate in a:candidates + let candidate.abbr = s:format_bookmark(candidate) + endfor + return a:candidates +endfunction " }}} + +function! unite#filters#converter_vim_bookmarks_long#define() " {{{ + return s:converter +endfunction " }}} +call unite#define_filter(s:converter) + +let &cpo = s:save_cpo +unlet s:save_cpo +"vim: sts=2 sw=2 smarttab et ai textwidth=0 fdm=marker diff --git a/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_short.vim b/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_short.vim new file mode 100644 index 000000000..5393d9cef --- /dev/null +++ b/bundle/vim-bookmarks/autoload/unite/filters/converter_vim_bookmarks_short.vim @@ -0,0 +1,35 @@ +let s:save_cpo = &cpo +set cpo&vim + + +function! s:format_bookmark(candidate) " {{{ + let file = a:candidate.action__path + let line_nr = a:candidate.action__line + let bookmark = a:candidate.action__bookmark + return printf("%s:%d | %s", pathshorten(file), line_nr, + \ bookmark.annotation !=# '' + \ ? "Annotation: " . bookmark.annotation + \ : (bookmark.content !=# "" ? bookmark.content + \ : "empty line") + \ ) +endfunction " }}} +let s:converter = { + \ 'name': 'converter_vim_bookmarks_short', + \ 'description': 'vim-bookmarks converter which show short informations', + \} +function! s:converter.filter(candidates, context) " {{{ + let candidates = copy(a:candidates) + for candidate in candidates + let candidate.abbr = s:format_bookmark(candidate) + endfor + return candidates +endfunction " }}} + +function! unite#filters#converter_vim_bookmarks_short#define() " {{{ + return s:converter +endfunction " }}} +call unite#define_filter(s:converter) + +let &cpo = s:save_cpo +unlet s:save_cpo +"vim: sts=2 sw=2 smarttab et ai textwidth=0 fdm=marker diff --git a/bundle/vim-bookmarks/autoload/unite/kinds/vim_bookmarks.vim b/bundle/vim-bookmarks/autoload/unite/kinds/vim_bookmarks.vim new file mode 100644 index 000000000..ef75a0efa --- /dev/null +++ b/bundle/vim-bookmarks/autoload/unite/kinds/vim_bookmarks.vim @@ -0,0 +1,82 @@ +let s:save_cpo = &cpo +set cpo&vim + + +let s:kind = { + \ 'name': 'vim_bookmarks', + \ 'parents': ['jump_list'], + \ 'default_action': 'open', + \ 'action_table': { + \ 'delete': { + \ 'description': 'delete the selected bookmarks', + \ 'is_selectable': 1, + \ 'is_quit': 0, + \ }, + \ 'yank': { + \ 'description': 'yank path and content of the selected bookmarks', + \ 'is_selectable': 1, + \ }, + \ 'yank_path': { + \ 'description': 'yank path of the selected bookmarks', + \ 'is_selectable': 1, + \ }, + \ 'yank_content': { + \ 'description': 'yank content of the selected bookmarks', + \ 'is_selectable': 1, + \ }, + \ 'yank_annotation': { + \ 'description': 'yank annotation of the selected bookmarks', + \ 'is_selectable': 1, + \ }, + \}} +function! s:yank(text) + let @" = a:text + echo 'Yanked: ' . a:text + + if has('clipboard') + call setreg(v:register, a:text) + endif +endfunction + +function! s:kind.action_table.delete.func(candidates) " {{{ + for candidate in a:candidates + call bm_sign#del( + \ candidate.action__path, + \ candidate.action__bookmark.sign_idx, + \) + call bm#del_bookmark_at_line( + \ candidate.action__path, + \ candidate.action__line, + \) + endfor + call unite#force_redraw() +endfunction " }}} +function! s:kind.action_table.yank.func(candidates) " {{{ + let text = join(map(copy(a:candidates), + \ 'v:val.word'), "\n") + call s:yank(text) +endfunction " }}} +function! s:kind.action_table.yank_path.func(candidates) " {{{ + let text = join(map(copy(a:candidates), + \ 'v:val.action__path'), "\n") + call s:yank(text) +endfunction " }}} +function! s:kind.action_table.yank_content.func(candidates) " {{{ + let text = join(map(copy(a:candidates), + \ 'v:val.action__bookmark.content'), "\n") + call s:yank(text) +endfunction " }}} +function! s:kind.action_table.yank_annotation.func(candidates) " {{{ + let text = join(map(copy(a:candidates), + \ 'v:val.action__bookmark.annotation'), "\n") + call s:yank(text) +endfunction " }}} + +function! unite#kinds#vim_bookmarks#define() + return s:kind +endfunction +call unite#define_kind(s:kind) " required for reloading + +let &cpo = s:save_cpo +unlet s:save_cpo +"vim: sts=2 sw=2 smarttab et ai textwidth=0 fdm=marker diff --git a/bundle/vim-bookmarks/autoload/unite/sources/vim_bookmarks.vim b/bundle/vim-bookmarks/autoload/unite/sources/vim_bookmarks.vim new file mode 100644 index 000000000..8f788673f --- /dev/null +++ b/bundle/vim-bookmarks/autoload/unite/sources/vim_bookmarks.vim @@ -0,0 +1,64 @@ +let s:save_cpo = &cpo +set cpo&vim + +function! unite#sources#vim_bookmarks#define_highlights() abort " {{{ + highlight default link BookmarkUnitePath Comment + highlight default link BookmarkUniteContent Normal +endfunction " }}} + +let s:source = { + \ 'name': 'vim_bookmarks', + \ 'description': 'manipulate bookmarks of vim-bookmarks', + \ 'hooks': {}, + \} +function! s:source.gather_candidates(args, context) abort " {{{ + let files = sort(bm#all_files()) + let candidates = [] + for file in files + let line_nrs = sort(bm#all_lines(file), "bm#compare_lines") + for line_nr in line_nrs + let bookmark = bm#get_bookmark_by_line(file, line_nr) + call add(candidates, { + \ 'word': printf("%s:%d | %s", file, line_nr, + \ bookmark.annotation !=# '' + \ ? "Annotation: " . bookmark.annotation + \ : (bookmark.content !=# "" ? bookmark.content + \ : "empty line") + \ ), + \ 'kind': 'vim_bookmarks', + \ 'action__path': file, + \ 'action__line': line_nr, + \ 'action__bookmark': bookmark, + \}) + endfor + endfor + return copy(candidates) +endfunction " }}} +function! s:source.hooks.on_syntax(args, context) abort " {{{ + call unite#sources#vim_bookmarks#define_highlights() + highlight default link uniteSource__VimBookmarks_path BookmarkUnitePath + highlight default link uniteSource__VimBookmarks_content BookmarkUniteContent + + execute 'syntax match uniteSource__VimBookmarks_path' + \ '/[^|]\+/' + \ 'contained containedin=uniteSource__VimBookmarks' + execute 'syntax match uniteSource__VimBookmarks_content' + \ '/|.*$/' + \ 'contained containedin=uniteSource__VimBookmarks' +endfunction " }}} + +function! unite#sources#vim_bookmarks#define() + return s:source +endfunction +call unite#define_source(s:source) " Required for reloading + +" define default converter +call unite#custom_source( + \ 'vim_bookmarks', + \ 'converters', + \ 'converter_vim_bookmarks_short') + + +let &cpo = s:save_cpo +unlet s:save_cpo +"vim: sts=2 sw=2 smarttab et ai textwidth=0 fdm=marker diff --git a/bundle/vim-bookmarks/autoload/util.vim b/bundle/vim-bookmarks/autoload/util.vim new file mode 100644 index 000000000..e5b88f200 --- /dev/null +++ b/bundle/vim-bookmarks/autoload/util.vim @@ -0,0 +1,6 @@ +function! util#redir_execute(command) + redir => output + silent execute a:command + redir END + return substitute(output, '^\n', '\1', '') +endfunction diff --git a/bundle/vim-bookmarks/doc/bookmarks.txt b/bundle/vim-bookmarks/doc/bookmarks.txt new file mode 100644 index 000000000..a9c81d977 --- /dev/null +++ b/bundle/vim-bookmarks/doc/bookmarks.txt @@ -0,0 +1,356 @@ +*bookmarks.txt* A Vim plugin for using line-based bookmarks. + + + Vim Bookmarks + + +Author: Mattes Groeger +Plugin Homepage: + +=============================================================================== +CONTENTS *BookmarksContents* + + 1. Introduction ................. |BookmarksIntroduction| + 2. Installation ................. |BookmarksInstallation| + 3. Usage ........................ |BookmarksUsage| + 4. Commands ..................... |BookmarksCommands| + 5. Customisation ................ |BookmarksCustomisation| + 6. Extending .................... |BookmarksExtening| + 7. Unite Integration ............ |BookmarksUniteIntegration| + 8. CtrlP Integration ............ |BookmarksCtrlPIntegration| + 9. FAQ .......................... |BookmarksFAQ| + +=============================================================================== +1. INTRODUCTION *BookmarksIntroduction* + *Bookmarks* + +This vim plugin allows toggling bookmarks per line. A quickfix window gives +access to all bookmarks. Annotations can be added as well. These are special +bookmarks with a comment attached. They are useful for preparing code reviews. +All bookmarks will be restored on the next startup. + +=============================================================================== +2. INSTALLATION *BookmarksInstallation* + +Use your favorite plugin manager: + +Pathogen: +> + git clone https://github.com/MattesGroeger/vim-bookmarks.git \ + ~/.vim/bundle/vim-bookmarks +< + +Vundle: + 1. Add Plugin 'MattesGroeger/vim-bookmarks' to .vimrc + 2. Run :PluginInstall + +NeoBundle: + 1. Add NeoBundle 'MattesGroeger/vim-bookmarks' to .vimrc + 2. Run :NeoBundleInstall + +vim-plug: + 1. Add Plug 'MattesGroeger/vim-bookmarks' to .vimrc + 2. Run :PlugInstall + +=============================================================================== +3. USAGE *BookmarksUsage* + +You don't have to do anything: it just works. + +=============================================================================== +4. COMMANDS *BookmarksCommands* + +Commands for using Bookmarks + + :BookmarkToggle *:BookmarkToggle* + Add or remove bookmark at current line + + :BookmarkAnnotate *:BookmarkAnnotate* + Add/edit/remove annotation at current line + + :BookmarkNext *:BookmarkNext* + Jump to the next bookmark downwards + + :BookmarkPrev *:BookmarkPrev* + Jump to the next bookmark upwards + + :BookmarkShowAll *:BookmarkShowAll* + Show bookmarks across all buffers in new window (toggle) + + :BookmarkClear *:BookmarkClear* + Removes bookmarks for current buffer + + :BookmarkClearAll *:BookmarkClearAll* + Removes bookmarks for all buffer + + :BookmarkMoveUp [] *:BookmarkMoveUp* + Move up the bookmark at current line by the specified amount of lines + (default: 1) + + :BookmarkMoveDown [] *:BookmarkMoveDown* + Move down the bookmark at current line by the specified amount of lines + (default: 1) + + :BookmarkMoveToLine *:BookmarkMoveToLine* + Move the bookmark at current line to the specified + + :BookmarkSave *:BookmarkSave* + Saves all bookmarks to a file so you can load them back in later + + :BookmarkLoad *:BookmarkLoad* + Loads bookmarks from a file (see :BookmarkSave) + +=============================================================================== +5. CUSTOMISATION *BookmarksCustomisation* + +You can customise: + +- The sign column's colours +- The signs' colours and symbols +- Line highlights +- Key mappings +- Whether or not line highlighting is on (defaults to off) +- Whether to close bookmarks split when jumping to a bookmark or not. + +Please note that vim-bookmarks won't override any colours or highlights you've +set in your colorscheme. + +SIGN COLUMN + +The background colour of the sign column is controlled by the |hlSignColumn| +highlight group. This will be either set in your colorscheme or Vim's default. + +To find out where it's set, and to what it's set, use: +> + :verbose highlight SignColumn +< + +To change your sign column's appearance, update your colorscheme or |vimrc| +like this: + + Desired appearance Command ~ + Same as line number column highlight clear SignColumn + User-defined (terminal Vim) highlight SignColumn ctermbg={whatever} + User-defined (graphical Vim) highlight SignColumn guibg={whatever} + +SIGNS' COLOURS AND SYMBOLS + +To customise the colours, set up the following highlight groups in your +colorscheme or |vimrc|: +> + BookmarkSign " the sign + BookmarkAnnotationSign " the annotation sign +< + +You can either set these with `highlight BookmarkSign {key}={arg}...` or link +them to existing highlight groups with, say: +> + highlight link BookmarkSign Todo +< + +To customise the symbols, add the following to your |vimrc|: +> + let g:bookmark_sign = '>>' + let g:bookmark_annotation_sign = '##' +< + +LINE HIGHLIGHTS + +Similarly to the signs' colours, set up the following highlight group in your +colorscheme or |vimrc|: +> + BookmarkLine " the highlighted line + BookmarkAnnotationLine " the highlighted annotation line +< + +KEY MAPPINGS + +To change the default keys: +> + nmap BookmarkToggle + nmap i BookmarkAnnotate + nmap a BookmarkShowAll + nmap j BookmarkNext + nmap k BookmarkPrev + nmap c BookmarkClear + nmap x BookmarkClearAll + + " these will also work with a [count] prefix + nmap kk BookmarkMoveUp + nmap jj BookmarkMoveDown + nmap g BookmarkMoveToLine +< + +To prevent any default mappings from being created (default: 0): +> + let g:bookmark_no_default_key_mappings = 1 +< + +MORE OPTIONS + +Change the following options in your |vimrc| to customize the plugin behaviour. + +Save/show bookmarks per working directory/project (default 0): + +> + let g:bookmark_save_per_working_dir = 1 +< + +When this option is enabled, a file called `.vim-bookmarks` will be stored in +the current working directory whenever you create bookmarks. You should add this +file to your (global) `.gitignore` file so it doesn't get checked into version +control. + +Enable bookmark management on a per buffer basis: + +> + let g:bookmark_manage_per_buffer = 1 +< + +This will save and load the bookmarks file whenever the buffer changes (e.g., +switching tabs/buffers). It makes most sense when g:bookmark_save_per_working_dir +is turned on as well, or when a customizing function is used. + +Disable auto saving (default 1): + +> + let g:bookmark_auto_save = 0 +< + +Change file for auto saving (default $HOME .'/.vim-bookmarks'): + +> + let g:bookmark_auto_save_file = '/tmp/my_bookmarks' +< + +Turn on line highlighting (default 0): + +> + let g:bookmark_highlight_lines = 1 +< + +Turn off the warning when clearing all bookmarks (default 1): + +> + let g:bookmark_show_warning = 0 +< + +Turn off the warning when toggling to clear a bookmark with annotation (default 1): + +> + let g:bookmark_show_toggle_warning = 0 +< + +Turn on vertical line centering when jumping to bookmark (default 0): + +> + let g:bookmark_center = 1 +< + +Automatically close bookmarks split when jumping to a bookmark (default 0): + +> + let g:bookmark_auto_close = 1 +< + +Use the location list to show all bookmarks (default 0): + +> + let g:bookmark_location_list = 1 + + +You can disable Ctrlp by setting (default 0) +> + let g:bookmark_disable_ctrlp = 1 +< +=============================================================================== +6. EXTENDING *BookmarksExtending* + +You can implement automatic switching of bookmarks file on your desired event. +To do so, use functions BookmarkSave, BookmarkLoad and BookmarkClearAll with +the last argument set to 1. This will suppress any messages and prompts these +functions normally issue. The BookmarkLoad function will return 1 to indicate +that file loading was successful, and 0 for failure. An example script using +this feature is located in the 'examples/bm-autoload-example.vim' file. + +=============================================================================== +7. Unite Integration *BookmarksUniteIntegration* + +Unite is a multi-purpose user-interface plugin platform. vim-bookmarks provide +an Unite source called *vim_bookmarks* so users who use Unite will handle +bookmarks with the Unite interface. When showing all your bookmarks, Unite is +detected and the plugin will open ':Unite vim_bookmarks' instead of Vim's +quickfix window. Note that |g:bookmark_auto_close| is no longer applied, once +opened, the window is managed by Unite. + +To set a global per-source context setting, that will apply to Unite's quickfix +source everytime it's opened, you can add this to your |vimrc|: +> + call unite#custom#profile('source/vim_bookmarks', 'context', { + \ 'winheight': 13, + \ 'direction': 'botright', + \ 'start_insert': 0, + \ 'keep_focus': 1, + \ 'no_quit': 1, + \ }) +< + +With the Unite interface, when you select bookmarks, you can perform the +following actions. + +- Open the selected bookmarks in various ways (open in right, open in above, + open in new tab, etc.) +- Yank the informations of selected bookmarks (path and line number, the line + content, annotation, etc.) +- Highlight the lines of the selected bookmarks +- Replace the contents of selected bookmarks with vim-qfreplace +- Delete the selected bookmarks +- And more! + +For more information about Unite, start reading |:Unite|. + +https://github.com/Shougo/unite.vim +https://github.com/thinca/vim-qfreplace + +=============================================================================== +8. CtrlP Integration *BookmarksCtrlPIntegration* + +ctrlp.vim is a Full path fuzzy file, buffer, mru, tag, ... finder for Vim. + +Additionally, when showing all your bookmarks, CtrlP is detected and the plugin +will open *:CtrlPBookmark* instead of Vim's quickfix window. Note that +|g:bookmark_auto_close| is no longer applied. Once opened, the window is managed +by CtrlP. + +With the CtrlP interface, when you select bookmarks, you can perform the +following actions: + +* Open the selected bookmarks in various ways (open to the right, open above, + open in new tab, etc.) +* And more... + +For more information about CtrlP start reading |CtrlP@en|. + +https://github.com/ctrlpvim/ctrlp.vim + +=============================================================================== +9. FAQ *BookmarksFAQ* + +a. Why are the colours in the sign column weird? + + Your colorscheme is configuring the |hl-SignColumn| highlight group weirdly. + Please see |BookmarksCustomisation| on customising the sign column. + +b. What happens if I also use another plugin which uses signs (e.g. Syntastic)? + + Vim only allows one sign per line. Therefore bookmarks will override any + existing sign. When removing the bookmark the original sign will show up + again. In other words vim-bookmarks won't remove another plugin's signs. + +c. Why aren't any signs showing at all? + + Make sure your vim supports signs. This should return "1": +> + :echo has('signs') +< +vim:ft=help:et:ts=2:sw=2:sts=2:norl diff --git a/bundle/vim-bookmarks/examples/bm-autoload-example.vim b/bundle/vim-bookmarks/examples/bm-autoload-example.vim new file mode 100644 index 000000000..2c78e3fcc --- /dev/null +++ b/bundle/vim-bookmarks/examples/bm-autoload-example.vim @@ -0,0 +1,47 @@ +let s:autoload_roots = [] +let s:last_root = '' +let s:last_file = '' + +function! AddBookmarkRoot(root) + call add(s:autoload_roots, a:root) +endfunction! + +function! AutoloadBookmarks(file_name) + let root_is_found = 0 + let found_root = 0 + let name_len = strlen(a:file_name) + + for root in s:autoload_roots + let root_len = strlen(root) + if (name_len > root_len && strpart(a:file_name, 0, root_len) == root) + let root_is_found = 1 + let found_root = root + break + endif + endfor + + if (root_is_found && found_root != s:last_root) + let s:last_file = found_root . '/.bookmarks' + let s:last_root = found_root + call BookmarkLoad(s:last_file, 0, 1) + + augroup AutoSaveBookmarks + autocmd! + autocmd BufLeave * call s:remove_group() + augroup END + else + let s:last_root = '' + endif +endfunction + +augroup AutoLoadBookmarks + autocmd! + autocmd BufEnter * call AutoloadBookmarks(expand(":p")) +augroup END + +function! s:remove_group() + call BookmarkSave(s:last_file, 1) + augroup AutoSaveBookmarks + autocmd! + augroup END +endfunction diff --git a/bundle/vim-bookmarks/plugin/bookmark.vim b/bundle/vim-bookmarks/plugin/bookmark.vim new file mode 100644 index 000000000..3515fa976 --- /dev/null +++ b/bundle/vim-bookmarks/plugin/bookmark.vim @@ -0,0 +1,569 @@ +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(':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', [, 0]) +command! -nargs=* BookmarkAnnotate call BookmarkAnnotate(, 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" + 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() + belowright lopen + else + cgetexpr bm#location_list() + 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', [, 0]) +command! -nargs=1 BookmarkSave call BookmarkSave(, 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', [, 0, 0]) +command! -nargs=1 BookmarkLoad call BookmarkLoad(, 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(, -1) +command! -nargs=? BookmarkMoveDown call s:move_relative(, 1) +command! -nargs=? BookmarkMoveToLine call s:move_absolute() + +" }}} + + +" 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' or '' (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(':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 ". a:command ." :". a:command ." v:count" + else + execute "nnoremap ". a:command ." :". a:command ."" + endif + if !hasmapto("". a:command) + \ && !get(g:, 'bookmark_no_default_key_mappings', 0) + \ && maparg(a:shortcut, 'n') ==# '' + execute "nmap ". a:shortcut ." ". 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(':p')) +else + call s:init(expand('%:p')) +endif diff --git a/bundle/vim-bookmarks/preview.gif b/bundle/vim-bookmarks/preview.gif new file mode 100644 index 0000000000000000000000000000000000000000..84ce95724615a39c31a026fe87cc29ffdb04d910 GIT binary patch literal 975672 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