1
0
mirror of https://github.com/amix/vimrc synced 2025-06-23 06:35:01 +08:00

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

@ -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