Permalink
Browse files

Merge pull request #3 from eapache/multi-char-maps

Replay multi-character maps
  • Loading branch information...
kristijanhusak committed Aug 8, 2014
2 parents 0ed03a5 + e03f046 commit d3ecc03a6a29f9899adba3219b2717dda5100986
Showing with 94 additions and 25 deletions.
  1. +24 −6 README.md
  2. +67 −19 autoload/multiple_cursors.vim
  3. +3 −0 plugin/multiple_cursors.vim
@@ -20,7 +20,9 @@ To see what keystrokes are used for the above example, see [this issue](https://
## 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
- Works in Normal, Insert, and Visual mode for any commands (including
multi-key commands, assuming you set `g:multicursor_insert_maps` and
`g:multicursor_normal_maps`; see Settings below for details)

## Installation
Install using [Pathogen], [Vundle], [Neobundle], or your favorite Vim package manager.
@@ -61,21 +63,39 @@ By default, the 'next' key is also used to enter multicursor mode. If you want t
let g:multi_cursor_start_key='<F6>'
```

**IMPORTANT:** Please note that currently only single keystrokes and special keys can be mapped. This contraint is also the reason why multikey commands such as `ciw` do not work and cause unexpected behavior in Normal mode. This means that a mapping like `<Leader>n` will NOT work correctly. For a list of special keys that are supported, see `help :key-notation`
**IMPORTANT:** Please note that currently only single keystrokes and special keys can be mapped. This means that a mapping like `<Leader>n` will NOT work correctly. For a list of special keys that are supported, see `help :key-notation`

**NOTE:** Please make sure to always map something to `g:multi_cursor_quit_key`, otherwise you'll have a tough time quitting from multicursor mode.

**NOTE:** Prior to version 1.3, the recommended way to map the keys is using the expressoin quote syntax in Vim, using something like `"\<C-n>"` or `"\<Esc>"` (see h: expr-quote). After 1.3, the recommended way is to use a raw string like above. If your key mappings don't appear to work, give the new syntax a try.
**NOTE:** Prior to version 1.3, the recommended way to map the keys is using the expression quote syntax in Vim, using something like `"\<C-n>"` or `"\<Esc>"` (see h: expr-quote). After 1.3, the recommended way is to use a raw string like above. If your key mappings don't appear to work, give the new syntax a try.

## Setting
Currently there're two additional global settings one can tweak:
Currently there're three additional global settings one can tweak:
### ```g:multi_cursor_exit_from_visual_mode``` (Default: 1)

If set to 0, then pressing `g:multi_cursor_quit_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_quit_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.

### ```g:multi_cursor_insert_maps``` (Default: `{}`)
Any key in this map (values are ignored) will cause multi-cursor _Insert_ mode
to pause for `timeoutlen` waiting for map completion just like normal vim.
Otherwise keys mapped in insert mode are ignored when multiple cursors are
active. For example, setting it to `{'\':1}` will make insert-mode mappings
beginning with the default leader key work in multi-cursor mode. You have to
manually set this because vim doesn't provide a way to see which keys *start*
mappings.

### ```g:multi_cursor_normal_maps``` (Default: `{}`)
Any key in this map (values are ignored) will cause multi-cursor _Normal_ mode
to pause for map completion just like normal vim. Otherwise keys mapped in
normal mode will "fail to replay" when multiple cursors are active. For example,
setting it to `{'d':1}` will make normal-mode mappings beginning with `d` (such
as `dw` to delete a word) work in multi-cursor mode. You have to
manually set this because vim doesn't provide a way to see which keys *start*
mappings; setting it to include motion commands like `j` can break things.

### 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:

@@ -86,8 +106,6 @@ 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.
- Select mode is not implemented

## Changelog
@@ -444,6 +444,8 @@ function! s:CursorManager.update_current() dict
call cur.update_visual_selection(s:get_visual_region(s:pos('.')))
elseif s:from_mode ==# 'v' || s:from_mode ==# 'V'
call cur.remove_visual_selection()
elseif s:from_mode ==# 'i' && s:to_mode ==# 'n' && self.current_index == self.size() - 1
normal! `^
endif
let vdelta = line('$') - s:saved_linecount
" If the total number of lines changed in the buffer, we need to potentially
@@ -738,28 +740,31 @@ endfunction
function! s:revert_mode(from, to)
if a:to ==# 'v'
call s:cm.reapply_visual_selection()
endif
if a:to ==# 'V'
elseif a:to ==# 'V'
call s:cm.reapply_visual_selection()
normal! V
endif
if a:to ==# 'n' && a:from ==# 'i'
elseif a:to ==# 'n' && a:from ==# 'i'
stopinsert
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...
let s:saved_keys = ""
function! s:feedkeys(keys)
while 1
let c = getchar(0)
let char_type = type(c)
" 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
if char_type == 0
if c == 0
break
else
let s:saved_keys .= nr2char(c)
endif
elseif char_type == 1 " char with more than 8 bits (as string)
let s:saved_keys .= c
endif
endwhile
call feedkeys(a:keys)
@@ -798,7 +803,7 @@ function! s:process_user_input()
else
call s:feedkeys(s:char."\<Plug>(a)")
endif

" Even when s:char produces invalid input, this method is always called. The
" 't' here is important
call feedkeys("\<Plug>(d)", 't')
@@ -886,7 +891,7 @@ endfunction
" Quits multicursor mode and clears all cursors. Return true if exited
" successfully.
function! s:exit()
if s:char !=# g:multi_cursor_quit_key
if s:last_char() !=# g:multi_cursor_quit_key
return 0
endif
let exit = 0
@@ -954,12 +959,20 @@ function! s:revert_highlight_fix()
let s:saved_line = 0
endfunction

let s:retry_keys = ""
function! s:display_error()
if s:bad_input > 0
echohl ErrorMsg |
\ echo "Key '".s:char."' cannot be replayed at ".
\ s:bad_input." cursor location".(s:bad_input == 1 ? '' : 's') |
\ echohl Normal
if s:bad_input == s:cm.size() && has_key(g:multi_cursor_normal_maps, s:char[0])
" we couldn't replay it anywhere but we're told it's the beginning of a
" multi-character map like the `d` in `dw`
let s:retry_keys = s:char
else
let s:retry_keys = ""
if s:bad_input > 0
echohl ErrorMsg |
\ echo "Key '".s:char."' cannot be replayed at ".
\ s:bad_input." cursor location".(s:bad_input == 1 ? '' : 's') |
\ echohl Normal
endif
endif
let s:bad_input = 0
endfunction
@@ -995,6 +1008,10 @@ function! s:end_latency_measure()
let s:skip_latency_measure = 0
endfunction

function! s:last_char()
return s:char[len(s:char)-1]
endfunction

function! s:wait_for_user_input(mode)
let s:from_mode = a:mode
if empty(a:mode)
@@ -1014,7 +1031,38 @@ function! s:wait_for_user_input(mode)

call s:end_latency_measure()

let s:char = s:get_char()
let s:char = s:retry_keys . s:saved_keys
if len(s:saved_keys) == 0
let s:char .= s:get_char()
else
let s:saved_keys = ""
endif

if s:from_mode ==# 'i' && has_key(g:multi_cursor_insert_maps, s:last_char())
let c = getchar(0)
let char_type = type(c)
let poll_count = 0
while char_type == 0 && c == 0 && poll_count < &timeoutlen
sleep 1m
let c = getchar(0)
let char_type = type(c)
let poll_count += 1
endwhile

if char_type == 0 && c != 0
let s:char .= nr2char(c)
elseif char_type == 1 " char with more than 8 bits (as string)
let s:char .= c
endif
elseif s:from_mode !=# 'i' && s:char[0] ==# ":"
call feedkeys(s:char)
call s:cm.reset(1, 1)
return
elseif s:from_mode ==# 'n'
while match(s:last_char(), "\\d") == 0
let s:char .= s:get_char()
endwhile
endif

call s:start_latency_measure()

@@ -1026,8 +1074,8 @@ function! s:wait_for_user_input(mode)
endif

" If the key is a special key and we're in the right mode, handle it
if index(get(s:special_keys, s:from_mode, []), s:char) != -1
call s:handle_special_key(s:char, s:from_mode)
if index(get(s:special_keys, s:from_mode, []), s:last_char()) != -1
call s:handle_special_key(s:last_char(), s:from_mode)
call s:skip_latency_measure()
else
call s:cm.start_loop()
@@ -33,6 +33,9 @@ let s:settings = {
\ 'debug_latency': 0,
\ }

let g:multi_cursor_insert_maps = get(g:, 'multi_cursor_insert_maps', {})
let g:multi_cursor_normal_maps = get(g:, 'multi_cursor_normal_maps', {})

let s:settings_if_default = {
\ 'quit_key': '<Esc>',
\ 'next_key': '<C-n>',

0 comments on commit d3ecc03

Please sign in to comment.