Added two new plugins: vim-expand-region and vim-multiple-cursors.

They are both super useful. Read more on their GitHub pages for more info:
https://github.com/terryma/vim-expand-region
https://github.com/terryma/vim-multiple-cursors
This commit is contained in:
amix 2013-04-14 12:48:31 -03:00
parent c1bacbbc80
commit 8b5bc388b0
19 changed files with 1743 additions and 1 deletions

View file

@ -71,6 +71,8 @@ I recommend reading the docs of these plugins to understand them better. Each of
* [zencoding](https://github.com/mattn/zencoding-vim): Expanding abbreviation like zen-coding, very useful for editing XML, HTML.
* [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object): Defines a new text object representing lines of code at the same indent level. Useful for python/vim scripts
* [taglist.vim](https://github.com/vim-scripts/taglist.vim): Source code browser (supports C/C++, java, perl, python, tcl, sql, php, etc)
* [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors): Sublime Text style multiple selections for Vim, CTRL+N is remapped to CTRL+A (due to YankRing)
* [vim-expand-region](https://github.com/terryma/vim-expand-region): Allows you to visually select increasingly larger regions of text using the same key combination.
## What color schemes are included?
@ -134,6 +136,7 @@ Managing the [NERD Tree](https://github.com/scrooloose/nerdtree) plugin:
map <leader>nf :NERDTreeFind<cr>
### Normal mode mappings
Fast saving of a buffer:
nmap <leader>w :w!<cr>
@ -261,7 +264,7 @@ Bash like keys for the command line:
cnoremap <C-P> <Up>
cnoremap <C-N> <Down>
Write the file as sudo (only on Unix). [Vim tip](http://vim.wikia.com/wiki/Su-write):
Write the file as sudo (only on Unix). Super useful when you open a file and you don't have permissions to save your changes. [Vim tip](http://vim.wikia.com/wiki/Su-write):
:W

View file

@ -0,0 +1,20 @@
Copyright 2013 Terry Ma
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.

View file

@ -0,0 +1,92 @@
# vim-expand-region
## About
[vim-expand-region] is a Vim plugin that allows you to visually select increasingly larger regions of text using the same key combination. It is similar to features from other editors:
- Emac's [expand region](https://github.com/magnars/expand-region.el)
- IntelliJ's [syntax aware selection](http://www.jetbrains.com/idea/documentation/tips/#tips_code_editing)
- Eclipse's [select enclosing element](http://stackoverflow.com/questions/4264047/intellij-ctrlw-equivalent-shortcut-in-eclipse)
<p align="center">
<img src="https://raw.github.com/terryma/vim-expand-region/master/expand-region.gif" alt="vim-expand-region" />
</p>
## Installation
Install using [Pathogen], [Vundle], [Neobundle], or your favorite Vim package manager.
## Quick Start
Press ```+``` to expand the visual selection and ```_``` to shrink it.
## Mapping
Customize the key mapping if you don't like the default.
```
map K <Plug>(expand_region_expand)
map J <Plug>(expand_region_shrink)
```
## Setting
### Customize selected regions
The plugin uses __your own__ text objects to determine the expansion. You can customize the text objects the plugin knows about with ```g:expand_region_text_objects```.
```vim
" Default settings. (NOTE: Remove comments in dictionary before sourcing)
let g:expand_region_text_objects = {
\ 'iw' :0,
\ 'iW' :0,
\ 'i"' :0,
\ 'i''' :0,
\ 'i]' :1, " Support nesting of square brackets
\ 'ib' :1, " Support nesting of parentheses
\ 'iB' :1, " Support nesting of braces
\ 'il' :0, " 'inside line'. Available through https://github.com/kana/vim-textobj-line
\ 'ip' :0,
\ 'ie' :0, " 'entire file'. Available through https://github.com/kana/vim-textobj-entire
\ }
```
You can extend the global default dictionary by calling ```expand_region#custom_text_objects```:
```vim
" Extend the global default (NOTE: Remove comments in dictionary before sourcing)
call expand_region#custom_text_objects({
\ "\/\\n\\n\<CR>": 1, " Motions are supported as well. Here's a search motion that finds a blank line
\ 'a]' :1, " Support nesting of 'around' brackets
\ 'ab' :1, " Support nesting of 'around' parentheses
\ 'aB' :1, " Support nesting of 'around' braces
\ 'ii' :0, " 'inside indent'. Available through https://github.com/kana/vim-textobj-indent
\ 'ai' :0, " 'around indent'. Available through https://github.com/kana/vim-textobj-indent
\ })
```
You can further customize the text objects dictionary on a per filetype basis by defining global variables like ```g:expand_region_text_objects_{ft}```.
```vim
" Use the following setting for ruby. (NOTE: Remove comments in dictionary before sourcing)
let g:expand_region_text_objects_ruby = {
\ 'im' :0, " 'inner method'. Available through https://github.com/vim-ruby/vim-ruby
\ 'am' :0, " 'around method'. Available through https://github.com/vim-ruby/vim-ruby
\ }
```
Note that this completely replaces the default dictionary. To extend the default on a per filetype basis, you can call ```expand_region#custom_text_objects``` by passing in the filetype in the first argument:
```vim
" Use the global default + the following for ruby
call expand_region#custom_text_objects('ruby', {
\ 'im' :0,
\ 'am' :0,
\ })
```
### Customize selection mode
By default, after an expansion, the plugin leaves you in visual mode. If your ```selectmode```(h:selectmode)) contains ```cmd```, then the plugin will respect that setting and leave you in select mode. If you don't have ```selectmode``` set, but would like to default the expansion in select mode, you can use the global setting below:
```vim
let g:expand_region_use_select_mode = 1
```
[vim-expand-region]:http://github.com/terryma/vim-expand-region
[Pathogen]:http://github.com/tpope/vim-pathogen
[Vundle]:http://github.com/gmarik/vundle
[Neobundle]:http://github.com/Shougo/neobundle.vim

View file

@ -0,0 +1,350 @@
" ==============================================================================
" File: expand_region.vim
" Author: Terry Ma
" Last Modified: March 30, 2013
" ==============================================================================
let s:save_cpo = &cpo
set cpo&vim
" ==============================================================================
" Settings
" ==============================================================================
" Init global vars
function! expand_region#init()
if exists('g:expand_region_init') && g:expand_region_init
return
endif
let g:expand_region_init = 1
" Dictionary of text objects that are supported by default. Note that some of
" the text objects are not available in vanilla vim. '1' indicates that the
" text object is recursive (think of nested parens or brackets)
let g:expand_region_text_objects = get(g:, 'expand_region_text_objects', {
\ 'iw' :0,
\ 'iW' :0,
\ 'i"' :0,
\ 'i''' :0,
\ 'i]' :1,
\ 'ib' :1,
\ 'iB' :1,
\ 'il' :0,
\ 'ip' :0,
\ 'ie' :0,
\})
" Option to default to the select mode when selecting a new region
let g:expand_region_use_select_mode = get(g:, 'expand_region_use_select_mode', 0)
endfunction
call expand_region#init()
" ==============================================================================
" Global Functions
" ==============================================================================
" Allow user to customize the global dictionary, or the per file type dictionary
function! expand_region#custom_text_objects(...)
if a:0 == 1
call extend(g:expand_region_text_objects, a:1)
elseif a:0 == 2
if !exists("g:expand_region_text_objects_".a:1)
let g:expand_region_text_objects_{a:1} = {}
call extend(g:expand_region_text_objects_{a:1}, g:expand_region_text_objects)
endif
call extend(g:expand_region_text_objects_{a:1}, a:2)
endif
endfunction
" Returns whether we should perform the region highlighting use visual mode or
" select mode
function! expand_region#use_select_mode()
return g:expand_region_use_select_mode || index(split(s:saved_selectmode, ','), 'cmd') != -1
endfunction
" Main function
function! expand_region#next(mode, direction)
call s:expand_region(a:mode, a:direction)
endfunction
" ==============================================================================
" Variables
" ==============================================================================
" The saved cursor position when user initiates expand. This is the position we
" use to calcuate the region for all of our text objects. This is also used to
" restore the original cursor position when the region is completely shrinked.
let s:saved_pos = []
" Index into the list of filtered text objects(s:candidates), the text object
" this points to is the currently selected region.
let s:cur_index = -1
" The list of filtered text objects used to expand/shrink the visual selection.
" This is computed when expand-region is called the first time.
" Each item is a dictionary containing the following:
" text_object: The actual text object string
" start_pos: The result of getpos() on the starting position of the text object
" end_pos: The result of getpos() on the ending position of the text object
" length: The number of characters for the text object
let s:candidates = []
" This is used to save the user's selectmode setting. If the user's selectmode
" contains 'cmd', then our expansion should result in the region selected under
" select mode.
let s:saved_selectmode = &selectmode
" ==============================================================================
" Functions
" ==============================================================================
" Sort the text object by length in ascending order
function! s:sort_text_object(l, r)
return a:l.length - a:r.length
endfunction
" Compare two position arrays. Each input is the result of getpos(). Return a
" negative value if lhs occurs before rhs, positive value if after, and 0 if
" they are the same.
function! s:compare_pos(l, r)
" If number lines are the same, compare columns
return a:l[1] ==# a:r[1] ? a:l[2] - a:r[2] : a:l[1] - a:r[1]
endfunction
" Boundary check on the cursor position to make sure it's inside the text object
" region. Return 1 if the cursor is within range, 0 otherwise.
function! s:is_cursor_inside(pos, region)
if s:compare_pos(a:pos, a:region.start_pos) < 0
return 0
endif
if s:compare_pos(a:pos, a:region.end_pos) > 0
return 0
endif
return 1
endfunction
" Remove duplicates from the candidate list. Two candidates are duplicates if
" they cover the exact same region (same length and same starting position)
function! s:remove_duplicate(input)
let i = len(a:input) - 1
while i >= 1
if a:input[i].length ==# a:input[i-1].length &&
\ a:input[i].start_pos ==# a:input[i-1].start_pos
call remove(a:input, i)
endif
let i-=1
endwhile
endfunction
" Return a single candidate dictionary. Each dictionary contains the following:
" text_object: The actual text object string
" start_pos: The result of getpos() on the starting position of the text object
" end_pos: The result of getpos() on the ending position of the text object
" length: The number of characters for the text object
function! s:get_candidate_dict(text_object)
" Store the current view so we can restore it at the end
let winview = winsaveview()
" Use ! as much as possible
exec 'normal! v'
exec 'silent! normal '.a:text_object
" The double quote is important
exec "normal! \<Esc>"
let selection = s:get_visual_selection()
let ret = {
\ "text_object": a:text_object,
\ "start_pos": selection.start_pos,
\ "end_pos": selection.end_pos,
\ "length": selection.length,
\}
" Restore peace
call winrestview(winview)
return ret
endfunction
" Return dictionary of text objects that are to be used for the current
" filetype. Filetype-specific dictionaries will be loaded if they exist
" and the global dictionary will be used as a fallback.
function! s:get_configuration()
let configuration = {}
for ft in split(&ft, '\.')
if exists("g:expand_region_text_objects_".ft)
call extend(configuration, g:expand_region_text_objects_{ft})
endif
endfor
if empty(configuration)
call extend(configuration, g:expand_region_text_objects)
endif
return configuration
endfunction
" Return list of candidate dictionary. Each dictionary contains the following:
" text_object: The actual text object string
" start_pos: The result of getpos() on the starting position of the text object
" length: The number of characters for the text object
function! s:get_candidate_list()
" Turn off wrap to allow recursive search to work without triggering errors
let save_wrapscan = &wrapscan
set nowrapscan
let config = s:get_configuration()
" Generate the candidate list for every defined text object
let candidates = keys(config)
call map(candidates, "s:get_candidate_dict(v:val)")
" For the ones that are recursive, generate them until they no longer match
" any region
let recursive_candidates = []
for i in candidates
" Continue if not recursive
if !config[i.text_object]
continue
endif
" If the first level is already empty, no point in going any further
if i.length ==# 0
continue
endif
let l:count = 2
let previous = i.length
while 1
let test = l:count.i.text_object
let candidate = s:get_candidate_dict(test)
if candidate.length ==# 0
break
endif
" If we're not producing larger regions, end early
if candidate.length ==# previous
break
endif
call add(recursive_candidates, candidate)
let l:count+=1
let previous = candidate.length
endwhile
endfor
" Restore wrapscan
let &wrapscan = save_wrapscan
return extend(candidates, recursive_candidates)
endfunction
" Return a dictionary containing the start position, end position and length of
" the current visual selection.
function! s:get_visual_selection()
let start_pos = getpos("'<")
let end_pos = getpos("'>")
let [lnum1, col1] = start_pos[1:2]
let [lnum2, col2] = end_pos[1:2]
let lines = getline(lnum1, lnum2)
let lines[-1] = lines[-1][: col2 - 1]
let lines[0] = lines[0][col1 - 1:]
return {
\ 'start_pos': start_pos,
\ 'end_pos': end_pos,
\ 'length': len(join(lines, "\n"))
\}
endfunction
" Figure out whether we should compute the candidate text objects, or we're in
" the middle of an expand/shrink.
function! s:should_compute_candidates(mode)
if a:mode ==# 'v'
" Check that current visual selection is idential to our last expanded
" region
if s:cur_index >= 0
let selection = s:get_visual_selection()
if s:candidates[s:cur_index].start_pos ==# selection.start_pos
\ && s:candidates[s:cur_index].length ==# selection.length
return 0
endif
endif
endif
return 1
endfunction
" Computes the list of text object candidates to be used given the current
" cursor position.
function! s:compute_candidates(cursor_pos)
" Reset index into the candidates list
let s:cur_index = -1
" Save the current cursor position so we can restore it later
let s:saved_pos = a:cursor_pos
" Compute a list of candidate regions
let s:candidates = s:get_candidate_list()
" Sort them and remove the ones with 0 or 1 length
call filter(sort(s:candidates, "s:sort_text_object"), 'v:val.length > 1')
" Filter out the ones where the cursor falls outside of its region. i" and i'
" can start after the cursor position, and ib can start before, so both checks
" are needed
call filter(s:candidates, 's:is_cursor_inside(s:saved_pos, v:val)')
" Remove duplicates
call s:remove_duplicate(s:candidates)
endfunction
" Perform the visual selection at the end. If the user wants to be left in
" select mode, do so
function! s:select_region()
exec 'normal! v'
exec 'normal '.s:candidates[s:cur_index].text_object
if expand_region#use_select_mode()
exec "normal! \<C-g>"
endif
endfunction
" Expand or shrink the visual selection to the next candidate in the text object
" list.
function! s:expand_region(mode, direction)
" Save the selectmode setting, and remove the setting so our 'v' command do
" not get interfered
let s:saved_selectmode = &selectmode
let &selectmode=""
if s:should_compute_candidates(a:mode)
call s:compute_candidates(getpos('.'))
else
call setpos('.', s:saved_pos)
endif
if a:direction ==# '+'
" Expanding
if s:cur_index ==# len(s:candidates) - 1
normal! gv
else
let s:cur_index+=1
" Associate the window view with the text object
let s:candidates[s:cur_index].prev_winview = winsaveview()
call s:select_region()
endif
else
"Shrinking
if s:cur_index <=# 0
" In visual mode, doing nothing here will return us to normal mode. For
" select mode, the following is needed.
if expand_region#use_select_mode()
exec "normal! gV"
endif
else
" Restore the window view
call winrestview(s:candidates[s:cur_index].prev_winview)
let s:cur_index-=1
call s:select_region()
endif
endif
" Restore the selectmode setting
let &selectmode = s:saved_selectmode
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View file

@ -0,0 +1,126 @@
*vim-expand-region.txt* Incremental visual selection
__ _
___ _ ______ ____ _____ ____/ / ________ ____ _(_)___ ____
/ _ \| |/_/ __ \/ __ `/ __ \/ __ / / ___/ _ \/ __ `/ / __ \/ __ \
/ __/> </ /_/ / /_/ / / / / /_/ / / / / __/ /_/ / / /_/ / / / /
\___/_/|_/ .___/\__,_/_/ /_/\__,_/ /_/ \___/\__, /_/\____/_/ /_/
/_/ /____/
Reference Manual~
==============================================================================
CONTENTS *expand-region-contents*
1.Intro...................................|expand-region-intro|
2.Usage...................................|expand-region-usage|
3.Mappings................................|expand-region-mappings|
4.Global Options..........................|expand-region-global-options|
5.About...................................|expand-region-about|
==============================================================================
1. Intro *expand-region-intro*
*vim-expand-regions* brings the incremental visual selection feature from
other text editors into Vim.
Emac's 'expand-region': https://github.com/magnars/expand-region.el
IntellJ's 'syntax aware selection':
http://www.jetbrains.com/idea/documentation/tips/#tips_code_editing
Eclipse's 'select enclosing element':
http://stackoverflow.com/questions/4264047/intellij-ctrlw-equivalent-shortcut-in-eclipse
==============================================================================
2. Usage *expand-region-usage*
Press '+' to expand the visual selection and '_' to shrink it.
==============================================================================
2. Mappings *expand-region-mappings*
Customize the key mappings if you don't like the default. >
map K <Plug>(expand_region_expand)
map J <Plug>(expand_region_shrink)
<
==============================================================================
4. Global Options *expand-region-global-options*
*expand_region_text_objects*
Default: See below
Dictionary containing the text objects the plugin uses to search for the
available regions to expand/shrink to. The value corresponding to each plugin
indicates whether text object is recursive. A recursive text object is
continually expanded until the region no longer gets larger. >
" Default settings. (NOTE: Remove comments in dictionary before sourcing)
let g:expand_region_text_objects = {
\ 'iw' :0,
\ 'iW' :0,
\ 'i"' :0,
\ 'i''' :0,
\ 'i]' :1, " Support nesting of square brackets
\ 'ib' :1, " Support nesting of parentheses
\ 'iB' :1, " Support nesting of braces
\ 'il' :0, " 'inside line'. Available through https://github.com/kana/vim-textobj-line
\ 'ip' :0,
\ 'ie' :0, " 'entire file'. Available through https://github.com/kana/vim-textobj-entire
\ }
<
You can extend the global default dictionary by calling
'expand_region#custom_text_objects'. >
" Extend the global default (NOTE: Remove comments in dictionary before sourcing)
call expand_region#custom_text_objects({
\ "\/\\n\\n\<CR>": 1, " Motions are supported as well. Here's a search motion that finds a blank line
\ 'a]' :1, " Support nesting of 'around' brackets
\ 'ab' :1, " Support nesting of 'around' parentheses
\ 'aB' :1, " Support nesting of 'around' braces
\ 'ii' :0, " 'inside indent'. Available through https://github.com/kana/vim-textobj-indent
\ 'ai' :0, " 'around indent'. Available through https://github.com/kana/vim-textobj-indent
\ })
<
You can further customize the text objects dictionary on a per filetype basis
by defining global variables like 'g:expand_region_text_objects_{ft}'. >
" Use the following setting for ruby. (NOTE: Remove comments in dictionary before sourcing)
let g:expand_region_text_objects_ruby = {
\ 'im' :0, " 'inner method'. Available through https://github.com/vim-ruby/vim-ruby
\ 'am' :0, " 'around method'. Available through https://github.com/vim-ruby/vim-ruby
\ }
<
Note that this completely replaces the default dictionary. To extend the
default on a per filetype basis, you can call
'expand_region#custom_text_objects' by passing in the filetype in the first
argument: >
" Use the global default + the following for ruby
call expand_region#custom_text_objects('ruby', {
\ 'im' :0,
\ 'am' :0,
\ })
<
*expand_region_use_select_mode*
Default: 0
By default, after an expansion, the plugin leaves you in visual mode. If your
'selectmode' contains "cmd", then the plugin will respect that setting and
leave you in select mode. If you don't have 'selectmode' set, but would
like to default the expansion in select mode, you can use the global setting
below: >
let g:expand_region_use_select_mode = 1
<
==============================================================================
5. About *expand-region-about*
==============================================================================
Find the latest version of the plugin here:
http://github.com/terryma/vim-expand-region
vim:tw=78:sw=4:ft=help:norl:

View file

@ -0,0 +1,10 @@
expand-region-about expand_region.txt /*expand-region-about*
expand-region-contents expand_region.txt /*expand-region-contents*
expand-region-global-options expand_region.txt /*expand-region-global-options*
expand-region-intro expand_region.txt /*expand-region-intro*
expand-region-mappings expand_region.txt /*expand-region-mappings*
expand-region-usage expand_region.txt /*expand-region-usage*
expand_region_text_objects expand_region.txt /*expand_region_text_objects*
expand_region_use_select_mode expand_region.txt /*expand_region_use_select_mode*
vim-expand-region.txt expand_region.txt /*vim-expand-region.txt*
vim-expand-regions expand_region.txt /*vim-expand-regions*

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 KiB

View file

@ -0,0 +1,43 @@
" ==============================================================================
" File: expand_region.vim
" Author: Terry Ma
" Description: Incrementally select larger regions of text in visual mode by
" repeating the same key combination
" Last Modified: March 30, 2013
" ==============================================================================
let s:save_cpo = &cpo
set cpo&vim
" Init global vars
call expand_region#init()
" ==============================================================================
" Mappings
" ==============================================================================
if !hasmapto('<Plug>(expand_region_expand)')
nmap + <Plug>(expand_region_expand)
vmap + <Plug>(expand_region_expand)
endif
if !hasmapto('<Plug>(expand_region_shrink)')
vmap _ <Plug>(expand_region_shrink)
nmap _ <Plug>(expand_region_shrink)
endif
nnoremap <silent> <Plug>(expand_region_expand)
\ :<C-U>call expand_region#next('n', '+')<CR>
" Map keys differently depending on which mode is desired
if expand_region#use_select_mode()
snoremap <silent> <Plug>(expand_region_expand)
\ :<C-U>call expand_region#next('v', '+')<CR>
snoremap <silent> <Plug>(expand_region_shrink)
\ :<C-U>call expand_region#next('v', '-')<CR>
else
xnoremap <silent> <Plug>(expand_region_expand)
\ :<C-U>call expand_region#next('v', '+')<CR>
xnoremap <silent> <Plug>(expand_region_shrink)
\ :<C-U>call expand_region#next('v', '-')<CR>
endif
let &cpo = s:save_cpo
unlet s:save_cpo

View file

@ -0,0 +1,20 @@
Copyright 2013 Terry Ma
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.

View file

@ -0,0 +1,90 @@
# vim-multiple-cursors
## About
[There](https://github.com/paradigm/vim-multicursor) [have](https://github.com/felixr/vim-multiedit) [been](https://github.com/hlissner/vim-multiedit) [many](https://github.com/adinapoli/vim-markmultiple) [attempts](https://github.com/AndrewRadev/multichange.vim) at bringing Sublime Text's awesome [multiple selection][sublime-multiple-selection] feature into Vim, but none so far have been in my opinion a faithful port that is simplistic to use, yet powerful and intuitive enough for an existing Vim user. [vim-multiple-cursors] is yet another attempt at that.
### It's great for quick refactoring
![Example1](assets/example1.gif?raw=true)
### Add a cursor to each line of your visual selection
![Example2](assets/example2.gif?raw=true)
### Do it backwards too! This is not just a replay of the above gif :)
![Example3](assets/example3.gif?raw=true)
## Features
- Live update in Insert mode
- One key to rule it all! See [Quick Start](#quick-start) on what the key does in different scenarios
- Works in Normal, Insert, and Visual mode for SINGLE key command
## Installation
Install using [Pathogen], [Vundle], [Neobundle], or your favorite Vim package manager.
## Quick Start
Out of the box, all you need to know is a single key `Ctrl-n`. Pressing the key in Normal mode highlights the current word under the cursor in Visual mode and places a virtual cursor at the end of it. Pressing it again finds the next ocurrence and places another virtual cursor at the end of the visual selection. If you select multiple lines in Visual mode, pressing the key puts a virtual cursor at every line and leaves you in Normal mode.
After you've marked all your locations with `Ctrl-n`, you can change the visual selection with normal Vim motion commands in Visual mode. You could go to Normal mode by pressing `v` and wield your motion commands there. Single key command to switch to Insert mode such as `c` or `s` from Visual mode or `i`, `a`, `I`, `A` in Normal mode should work without any issues.
At any time, you can press `<Esc>` to exit back to regular Vim.
Two additional keys are also mapped:
- `Ctrl-p` in Visual mode will remove the current virtual cursor and go back to the previous virtual cursor location. This is useful if you are trigger happy with `Ctrl-n` and accidentally went too far.
- `Ctrl-x` in Visual mode will remove the current virtual cursor and skip to the next virtual cursor location. This is useful if you don't want the current selection to be a candidate to operate on later.
**NOTE**: The plugin is still somewhat buggy, if at any time you have lingering cursors on screen, you can press `Ctrl-n` in Normal mode and it will remove all prior cursors before starting a new one.
## Mapping
Out of the box, `Ctrl-n`, `Ctrl-p`, and `Ctrl-x` are mapped by default. If you don't like the plugin taking over your favorite key bindings, then turn off the default with
```
let g:multi_cursor_use_default_mapping=0
```
You can map the 'next', 'previous', 'skip', and 'exit' keys like the following:
```
" Default mapping
let g:multi_cursor_next_key="\<C-n>"
let g:multi_cursor_prev_key="\<C-p>"
let g:multi_cursor_skip_key="\<C-x>"
let g:multi_cursor_exit_key="\<Esc>"
```
## Setting
Currently there're two additional global settings one can tweak:
### ```g:multi_cursor_exit_from_visual_mode``` (Defaut: 1)
If set to 0, then pressing `g:multi_cursor_exit_key` in _Visual_ mode will not quit and delete all existing cursors. This is useful if you want to press Escape and go back to Normal mode, and still be able to operate on all the cursors.
### ```g:multi_cursor_exit_from_insert_mode``` (Default: 1)
If set to 0, then pressing `g:multi_cursor_exit_key` in _Insert_ mode will not quit and delete all existing cursors. This is useful if you want to press Escape and go back to Normal mode, and still be able to operate on all the cursors.
### Highlight
The plugin uses the highlight group `multiple_cursors_cursor` and `multiple_cursors_visual` to highlight the virtual cursors and their visual selections respectively. You can customize them by putting something similar like the following in your vimrc:
```
" Default highlighting (see help :highlight and help :highlight-link)
highlight multiple_cursors_cursor term=reverse cterm=reverse gui=reverse
highlight link multiple_cursors_visual Visual
```
## Issues
- Multi key commands like `ciw` do not work at the moment
- All user input typed before Vim is able to fan out the last operation to all cursors is lost. This is a implementation decision to keep the input perfectly synced in all locations, at the cost of potentially losing user input.
- Single key commands that do not terminate properly cause unexpected behavior. For example, if the cursor is on the first character in the buffer and 'b' is pressed.
- Undo behavior is unpredictable
- Performance in terminal vim degrades significantly with more cursors
- Select mode is not implemented
- Buggy when `wrap` is turned on
- Cursor highlighting is off. The last column on the same row as Vim's cursor is not highlighted incorrectly. Setting virtualedit=all might help
## Contributing
As one can see, there're still many issues to be resolved, patches and suggestions are always welcome!
## Credit
Obviously inspired by Sublime Text's [multiple selection][sublime-multiple-selection] feature, also encouraged by Emac's [multiple cursors][emacs-multiple-cursors] implemetation by Magnar Sveen
[vim-multiple-cursors]:http://github.com/terryma/vim-multiple-cursors
[sublime-multiple-selection]:http://www.sublimetext.com/docs/2/multiple_selection_with_the_keyboard.html
[Pathogen]:http://github.com/tpope/vim-pathogen
[Vundle]:http://github.com/gmarik/vundle
[Neobundle]:http://github.com/Shougo/neobundle.vim
[emacs-multiple-cursors]:https://github.com/magnars/multiple-cursors.el

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View file

@ -0,0 +1,689 @@
"===============================================================================
" Internal Mappings
"===============================================================================
inoremap <silent> <Plug>(multi_cursor_process_user_input)
\ <C-o>:call <SID>process_user_inut('i')<CR>
nnoremap <silent> <Plug>(multi_cursor_process_user_input)
\ :call <SID>process_user_inut('n')<CR>
xnoremap <silent> <Plug>(multi_cursor_process_user_input)
\ :<C-u>call <SID>process_user_inut('v')<CR>
inoremap <silent> <Plug>(multi_cursor_apply_user_input_next)
\ <C-o>:call <SID>apply_user_input_next('i')<CR>
nnoremap <silent> <Plug>(multi_cursor_apply_user_input_next)
\ :call <SID>apply_user_input_next('n')<CR>
xnoremap <silent> <Plug>(multi_cursor_apply_user_input_next)
\ :<C-u>call <SID>apply_user_input_next('v')<CR>
"===============================================================================
" Public Functions
"===============================================================================
" Reset everything the plugin has done
function! multiple_cursors#reset()
call s:cm.reset()
endfunction
" Creates a new cursor. Different logic applies depending on the mode the user
" is in and the current state of the buffer.
" 1. In normal mode, a new cursor is created at the end of the word under Vim's
" normal cursor
" 2. In visual mode, if the visual selection covers more than one line, a new
" cursor is created at the beginning of each line
" 3. In visual mode, if the visual selection covers a single line, a new cursor
" is created at the end of the visual selection. Another cursor will be
" attempted to be created at the next occurrence of the visual selection
function! multiple_cursors#new(mode)
if a:mode ==# 'n'
" Reset all existing cursors
call s:cm.reset()
" Select the word under cursor to set the '< and '> marks
exec "normal! viw\<Esc>"
normal! gv
call s:create_cursor_from_visual_selection()
call s:wait_for_user_input('v')
elseif a:mode ==# 'v'
" If the visual area covers the same line, then do a search for next
" occurrence
let start = line("'<")
let finish = line("'>")
if start != finish
call s:cm.reset()
for line in range(line("'<"), line("'>"))
call cursor(line, col("'<"))
call s:cm.add()
endfor
" Start in normal mode
call s:wait_for_user_input('n')
else
" Came directly from visual mode
if s:cm.is_empty()
call s:create_cursor_from_visual_selection()
call s:exit_visual_mode()
endif
" Select the next ocurrence
call s:select_next_occurrence(s:get_visual_selection())
" Try to place a cursor there, reselect old cursor if fails
if !s:cm.add()
call s:exit_visual_mode()
" Adding cursor failed, this mean the new cursor is already added
call s:cm.reapply_visual_selection()
endif
call s:wait_for_user_input('v')
endif
endif
endfunction
" Delete the current cursor and move Vim's cursor back to the previous cursor
function! multiple_cursors#prev()
if s:cm.is_empty()
normal! gv
return
endif
call s:cm.delete_current()
" If that was the last cursor, go back to normal mode
if s:cm.is_empty()
call s:cm.reset()
else
call s:cm.reapply_visual_selection()
call s:wait_for_user_input('v')
endif
endfunction
" Skip the current cursor and move to the next cursor
function! multiple_cursors#skip()
if s:cm.is_empty()
normal! gv
return
endif
call s:cm.delete_current()
call s:select_next_occurrence(s:get_visual_selection())
call s:cm.add()
call s:wait_for_user_input('v')
endfunction
"===============================================================================
" Cursor class
"===============================================================================
let s:Cursor = {}
" Create a new cursor. Highlight it and save the current line length
function! s:Cursor.new(position)
let obj = copy(self)
let obj.position = a:position
let obj.visual = []
let obj.cursor_hi_id = s:highlight_cursor(a:position)
let obj.visual_hi_id = 0
let obj.line_length = col([a:position[1], '$'])
return obj
endfunction
" Return the line the cursor is on
function! s:Cursor.line() dict
return self.position[1]
endfunction
" Return the column the cursor is on
function! s:Cursor.column() dict
return self.position[2]
endfunction
" Move the cursor location by the number of lines and columns specified in the
" input. The input can be negative.
function! s:Cursor.move(line, column) dict
let self.position[1] += a:line
let self.position[2] += a:column
if !empty(self.visual)
let self.visual[0][1] += a:line
let self.visual[0][2] += a:column
let self.visual[1][1] += a:line
let self.visual[1][2] += a:column
endif
call self.update_highlight()
endfunction
" Update the current position of the cursor
function! s:Cursor.update_position(pos) dict
let self.position = a:pos
call self.update_highlight()
endfunction
" Reapply the highlight on the cursor
function! s:Cursor.update_highlight() dict
call s:cm.remove_highlight(self.cursor_hi_id)
let self.cursor_hi_id = s:highlight_cursor(self.position)
endfunction
" Refresh the length of the line the cursor is on. This could change from
" underneath
function! s:Cursor.update_line_length() dict
let self.line_length = col([self.line(), '$'])
endfunction
" Update the visual selection and its highlight
function! s:Cursor.update_visual_selection(region) dict
let self.visual = a:region
call s:cm.remove_highlight(self.visual_hi_id)
let self.visual_hi_id = s:highlight_region(a:region)
endfunction
" Remove the visual selection and its highlight
function! s:Cursor.remove_visual_selection() dict
let self.visual = []
" TODO(terryma): Move functionality into separate class
call s:cm.remove_highlight(self.visual_hi_id)
let self.visual_hi_id = 0
endfunction
"===============================================================================
" CursorManager class
"===============================================================================
let s:CursorManager = {}
" Constructor
function! s:CursorManager.new()
let obj = copy(self)
let obj.cursors = []
let obj.current_index = -1
let obj.starting_index = -1
let obj.saved_settings = {
\ 'virtualedit': &virtualedit,
\ 'cursorline': &cursorline,
\ }
return obj
endfunction
" Clear all cursors and their highlights
function! s:CursorManager.reset() dict
call clearmatches()
let self.cursors = []
let self.current_index = -1
let self.starting_index = -1
call self.restore_user_settings()
" FIXME(terryma): Doesn't belong here
let s:from_mode = ''
let s:to_mode = ''
endfunction
" Returns 0 if it's not managing any cursors at the moment
function! s:CursorManager.is_empty() dict
return self.size() == 0
endfunction
" Returns the number of cursors it's managing
function! s:CursorManager.size() dict
return len(self.cursors)
endfunction
" Returns the current cursor
function! s:CursorManager.get_current() dict
return self.cursors[self.current_index]
endfunction
" Returns the cursor at index i
function! s:CursorManager.get(i) dict
return self.cursors[a:i]
endfunction
" Removes the current cursor and all its associated highlighting. Also update
" the current index
function! s:CursorManager.delete_current() dict
call self.remove_highlight(self.get_current().cursor_hi_id)
call self.remove_highlight(self.get_current().visual_hi_id)
call remove(self.cursors, self.current_index)
let self.current_index -= 1
endfunction
" Remove the highlighting if it matchid exists
function! s:CursorManager.remove_highlight(hi_id) dict
if a:hi_id
call matchdelete(a:hi_id)
endif
endfunction
function! s:CursorManager.debug() dict
let i = 0
for c in self.cursors
echom 'cursor #'.i.': '.string(c)
let i+=1
endfor
echom 'last key = '.s:char
echom 'current cursor = '.self.current_index
echom 'current pos = '.string(getpos('.'))
echom 'last visual begin = '.string(getpos("'<"))
echom 'last visual end = '.string(getpos("'>"))
echom 'current mode = '.mode()
echom 'current mode custom = '.s:to_mode
echom 'prev mode custom = '.s:from_mode
echom ' '
endfunction
" Sync the current cursor to the current Vim cursor. This includes updating its
" location, its highlight, and potentially its visual region. Return true if the
" position changed, false otherwise
function! s:CursorManager.update_current() dict
let cur = self.get_current()
if s:to_mode ==# 'v'
call cur.update_visual_selection(s:get_current_visual_selection())
call s:exit_visual_mode()
else
call cur.remove_visual_selection()
endif
let vdelta = line('$') - s:saved_linecount
" If the cursor changed line, and the total number of lines changed
if vdelta != 0 && cur.line() != line('.')
if self.current_index != self.size() - 1
let cur_line_length = len(getline(cur.line()))
let new_line_length = len(getline('.'))
for i in range(self.current_index+1, self.size()-1)
let hdelta = 0
" If there're other cursors on the same line, we need to adjust their
" columns. This needs to happen before we adjust their line!
if cur.line() == self.get(i).line()
if vdelta > 0
" Added a line
let hdelta = cur_line_length * -1
else
" Removed a line
let hdelta = new_line_length
endif
endif
call self.get(i).move(vdelta, hdelta)
endfor
endif
else
" If the line length changes, for all the other cursors on the same line as
" the current one, update their cursor location as well
let hdelta = col('$') - cur.line_length
" Only do this if we're still on the same line as before
if hdelta != 0 && cur.line() == line('.')
" Update all the cursor's positions that occur after the current cursor on
" the same line
if self.current_index != self.size() - 1
for i in range(self.current_index+1, self.size()-1)
" Only do it for cursors on the same line
if cur.line() == self.get(i).line()
call self.get(i).move(0, hdelta)
else
" Early exit, if we're not on the same line, neither will any cursor
" that come after this
break
endif
endfor
endif
endif
endif
let pos = getpos('.')
if cur.position == pos
return 0
endif
call cur.update_position(pos)
return 1
endfunction
" Advance to the next cursor
function! s:CursorManager.next() dict
let self.current_index = (self.current_index + 1) % self.size()
endfunction
" Start tracking cursor updates
function! s:CursorManager.start_loop() dict
let self.starting_index = self.current_index
endfunction
" Returns true if we're cycled through all the cursors
function! s:CursorManager.loop_done() dict
return self.current_index == self.starting_index
endfunction
" Tweak some user settings. This is called every time multicursor mode is
" entered.
" virtualedit needs to be set to onemore for updates to work correctly
" cursorline needs to be turned off for the cursor highlight to work on the line
" where the real vim cursor is
function! s:CursorManager.initialize() dict
let &virtualedit = "onemore"
let &cursorline = 0
endfunction
" Restore user settings.
function! s:CursorManager.restore_user_settings() dict
if !empty(self.saved_settings)
let &virtualedit = self.saved_settings['virtualedit']
let &cursorline = self.saved_settings['cursorline']
endif
endfunction
" Reselect the current cursor's region in visual mode
function! s:CursorManager.reapply_visual_selection() dict
call s:select_in_visual_mode(self.get_current().visual)
endfunction
" Creates a new multicursor at the current Vim cursor location. Return true if
" the cursor has been successfully added, false otherwise
function! s:CursorManager.add() dict
" Lazy init
if self.is_empty()
call self.initialize()
endif
let pos = getpos('.')
" Don't add duplicates
let i = 0
for c in self.cursors
if c.position == pos
return 0
endif
let i+=1
endfor
let cursor = s:Cursor.new(pos)
" Save the visual selection
if mode() ==# 'v'
call cursor.update_visual_selection(s:get_current_visual_selection())
endif
call add(self.cursors, cursor)
let self.current_index += 1
return 1
endfunction
"===============================================================================
" Variables
"===============================================================================
" This is the last user input that we're going to replicate, in its string form
let s:char = ''
" This is the mode the user is in before s:char
let s:from_mode=''
" This is the mode the user is in after s:char
let s:to_mode=''
" This is the total number of lines in the buffer before processing s:char
let s:saved_linecount=-1
" These keys will not be replcated at every cursor location
let s:special_keys = [
\ g:multi_cursor_next_key,
\ g:multi_cursor_prev_key,
\ g:multi_cursor_skip_key,
\ ]
" The highlight group we use for all the cursors
let s:hi_group_cursor = 'multiple_cursors_cursor'
" The highlight group we use for all the visual selection
let s:hi_group_visual = 'multiple_cursors_visual'
" Singleton cursor manager instance
let s:cm = s:CursorManager.new()
"===============================================================================
" Initialization
"===============================================================================
if !hlexists(s:hi_group_cursor)
exec "highlight ".s:hi_group_cursor." term=reverse cterm=reverse gui=reverse"
endif
if !hlexists(s:hi_group_visual)
exec "highlight link ".s:hi_group_visual." Visual"
endif
"===============================================================================
" Utility functions
"===============================================================================
" Exit visual mode and go back to normal mode
" Precondition: In visual mode
" Postcondition: In normal mode, cursor in the same location as visual mode
" The reason for the additional gv\<Esc> is that it allows the cursor to stay
" on where it was before exiting
function! s:exit_visual_mode()
exec "normal! \<Esc>gv\<Esc>"
endfunction
" Visually select input region, where region is an array containing the start
" and end position. If start is after end, the selection simply goes backwards.
" Typically m<, m>, and gv would be a simple way of accomplishing this, but on
" some systems, the m< and m> marks are not supported. Note that v`` has random
" behavior if `` is the same location as the cursor location.
" Precondition: In normal mode
" Postcondition: In visual mode, with the region selected
function! s:select_in_visual_mode(region)
call setpos('.', a:region[0])
call setpos("'`", a:region[1])
if getpos('.') == getpos("'`")
normal! v
else
normal! v``
endif
endfunction
" Highlight the position using the cursor highlight group
function! s:highlight_cursor(pos)
" Give cursor highlight high priority, to overrule visual selection
return matchadd(s:hi_group_cursor, '\%'.a:pos[1].'l\%'.a:pos[2].'v', 99999)
endfunction
" Compare two position arrays. Each input is the result of getpos(). Return a
" negative value if lhs occurs before rhs, positive value if after, and 0 if
" they are the same.
function! s:compare_pos(l, r)
" If number lines are the same, compare columns
return a:l[1] ==# a:r[1] ? a:l[2] - a:r[2] : a:l[1] - a:r[1]
endfunction
" Highlight the area bounded by the input region. The logic here really stinks,
" it's frustrating that Vim doesn't have a built in easier way to do this. None
" of the \%V or \%'m solutions work because we need the highlighting to stay for
" multiple places.
function! s:highlight_region(region)
let s = sort(copy(a:region), "s:compare_pos")
if (s[0][1] == s[1][1])
" Same line
let pattern = '\%'.s[0][1].'l\%>'.(s[0][2]-1).'v.*\%<'.(s[1][2]+1).'v.'
else
" Two lines
let s1 = '\%'.s[0][1].'l.\%>'.s[0][2].'v.*'
let s2 = '\%'.s[1][1].'l.*\%<'.s[1][2].'v..'
let pattern = s1.'\|'.s2
if (s[1][1] - s[0][1] > 1)
let pattern = pattern.'\|\%>'.s[0][1].'l\%<'.s[1][1].'l'
endif
endif
return matchadd(s:hi_group_visual, pattern)
endfunction
" Perform the operation that's necessary to revert us from one mode to another
function! s:revert_mode(from, to)
if a:to ==# 'v'
call s:cm.reapply_visual_selection()
endif
if a:to ==# 'i'
startinsert
endif
if a:to ==# 'n' && a:from ==# 'i'
stopinsert
endif
if a:to ==# 'n' && a:from ==# 'v'
" TODO(terryma): Hmm this would cause visual to normal mode to break.
" Strange
" call s:exit_visual_mode()
endif
endfunction
" Consume all the additional character the user typed between the last
" getchar() and here, to avoid potential race condition.
" TODO(terryma): This solves the problem of cursors getting out of sync, but
" we're potentially losing user input. We COULD replay these characters as
" well...
function! s:feedkeys(keys)
while 1
let c = getchar(0)
" Checking type is important, when strings are compared with integers,
" strings are always converted to ints, and all strings are equal to 0
if type(c) == 0 && c == 0
break
endif
endwhile
call feedkeys(a:keys)
endfunction
" Take the user input and apply it at every cursor
function! s:process_user_inut(mode)
" Grr this is frustrating. In Insert mode, between the feedkey call and here,
" the current position could actually CHANGE for some odd reason. Forcing a
" position reset here
call setpos('.', s:cm.get_current().position)
" Before applying the user input, we need to revert back to the mode the user
" was in when the input was entered
call s:revert_mode(s:to_mode, s:from_mode)
" Update the line length BEFORE applying any actions. TODO(terryma): Is there
" a better place to do this?
call s:cm.get_current().update_line_length()
let s:saved_linecount = line('$')
" Apply the user input. Note that the above could potentially change mode, we
" use the mapping below to help us determine what the new mode is
call s:feedkeys(s:char."\<Plug>(multi_cursor_apply_user_input_next)")
endfunction
" Apply the user input at the next cursor location
function! s:apply_user_input_next(mode)
" Save the current mode
let s:to_mode = a:mode
" Update the current cursor's information
let changed = s:cm.update_current()
" Advance the cursor index
call s:cm.next()
" Update Vim's cursor
call setpos('.', s:cm.get_current().position)
" We're done if we're made the full round
if s:cm.loop_done()
" If we stay in visual mode, we need to reselect the original cursor
if s:to_mode ==# 'v'
call s:cm.reapply_visual_selection()
endif
call s:wait_for_user_input(s:to_mode)
else
" Continue to next
call s:process_user_inut(s:from_mode)
endif
endfunction
" Precondition: In visual mode with selected text
" Postcondition: A new cursor is placed at the end of the selected text
function! s:create_cursor_from_visual_selection()
" Get the text for the current visual selection
let selection = s:get_visual_selection()
" Go to the end of the visual selection
call cursor(line("'<"), col("'>"))
" Add the current at the new location
call s:cm.add()
endfunction
" Precondition: In visual mode
" Postcondition: Remain in visual mode
" Return array of start and end position of visual selection
" This should be available from the '< and '> registers, but it fails to work
" correctly on some systems until visual mode quits. So we force quitting in
" visual mode and reselecting the region afterwards
function! s:get_current_visual_selection()
call s:exit_visual_mode()
let left = getpos("'<")
let right = getpos("'>")
if getpos('.') == left
let region = [right, left]
else
let region = [left, right]
endif
call s:select_in_visual_mode(region)
return region
endfunction
" Return the content of the current visual selection. This is used to find the
" next match in the buffer
function! s:get_visual_selection()
normal! gv
let start_pos = getpos("'<")
let end_pos = getpos("'>")
let [lnum1, col1] = start_pos[1:2]
let [lnum2, col2] = end_pos[1:2]
let lines = getline(lnum1, lnum2)
let lines[-1] = lines[-1][: col2 - 1]
let lines[0] = lines[0][col1 - 1:]
return join(lines, "\n")
endfunction
" Visually select the next occurrence of the input text in the buffer
function! s:select_next_occurrence(text)
call s:exit_visual_mode()
let pattern = '\V\C'.substitute(escape(a:text, '\'), '\n', '\\n', 'g')
call search(pattern)
let start = getpos('.')
call search(pattern, 'ce')
let end = getpos('.')
call s:select_in_visual_mode([start, end])
endfunction
" Wrapper around getchar() that returns the string representation of the user
" input
function! s:get_char()
let c = getchar()
" If the character is a number, then it's not a special key
if type(c) == 0
let c = nr2char(c)
endif
return c
endfunction
" Quits multicursor mode and clears all cursors. Return true if exited
" successfully.
function! s:exit()
if s:char ==# g:multi_cursor_quit_key &&
\ (s:from_mode ==# 'n' ||
\ s:from_mode ==# 'v' && g:multi_cursor_exit_from_visual_mode ||
\ s:from_mode ==# 'i' && g:multi_cursor_exit_from_insert_mode)
if s:from_mode ==# 'i'
stopinsert
elseif s:from_mode ==# 'v'
call s:exit_visual_mode()
endif
call s:cm.reset()
return 1
endif
return 0
endfunction
" Take users input and figure out what to do with it
function! s:wait_for_user_input(mode)
let s:from_mode = a:mode
let s:to_mode = ''
redraw
let s:char = s:get_char()
redraw
if s:exit()
return
endif
let feedkeys = ''
if index(s:special_keys, s:char) != -1
let feedkeys = s:char
else
call s:cm.start_loop()
let feedkeys = "\<Plug>(multi_cursor_process_user_input)"
endif
call s:feedkeys(feedkeys)
endfunction

View file

@ -0,0 +1,174 @@
*vim-multiple-cursors.txt* True Sublime Text multiple selection in Vim
____ _ __
____ ___ __ __/ / /_(_)___ / /__ _______ ________________ __________
/ __ `__ \/ / / / / __/ / __ \/ / _ \ / ___/ / / / ___/ ___/ __ \/ ___/ ___/
/ / / / / / /_/ / / /_/ / /_/ / / __/ / /__/ /_/ / / (__ ) /_/ / / (__ )
/_/ /_/ /_/\__,_/_/\__/_/ .___/_/\___/ \___/\__,_/_/ /____/\____/_/ /____/
/_/
Reference Manual~
==============================================================================
CONTENTS *multiple-cursors-contents*
1.Intro...................................|multiple-cursors-intro|
2.Usage...................................|multiple-cursors-usage|
3.Mappings................................|multiple-cursors-mappings|
4.Global Options..........................|multiple-cursors-global-options|
5.Issues..................................|multiple-cursors-issues|
6.Contributing............................|multiple-cursors-contributing|
7.License.................................|multiple-cursors-license|
8.Credit..................................|multiple-cursors-credit|
9.References..............................|multiple-cursors-references|
==============================================================================
1. Intro *multiple-cursors-intro*
There [1] have [2] been [3] many [4] attempts [5] at bringing Sublime Text's
awesome multiple selection [6] feature into Vim, but none so far have been in
my opinion a faithful port that is simplistic to use, yet powerful and
intuitive enough for an existing Vim user. *vim-multiple-cursors* is yet
another attempt at that.
==============================================================================
2. Usage *multiple-cursors-usage*
Out of the box, all you need to know is a single key CTRL-N. Pressing the key
in Normal mode highlights the current word under the cursor in Visual mode and
places a virtual cursor at the end of it. Pressing it again finds the next
ocurrence and places another virtual cursor at the end of the visual
selection. If you select multiple lines in Visual mode, pressing the key puts
a virtual cursor at every line and leaves you in Normal mode.
After you've marked all your locations with CTRL-N, you can change the visual
selection with normal Vim motion commands in Visual mode. You could go to
Normal mode by pressing v and wield your motion commands there. Single key
command to switch to Insert mode such as `c` or `s` from Visual mode or `i`,
`a`, `I`, `A` in Normal mode should work without any issues.
At any time, you can press <Esc> to exit back to regular Vim.
Two additional keys are also mapped:
CTRL-P in Visual mode will remove the current virtual cursor and go back to
the previous virtual cursor location. This is useful if you are trigger happy
with Ctrl-n and accidentally went too far.
CTRL-X in Visual mode will remove the current virtual cursor and skip to the
next virtual cursor location. This is useful if you don't want the current
selection to be a candidate to operate on later.
**NOTE**: The plugin is still somewhat buggy, if at any time you have
lingering cursors on screen, you can press CTRL-N in Normal mode and it will
remove all prior cursors before starting a new one.
==============================================================================
3. Mappings *multiple-cursors-mappings*
*g:multi_cursor_use_default_mapping* (Default: 1)
Out of the box, CTRL-N, CTRL-P, and CTRL-X are mapped by default. If you don't
like the plugin taking over your favorite key bindings, then turn off the
default with >
let g:multi_cursor_use_default_mapping=0
<
*g:multi_cursor_next_key* (Default: "\<C-n>")
*g:multi_cursor_prev_key* (Default: "\<C-p>")
*g:multi_cursor_skip_key* (Default: "\<C-x>")
*g:multi_cursor_exit_key* (Default: "\<Esc>")
You can map the 'next', 'previous', 'skip', and 'exit' keys like the
following: >
" Default mapping
let g:multi_cursor_next_key="\<C-n>"
let g:multi_cursor_prev_key="\<C-p>"
let g:multi_cursor_skip_key="\<C-x>"
let g:multi_cursor_exit_key="\<Esc>"
<
==============================================================================
4. Global Options *multiple-cursors-global-options*
Currently there're two additional global settings one can tweak:
*g:multi_cursor_exit_from_visual_mode* (Defaut: 1)
If set to 0, then pressing |g:multi_cursor_exit_key| in Visual mode will not
quit and delete all existing cursors. This is useful if you want to press
Escape and go back to Normal mode, and still be able to operate on all the
cursors.
*g:multi_cursor_exit_from_insert_mode* (Default: 1)
If set to 0, then pressing |g:multi_cursor_exit_key| in Insert mode will not
quit and delete all existing cursors. This is useful if you want to press
Escape and go back to Normal mode, and still be able to operate on all the
cursors.
The plugin uses the highlight group `multiple_cursors_cursor` and
`multiple_cursors_visual` to highlight the virtual cursors and their visual
selections respectively. You can customize them by putting something similar
like the following in your vimrc: >
" Default highlighting (see help :highlight and help :highlight-link)
highlight multiple_cursors_cursor term=reverse cterm=reverse gui=reverse
highlight link multiple_cursors_visual Visual
<
==============================================================================
5. Issues *multiple-cursors-issues*
- Multi key commands like ciw do not work at the moment
- All user input typed before Vim is able to fan out the last operation to all
cursors is lost. This is a implementation decision to keep the input
perfectly synced in all locations, at the cost of potentially losing user
input.
- Single key commands that do not terminate properly cause unexpected
behavior. For example, if the cursor is on the first character in the buffer
and 'b' is pressed.
- Undo behavior is unpredictable
- Performance in terminal vim degrades significantly with more cursors
- Select mode is not implemented
- Buggy when wrap is turned on
- Cursor highlighting is off. The last column on the same row as Vim's cursor
is not highlighted incorrectly. Setting virtualedit=all might help
==============================================================================
6. Contributing *multiple-cursors-contributing*
The project is hosted on Github. Patches, feature requests and suggestions are
always welcome!
Find the latest version of the plugin here:
http://github.com/terryma/vim-multiple-cursors
==============================================================================
7. License *multiple-cursors-license*
The project is licensed under the MIT license [7]. Copyrigth 2013 Terry Ma
==============================================================================
8. Credit *multiple-cursors-credit*
The plugin is obviously inspired by Sublime Text's awesome multiple selection
[6] feature. Some inspiration was also taken from Emac's multiple cursors [8]
implementation.
==============================================================================
9. References *multiple-cursors-references*
[1] https://github.com/paradigm/vim-multicursor
[2] https://github.com/felixr/vim-multiedit
[3] https://github.com/hlissner/vim-multiedit
[4] https://github.com/adinapoli/vim-markmultiple
[5] https://github.com/AndrewRadev/multichange.vim
[6] http://www.sublimetext.com/docs/2/multiple_selection_with_the_keyboard.html
[7] http://opensource.org/licenses/MIT
[8] https://github.com/magnars/multiple-cursors.el
vim:tw=78:sw=4:ft=help:norl:

View file

@ -0,0 +1,19 @@
g:multi_cursor_exit_from_insert_mode multiple_cursors.txt /*g:multi_cursor_exit_from_insert_mode*
g:multi_cursor_exit_from_visual_mode multiple_cursors.txt /*g:multi_cursor_exit_from_visual_mode*
g:multi_cursor_exit_key multiple_cursors.txt /*g:multi_cursor_exit_key*
g:multi_cursor_next_key multiple_cursors.txt /*g:multi_cursor_next_key*
g:multi_cursor_prev_key multiple_cursors.txt /*g:multi_cursor_prev_key*
g:multi_cursor_skip_key multiple_cursors.txt /*g:multi_cursor_skip_key*
g:multi_cursor_use_default_mapping multiple_cursors.txt /*g:multi_cursor_use_default_mapping*
multiple-cursors-contents multiple_cursors.txt /*multiple-cursors-contents*
multiple-cursors-contributing multiple_cursors.txt /*multiple-cursors-contributing*
multiple-cursors-credit multiple_cursors.txt /*multiple-cursors-credit*
multiple-cursors-global-options multiple_cursors.txt /*multiple-cursors-global-options*
multiple-cursors-intro multiple_cursors.txt /*multiple-cursors-intro*
multiple-cursors-issues multiple_cursors.txt /*multiple-cursors-issues*
multiple-cursors-license multiple_cursors.txt /*multiple-cursors-license*
multiple-cursors-mappings multiple_cursors.txt /*multiple-cursors-mappings*
multiple-cursors-references multiple_cursors.txt /*multiple-cursors-references*
multiple-cursors-usage multiple_cursors.txt /*multiple-cursors-usage*
vim-multiple-cursors multiple_cursors.txt /*vim-multiple-cursors*
vim-multiple-cursors.txt multiple_cursors.txt /*vim-multiple-cursors.txt*

View file

@ -0,0 +1,99 @@
"===============================================================================
" File: multiple_cursors.vim
" Author: Terry Ma
" Description: Emulate Sublime Text's multi selection feature
" Issues:
" - Performance in terminal vim degrades significantly with more cursors
" - All user input typed before Vim is able to fan out the last operation to all
" cursors is lost. This is a implementation decision to keep the input
" perfectly synced in all locations, at the cost of potentially losing user
" input.
" - Multi key commands is not supported
" - Single key commands that do not terminate properly cause unexpected
" behavior. For example, if the cursor is on the first character in the buffer
" and 'b' is pressed.
" - Undo behavior is unpredictable
" - Select mode is not implemented
" - There is a bug with selection and highlight when wrap is on
"
" Potential Features:
" - Create a blinking cursor effect? Good place to do it would be instead of
" waiting for user input, cycle through the highlight
" - Integrate with the status line? Maybe show a special multicursor mode?
" - Support mouse? Ctrl/Cmd click to set cursor?
"
" Features:
" - Real time update of cursor locations
" - In normal mode, pressing <C-n> will highlight the current word under cursor,
" and places a 'multicursor' at the end of the word, and goes to visual mode
" - In visual mode, right after the above operation, pressing <C-n> again will
" search for the word forward, and places a new cursor at the end of the
" resulting search, one can continue to do this in Visual mode, this resembles
" the Cmd-D feature of Sublime
" - In insert mode, insert operations are captures and replayed at all the
" cursor locations
" - Pressing <Esc> in Normal mode quits multicursor mode and clears all cursors
" - Normal mode single keystroke commands work:
" - Works: 'w,e,i,p,a,h,j,k,l,x,v,b'
" - Does not work: ''
" - Replace mode just seems to work
" - Visual mode
" - Works: 'w,e,b,h,j,k,l,o'
" - Does not work: 'A, I', because <C-o> does not get it out of normal mode
" for these commands. It takes two
"===============================================================================
let s:save_cpo = &cpo
set cpo&vim
function! s:init_settings(settings)
for [key, value] in items(a:settings)
let sub = ''
if type(value) == 0
let sub = '%d'
elseif type(value) == 1
let sub = '"%s"'
endif
let fmt = printf("let g:multi_cursor_%%s=get(g:, 'multi_cursor_%%s', %s)",
\ sub)
exec printf(fmt, key, key, value)
endfor
endfunction
" Settings
let s:settings = {
\ 'exit_from_visual_mode': 1,
\ 'exit_from_insert_mode': 1,
\ 'use_default_mapping': 1,
\ }
let s:settings_if_default = {
\ 'quit_key': "\<Esc>",
\ 'next_key': "\<C-n>",
\ 'prev_key': "\<C-p>",
\ 'skip_key': "\<C-x>",
\ }
call s:init_settings(s:settings)
if g:multi_cursor_use_default_mapping
call s:init_settings(s:settings_if_default)
endif
" External mappings
if exists('g:multi_cursor_next_key')
exec 'nnoremap <silent> '.g:multi_cursor_next_key.
\' :call multiple_cursors#new("n")<CR>'
exec 'xnoremap <silent> '.g:multi_cursor_next_key.
\' :<C-u>call multiple_cursors#new("v")<CR>'
endif
if exists('g:multi_cursor_prev_key')
exec 'xnoremap <silent> '.g:multi_cursor_prev_key.
\' :<C-u>call multiple_cursors#prev()<CR>'
endif
if exists('g:multi_cursor_skip_key')
exec 'xnoremap <silent> '.g:multi_cursor_skip_key.
\' :<C-u>call multiple_cursors#skip()<CR>'
endif
let &cpo = s:save_cpo
unlet s:save_cpo

View file

@ -29,6 +29,7 @@ vim-pyte https://github.com/therubymug/vim-pyte
vim-snipmate https://github.com/garbas/vim-snipmate
vim-snippets https://github.com/honza/vim-snippets
vim-surround https://github.com/tpope/vim-surround
vim-expand-region https://github.com/terryma/vim-expand-region
""".strip()
GITHUB_ZIP = '%s/archive/master.zip'

View file

@ -87,6 +87,12 @@ map <leader>nb :NERDTreeFromBookmark
map <leader>nf :NERDTreeFind<cr>
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => vim-multiple-cursors
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:multi_cursor_next_key="\<C-a>"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => surround.vim config
" Annotate strings with gettext http://amix.dk/blog/post/19678