├── .github ├── issue_template.md └── workflows │ └── luarocks.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── autoload ├── vm.vim └── vm │ ├── commands.vim │ ├── comp.vim │ ├── cursors.vim │ ├── ecmds1.vim │ ├── ecmds2.vim │ ├── edit.vim │ ├── funcs.vim │ ├── global.vim │ ├── icmds.vim │ ├── insert.vim │ ├── maps.vim │ ├── maps │ └── all.vim │ ├── operators.vim │ ├── plugs.vim │ ├── region.vim │ ├── search.vim │ ├── special │ ├── case.vim │ └── commands.vim │ ├── themes.vim │ ├── variables.vim │ └── visual.vim ├── doc ├── visual-multi.txt ├── vm-ex-commands.txt ├── vm-faq.txt ├── vm-mappings.txt ├── vm-settings.txt ├── vm-troubleshooting.txt └── vm-tutorial ├── plugin └── visual-multi.vim ├── python └── vm.py ├── run_tests ├── test ├── README.md ├── default │ └── vimrc.vim ├── requirements.txt ├── test.py └── tests │ ├── abbrev │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── alignment │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── backspace │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── change │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── change2 │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── cquote │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── curs2 │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── curs_del │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── dot │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── example │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── example2 │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── getcc │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── oO │ ├── commands.py │ ├── expected_output_file.txt │ ├── input_file.txt │ └── vimrc.vim │ ├── pasteatcur │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── regex │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── repl │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ ├── trans │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt │ └── vmsearch │ ├── commands.py │ ├── expected_output_file.txt │ └── input_file.txt └── tutorialrc /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 9 | **Describe the issue:** 10 | 11 | 12 | 13 | **Steps to reproduce** 14 | 15 | 1. 16 | 2. 17 | 3. 18 | 19 | 33 | 34 | ----- 35 | 36 | - **Operating System**: 37 | - **Vim Version**: 38 | - **commit SHA/branch**: 40 | -------------------------------------------------------------------------------- /.github/workflows/luarocks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Push to Luarocks 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | release: 9 | types: 10 | - created 11 | tags: 12 | - '*' 13 | workflow_dispatch: 14 | 15 | jobs: 16 | luarocks-upload: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 # Required to count the commits 22 | - name: Get Version 23 | run: echo "LUAROCKS_VERSION=$(git describe --abbrev=0 --tags)" >> $GITHUB_ENV 24 | - name: LuaRocks Upload 25 | uses: nvim-neorocks/luarocks-tag-release@v5 26 | env: 27 | LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} 28 | with: 29 | version: ${{ env.LUAROCKS_VERSION }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *vim-bookmarks 2 | .TODO 3 | tags 4 | doc/tags 5 | *.sw* 6 | generated_output_file.txt 7 | test.log 8 | __pycache__ 9 | test/.python-version 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: "3.6" 3 | #------------------------------ 4 | # install vim from source 5 | #------------------------------ 6 | before_install: 7 | - curl https://raw.githubusercontent.com/kana/vim-version-manager/master/bin/vvm | python2 - setup; true 8 | - source ~/.vvm/etc/login 9 | - vvm update_itself 10 | - vvm use vimorg--v8.0.1529 --install --with-features=huge 11 | #------------------------------ 12 | install: pip install -r test/requirements.txt 13 | before_script: 14 | - "export DISPLAY=:99.0" 15 | - "sh -e /etc/init.d/xvfb start" 16 | script: 17 | - cd test 18 | - python3 test.py 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gianmaria Bajo (mg1979.git@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vim-visual-multi 2 | 3 | It's called ___vim-visual-multi___ in analogy with _visual-block_, but the plugin works mostly from normal mode. 4 | 5 | Basic usage: 6 | 7 | - select words with Ctrl-N (like `Ctrl-d` in Sublime Text/VS Code) 8 | - create cursors vertically with Ctrl-Down/Ctrl-Up 9 | - select one character at a time with Shift-Arrows 10 | - press n/N to get next/previous occurrence 11 | - press [/] to select next/previous cursor 12 | - press q to skip current and get next occurrence 13 | - press Q to remove current cursor/selection 14 | - start insert mode with i,a,I,A 15 | 16 | Two main modes: 17 | 18 | - in _cursor mode_ commands work as they would in normal mode 19 | - in _extend mode_ commands work as they would in visual mode 20 | - press Tab to switch between «cursor» and «extend» mode 21 | 22 | Most vim commands work as expected (motions, r to replace characters, ~ to change case, etc). Additionally you can: 23 | 24 | - run macros/ex/normal commands at cursors 25 | - align cursors 26 | - transpose selections 27 | - add patterns with regex, or from visual mode 28 | 29 | And more... of course, you can enter insert mode and autocomplete will work. 30 | 31 | 32 | ### Installation 33 | 34 | With vim-plug: 35 | 36 | Plug 'mg979/vim-visual-multi', {'branch': 'master'} 37 | 38 | With Vim 8+: 39 | 40 | mkdir -p ~/.vim/pack/plugins/start && git clone https://github.com/mg979/vim-visual-multi ~/.vim/pack/plugins/start/vim-visual-multi 41 | 42 | 43 | ### Documentation 44 | 45 | :help visual-multi 46 | 47 | For some specific topic it's often: 48 | 49 | :help vm-some-topic 50 | 51 | ### Tutorial 52 | 53 | To run the tutorial: 54 | 55 | vim -Nu path/to/visual-multi/tutorialrc 56 | 57 | 58 | ### [Wiki](https://github.com/mg979/vim-visual-multi/wiki) 59 | 60 | The wiki was the first documentation for the plugin, but many pictures are 61 | outdated and contain wrong mappings. Still, you can take a look. 62 | 63 | You could read at least the [Quick Start](https://github.com/mg979/vim-visual-multi/wiki/Quick-start). 64 | 65 | ------- 66 | Some (sometimes very old) random pics: 67 | 68 | ------- 69 | Insert mode with autocomplete, alignment (mappings in pic have changed, don't trust them) 70 | 71 | ![Imgur](https://i.imgur.com/u5pPY5W.gif) 72 | 73 | ------- 74 | Undo/Redo edits and selections 75 | 76 | ![Imgur](https://i.imgur.com/gwFfUxq.gif) 77 | 78 | ------- 79 | Alternate cursor/extend mode, motions (even %), reverse direction (as in visual mode) and extend from the back. At any time you can switch from extend to cursor mode and viceversa. 80 | 81 | ![Imgur](https://i.imgur.com/ggQr1Ve.gif) 82 | 83 | ------- 84 | Select inside/around brackets/quotes/etc: 85 | 86 | ![Imgur](https://i.imgur.com/GAXQLao.gif) 87 | 88 | ------- 89 | Select operator, here shown with 'wellle/targets.vim' plugin: sib, sia, saa + selection shift 90 | 91 | ![Imgur](https://i.imgur.com/yM3Fele.gif) 92 | 93 | ------- 94 | Synched column transposition 95 | 96 | ![Imgur](https://i.imgur.com/9JDaLBi.gif) 97 | 98 | ------- 99 | Unsynched transposition (cycle all regions, also in different lines) 100 | 101 | ![Imgur](https://i.imgur.com/UQOCxyf.gif) 102 | 103 | ------- 104 | Shift regions left and right (M-S-\<\>) 105 | 106 | ![Imgur](https://i.imgur.com/Q7EF8YI.gif) 107 | 108 | ------ 109 | Find words under cursor, add new words (patterns stack), navigate regions, skip them, add regions with regex. 110 | 111 | ![Imgur](https://i.imgur.com/zWtelNO.gif) 112 | 113 | ------- 114 | Normal/Visual/Ex commands at cursors 115 | 116 | ![Imgur](https://i.imgur.com/5aiQscj.gif) 117 | 118 | ------- 119 | Macros. Shorter lines are skipped when adding cursors vertically. 120 | 121 | ![Imgur](https://i.imgur.com/3IsZzF3.gif) 122 | 123 | ------- 124 | Some editing functions: yank, delete, paste from register, paste block from yanked regions 125 | 126 | ![Imgur](https://i.imgur.com/0jRkVdp.gif) 127 | 128 | ---------------------------------------- 129 | 130 | Case conversion 131 | 132 | ![Imgur](https://i.imgur.com/W6EP0dy.gif) 133 | 134 | -------------------------------------------------------------------------------- /autoload/vm.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Initialize global variables 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let g:VM_live_editing = get(g:, 'VM_live_editing', 1) 6 | 7 | let g:VM_custom_commands = get(g:, 'VM_custom_commands', {}) 8 | let g:VM_commands_aliases = get(g:, 'VM_commands_aliases', {}) 9 | let g:VM_debug = get(g:, 'VM_debug', 0) 10 | let g:VM_reselect_first = get(g:, 'VM_reselect_first', 0) 11 | let g:VM_case_setting = get(g:, 'VM_case_setting', '') 12 | let g:VM_use_first_cursor_in_line = get(g:, 'VM_use_first_cursor_in_line', 0) 13 | let g:VM_disable_syntax_in_imode = get(g:, 'VM_disable_syntax_in_imode', 0) 14 | 15 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 16 | "Reindentation after insert mode 17 | 18 | let g:VM_reindent_filetypes = get(g:, 'VM_reindent_filetypes', []) 19 | 20 | call vm#themes#init() 21 | call vm#plugs#buffer() 22 | 23 | 24 | 25 | 26 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 27 | " Initialize buffer 28 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 29 | " b:VM_Selection (= s:V) contains Regions, Vars (= s:v = plugin variables), 30 | " function classes (Global, Funcs, Edit, Search, Insert, etc) 31 | 32 | " Parameters: 33 | " cmd_type: if > 0, the search register will be set to an empty string 34 | " adding cursors uses 1, starting regex uses 2 35 | 36 | fun! vm#init_buffer(cmd_type) abort 37 | " If already initialized, return current instance. 38 | let v:errmsg = "" 39 | try 40 | if exists('b:visual_multi') | return s:V | endif 41 | 42 | let b:VM_Selection = {'Vars': {}, 'Regions': [], 'Bytes': {}} 43 | let b:visual_multi = 1 44 | 45 | let b:VM_Debug = get(b:, 'VM_Debug', {'lines': []}) 46 | let b:VM_Backup = {'ticks': [], 'last': 0, 'first': undotree().seq_cur} 47 | 48 | " funcs script must be sourced first 49 | let s:V = b:VM_Selection 50 | let s:v = s:V.Vars 51 | let s:V.Funcs = vm#funcs#init() 52 | 53 | " init plugin variables 54 | call vm#variables#init() 55 | 56 | if get(g:, 'VM_filesize_limit', 0) && s:V.Funcs.size() > gVM_filesize_limit 57 | call vm#variables#reset_globals() 58 | let v:errmsg = 'VM cannot start, buffer too big.' 59 | return v:errmsg 60 | endif 61 | 62 | " init search register 63 | let @/ = a:cmd_type ? '' : @/ 64 | 65 | " hooks and compatibility tweaks before applying mappings 66 | call vm#comp#init() 67 | 68 | " init classes 69 | let s:V.Maps = vm#maps#init() 70 | let s:V.Global = vm#global#init() 71 | let s:V.Search = vm#search#init() 72 | let s:V.Edit = vm#edit#init() 73 | let s:V.Insert = vm#insert#init() 74 | let s:V.Case = vm#special#case#init() 75 | 76 | call s:V.Maps.enable() 77 | 78 | call vm#region#init() 79 | call vm#commands#init() 80 | call vm#operators#init() 81 | call vm#special#commands#init() 82 | 83 | call vm#augroup(0) 84 | call vm#au_cursor(0) 85 | 86 | " set vim variables 87 | call vm#variables#set() 88 | 89 | if !empty(g:VM_highlight_matches) 90 | if !has_key(g:Vm, 'Search') 91 | call vm#themes#init() 92 | else 93 | call vm#themes#search_highlight() 94 | endif 95 | hi clear Search 96 | exe 'hi! ' . g:Vm.Search 97 | endif 98 | 99 | if !v:hlsearch && a:cmd_type != 2 100 | call s:enable_hls() 101 | endif 102 | 103 | call s:V.Funcs.set_statusline(0) 104 | 105 | " backup sync settings for the buffer 106 | if !exists('b:VM_sync_minlines') 107 | let b:VM_sync_minlines = s:V.Funcs.sync_minlines() 108 | endif 109 | 110 | let g:Vm.buffer = bufnr('') 111 | return s:V 112 | catch 113 | let v:errmsg = 'VM cannot start, unhandled exception.' 114 | call vm#variables#reset_globals() 115 | return v:errmsg 116 | endtry 117 | endfun 118 | 119 | fun! s:enable_hls() 120 | if mode(1) == 'n' 121 | call feedkeys("\(VM-Hls)") 122 | else 123 | call timer_start(50, { t -> s:enable_hls() }) 124 | endif 125 | endfun 126 | 127 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 128 | " Reset 129 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 130 | 131 | fun! vm#reset(...) 132 | if !exists('b:visual_multi') 133 | return {} 134 | endif 135 | call vm#variables#reset() 136 | call vm#commands#regex_reset() 137 | 138 | call s:V.Global.remove_highlight() 139 | call s:V.Global.backup_last_regions() 140 | 141 | call s:V.Funcs.restore_regs() 142 | call s:V.Maps.disable(1) 143 | silent! call s:V.Insert.auto_end() 144 | 145 | call vm#maps#reset() 146 | call vm#comp#reset() 147 | call vm#augroup(1) 148 | call vm#au_cursor(1) 149 | 150 | " reenable folding, but keep winline and open current fold 151 | if exists('s:v.oldfold') 152 | call s:V.Funcs.Scroll.get(1) 153 | normal! zizv 154 | call s:V.Funcs.Scroll.restore() 155 | endif 156 | 157 | if !empty(g:VM_highlight_matches) 158 | hi clear Search 159 | exe 'hi! ' . g:Vm.search_hi 160 | endif 161 | 162 | if g:Vm.oldupdate && &updatetime != g:Vm.oldupdate 163 | let &updatetime = g:Vm.oldupdate 164 | endif 165 | 166 | call vm#comp#exit() 167 | 168 | call s:V.Funcs.restore_visual_marks() 169 | 170 | "exiting manually 171 | if !get(g:, 'VM_silent_exit', 0) && !a:0 172 | call s:V.Funcs.msg('Exited Visual-Multi.') 173 | else 174 | redraw! 175 | endif 176 | 177 | call vm#variables#reset_globals() 178 | call vm#special#commands#unset() 179 | unlet b:visual_multi 180 | call garbagecollect() 181 | return {} 182 | endfun 183 | 184 | "------------------------------------------------------------------------------ 185 | 186 | fun! vm#hard_reset() 187 | silent! call vm#reset(1) 188 | call vm#clearmatches() 189 | endfun 190 | 191 | "------------------------------------------------------------------------------ 192 | 193 | fun! vm#clearmatches() abort 194 | for m in getmatches() 195 | if m.group == 'VM_Extend' || m.group == 'MultiCursor' 196 | silent! call matchdelete(m.id) 197 | endif 198 | endfor 199 | endfun 200 | 201 | 202 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 203 | " Autocommands 204 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 205 | 206 | fun! vm#augroup(end) abort 207 | if a:end 208 | autocmd! VM_global 209 | augroup! VM_global 210 | return 211 | endif 212 | 213 | augroup VM_global 214 | au! 215 | au BufLeave * call s:buffer_leave() 216 | au BufEnter * call s:buffer_enter() 217 | 218 | if exists("##TextYankPost") 219 | au TextYankPost call s:set_reg() 220 | au TextYankPost call vm#operators#after_yank() 221 | else 222 | au CursorMoved call s:set_reg() 223 | au CursorMoved call vm#operators#after_yank() 224 | au CursorHold call vm#operators#after_yank() 225 | endif 226 | augroup END 227 | endfun 228 | 229 | fun! vm#au_cursor(end) abort 230 | if a:end 231 | autocmd! VM_cursormoved 232 | augroup! VM_cursormoved 233 | return 234 | endif 235 | 236 | augroup VM_cursormoved 237 | au! 238 | au CursorMoved call s:cursor_moved() 239 | au CursorMoved call s:V.Funcs.set_statusline(2) 240 | au CursorHold call s:V.Funcs.set_statusline(1) 241 | augroup END 242 | endfun 243 | 244 | fun! s:cursor_moved() abort 245 | if !s:v.eco 246 | " if currently on a region, set the index to this region 247 | " so that it's possible to select next/previous from it 248 | let r = s:V.Global.region_at_pos() 249 | if !empty(r) | let s:v.index = r.index | endif 250 | endif 251 | endfun 252 | 253 | fun! s:buffer_leave() abort 254 | if exists('b:VM_skip_reset_once_on_bufleave') 255 | unlet b:VM_skip_reset_once_on_bufleave 256 | elseif !empty(get(b:, 'VM_Selection', {})) && !b:VM_Selection.Vars.insert 257 | call vm#reset(1) 258 | endif 259 | endfun 260 | 261 | fun! s:buffer_enter() abort 262 | if empty(get(b:, 'VM_Selection', {})) 263 | let b:VM_Selection = {} 264 | endif 265 | endfun 266 | 267 | fun! s:set_reg() abort 268 | " Replace old default register if yanking in VM outside a region or cursor. 269 | if s:v.yanked 270 | let s:v.yanked = 0 271 | let g:Vm.registers['"'] = [] 272 | let s:v.oldreg = s:V.Funcs.get_reg(v:register) 273 | endif 274 | endfun 275 | 276 | 277 | 278 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 279 | " Python section 280 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 281 | 282 | if !has('python3') 283 | let g:VM_use_python = 0 284 | finish 285 | endif 286 | 287 | let g:VM_use_python = get(g:, 'VM_use_python', !has('nvim')) 288 | if !g:VM_use_python | finish | endif 289 | 290 | let s:root_dir = fnamemodify(resolve(expand(':p')), ':h') 291 | 292 | python3 << EOF 293 | import sys 294 | from os.path import normpath, join 295 | import vim 296 | root_dir = vim.eval('s:root_dir') 297 | python_root_dir = normpath(join(root_dir, '..', 'python')) 298 | sys.path.insert(0, python_root_dir) 299 | import vm 300 | EOF 301 | 302 | " vim: et ts=4 sw=4 sts=4 : 303 | -------------------------------------------------------------------------------- /autoload/vm/comp.vim: -------------------------------------------------------------------------------- 1 | "script to handle compatibility issues with other plugins 2 | 3 | let s:plugins = extend({ 4 | \'ctrlsf': { 5 | \ 'test': { -> &ft == 'ctrlsf' }, 6 | \ 'enable': 'call ctrlsf#buf#ToggleMap(1)', 7 | \ 'disable': 'call ctrlsf#buf#ToggleMap(0)', 8 | \}, 9 | \'AutoPairs': { 10 | \ 'test': { -> exists('b:autopairs_enabled') && b:autopairs_enabled && exists('*AutoPairsTryInit') }, 11 | \ 'enable': 'unlet b:autopairs_loaded | call AutoPairsTryInit() | let b:autopairs_enabled = 1', 12 | \ 'disable': 'let b:autopairs_enabled = 0', 13 | \}, 14 | \'smartinput': { 15 | \ 'test': { -> exists('g:loaded_smartinput') && g:loaded_smartinput == 1 }, 16 | \ 'enable': 'unlet! b:smartinput_disabled', 17 | \ 'disable': 'let b:smartinput_disabled = 1', 18 | \}, 19 | \'tagalong': { 20 | \ 'test': { -> exists('b:tagalong_initialized') }, 21 | \ 'enable': 'TagalongInit', 22 | \ 'disable': 'TagalongDeinit' 23 | \}, 24 | \}, get(g:, 'VM_plugins_compatibilty', {})) 25 | 26 | let s:disabled_deoplete = 0 27 | let s:disabled_ncm2 = 0 28 | 29 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 30 | 31 | fun! vm#comp#init() abort 32 | " Set variables according to plugin needs. "{{{1 33 | let s:V = b:VM_Selection 34 | let s:v = s:V.Vars 35 | let s:v.disabled_plugins = [] 36 | 37 | silent! call VM_Start() 38 | silent doautocmd User visual_multi_start 39 | 40 | if exists('g:loaded_youcompleteme') 41 | let g:VM_use_first_cursor_in_line = 1 42 | endif 43 | 44 | if exists('b:doge_interactive') 45 | call doge#deactivate() 46 | endif 47 | 48 | for plugin in keys(s:plugins) 49 | let p = s:plugins[plugin] 50 | 51 | if p.test() 52 | exe p.disable 53 | call add(s:v.disabled_plugins, plugin) 54 | endif 55 | endfor 56 | endfun "}}} 57 | 58 | 59 | fun! vm#comp#icmds() abort 60 | " Insert mode starts: temporarily disable autocompletion engines. {{{1 61 | if exists('g:loaded_deoplete') && g:deoplete#is_enabled() 62 | call deoplete#disable() 63 | let s:disabled_deoplete = 1 64 | elseif exists('b:ncm2_enable') && b:ncm2_enable 65 | let b:ncm2_enable = 0 66 | let s:disabled_ncm2 = 1 67 | endif 68 | endfun "}}} 69 | 70 | 71 | fun! vm#comp#TextChangedI() abort 72 | " Insert mode change: re-enable autocompletion engines. {{{1 73 | if exists('g:loaded_deoplete') && s:disabled_deoplete 74 | call deoplete#enable() 75 | let s:disabled_deoplete = 0 76 | elseif s:disabled_ncm2 77 | let b:ncm2_enable = 1 78 | let s:disabled_ncm2 = 0 79 | endif 80 | endfun "}}} 81 | 82 | 83 | fun! vm#comp#conceallevel() abort 84 | " indentLine compatibility. {{{1 85 | return exists('b:indentLine_ConcealOptionSet') && b:indentLine_ConcealOptionSet 86 | endfun "}}} 87 | 88 | 89 | fun! vm#comp#iobj() abort 90 | " Inner text objects that should avoid using the select operator. {{{1 91 | return exists('g:loaded_targets') ? ['q'] : [] 92 | endfun "}}} 93 | 94 | 95 | fun! vm#comp#reset() abort 96 | " Called during VM exit. "{{{1 97 | if exists('g:loaded_deoplete') && s:disabled_deoplete 98 | call deoplete#enable() 99 | let s:disabled_deoplete = 0 100 | elseif s:disabled_ncm2 101 | let b:ncm2_enable = 1 102 | let s:disabled_ncm2 = 0 103 | endif 104 | 105 | "restore plugins functionality if necessary 106 | for plugin in keys(s:plugins) 107 | if index(s:v.disabled_plugins, plugin) >= 0 108 | exe s:plugins[plugin].enable 109 | endif 110 | endfor 111 | endfun "}}} 112 | 113 | 114 | fun! vm#comp#exit() abort 115 | " Called last on VM exit. "{{{1 116 | silent! call VM_Exit() 117 | silent doautocmd User visual_multi_exit 118 | endfun "}}} 119 | 120 | 121 | fun! vm#comp#add_line() abort 122 | " Ensure a line is added with these text objects, while changing in cursor mode. "{{{1 123 | 124 | let l = [] 125 | if exists('g:loaded_textobj_indent') 126 | let l += ['ii', 'ai', 'iI', 'aI'] 127 | endif 128 | if exists('g:loaded_textobj_function') 129 | let l += ['if', 'af', 'iF', 'aF'] 130 | endif 131 | return l 132 | endfun "}}} 133 | 134 | 135 | fun! vm#comp#no_reindents() abort 136 | " Don't reindent for filetypes. "{{{1 137 | return ['ctrlsf'] 138 | endfun "}}} 139 | 140 | " vim: et sw=4 ts=4 sts=4 fdm=marker 141 | -------------------------------------------------------------------------------- /autoload/vm/cursors.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Operations at cursors (yank, delete, change) 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | fun! vm#cursors#operation(op, n, register, ...) abort 6 | " Operations at cursors (yank, delete, change) 7 | call s:init() 8 | let reg = a:register | let oper = a:op 9 | 10 | "shortcut for command in a:1 11 | if a:0 | call s:process(oper, a:1, reg, 0) | return | endif 12 | 13 | call s:F.msg('[VM] ') 14 | 15 | "starting string 16 | let M = (a:n>1? a:n : '') . (reg == s:v.def_reg? '' : '"'.reg) . oper 17 | 18 | "preceding count 19 | let n = a:n>1? a:n : 1 20 | 21 | echon M 22 | while 1 23 | let c = nr2char(getchar()) 24 | let is_user_op = index(keys(g:Vm.user_ops), M . c) >= 0 25 | 26 | if is_user_op 27 | " let the entered characters be our operator 28 | echon c | let M .= c | let oper = M 29 | if !g:Vm.user_ops[M] 30 | " accepts a regular text object 31 | continue 32 | else 33 | " accepts a specific number of any characters 34 | let chars2read = g:Vm.user_ops[M] 35 | while chars2read 36 | let c = nr2char(getchar()) 37 | echon c | let M .= c 38 | let chars2read -= 1 39 | endwhile 40 | break 41 | endif 42 | 43 | elseif s:double(c) | echon c | let M .= c 44 | let c = nr2char(getchar()) | echon c | let M .= c | break 45 | 46 | elseif oper ==# 'c' && c==?'r' | echon c | let M .= c 47 | let c = nr2char(getchar()) | echon c | let M .= c | break 48 | 49 | elseif oper ==# 'c' && c==?'s' | echon c | let M .= c 50 | let c = nr2char(getchar()) | echon c | let M .= c 51 | let c = nr2char(getchar()) | echon c | let M .= c | break 52 | 53 | elseif oper ==# 'y' && c==?'s' | echon c | let M .= c 54 | let c = nr2char(getchar()) | echon c | let M .= c 55 | if s:double(c) 56 | let c = nr2char(getchar()) | echon c | let M .= c 57 | endif 58 | let c = nr2char(getchar()) | echon c 59 | if c == '<' || c == 't' 60 | redraw 61 | let tag = s:V.Edit.surround_tags() 62 | if tag == '' 63 | echon ' ...Aborted' | return 64 | else 65 | let M .= tag | echon c | break 66 | endif 67 | else 68 | let M .= c | echon c | break 69 | endif 70 | 71 | elseif oper ==# 'd' && c==#'s' | echon c | let M .= c 72 | let c = nr2char(getchar()) | echon c | let M .= c | break 73 | 74 | elseif s:single(c) | echon c | let M .= c | break 75 | 76 | elseif str2nr(c) > 0 | echon c | let M .= c 77 | 78 | " if the entered char is the last character of the operator (eg 'yy', 'gUU') 79 | elseif oper[-1:-1] ==# c | echon c | let M .= '_' | break 80 | 81 | else | echon ' ...Aborted' | return 82 | endif 83 | endwhile 84 | 85 | call s:process(oper, M, reg, n) 86 | endfun 87 | 88 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 89 | " Function: s:process 90 | " @param op: the operator 91 | " @param M: the whole command 92 | " @param reg: the register 93 | " @param n: the count 94 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 95 | "" 96 | fun! s:process(op, M, reg, n) abort 97 | " Process the whole command 98 | let s:v.dot = a:M 99 | let s:v.deleting = a:op == 'd' || a:op == 'c' 100 | 101 | if a:op ==# 'd' | call s:delete_at_cursors(a:M, a:reg, a:n) 102 | elseif a:op ==# 'c' | call s:change_at_cursors(a:M, a:reg, a:n) 103 | elseif a:op ==# 'y' | call s:yank_at_cursors(a:M, a:reg, a:n) 104 | else 105 | " if it's a custom operator, pass the mapping as-is, and hope for the best 106 | call s:V.Edit.run_normal(a:M, {'count': a:n, 'recursive': 1}) 107 | endif 108 | endfun 109 | 110 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 111 | " Function: s:parse_cmd 112 | " @param M: the whole command 113 | " @param r: the register 114 | " @param n: count that comes before the operator 115 | " @param op: the operator 116 | " Returns: [ text object, count ] 117 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 118 | "" 119 | fun! s:parse_cmd(M, r, n, op) abort 120 | " Parse command, so that the exact count is found. 121 | 122 | "remove register 123 | let Cmd = substitute(a:M, a:r, '', '') 124 | 125 | "what comes after operator 126 | let Obj = substitute(Cmd, '^\d*'.a:op.'\(.*\)$', '\1', '') 127 | 128 | "if object is n/N, ensure there is a search pattern 129 | if Obj ==? 'n' && empty(@/) 130 | let @/ = s:v.oldsearch[0] 131 | endif 132 | 133 | "count that comes after operator 134 | let x = match(Obj, '^\d') >= 0? substitute(Obj, '^\d\zs.*', '', 'g') : 0 135 | if x | let Obj = substitute(Obj, '^' . x, '', '') | endif 136 | 137 | "final count 138 | let n = a:n 139 | let N = x? n*x : n>1? n : 1 140 | let N = N>1? N : '' 141 | 142 | " if the text object is the last character of the operator (eg 'yy') 143 | if Obj ==# a:op[-1:-1] 144 | let Obj = '_' 145 | endif 146 | 147 | return [Obj, N] 148 | endfun 149 | 150 | 151 | fun! s:delete_at_cursors(M, reg, n) abort 152 | " delete operation at cursors 153 | let Cmd = a:M 154 | 155 | "ds surround 156 | if Cmd[:1] ==# 'ds' | return s:V.Edit.run_normal(Cmd) | endif 157 | 158 | let [Obj, N] = s:parse_cmd(Cmd, '"'.a:reg, a:n, 'd') 159 | 160 | "for D, d$, dd: ensure there is only one region per line 161 | if (Obj == '$' || Obj == '_') | call s:G.one_region_per_line() | endif 162 | 163 | "no matter the entered register, we're using default register 164 | "we're passing the register in the options dictionary instead 165 | "fill_register function will be called and take care of it, if appropriate 166 | call s:V.Edit.run_normal('d'.Obj, {'count': N, 'store': a:reg, 'recursive': s:recursive}) 167 | call s:G.reorder_regions() 168 | call s:G.merge_regions() 169 | endfun 170 | 171 | 172 | fun! s:yank_at_cursors(M, reg, n) abort 173 | " yank operation at cursors 174 | let Cmd = a:M 175 | 176 | "ys surround 177 | if Cmd[:1] ==? 'ys' | return s:V.Edit.run_normal(Cmd) | endif 178 | 179 | "reset dot for yank command 180 | let s:v.dot = '' 181 | 182 | call s:G.change_mode() 183 | 184 | let [Obj, N] = s:parse_cmd(Cmd, '"'.a:reg, a:n, 'y') 185 | 186 | "for Y, y$, yy, ensure there is only one region per line 187 | if (Obj == '$' || Obj == '_') | call s:G.one_region_per_line() | endif 188 | 189 | call s:V.Edit.run_normal('y'.Obj, {'count': N, 'store': a:reg, 'vimreg': 1}) 190 | endfun 191 | 192 | 193 | fun! s:change_at_cursors(M, reg, n) abort 194 | " change operation at cursors 195 | let Cmd = a:M 196 | 197 | "cs surround 198 | if Cmd[:1] ==? 'cs' | return s:V.Edit.run_normal(Cmd) | endif 199 | 200 | "cr coerce (vim-abolish) 201 | if Cmd[:1] ==? 'cr' | return feedkeys("\(VM-Run-Normal)".Cmd."\") | endif 202 | 203 | let [Obj, N] = s:parse_cmd(Cmd, '"'.a:reg, a:n, 'c') 204 | 205 | "convert w,W to e,E (if motions), also in dot 206 | if Obj ==# 'w' | let Obj = 'e' | call substitute(s:v.dot, 'w', 'e', '') 207 | elseif Obj ==# 'W' | let Obj = 'E' | call substitute(s:v.dot, 'W', 'E', '') 208 | endif 209 | 210 | "for c$, cc, ensure there is only one region per line 211 | if (Obj == '$' || Obj == '_') | call s:G.one_region_per_line() | endif 212 | 213 | "replace c with d because we're doing a delete followed by multi insert 214 | let Obj = substitute(Obj, '^c', 'd', '') 215 | 216 | "we're using _ register, unless a register has been specified 217 | let reg = a:reg != s:v.def_reg? a:reg : "_" 218 | 219 | if Obj == '_' 220 | call vm#commands#motion('^', 1, 0, 0) 221 | call vm#operators#select(1, '$') 222 | let s:v.changed_text = s:V.Edit.delete(1, reg, 1, 0) 223 | call s:V.Insert.key('i') 224 | 225 | elseif index(['ip', 'ap'], Obj) >= 0 226 | call s:V.Edit.run_normal('d'.Obj, {'count': N, 'store': reg, 'recursive': s:recursive}) 227 | call s:V.Insert.key('O') 228 | 229 | elseif s:recursive && index(vm#comp#add_line(), Obj) >= 0 230 | call s:V.Edit.run_normal('d'.Obj, {'count': N, 'store': reg}) 231 | call s:V.Insert.key('O') 232 | 233 | elseif Obj=='$' 234 | call vm#operators#select(1, '$') 235 | let s:v.changed_text = s:V.Edit.delete(1, reg, 1, 0) 236 | call s:V.Insert.key('i') 237 | 238 | elseif Obj=='l' 239 | call s:G.extend_mode() 240 | if N > 1 241 | call vm#commands#motion('l', N-1, 0, 0) 242 | endif 243 | call feedkeys('"'.reg."c") 244 | 245 | elseif s:forward(Obj) || s:ia(Obj) && !s:inside(Obj) 246 | call vm#operators#select(1, N.Obj) 247 | call feedkeys('"'.reg."c") 248 | 249 | else 250 | call s:V.Edit.run_normal('d'.Obj, {'count': N, 'store': reg, 'recursive': s:recursive}) 251 | call s:G.merge_regions() 252 | call s:V.Insert.key('i') 253 | endif 254 | endfun 255 | 256 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 257 | " Helpers 258 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 259 | 260 | fun! s:init() abort 261 | "set up script variables 262 | let s:V = b:VM_Selection 263 | let s:v = s:V.Vars 264 | let s:G = s:V.Global 265 | let s:F = s:V.Funcs 266 | let s:Search = s:V.Search 267 | 268 | let s:recursive = get(g:, 'VM_recursive_operations_at_cursors', 1) 269 | call s:G.cursor_mode() 270 | endfun 271 | 272 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 273 | " Lambdas 274 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 275 | 276 | let s:R = { -> s:V.Regions } 277 | 278 | " motions that move the cursor forward 279 | let s:forward = { c -> index(split('weWE%', '\zs'), c) >= 0 } 280 | 281 | " text objects starting with 'i' or 'a' 282 | let s:ia = { c -> index(['i', 'a'], c[:0]) >= 0 } 283 | 284 | " inside brackets/quotes/tags 285 | let s:inside = { c -> c[:0] == 'i' && index(split('bBt[](){}"''`<>', '\zs') + vm#comp#iobj(), c[1:1]) >= 0 } 286 | 287 | " single character motions 288 | let s:single = { c -> index(split('hljkwebWEB$^0{}()%nN_', '\zs'), c) >= 0 } 289 | 290 | " motions that expect a second character 291 | let s:double = { c -> index(split('iafFtTg', '\zs'), c) >= 0 } 292 | 293 | " vim: et sw=2 ts=2 sts=2 fdm=indent fdn=1 294 | -------------------------------------------------------------------------------- /autoload/vm/ecmds1.vim: -------------------------------------------------------------------------------- 1 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Edit commands #1 (yank, delete, paste, replace) 3 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let s:Edit = {} 6 | let s:old_text = [] 7 | 8 | fun! vm#ecmds1#init() abort 9 | let s:V = b:VM_Selection 10 | let s:v = s:V.Vars 11 | let s:G = s:V.Global 12 | let s:F = s:V.Funcs 13 | 14 | return extend(s:Edit, vm#ecmds2#init()) 15 | endfun 16 | 17 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 18 | " Lambdas 19 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 20 | 21 | 22 | let s:R = { -> s:V.Regions } 23 | let s:X = { -> g:Vm.extend_mode } 24 | let s:min = { n -> s:X() && len(s:R()) >= n } 25 | 26 | 27 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 28 | " Yank 29 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 30 | 31 | 32 | fun! s:Edit.yank(reg, silent, ...) abort 33 | " Yank the regions contents in a VM register. {{{1 34 | let register = (s:v.use_register != s:v.def_reg) ? s:v.use_register : a:reg 35 | 36 | if !s:X() | return vm#cursors#operation('y', v:count, register) | endif 37 | if !s:min(1) | return s:F.msg('No regions selected.') | endif 38 | 39 | "write custom and possibly vim registers. 40 | let [text, type] = self.fill_register(register, s:G.regions_text(), 0) 41 | 42 | "restore default register if a different register was provided 43 | if register !=# s:v.def_reg | call s:F.restore_reg() | endif 44 | 45 | "reset temp register 46 | let s:v.use_register = s:v.def_reg 47 | 48 | if !a:silent 49 | call s:F.msg('Yanked the content of '.len(s:R()).' regions.') 50 | endif 51 | if a:0 | call s:G.change_mode() | endif 52 | endfun " }}} 53 | 54 | 55 | 56 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 57 | " Delete 58 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 59 | 60 | 61 | fun! s:Edit.delete(X, register, count, manual) abort 62 | " Delete the selected text and change to cursor mode. 63 | " Return the deleted text. 64 | " {{{1 65 | if s:F.no_regions() | return | endif 66 | if !s:v.direction | call vm#commands#invert_direction() | endif 67 | 68 | if !a:X "ask for motion 69 | return vm#cursors#operation('d', a:count, a:register) 70 | endif 71 | 72 | let winline = winline() 73 | let size = s:F.size() 74 | let change = 0 75 | let ix = s:G.select_region_at_pos('.').index 76 | let s:old_text = s:G.regions_text() 77 | let retVal = copy(s:old_text) 78 | let s:v.deleting = 1 79 | 80 | " manual deletion: backup current regions 81 | if a:manual | call s:G.backup_regions() | endif 82 | 83 | for r in s:R() 84 | call r.shift(change, change) 85 | call self.extra_spaces.add(r) 86 | call cursor(r.l, r.a) 87 | if r.w == 1 88 | normal! "_dl 89 | else 90 | normal! m[ 91 | call cursor(r.L, r.b>1? r.b+1 : 1) 92 | normal! m]`["_d`] 93 | endif 94 | 95 | "update changed size 96 | let change = s:F.size() - size 97 | endfor 98 | 99 | "write custom and possibly vim registers. 100 | call self.fill_register(a:register, s:old_text, a:manual) 101 | 102 | call s:G.change_mode() 103 | call s:G.select_region(ix) 104 | 105 | if a:manual 106 | call self.extra_spaces.remove() 107 | call s:G.update_and_select_region() 108 | endif 109 | if a:register == "_" | call s:F.restore_reg() | endif 110 | call s:F.Scroll.force(winline) 111 | let s:old_text = [] 112 | return retVal 113 | endfun " }}} 114 | 115 | 116 | fun! s:Edit.xdelete(key, cnt) abort 117 | " Delete with 'x' or 'X' key, use black hole register in extend mode {{{1 118 | if s:X() 119 | call self.delete(1, '_', a:cnt, 1) 120 | else 121 | call self.run_normal(a:key, {'count': a:cnt, 'recursive': 0}) 122 | endif 123 | endfun "}}} 124 | 125 | 126 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 127 | " Paste 128 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 129 | 130 | 131 | fun! s:Edit.paste(before, vim_reg, reselect, register, ...) abort 132 | " Perform a paste of the appropriate type. {{{1 133 | " @param before: 'P' or 'p' behaviour 134 | " @param vim_reg: if forcing regular vim registers 135 | " @param reselect: trigger reselection if run from extend mode 136 | " @param register: the register being used 137 | " @param ...: optional list with replacement text for regions 138 | let X = s:X() 139 | let s:v.use_register = a:register 140 | let vim_reg = a:vim_reg || !has_key(g:Vm.registers, a:register) || 141 | \ empty(g:Vm.registers[a:register]) 142 | let vim_V = vim_reg && getregtype(a:register) ==# 'V' 143 | 144 | if empty(s:old_text) | let s:old_text = s:G.regions_text() | endif 145 | 146 | if vim_V 147 | return self.run_normal('"' . a:register . 'p', {'recursive': 0}) 148 | 149 | elseif a:0 | let s:v.new_text = a:1 150 | elseif vim_reg | let s:v.new_text = self.convert_vimreg(a:vim_reg) 151 | else | let s:v.new_text = s:fix_regions_text(g:Vm.registers[a:register]) 152 | endif 153 | 154 | call s:G.backup_regions() 155 | 156 | if X | call self.delete(1, "_", 1, 0) | endif 157 | 158 | call self.block_paste(a:before) 159 | 160 | let s:v.W = self.store_widths(s:v.new_text) 161 | call self.post_process((X? 1 : a:reselect), !a:before) 162 | let s:old_text = [] 163 | endfun " }}} 164 | 165 | 166 | fun! s:Edit.block_paste(before) abort 167 | " Paste the new text (list-type) at cursors. {{{1 168 | let size = s:F.size() 169 | let text = copy(s:v.new_text) 170 | let change = 0 171 | let s:v.eco = 1 172 | 173 | for r in s:R() 174 | if !empty(text) 175 | call r.shift(change, change) 176 | call cursor(r.l, r.a) 177 | let s = remove(text, 0) 178 | call s:F.set_reg(s) 179 | 180 | if a:before 181 | normal! P 182 | else 183 | normal! p 184 | if !exists('s:v.dont_move_cursors') 185 | call r.update_cursor_pos() 186 | endif 187 | endif 188 | 189 | "update changed size 190 | let change = s:F.size() - size 191 | else 192 | break 193 | endif 194 | endfor 195 | silent! unlet s:v.dont_move_cursors 196 | call s:F.restore_reg() 197 | endfun " }}} 198 | 199 | 200 | 201 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 202 | " Replace 203 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 204 | 205 | 206 | fun! s:Edit.replace_chars() abort 207 | " Replace single characters or selections with character. {{{1 208 | if s:X() 209 | let char = nr2char(getchar()) 210 | if char ==? "\" | return | endif 211 | 212 | if s:v.multiline 213 | call s:F.toggle_option('multiline') 214 | call s:G.remove_empty_lines() 215 | endif 216 | 217 | let s:v.W = self.store_widths() | let s:v.new_text = [] 218 | 219 | for i in range(len(s:v.W)) 220 | let r = '' 221 | while len(r) < s:v.W[i] | let r .= char | endwhile 222 | let s:v.W[i] -= 1 223 | call add(s:v.new_text, r) 224 | endfor 225 | 226 | call self.delete(1, "_", 1, 0) 227 | call self.block_paste(1) 228 | call self.post_process(1, 0) 229 | else 230 | call s:F.msg('Replace char... ') 231 | let char = nr2char(getchar()) 232 | if char ==? "\" | return s:F.msg('Canceled.') | endif 233 | call self.run_normal('r'.char, {'recursive': 0, 'stay_put': 1}) 234 | endif 235 | endfun " }}} 236 | 237 | 238 | fun! s:Edit.replace() abort 239 | " Replace a pattern in all regions, or start replace mode. {{{1 240 | if !s:X() 241 | let s:V.Insert.replace = 1 242 | return s:V.Insert.key('i') 243 | endif 244 | 245 | let ix = s:v.index 246 | call s:F.Scroll.get() 247 | 248 | echohl Type 249 | let pat = input('Pattern to replace > ') 250 | if empty(pat) 251 | return s:F.msg('Command aborted.') 252 | endif 253 | let repl = input('Replacement > ') 254 | if empty(repl) 255 | call s:F.msg('Hit Enter for an empty replacement... ') 256 | if getchar() != 13 257 | return s:F.msg('Command aborted.') 258 | endif 259 | endif 260 | echohl None 261 | 262 | let text = s:G.regions_text() 263 | let T = [] 264 | for t in text 265 | call add(T, substitute(t, '\C' . pat, repl, 'g')) 266 | endfor 267 | call self.replace_regions_with_text(T) 268 | call s:G.select_region(ix) 269 | endfun " }}} 270 | 271 | 272 | fun! s:Edit.replace_expression() abort 273 | " Replace all regions with the result of an expression. {{{1 274 | if !s:X() | return | endif 275 | let ix = s:v.index | call s:F.Scroll.get() 276 | 277 | echohl Type | let expr = input('Expression > ', '', 'expression') | echohl None 278 | if empty(expr) | return s:F.msg('Command aborted.') | endif 279 | 280 | let T = [] | let expr = s:F.get_expr(expr) 281 | for r in s:R() 282 | call add(T, eval(expr)) 283 | endfor 284 | call map(T, 'type(v:val) != v:t_string ? string(v:val) : v:val') 285 | call self.replace_regions_with_text(T) 286 | call s:G.select_region(ix) 287 | endfun " }}} 288 | 289 | 290 | 291 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 292 | " Helper functions 293 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 294 | 295 | 296 | fun! s:fix_regions_text(replacement) abort 297 | " Ensure there are enough elements for all regions. {{{1 298 | let L = a:replacement 299 | let i = len(s:R()) - len(L) 300 | 301 | while i>0 302 | call add(L, empty(s:old_text)? '' : s:old_text[-i]) 303 | let i -= 1 304 | endwhile 305 | return L 306 | endfun " }}} 307 | 308 | 309 | fun! s:Edit.convert_vimreg(as_block) abort 310 | " Fill the content to paste with the chosen vim register. {{{1 311 | let text = [] 312 | let block = char2nr(getregtype(s:v.use_register)[0]) == 22 313 | 314 | if block 315 | "default register is of block type, assign a line to each region 316 | let width = getregtype(s:v.use_register)[1:] 317 | let content = split(getreg(s:v.use_register), "\n") 318 | 319 | "ensure all regions have the same width, fill the rest with spaces 320 | if a:as_block 321 | for t in range(len(content)) 322 | while len(content[t]) < width | let content[t] .= ' ' | endwhile 323 | endfor 324 | endif 325 | 326 | call s:fix_regions_text(content) 327 | 328 | for n in range(len(s:R())) 329 | call add(text, content[n]) 330 | endfor 331 | else 332 | for n in range(len(s:R())) | call add(text, getreg(s:v.use_register)) | endfor 333 | endif 334 | return text 335 | endfun " }}} 336 | 337 | 338 | fun! s:Edit.store_widths(...) abort 339 | " Build a list that holds the widths(integers) of each region {{{1 340 | " It will be used for various purposes (reselection, paste as block...) 341 | 342 | let W = [] | let x = s:X() 343 | let use_text = 0 344 | let use_list = 0 345 | 346 | if a:0 347 | if type(a:1) == type("") | let text = len(a:1)-1 | let use_text = 1 348 | else | let list = a:1 | let use_list = 1 349 | endif 350 | endif 351 | 352 | "mismatching blocks must be corrected 353 | if use_list | call s:fix_regions_text(list) | endif 354 | 355 | for r in s:R() 356 | "if using list, w must be len[i]-1, but always >= 0, set it to 0 if empty 357 | if use_list | let w = len(list[r.index]) | endif 358 | call add( W, use_text? text : use_list? (w? w-1 : 0) : r.w ) 359 | endfor 360 | return W 361 | endfun " }}} 362 | 363 | 364 | fun! s:Edit.fill_register(reg, text, force_ow) abort 365 | " Write custom and possibly vim registers. {{{1 366 | 367 | "if doing a change/deletion, write the VM - register 368 | if s:v.deleting 369 | let g:Vm.registers['-'] = a:text 370 | let s:v.deleting = 0 371 | endif 372 | 373 | if a:reg == "_" | return | endif 374 | 375 | let text = a:text 376 | let reg = empty(a:reg) ? '"' : a:reg 377 | let temp_reg = reg == '§' 378 | let overwrite = reg ==# s:v.def_reg || reg == '+' || ( a:force_ow && !temp_reg ) 379 | let maxw = max(map(copy(text), 'len(v:val)')) 380 | let type = s:v.multiline? 'V' : ( len(s:R())>1? 'b'.maxw : 'v' ) 381 | 382 | " set VM register, overwrite backup register unless temporary 383 | if !temp_reg 384 | let g:Vm.registers[s:v.def_reg] = text 385 | let s:v.oldreg = [s:v.def_reg, join(text, "\n"), type] 386 | endif 387 | " don't store the system register 388 | if reg != '+' 389 | let g:Vm.registers[reg] = text 390 | endif 391 | 392 | "vim register is overwritten if unnamed, or if forced 393 | if overwrite 394 | call setreg(reg, join(text, "\n"), type) 395 | endif 396 | 397 | return [text, type] 398 | endfun " }}} 399 | 400 | 401 | fun! s:Edit.replace_regions_with_text(text, ...) abort 402 | " Paste a custom list of strings into current regions. {{{1 403 | call self.fill_register('"', a:text, 0) 404 | let before = !a:0 || !a:1 405 | call self.paste(before, 0, s:X(), '"') 406 | endfun " }}} 407 | 408 | 409 | " vim: et sw=4 ts=4 sts=4 fdm=marker 410 | -------------------------------------------------------------------------------- /autoload/vm/ecmds2.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Edit commands #2 (special commands) 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let s:Edit = {} 6 | 7 | fun! vm#ecmds2#init() abort 8 | let s:V = b:VM_Selection 9 | let s:v = s:V.Vars 10 | let s:G = s:V.Global 11 | let s:F = s:V.Funcs 12 | return s:Edit 13 | endfun 14 | 15 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 16 | " Lambdas 17 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 18 | 19 | let s:R = { -> s:V.Regions } 20 | let s:X = { -> g:Vm.extend_mode } 21 | 22 | 23 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 24 | " Duplicate 25 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 26 | 27 | fun! s:Edit.duplicate() abort 28 | if !s:min(1) | return | endif 29 | 30 | call self.yank('§', 1) 31 | call s:G.change_mode() 32 | call self.paste(1, 0, 1, '§') 33 | endfun 34 | 35 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 36 | " Change 37 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 38 | 39 | fun! s:Edit.change(X, count, reg, smart_case) abort 40 | if !len(s:R()) | return | endif 41 | if !s:v.direction | call vm#commands#invert_direction() | endif 42 | if a:smart_case && !exists('s:v.smart_case_change') 43 | let s:v.smart_case_change = 1 44 | endif 45 | if a:X 46 | "delete existing region contents and leave the cursors 47 | let reg = a:reg != s:v.def_reg? a:reg : "_" 48 | let s:v.changed_text = self.delete(1, reg, 1, 0) 49 | call s:V.Insert.key('i') 50 | else 51 | call vm#cursors#operation('c', a:count, a:reg) 52 | endif 53 | endfun 54 | 55 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 56 | " Non-live edit mode 57 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 58 | 59 | fun! s:Edit.apply_change() abort 60 | call s:V.Insert.auto_end() 61 | let self.skip_index = s:v.index 62 | call self.process('normal! .') 63 | "reset index to skip 64 | let self.skip_index = -1 65 | endfun 66 | 67 | 68 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 69 | " Special commands 70 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 71 | 72 | fun! s:Edit.surround() abort 73 | if !len(s:R()) | return | endif 74 | if !s:X() | call vm#operators#select(1, 'iw') | endif 75 | 76 | if !s:v.direction | call vm#commands#invert_direction() | endif 77 | let s:v.W = self.store_widths() 78 | let reselect = 1 79 | 80 | let c = nr2char(getchar()) 81 | 82 | if c == '<' || c ==# 't' 83 | let reselect = 0 84 | let c = self.surround_tags() 85 | if c == '' 86 | redraw 87 | echo 88 | return 89 | endif 90 | endif 91 | 92 | let S = g:Vm.maps.surround 93 | 94 | exe 'silent! nunmap ' . S 95 | 96 | call self.run_visual(S . c, 1) 97 | 98 | if index(['[', '{', '('], c) >= 0 99 | call map(s:v.W, 'v:val + 3') 100 | else 101 | call map(s:v.W, 'v:val + 1') 102 | endif 103 | 104 | if reselect 105 | call self.post_process(1, 0) 106 | else 107 | call self.post_process(0) 108 | endif 109 | 110 | exe 'nmap ' . S . ' (VM-Surround)' 111 | endfun 112 | 113 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 114 | 115 | fun! s:Edit.surround_tags() abort 116 | let c = '<' 117 | echo c 118 | 119 | while v:true 120 | let ch = getchar() 121 | if ch == 27 "esc 122 | return '' 123 | endif 124 | if ch == "\" 125 | if strlen(c) > 1 126 | let c = c[:-2] 127 | else 128 | "no more chars 129 | return '' 130 | endif 131 | else 132 | let c .= nr2char(ch) 133 | if ch == 62 || ch == 13 "> or CR 134 | break 135 | endif 136 | endif 137 | redraw 138 | echo c 139 | endwhile 140 | 141 | return c 142 | endfun 143 | 144 | 145 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 146 | 147 | fun! s:Edit.rotate() abort 148 | """Non-inline transposition. 149 | if !s:min(2) | return | endif 150 | 151 | call self.yank('"', 1) 152 | 153 | let t = remove(g:Vm.registers[s:v.def_reg], 0) 154 | call add(g:Vm.registers[s:v.def_reg], t) 155 | call self.paste(1, 0, 1, '"') 156 | endfun 157 | 158 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 159 | 160 | fun! s:Edit.transpose() abort 161 | if !s:min(2) | return | endif 162 | let rlines = s:G.lines_with_regions(0) 163 | let klines = sort(keys(rlines), 'n') 164 | 165 | "check if there is the same nr of regions in each line 166 | let inline = len(klines) > 1 167 | if inline 168 | let n = 0 169 | for l in klines 170 | let nr = len(rlines[l]) 171 | 172 | if nr == 1 | let inline = 0 | break "line with 1 region 173 | elseif !n | let n = nr "set required n regions x line 174 | elseif nr != n | let inline = 0 | break "different number of regions 175 | endif 176 | endfor 177 | endif 178 | 179 | "non-inline transposition 180 | if !inline 181 | return self.rotate() 182 | endif 183 | 184 | call self.yank('"', 1) 185 | 186 | "inline transpositions 187 | for l in klines 188 | let t = remove(g:Vm.registers[s:v.def_reg], rlines[l][-1]) 189 | call insert(g:Vm.registers[s:v.def_reg], t, rlines[l][0]) 190 | endfor 191 | call self.delete(1, "_", 0, 0) 192 | call self.paste(1, 0, 1, '"') 193 | endfun 194 | 195 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 196 | 197 | fun! s:Edit.align() abort 198 | if s:v.multiline 199 | return s:F.msg('Not possible, multiline is enabled.') 200 | endif 201 | call s:G.cursor_mode() 202 | 203 | call self.run_normal('D', {'store': '§'}) 204 | let max = max(map(copy(s:R()), 'virtcol([v:val.l, v:val.a])')) 205 | let reg = g:Vm.registers['§'] 206 | for r in s:R() 207 | let spaces = '' 208 | let L = getline(r.l) 209 | if empty(L) 210 | while len(spaces) < max | let spaces .= ' ' | endwhile 211 | call setline(r.l, L[:r.a-1] . spaces . L[r.a:] . reg[r.index]) 212 | call r.update_cursor([r.l, r.a + len(spaces) - 1]) 213 | else 214 | while len(spaces) < (max - virtcol([r.l, r.a])) | let spaces .= ' ' | endwhile 215 | call setline(r.l, L[:r.a-1] . spaces . L[r.a:] . reg[r.index]) 216 | call r.update_cursor([r.l, r.a + len(spaces)]) 217 | endif 218 | endfor 219 | call s:G.update_and_select_region() 220 | call vm#commands#motion('l', 1, 0, 0) 221 | endfun 222 | 223 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 224 | 225 | fun! s:Edit.shift(dir) abort 226 | if !s:min(1) | return | endif 227 | 228 | call self.yank('"', 1) 229 | if a:dir 230 | let s:v.dont_move_cursors = 1 231 | call self.paste(0, 0, 1, '"') 232 | else 233 | call self.delete(1, "_", 0, 0) 234 | call vm#commands#motion('h', 1, 0, 0) 235 | call self.paste(1, 0, 1, '"') 236 | endif 237 | endfun 238 | 239 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 240 | " Insert numbers 241 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 242 | 243 | fun! s:Edit._numbers(start, step, separator, append) abort 244 | let start = str2nr(a:start) 245 | let step = str2nr(a:step) 246 | 247 | "------------------------------------------------ 248 | " build string from expression 249 | 250 | let text = [] 251 | for n in range(len(s:R())) 252 | if a:append 253 | let t = a:separator . string(start + step * n) 254 | else 255 | let t = string(start + step * n) . a:separator 256 | endif 257 | call add(text, t) 258 | endfor 259 | 260 | "------------------------------------------------ 261 | " paste string before/after the cursor/selection 262 | 263 | if s:X() 264 | let text = map(copy(s:R()), a:append 265 | \ ? '(v:val.txt).text[v:key]' 266 | \ : 'text[v:key].(v:val.txt)') 267 | call self.replace_regions_with_text(text) 268 | else 269 | call self.replace_regions_with_text(text, a:append) 270 | endif 271 | endfun 272 | 273 | fun! s:Edit.numbers(start, app) abort 274 | if !len(s:R()) | return | endif 275 | 276 | " fill the command line with [count]/default_step 277 | let x = input('Expression > ', a:start . '/1/') 278 | 279 | if empty(x) | return s:F.msg('Canceled') | endif 280 | 281 | "first char must be a digit or a negative sign 282 | if match(x, '^\d') < 0 && match(x, '^\-') < 0 283 | return s:F.msg('Invalid expression') 284 | endif 285 | 286 | "evaluate terms of the expression 287 | "/ is the separator, it must be escaped \/ to be used 288 | let x = split(x, '/', 1) 289 | let i = 0 290 | while i < len(x)-1 291 | if x[i][-1:-1] == '\' 292 | let x[i] = x[i][:-2].'/'.remove(x, i+1) 293 | else 294 | let i += 1 295 | endif 296 | endwhile 297 | call filter(x, '!empty(v:val)') 298 | let n = len(x) 299 | 300 | " true for a number, false for a separator 301 | let l:Num = { x -> match(x, '^\d') >= 0 || match(x, '^\-\d') >= 0 } 302 | 303 | "------------------------------------------- start step separ. append? 304 | if n == 1 | call self._numbers ( x[0], 1, '', a:app ) 305 | 306 | elseif n == 2 307 | 308 | if l:Num(x[1]) | call self._numbers ( x[0], x[1], '', a:app ) 309 | else | call self._numbers ( x[0], 1, x[1], a:app ) 310 | endif 311 | 312 | elseif n == 3 | call self._numbers ( x[0], x[1], x[2], a:app ) 313 | endif 314 | endfun 315 | 316 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 317 | 318 | fun! s:min(n) abort 319 | return s:X() && len(s:R()) >= a:n 320 | endfun 321 | 322 | " vim: et ts=4 sw=4 sts=4 : 323 | -------------------------------------------------------------------------------- /autoload/vm/icmds.vim: -------------------------------------------------------------------------------- 1 | "script to handle several insert mode commands 2 | 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | fun! vm#icmds#init() abort 6 | let s:V = b:VM_Selection 7 | let s:v = s:V.Vars 8 | let s:G = s:V.Global 9 | let s:F = s:V.Funcs 10 | endfun 11 | 12 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 13 | " Lambdas 14 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 15 | 16 | let s:R = { -> s:V.Regions } 17 | let s:X = { -> g:Vm.extend_mode } 18 | 19 | 20 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 21 | 22 | fun! vm#icmds#x(cmd) abort 23 | let size = s:F.size() 24 | let change = 0 | let s:v.eco = 1 25 | if empty(s:v.storepos) | let s:v.storepos = getpos('.')[1:2] | endif 26 | let active = s:R()[s:V.Insert.index] 27 | 28 | for r in s:R() 29 | if s:v.single_region && r isnot active 30 | if r.l == active.l 31 | call r.shift(change, change) 32 | endif 33 | continue 34 | endif 35 | 36 | call r.shift(change, change) 37 | call s:F.Cursor(r.A) 38 | 39 | " we want to emulate the behaviour that and have in insert 40 | " mode, but implemented as normal mode commands 41 | 42 | if s:V.Insert.replace 43 | " in replace mode, we don't allow line joining 44 | if a:cmd ==# 'X' && r.a > 1 45 | let original = s:V.Insert._lines[r.l] " the original line 46 | if strpart(getline(r.l), r.a) =~ '\s*$' " at EOL 47 | call search('\s*$', '', r.l) 48 | endif 49 | "FIXME this part is bugged with multibyte chars 50 | call r.shift(-1,-1) 51 | if r.a > 1 52 | let t1 = strpart(getline('.'), 0, r.a - 1) 53 | let wd = strwidth(t1) 54 | let tc = strcharpart(original, wd, 1) 55 | let t2 = strcharpart(original, wd + 1) 56 | call setline(r.l, t1 . tc . t2) 57 | else 58 | let pre = '' 59 | let post = original 60 | call setline(r.l, pre . post) 61 | endif 62 | endif 63 | elseif a:cmd ==# 'x' && s:eol(r) "at eol, join lines 64 | keepjumps normal! gJ 65 | elseif a:cmd ==# 'x' "normal delete 66 | keepjumps normal! x 67 | elseif a:cmd ==# 'X' && r.a == 1 "at bol, go up and join lines 68 | keepjumps normal! kgJ 69 | call r.shift(-1,-1) 70 | else "normal backspace 71 | keepjumps normal! X 72 | let w = strlen(@-) 73 | call r.shift(-w, -w) 74 | endif 75 | 76 | "update changed size 77 | let change = s:F.size() - size 78 | endfor 79 | 80 | call s:G.merge_regions() 81 | call s:G.select_region(s:V.Insert.index) 82 | endfun 83 | 84 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 85 | 86 | fun! vm#icmds#cw(ctrlu) abort 87 | let size = s:F.size() 88 | let change = 0 | let s:v.eco = 1 89 | let s:v.storepos = getpos('.')[1:2] 90 | let keep_line = get(g:, 'VM_icw_keeps_line', 1) 91 | 92 | for r in s:R() 93 | call r.shift(change, change) 94 | 95 | "TODO: deletion to line above can be bugged for now 96 | if keep_line && r.a == 1 | continue | endif 97 | 98 | call s:F.Cursor(r.A) 99 | 100 | if r.a > 1 && s:eol(r) "add extra space and move right 101 | call s:V.Edit.extra_spaces.add(r) 102 | call r.move('l') 103 | endif 104 | 105 | let L = getline(r.l) 106 | let ws_only = r.a > 1 && match(L[:(r.a-2)], '[^ \t]') < 0 107 | 108 | if a:ctrlu "ctrl-u 109 | keepjumps normal! d^ 110 | elseif r.a == 1 "at bol, go up and join lines 111 | keepjumps normal! kgJ 112 | elseif ws_only "whitespace only before, delete it 113 | keepjumps normal! d0 114 | else "normal deletion 115 | keepjumps normal! db 116 | endif 117 | call r.update_cursor_pos() 118 | 119 | "update changed size 120 | let change = s:F.size() - size 121 | endfor 122 | call s:V.Insert.start(1) 123 | endfun 124 | 125 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 126 | 127 | fun! vm#icmds#paste() abort 128 | call s:G.select_region(-1) 129 | call s:V.Edit.paste(1, 0, 1, '"') 130 | call s:G.select_region(s:V.Insert.index) 131 | endfun 132 | 133 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 134 | 135 | fun! vm#icmds#return() abort 136 | "invert regions order, so that they are processed from bottom to top 137 | let s:V.Regions = reverse(s:R()) 138 | 139 | for r in s:R() 140 | call cursor(r.l, r.a) 141 | let rline = getline('.') 142 | 143 | "we also consider at EOL cursors that have trailing spaces after them 144 | "if not at EOL, CR will cut the line and carry over the remaining text 145 | let at_eol = match(strpart(rline, r.a-1, len(rline)), '\s*$') == 0 146 | 147 | "if carrying over some text, delete it now, for better indentexpr 148 | "otherwise delete the trailing spaces that would be left at EOL 149 | if !at_eol | keepjumps normal! d$ 150 | else | keepjumps normal! "_d$ 151 | endif 152 | 153 | "append a line and get the indent 154 | noautocmd exe "silent keepjumps normal! o\=get_indent()\" 155 | 156 | "fill the line with tabs or spaces, according to the found indent 157 | "an extra space must be added, if not carrying over any text 158 | "also keep the indent whitespace only, removing any non-space character 159 | "such as comments, and everything after them 160 | let extra_space = at_eol ? ' ' : '' 161 | let indent = substitute(g:Vm.indent, '\S\+.*', '', 'g') 162 | call setline('.', indent . extra_space) 163 | 164 | "if carrying over some text, paste it after the indent 165 | "but strip preceding whitespace found in the text 166 | if !at_eol 167 | let @" = substitute(@", '^\s*', '', '') 168 | keepjumps normal! $p 169 | endif 170 | 171 | "cursor line will be moved down by the next cursors 172 | call r.update_cursor([line('.') + r.index, len(indent) + 1]) 173 | endfor 174 | 175 | "reorder regions 176 | let s:V.Regions = reverse(s:R()) 177 | 178 | "ensure cursors are at indent level 179 | keepjumps normal ^ 180 | endfun 181 | 182 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 183 | 184 | fun! vm#icmds#insert_line(above) abort 185 | "invert regions order, so that they are processed from bottom to top 186 | let s:V.Regions = reverse(s:R()) 187 | 188 | for r in s:R() 189 | "append a line below or above 190 | call cursor(r.l, r.a) 191 | noautocmd exe "silent keepjumps normal!" (a:above ? 'O' : 'o')."\=get_indent()\" 192 | 193 | "remove comment or other chars, fill the line with tabs or spaces 194 | let indent = substitute(g:Vm.indent, '[^ \t].*', '', 'g') 195 | call setline('.', indent . ' ') 196 | 197 | "cursor line will be moved down by the next cursors 198 | call r.update_cursor([line('.') + r.index, len(indent) + 1]) 199 | call add(s:v.extra_spaces, r.index) 200 | endfor 201 | 202 | "reorder regions 203 | let s:V.Regions = reverse(s:R()) 204 | 205 | "ensure cursors are at indent level 206 | keepjumps normal ^ 207 | endfun 208 | 209 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 210 | 211 | fun! vm#icmds#goto(next) abort 212 | """Used in single region mode. 213 | let s:v.single_mode_running = 1 214 | let t = ":call b:VM_Selection.Insert.key('".s:V.Insert.type."')\" 215 | if a:next 216 | return "\:call vm#commands#find_next(0,1)\".t 217 | else 218 | return "\:call vm#commands#find_prev(0,1)\".t 219 | endif 220 | endfun 221 | 222 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 223 | 224 | fun! s:get_indent() abort 225 | let g:Vm.indent = getline('.') 226 | return '' 227 | endfun 228 | 229 | fun! s:eol(r) 230 | return a:r.a == (col([a:r.l, '$']) - 1) 231 | endfun 232 | " vim: et ts=4 sw=4 sts=4 : 233 | -------------------------------------------------------------------------------- /autoload/vm/maps.vim: -------------------------------------------------------------------------------- 1 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | "Initialize 3 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let s:Maps = {} 6 | 7 | let g:VM_custom_noremaps = get(g:, 'VM_custom_noremaps', {}) 8 | let g:VM_custom_remaps = get(g:, 'VM_custom_remaps', {}) 9 | let g:VM_custom_motions = get(g:, 'VM_custom_motions', {}) 10 | let g:VM_check_mappings = get(g:, 'VM_check_mappings', 1) 11 | let g:VM_default_mappings = get(g:, 'VM_default_mappings', 1) 12 | let g:VM_mouse_mappings = get(g:, 'VM_mouse_mappings', 0) 13 | 14 | 15 | fun! vm#maps#default() abort 16 | " At vim start, permanent mappings are generated and applied. 17 | call s:build_permanent_maps() 18 | for m in g:Vm.maps.permanent | exe m | endfor 19 | endfun 20 | 21 | 22 | fun! vm#maps#init() abort 23 | " At VM start, buffer mappings are generated (once per buffer) and applied. 24 | let s:V = b:VM_Selection 25 | if !exists('b:VM_maps') | call s:build_buffer_maps() | endif 26 | 27 | call s:Maps.map_esc_and_toggle() 28 | call s:check_warnings() 29 | return s:Maps 30 | endfun 31 | 32 | 33 | fun! vm#maps#reset() abort 34 | " At VM reset, last buffer mappings are reset, and permanent maps are restored. 35 | call s:Maps.unmap_esc_and_toggle() 36 | for m in g:Vm.maps.permanent | exe m | endfor 37 | endfun 38 | 39 | 40 | 41 | 42 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 43 | " Mappings activation/deactivation 44 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 45 | 46 | fun! s:Maps.enable() abort 47 | " Enable mappings in current buffer. 48 | if !g:Vm.mappings_enabled 49 | let g:Vm.mappings_enabled = 1 50 | call self.start() 51 | endif 52 | endfun 53 | 54 | 55 | fun! s:Maps.disable(keep_permanent) abort 56 | " Disable mappings in current buffer. 57 | if g:Vm.mappings_enabled 58 | let g:Vm.mappings_enabled = 0 59 | call self.end(a:keep_permanent) 60 | endif 61 | endfun 62 | 63 | 64 | fun! s:Maps.mappings_toggle() abort 65 | " Toggle mappings in current buffer. 66 | if g:Vm.mappings_enabled 67 | call self.disable(1) 68 | else 69 | call self.enable() 70 | endif 71 | endfun 72 | 73 | 74 | 75 | 76 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 77 | " Apply mappings 78 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 79 | 80 | fun! s:Maps.start() abort 81 | " Apply mappings in current buffer. 82 | for m in g:Vm.maps.permanent | exe m | endfor 83 | for m in b:VM_maps | exe m | endfor 84 | 85 | nmap : (VM-:) 86 | nmap / (VM-/) 87 | nmap ? (VM-?) 88 | 89 | " user autocommand after mappings have been set 90 | silent doautocmd User visual_multi_mappings 91 | endfun 92 | 93 | 94 | fun! s:Maps.map_esc_and_toggle() abort 95 | " Esc and 'toggle' keys are handled separately. 96 | if !has('nvim') && !has('gui_running') 97 | nnoremap 98 | endif 99 | exe 'nmap ' g:Vm.maps.exit '(VM-Exit)' 100 | exe 'nmap ' g:Vm.maps.toggle '(VM-Toggle-Mappings)' 101 | endfun 102 | 103 | 104 | 105 | 106 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 107 | " Remove mappings 108 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 109 | 110 | fun! s:Maps.end(keep_permanent) abort 111 | " Remove mappings in current buffer. 112 | for m in g:Vm.unmaps | exe m | endfor 113 | for m in b:VM_unmaps | exe m | endfor 114 | 115 | nunmap : 116 | nunmap / 117 | nunmap ? 118 | silent! cunmap 119 | silent! cunmap 120 | 121 | " restore permanent mappings 122 | if a:keep_permanent 123 | for m in g:Vm.maps.permanent | exe m | endfor 124 | endif 125 | endfun 126 | 127 | 128 | fun! s:Maps.unmap_esc_and_toggle() abort 129 | " Esc and 'toggle' keys are handled separately. 130 | silent! exe 'nunmap ' g:Vm.maps.toggle 131 | silent! exe 'nunmap ' g:Vm.maps.exit 132 | if !has('nvim') && !has('gui_running') 133 | silent! nunmap 134 | endif 135 | endfun 136 | 137 | 138 | 139 | 140 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 141 | " Map helper functions 142 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 143 | 144 | fun! s:build_permanent_maps() abort 145 | " Run at vim start. Generate permanent mappings and integrate custom ones. 146 | 147 | "set default VM leader 148 | let ldr = get(g:, 'VM_leader', '\\') 149 | let g:Vm.leader = type(ldr) == v:t_string 150 | \ ? {'default': ldr, 'visual': ldr, 'buffer': ldr} 151 | \ : extend({'default':'\\', 'visual':'\\', 'buffer':'\\'}, ldr) 152 | 153 | "init vars and generate base permanent maps 154 | let g:VM_maps = get(g:, 'VM_maps', {}) 155 | let g:Vm.maps = {'permanent': []} 156 | let g:Vm.unmaps = [] 157 | let maps = vm#maps#all#permanent() 158 | 159 | "integrate custom maps 160 | for key in keys(g:VM_maps) 161 | silent! let maps[key][0] = g:VM_maps[key] 162 | endfor 163 | 164 | "generate list of 'exe' commands for map assignment 165 | for key in keys(maps) 166 | let mapping = s:assign(key, maps[key], 0) 167 | if !empty(mapping) 168 | call add(g:Vm.maps.permanent, mapping) 169 | endif 170 | endfor 171 | 172 | "generate list of 'exe' commands for unmappings 173 | for key in keys(maps) 174 | call add(g:Vm.unmaps, s:unmap(maps[key], 0)) 175 | endfor 176 | 177 | "store some mappings that need special handling 178 | let g:Vm.maps.toggle = get(g:VM_maps, 'Toggle Mappings', g:Vm.leader.buffer . '') 179 | let g:Vm.maps.exit = get(g:VM_maps, 'Exit', '') 180 | let g:Vm.maps.surround = get(g:VM_maps, 'Surround', 'S') 181 | endfun 182 | 183 | 184 | fun! s:build_buffer_maps() abort 185 | " Run once per buffer. Generate buffer mappings and integrate custom ones. 186 | let b:VM_maps = [] 187 | let b:VM_unmaps = [] 188 | let check_maps = get(b:, 'VM_check_mappings', g:VM_check_mappings) 189 | let force_maps = get(b:, 'VM_force_maps', get(g:, 'VM_force_maps', [])) 190 | 191 | "generate base buffer maps 192 | let maps = vm#maps#all#buffer() 193 | 194 | "integrate motions 195 | for m in (g:Vm.motions + g:Vm.find_motions) 196 | let maps['Motion ' . m] = [m, 'n'] 197 | endfor 198 | for m in keys(g:Vm.tobj_motions) 199 | let maps['Motion ' . g:Vm.tobj_motions[m]] = [m, 'n'] 200 | endfor 201 | for op in keys(g:Vm.user_ops) 202 | " don't map the operator if it starts with a key that would interfere 203 | " with VM operations in extend mode, eg. if 'cx' gets mapped, then 'c' 204 | " will not work as it should (it would have a delay in extend mode) 205 | if index(['y', 'c', 'd'], op[:0]) == -1 206 | let maps['User Operator ' . op] = [op, 'n'] 207 | endif 208 | endfor 209 | 210 | "integrate custom motions and commands 211 | for m in keys(g:VM_custom_motions) 212 | let maps['Motion ' . g:VM_custom_motions[m]] = [m, 'n'] 213 | endfor 214 | for m in keys(g:VM_custom_noremaps) 215 | let maps['Normal! ' . g:VM_custom_noremaps[m]] = [m, 'n'] 216 | endfor 217 | for m in keys(g:VM_custom_remaps) 218 | let maps['Remap ' . g:VM_custom_remaps[m]] = [m, 'n'] 219 | endfor 220 | for m in keys(g:VM_custom_commands) 221 | let maps[m] = [m, 'n'] 222 | endfor 223 | 224 | "integrate custom remappings 225 | for key in keys(g:VM_maps) 226 | silent! let maps[key][0] = g:VM_maps[key] 227 | endfor 228 | 229 | "generate list of 'exe' commands for map assignment 230 | for key in keys(maps) 231 | let mapping = s:assign(key, maps[key], 1, check_maps, force_maps) 232 | if !empty(mapping) 233 | call add(b:VM_maps, mapping) 234 | else 235 | " remove the mapping, so that it won't be unmapped either 236 | unlet maps[key] 237 | endif 238 | endfor 239 | 240 | "generate list of 'exe' commands for unmappings 241 | for key in keys(maps) 242 | call add(b:VM_unmaps, s:unmap(maps[key], 1)) 243 | endfor 244 | endfun 245 | 246 | 247 | fun! s:assign(plug, key, buffer, ...) abort 248 | " Create a map command that will be executed. 249 | let k = a:key[0] | if empty(k) | return '' | endif 250 | let m = a:key[1] 251 | 252 | "check if the mapping can be applied: this only runs for buffer mappings 253 | "a:1 is a bool that is true if mappings must be checked 254 | "a:2 can contain a list of mappings that will be applied anyway (forced) 255 | "otherwise, if a buffer mapping already exists, the remapping fails, and 256 | "a debug line is added 257 | if a:0 && a:1 && index(a:2, k) < 0 258 | let K = maparg(k, m, 0, 1) 259 | if !empty(K) && K.buffer 260 | let b = 'b'.bufnr('%').': ' 261 | " Handle Neovim mappings with Lua functions as rhs 262 | let rhs = has_key(K, 'rhs') ? K.rhs : '' 263 | if m != 'i' 264 | let s = b.'Could not map: '.k.' ('.a:plug.') -> ' . rhs 265 | call add(b:VM_Debug.lines, s) 266 | return '' 267 | else 268 | let s = b.'Overwritten imap: '.k.' ('.a:plug.') -> ' . rhs 269 | call add(b:VM_Debug.lines, s) 270 | endif 271 | endif 272 | endif 273 | 274 | let p = substitute(a:plug, ' ', '-', 'g') 275 | let _ = a:buffer? ' ' : ' ' 276 | return m."map "._.k.' (VM-'.p.")" 277 | endfun 278 | 279 | 280 | fun! s:unmap(key, buffer) abort 281 | " Create an unmap command that will be executed. 282 | let k = a:key[0] 283 | if empty(k) | return '' | endif 284 | let m = a:key[1] 285 | let b = a:buffer? ' ' : ' ' 286 | return "silent! ".m."unmap".b.k 287 | endfun 288 | 289 | 290 | fun! s:check_warnings() abort 291 | " Notify once per buffer if errors have happened. 292 | if get(g:, 'VM_show_warnings', 1) && !empty(b:VM_Debug.lines) 293 | \ && !has_key(b:VM_Debug, 'maps_warning') 294 | let b:VM_Debug.maps_warning = 1 295 | call s:V.Funcs.msg('VM has started with warnings. :VMDebug for more info') 296 | endif 297 | endfun 298 | 299 | " vim: et ts=4 sw=4 sts=4 fdm=indent fdn=1 : 300 | -------------------------------------------------------------------------------- /autoload/vm/maps/all.vim: -------------------------------------------------------------------------------- 1 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | "Key -> plug: 3 | " 'Select Operator' -> (VM-Select-Operator) 4 | 5 | "Contents of lists: 6 | " [0]: mapping 7 | " [1]: mode 8 | " 9 | " When adding a new mapping, the following is required: 10 | " 1. add a with a command 11 | " 2. add the reformatted plug name in this file (permanent or buffer section) 12 | 13 | let s:base = { 14 | \"Reselect Last": ['', 'n'], 15 | \"Add Cursor At Pos": ['', 'n'], 16 | \"Add Cursor At Word": ['', 'n'], 17 | \"Start Regex Search": ['', 'n'], 18 | \"Select All": ['', 'n'], 19 | \"Add Cursor Down": ['', 'n'], 20 | \"Add Cursor Up": ['', 'n'], 21 | \"Visual Regex": ['', 'x'], 22 | \"Visual All": ['', 'x'], 23 | \"Visual Add": ['', 'x'], 24 | \"Visual Find": ['', 'x'], 25 | \"Visual Cursors": ['', 'x'], 26 | \"Find Under": ['', 'n'], 27 | \"Find Subword Under": ['', 'x'], 28 | \"Select Cursor Down": ['', 'n'], 29 | \"Select Cursor Up": ['', 'n'], 30 | \"Select j": ['', 'n'], 31 | \"Select k": ['', 'n'], 32 | \"Select l": ['', 'n'], 33 | \"Select h": ['', 'n'], 34 | \"Select w": ['', 'n'], 35 | \"Select b": ['', 'n'], 36 | \"Select E": ['', 'n'], 37 | \"Select BBW": ['', 'n'], 38 | \"Mouse Cursor": ['', 'n'], 39 | \"Mouse Word": ['', 'n'], 40 | \"Mouse Column": ['', 'n'], 41 | \} 42 | 43 | fun! vm#maps#all#permanent() abort 44 | """Default permanent mappings dictionary.""" 45 | let maps = s:base 46 | let leader = g:Vm.leader.default 47 | let visual = g:Vm.leader.visual 48 | 49 | " map in any case 50 | let maps["Find Under"][0] = '' 51 | let maps["Find Subword Under"][0] = '' 52 | 53 | if g:VM_default_mappings 54 | let maps["Reselect Last"][0] = leader.'gS' 55 | let maps["Add Cursor At Pos"][0] = leader.'\' 56 | let maps["Start Regex Search"][0] = leader.'/' 57 | let maps["Select All"][0] = leader.'A' 58 | let maps["Add Cursor Down"][0] = '' 59 | let maps["Add Cursor Up"][0] = '' 60 | let maps["Select l"][0] = '' 61 | let maps["Select h"][0] = '' 62 | let maps["Visual Regex"][0] = visual.'/' 63 | let maps["Visual All"][0] = visual.'A' 64 | let maps["Visual Add"][0] = visual.'a' 65 | let maps["Visual Find"][0] = visual.'f' 66 | let maps["Visual Cursors"][0] = visual.'c' 67 | endif 68 | 69 | if g:VM_mouse_mappings 70 | let maps["Mouse Cursor"][0] = '' 71 | let maps["Mouse Word"][0] = '' 72 | let maps["Mouse Column"][0] = '' 73 | endif 74 | 75 | return maps 76 | endfun 77 | 78 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 79 | 80 | fun! vm#maps#all#buffer() abort 81 | """Default buffer mappings dictionary.""" 82 | 83 | let maps = {} 84 | let leader = g:Vm.leader.buffer 85 | let visual = g:Vm.leader.visual 86 | 87 | "basic 88 | call extend(maps, { 89 | \"Switch Mode": ['', 'n'], 90 | \"Toggle Single Region": [leader.'', 'n'], 91 | \}) 92 | 93 | "select 94 | call extend(maps, { 95 | \"Find Next": ['n', 'n'], 96 | \"Find Prev": ['N', 'n'], 97 | \"Goto Next": [']', 'n'], 98 | \"Goto Prev": ['[', 'n'], 99 | \"Seek Up": ['', 'n'], 100 | \"Seek Down": ['', 'n'], 101 | \"Skip Region": ['q', 'n'], 102 | \"Remove Region": ['Q', 'n'], 103 | \"Remove Last Region": [leader.'q', 'n'], 104 | \"Remove Every n Regions": [leader.'R', 'n'], 105 | \"Select Operator": ['s', 'n'], 106 | \"Find Operator": ['m', 'n'], 107 | \}) 108 | 109 | "utility 110 | call extend(maps, { 111 | \"Tools Menu": [leader.'`', 'n'], 112 | \"Show Registers": [leader.'"', 'n'], 113 | \"Case Setting": [leader.'c', 'n'], 114 | \"Toggle Whole Word": [leader.'w', 'n'], 115 | \"Case Conversion Menu": [leader.'C', 'n'], 116 | \"Search Menu": [leader.'S', 'n'], 117 | \"Rewrite Last Search": [leader.'r', 'n'], 118 | \"Show Infoline": [leader.'l', 'n'], 119 | \"One Per Line": [leader.'L', 'n'], 120 | \"Filter Regions": [leader.'f', 'n'], 121 | \"Toggle Multiline": ['M', 'n'], 122 | \}) 123 | 124 | "commands 125 | call extend(maps, { 126 | \"Undo": ['', 'n'], 127 | \"Redo": ['', 'n'], 128 | \"Surround": ['S', 'n'], 129 | \"Merge Regions": [leader.'m', 'n'], 130 | \"Transpose": [leader.'t', 'n'], 131 | \"Rotate": ['', 'n'], 132 | \"Duplicate": [leader.'d', 'n'], 133 | \"Align": [leader.'a', 'n'], 134 | \"Split Regions": [leader.'s', 'n'], 135 | \"Visual Subtract": [visual.'s', 'x'], 136 | \"Visual Reduce": [visual.'r', 'x'], 137 | \"Run Normal": [leader.'z', 'n'], 138 | \"Run Last Normal": [leader.'Z', 'n'], 139 | \"Run Visual": [leader.'v', 'n'], 140 | \"Run Last Visual": [leader.'V', 'n'], 141 | \"Run Ex": [leader.'x', 'n'], 142 | \"Run Last Ex": [leader.'X', 'n'], 143 | \"Run Macro": [leader.'@', 'n'], 144 | \"Run Dot": [leader.'.', 'n'], 145 | \"Align Char": [leader.'<', 'n'], 146 | \"Align Regex": [leader.'>', 'n'], 147 | \"Numbers": [leader.'N', 'n'], 148 | \"Numbers Append": [leader.'n', 'n'], 149 | \"Zero Numbers": [leader.'0N', 'n'], 150 | \"Zero Numbers Append": [leader.'0n', 'n'], 151 | \"Shrink": [leader.'-', 'n'], 152 | \"Enlarge": [leader.'+', 'n'], 153 | \"Goto Regex": [leader.'g', 'n'], 154 | \"Goto Regex!": [leader.'G', 'n'], 155 | \"Slash Search": ['g/', 'n'], 156 | \}) 157 | 158 | "arrows 159 | call extend(maps, { 160 | \"Select Cursor Down": ['', 'n'], 161 | \"Select Cursor Up": ['', 'n'], 162 | \"Add Cursor Down": ['', 'n'], 163 | \"Add Cursor Up": ['', 'n'], 164 | \"Select j": ['', 'n'], 165 | \"Select k": ['', 'n'], 166 | \"Select l": ['', 'n'], 167 | \"Select h": ['', 'n'], 168 | \"Single Select l": ['', 'n'], 169 | \"Single Select h": ['', 'n'], 170 | \"Select e": ['', 'n'], 171 | \"Select ge": ['', 'n'], 172 | \"Select w": ['', 'n'], 173 | \"Select b": ['', 'n'], 174 | \"Select E": ['', 'n'], 175 | \"Select BBW": ['', 'n'], 176 | \"Move Right": ['', 'n'], 177 | \"Move Left": ['', 'n'], 178 | \}) 179 | 180 | "insert 181 | call extend(maps, { 182 | \"I Arrow w": ['', 'i'], 183 | \"I Arrow b": ['', 'i'], 184 | \"I Arrow W": ['', 'i'], 185 | \"I Arrow B": ['', 'i'], 186 | \"I Arrow ge": ['', 'i'], 187 | \"I Arrow e": ['', 'i'], 188 | \"I Arrow gE": ['', 'i'], 189 | \"I Arrow E": ['', 'i'], 190 | \"I Left Arrow": ['', 'i'], 191 | \"I Right Arrow": ['', 'i'], 192 | \"I Up Arrow": ['', 'i'], 193 | \"I Down Arrow": ['', 'i'], 194 | \"I Return": ['', 'i'], 195 | \"I BS": ['', 'i'], 196 | \"I CtrlW": ['', 'i'], 197 | \"I CtrlU": ['', 'i'], 198 | \"I CtrlD": ['', 'i'], 199 | \"I Ctrl^": ['', 'i'], 200 | \"I Del": ['', 'i'], 201 | \"I Home": ['', 'i'], 202 | \"I End": ['', 'i'], 203 | \"I CtrlB": ['', 'i'], 204 | \"I CtrlF": ['', 'i'], 205 | \"I CtrlC": ['', 'i'], 206 | \"I CtrlO": ['', 'i'], 207 | \"I Replace": ['', 'i'], 208 | \}) 209 | 210 | let insert_keys = get(g:, 'VM_insert_special_keys', ['c-v']) 211 | if index(insert_keys, 'c-a') >= 0 212 | let maps["I CtrlA"] = ['', 'i'] 213 | endif 214 | if index(insert_keys, 'c-e') >= 0 215 | let maps["I CtrlE"] = ['', 'i'] 216 | endif 217 | if index(insert_keys, 'c-v') >= 0 218 | let maps["I Paste"] = ['', 'i'] 219 | endif 220 | 221 | "edit 222 | call extend(maps, { 223 | \"D": ['D', 'n'], 224 | \"Y": ['Y', 'n'], 225 | \"x": ['x', 'n'], 226 | \"X": ['X', 'n'], 227 | \"J": ['J', 'n'], 228 | \"~": ['~', 'n'], 229 | \"&": ['&', 'n'], 230 | \"Del": ['', 'n'], 231 | \"Dot": ['.', 'n'], 232 | \"Increase": ['', 'n'], 233 | \"Decrease": ['', 'n'], 234 | \"gIncrease": ['g', 'n'], 235 | \"gDecrease": ['g', 'n'], 236 | \"Alpha Increase": [leader.'','n'], 237 | \"Alpha Decrease": [leader.'','n'], 238 | \"a": ['a', 'n'], 239 | \"A": ['A', 'n'], 240 | \"i": ['i', 'n'], 241 | \"I": ['I', 'n'], 242 | \"o": ['o', 'n'], 243 | \"O": ['O', 'n'], 244 | \"c": ['c', 'n'], 245 | \"gc": ['gc', 'n'], 246 | \"gu": ['gu', 'n'], 247 | \"gU": ['gU', 'n'], 248 | \"C": ['C', 'n'], 249 | \"Delete": ['d', 'n'], 250 | \"Replace Characters": ['r', 'n'], 251 | \"Replace": ['R', 'n'], 252 | \"Transform Regions": [leader.'e', 'n'], 253 | \"p Paste": ['p', 'n'], 254 | \"P Paste": ['P', 'n'], 255 | \"Yank": ['y', 'n'], 256 | \}) 257 | 258 | return maps 259 | endfun 260 | 261 | " vim: et ts=2 sw=2 sts=2 : 262 | -------------------------------------------------------------------------------- /autoload/vm/operators.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Select operator 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | fun! vm#operators#init() abort 6 | let s:V = b:VM_Selection 7 | let s:v = s:V.Vars 8 | let s:G = s:V.Global 9 | let s:F = s:V.Funcs 10 | endfun 11 | 12 | fun! s:init() abort 13 | let g:Vm.extend_mode = 1 14 | if !g:Vm.buffer | call vm#init_buffer(0) | endif 15 | endfun 16 | 17 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 18 | " Lambdas 19 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 20 | 21 | let s:R = { -> s:V.Regions } 22 | let s:single = { c -> index(split('hljkwebWEB$^0{}()%nN', '\zs'), c) >= 0 } 23 | let s:double = { c -> index(split('iafFtTg', '\zs'), c) >= 0 } 24 | 25 | 26 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 27 | 28 | fun! vm#operators#select(count, ...) abort 29 | call s:init() 30 | let pos = getpos('.')[1:2] 31 | call s:F.Scroll.get(1) 32 | 33 | if a:0 | return s:select(a:1) | endif 34 | 35 | let [ abort, s, n ] = [ 0, '', '' ] 36 | let x = a:count>1? a:count : 1 37 | echo "Selecting: ".(x>1? x : '') 38 | 39 | while 1 40 | let c = getchar() 41 | 42 | if c == 27 | let abort = 1 | break 43 | else | let c = nr2char(c) | endif 44 | 45 | if str2nr(c) > 0 46 | let n .= c | echon c 47 | 48 | elseif s:single(c) 49 | let s .= c | echon c | break 50 | 51 | elseif s:double(c) || len(s) 52 | let s .= c | echon c 53 | if len(s) > 1 | break | endif 54 | 55 | else 56 | let abort = 1 | break 57 | endif 58 | endwhile 59 | 60 | if abort | return | endif 61 | 62 | " change $ in g_ 63 | let s = substitute(s, '\$', 'g_', 'g') 64 | 65 | let n = n<1? 1 : n 66 | let n = n*x>1? n*x : '' 67 | call s:select(n . s) 68 | call s:G.update_and_select_region(pos) 69 | call s:F.Scroll.restore() 70 | endfun 71 | 72 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 73 | 74 | fun! s:select(obj) abort 75 | call s:updatetime() 76 | call s:V.Maps.disable(1) 77 | 78 | if a:obj =~ '"' || a:obj =~ "'" 79 | let cmd = 'v' . a:obj . 'y' 80 | else 81 | let cmd = 'y' . a:obj 82 | endif 83 | 84 | silent! nunmap y 85 | 86 | let Rs = map(copy(s:R()), '[v:val.l, v:val.a]') 87 | call s:G.erase_regions() 88 | 89 | for r in Rs 90 | call cursor(r[0], r[1]) 91 | exe "normal" cmd 92 | call s:get_region(0) 93 | endfor 94 | 95 | call s:V.Maps.enable() 96 | call s:G.check_mutliline(1) 97 | 98 | nmap y (VM-Yank) 99 | 100 | if empty(s:v.search) | let @/ = '' | endif 101 | call s:old_updatetime() 102 | endfun 103 | 104 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 105 | 106 | fun! vm#operators#after_yank() abort 107 | "find operator 108 | if g:Vm.finding 109 | let g:Vm.finding = 0 110 | call vm#operators#find(0, s:v.visual_regex) 111 | let s:v.visual_regex = 0 112 | call s:old_updatetime() 113 | nmap y (VM-Yank) 114 | endif 115 | endfun 116 | 117 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 118 | 119 | fun! s:get_region(add_pattern) abort 120 | """Create region with select operator. 121 | let R = s:G.region_at_pos() 122 | if !empty(R) | return R | endif 123 | 124 | let R = vm#region#new(0) 125 | "R.txt can be different because yank != visual yank 126 | call R.update_content() 127 | if a:add_pattern 128 | call s:V.Search.add_if_empty() 129 | endif 130 | call s:F.restore_reg() 131 | return R 132 | endfun 133 | 134 | 135 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 136 | " Find operator 137 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 138 | 139 | fun! vm#operators#find(start, visual, ...) abort 140 | if a:start 141 | if !g:Vm.buffer 142 | call s:backup_map_find() 143 | if a:visual 144 | "use search register if just starting from visual mode 145 | call s:V.Search.get_slash_reg(s:v.oldsearch[0]) 146 | endif 147 | else 148 | call s:V.Search.ensure_is_set() 149 | call s:backup_map_find() 150 | endif 151 | 152 | call s:updatetime() 153 | let g:Vm.finding = 1 154 | let s:vblock = a:visual && mode() == "\" 155 | silent! nunmap y 156 | return 'y' 157 | endif 158 | 159 | "set the cursor to the start of the yanked region, then find occurrences until end mark is met 160 | let [endline, endcol] = getpos("']")[1:2] 161 | keepjumps normal! `[ 162 | let [startline, startcol] = getpos('.')[1:2] 163 | 164 | if !search(join(s:v.search, '\|'), 'znp', endline) 165 | call s:merge_find() 166 | if !len(s:R()) 167 | call vm#reset(1) 168 | endif 169 | return 170 | endif 171 | 172 | let ows = &wrapscan 173 | set nowrapscan 174 | silent keepjumps normal! ygn 175 | if s:vblock 176 | let R = getpos('.')[2] 177 | if !( R < startcol || R > endcol ) 178 | call s:G.new_region() 179 | endif 180 | else 181 | call s:G.new_region() 182 | endif 183 | 184 | while 1 185 | if !search(join(s:v.search, '\|'), 'znp', endline) | break | endif 186 | silent keepjumps normal! nygn 187 | if getpos("'[")[1] > endline 188 | break 189 | elseif s:vblock 190 | let R = getpos('.')[2] 191 | if ( R < startcol || R > endcol ) 192 | continue 193 | endif 194 | endif 195 | call s:G.new_region() 196 | endwhile 197 | let &wrapscan = ows 198 | call s:merge_find() 199 | endfun 200 | 201 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 202 | " Helpers 203 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 204 | 205 | fun! s:updatetime() abort 206 | """If not using TextYankPost, use CursorHold and reduce &updatetime. 207 | if g:Vm.oldupdate 208 | let &updatetime = 100 209 | endif 210 | endfun 211 | 212 | fun! s:old_updatetime() abort 213 | """Restore old &updatetime value. 214 | if g:Vm.oldupdate 215 | let &updatetime = g:Vm.oldupdate 216 | endif 217 | endfun 218 | 219 | fun! s:backup_map_find() abort 220 | "use temporary regions, they will be merged later 221 | call s:init() 222 | let s:Bytes = copy(s:V.Bytes) 223 | let s:V.Regions = [] 224 | let s:V.Bytes = {} 225 | let s:v.index = -1 226 | let s:v.no_search = 1 227 | let s:v.eco = 1 228 | endfun 229 | 230 | fun! s:merge_find() abort 231 | let new_map = copy(s:V.Bytes) 232 | let s:V.Bytes = s:Bytes 233 | call s:G.merge_maps(new_map) 234 | unlet new_map 235 | endfun 236 | 237 | " vim: et ts=4 sw=4 sts=4 : 238 | -------------------------------------------------------------------------------- /autoload/vm/search.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Initialize 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | fun! vm#search#init() abort 6 | let s:V = b:VM_Selection 7 | let s:v = s:V.Vars 8 | let s:F = s:V.Funcs 9 | let s:G = s:V.Global 10 | return s:Search 11 | endfun 12 | 13 | 14 | 15 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 16 | " Search 17 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 18 | 19 | let s:Search = {} 20 | let s:R = { -> s:V.Regions } 21 | let s:no_visual = { p -> substitute(p, '\\%V', '', 'g') } 22 | 23 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 24 | 25 | 26 | fun! s:update_search(p) abort 27 | " Update search patterns, unless s:v.no_search is set. 28 | if s:v.no_search | return | endif 29 | 30 | if !empty(a:p) && index(s:v.search, a:p) < 0 "not in list 31 | call insert(s:v.search, a:p) 32 | endif 33 | 34 | if s:v.eco | let @/ = s:v.search[0] 35 | else | call s:Search.join() 36 | endif 37 | endfun 38 | 39 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 40 | 41 | 42 | fun! s:Search.get_pattern(register) abort 43 | let t = getreg(a:register) 44 | let t = self.escape_pattern(t) 45 | let p = s:v.whole_word ? '\<'.t.'\>' : t 46 | "if whole word, ensure pattern can be found 47 | let p = search(p, 'ncw')? p : t 48 | return p 49 | endfun 50 | 51 | 52 | fun! s:Search.add(...) abort 53 | " Add a new search pattern. 54 | let pat = a:0? a:1 : self.get_pattern(s:v.def_reg) 55 | call s:update_search(pat) 56 | endfun 57 | 58 | 59 | fun! s:Search.add_if_empty(...) abort 60 | " Add a new search pattern, only if no pattern is set. 61 | if empty(s:v.search) 62 | if a:0 | call self.add(a:1) 63 | else | call self.add(s:R()[s:v.index].pat) 64 | endif 65 | endif 66 | endfun 67 | 68 | 69 | fun! s:Search.ensure_is_set(...) abort 70 | " Ensure there is an active search. 71 | if empty(s:v.search) 72 | if !len(s:R()) || empty(s:R()[0].txt) 73 | call self.get_slash_reg() 74 | else 75 | call self.add(self.escape_pattern(s:R()[0].txt)) 76 | endif 77 | endif 78 | endfun 79 | 80 | 81 | fun! s:Search.get_from_region() abort 82 | " Get a new search pattern from the selected region, with a fallback. 83 | let r = s:G.region_at_pos() 84 | if !empty(r) 85 | let pat = self.escape_pattern(r.txt) 86 | call s:update_search(pat) | return 87 | endif 88 | 89 | "fallback to first region.txt or @/, if no active search 90 | if empty(s:v.search) | call self.ensure_is_set() | endif 91 | endfun 92 | 93 | 94 | fun! s:Search.get_slash_reg(...) abort 95 | " Get pattern from current "/" register. Use backup register if empty. 96 | if a:0 | let @/ = a:1 | endif 97 | call s:update_search(s:no_visual(getreg('/'))) 98 | if empty(s:v.search) 99 | call s:update_search(s:no_visual(s:v.oldsearch[0])) 100 | endif 101 | endfun 102 | 103 | 104 | fun! s:Search.validate() abort 105 | " Check whether the current search is valid, if not, clear the search. 106 | if s:v.eco || empty(s:v.search) | return v:false | endif 107 | 108 | call self.join() 109 | 110 | "pattern found, ok 111 | if search(@/, 'cnw') | return v:true | endif 112 | 113 | while 1 114 | let i = 0 115 | for p in s:v.search 116 | if !search(@/, 'cnw') | call remove(s:v.search, i) | break | endif 117 | let i += 1 118 | endfor 119 | break 120 | endwhile 121 | call self.join() 122 | return v:true 123 | endfun 124 | 125 | 126 | fun! s:Search.update_patterns(...) abort 127 | " Update the search patterns if the active search isn't listed. 128 | let current = a:0? [a:1] : split(@/, '\\|') 129 | for p in current 130 | if index(s:v.search, p) >= 0 | return | endif 131 | endfor 132 | if a:0 | call self.get_from_region() 133 | else | call self.get_slash_reg() 134 | endif 135 | endfun 136 | 137 | 138 | fun! s:Search.escape_pattern(t) abort 139 | return substitute(escape(a:t, '\/.*$^~[]'), "\n", '\\n', "g") 140 | endfun 141 | 142 | 143 | fun! s:Search.join(...) abort 144 | " Join current patterns, optionally replacing them. 145 | if a:0 | let s:v.search = a:1 | endif 146 | let @/ = join(s:v.search, '\|') 147 | endfun 148 | 149 | 150 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 151 | " Search menu and options 152 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 153 | 154 | 155 | fun! s:pattern_rewritten(t, i) abort 156 | " Return true if a pattern has been rewritten. 157 | if @/ == '' | return | endif 158 | 159 | let p = s:v.search[a:i] 160 | if a:t =~ p || p =~ a:t 161 | let old = s:v.search[a:i] 162 | let s:v.search[a:i] = a:t 163 | call s:G.update_region_patterns(a:t) 164 | call s:Search.join() 165 | let [ wm, L ] = [ 'WarningMsg', 'Label' ] 166 | call s:F.msg([['Pattern updated: [', wm ], [old, L], 167 | \ ['] -> [', wm], [a:t, L], 168 | \ ["]\n", wm]]) 169 | return 1 170 | endif 171 | endfun 172 | 173 | 174 | fun! s:Search.rewrite(last) abort 175 | " Rewrite patterns, if substrings of the selected text. 176 | let r = s:G.region_at_pos() | if empty(r) | return | endif 177 | 178 | let t = self.escape_pattern(r.txt) 179 | 180 | if a:last 181 | "add a new pattern if not found 182 | if !s:pattern_rewritten(t, 0) 183 | call self.add(t) 184 | endif 185 | else 186 | "rewrite if found among any pattern, else do nothing 187 | for i in range ( len(s:v.search) ) 188 | if s:pattern_rewritten(t, i) | break | endif 189 | endfor 190 | endif 191 | endfun 192 | 193 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 194 | 195 | 196 | fun! s:update_current(...) abort 197 | " Update current search pattern to index 0 or . 198 | 199 | if empty(s:v.search) | let @/ = '' 200 | elseif !a:0 | let @/ = s:v.search[0] 201 | elseif a:1 < 0 | let @/ = s:v.search[0] 202 | elseif a:1 >= len(s:v.search) | let @/ = s:v.search[a:1-1] 203 | else | let @/ = s:v.search[a:1] 204 | endif 205 | endfun 206 | 207 | 208 | fun! s:Search.remove(also_regions) abort 209 | " Remove a search pattern, and optionally its associated regions. 210 | let pats = s:v.search 211 | 212 | if !empty(pats) 213 | let s1 = ['Which index? ', 'WarningMsg'] 214 | let s2 = [string(s:v.search), 'Type'] 215 | call s:F.msg([s1,s2]) 216 | let i = nr2char(getchar()) 217 | if ( i == "\" ) | return s:F.msg("\tCanceled.\n") | endif 218 | if ( i < 0 || i >= len(pats) ) | return s:F.msg("\tWrong index\n") | endif 219 | call s:F.msg("\n") 220 | let pat = pats[i] 221 | call remove(pats, i) 222 | call s:update_current() 223 | else 224 | return s:F.msg('No search patters yet.') 225 | endif 226 | 227 | if a:also_regions 228 | let i = len(s:R()) - 1 | let removed = 0 229 | while i>=0 230 | if s:R()[i].pat ==# pat 231 | call s:R()[i].remove() 232 | let removed += 1 233 | endif 234 | let i -= 1 235 | endwhile 236 | 237 | if removed | call s:G.update_and_select_region() | endif 238 | endif 239 | endfun 240 | 241 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 242 | 243 | 244 | fun! s:Search.case() abort 245 | " Cycle case settings. 246 | if &smartcase "smartcase -> case sensitive 247 | set nosmartcase 248 | set noignorecase 249 | call s:F.msg([['Search -> ', 'WarningMsg'], [' case sensitive', 'Label']]) 250 | 251 | elseif !&ignorecase "case sensitive -> ignorecase 252 | set ignorecase 253 | call s:F.msg([['Search -> ', 'WarningMsg'], [' ignore case', 'Label']]) 254 | 255 | else "ignore case -> smartcase 256 | set smartcase 257 | set ignorecase 258 | call s:F.msg([['Search -> ', 'WarningMsg'], [' smartcase', 'Label']]) 259 | endif 260 | endfun 261 | 262 | 263 | fun! s:Search.menu() abort 264 | echohl WarningMsg | echo "1 - " | echohl Type | echon "Rewrite Last Search" | echohl None 265 | echohl WarningMsg | echo "2 - " | echohl Type | echon "Rewrite All Search" | echohl None 266 | echohl WarningMsg | echo "3 - " | echohl Type | echon "Read From Search" | echohl None 267 | echohl WarningMsg | echo "4 - " | echohl Type | echon "Add To Search" | echohl None 268 | echohl WarningMsg | echo "5 - " | echohl Type | echon "Remove Search" | echohl None 269 | echohl WarningMsg | echo "6 - " | echohl Type | echon "Remove Search Regions" | echohl None 270 | echohl Directory | echo "Enter an option: " | echohl None 271 | let c = nr2char(getchar()) 272 | echon c "\t" 273 | if c == 1 274 | call self.rewrite(1) 275 | elseif c == 2 276 | call self.rewrite(0) 277 | elseif c == 3 278 | call self.get_slash_reg() 279 | elseif c == 4 280 | call self.get_from_region() 281 | elseif c == 5 282 | call self.remove(0) 283 | elseif c == 6 284 | call self.remove(1) 285 | endif 286 | call feedkeys("\", 'n') 287 | endfun 288 | 289 | 290 | " vim: et sw=4 ts=4 sts=4 fdm=indent fdn=1 291 | -------------------------------------------------------------------------------- /autoload/vm/special/case.vim: -------------------------------------------------------------------------------- 1 | "mostly from abolish.vim, with some minor additions 2 | "abolish.vim by Tim Pope 3 | "https://github.com/tpope/vim-abolish 4 | 5 | fun! vm#special#case#init() abort 6 | let s:V = b:VM_Selection 7 | return s:Case 8 | endfun 9 | 10 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 11 | 12 | let s:X = { -> g:Vm.extend_mode } 13 | let s:R = { -> s:V.Regions } 14 | 15 | 16 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 17 | 18 | let s:Case = {} 19 | 20 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 21 | 22 | fun! s:Case.pascal(word) abort 23 | return substitute(self.camel(a:word),'^.','\u&','') 24 | endfun 25 | 26 | fun! s:Case.camel(word) abort 27 | let word = substitute(a:word,'[.-]','_','g') 28 | let word = substitute(word,' ','_','g') 29 | if word !~# '_' && word =~# '\l' 30 | return substitute(word,'^.','\l&','') 31 | else 32 | return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g') 33 | endif 34 | endfun 35 | 36 | fun! s:Case.snake(word) abort 37 | let word = substitute(a:word,'::','/','g') 38 | let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g') 39 | let word = substitute(word,'\(\l\|\d\)\(\u\)','\1_\2','g') 40 | let word = substitute(word,'[.-]','_','g') 41 | let word = substitute(word,' ','_','g') 42 | let word = tolower(word) 43 | return word 44 | endfun 45 | 46 | fun! s:Case.snake_upper(word) abort 47 | return toupper(self.snake(a:word)) 48 | endfun 49 | 50 | fun! s:Case.dash(word) abort 51 | return substitute(self.snake(a:word),'_','-','g') 52 | endfun 53 | 54 | fun! s:Case.space(word) abort 55 | return substitute(self.snake(a:word),'_',' ','g') 56 | endfun 57 | 58 | fun! s:Case.dot(word) abort 59 | return substitute(self.snake(a:word),'_','.','g') 60 | endfun 61 | 62 | fun! s:Case.title(word) abort 63 | return substitute(self.space(a:word), '\(\<\w\)','\=toupper(submatch(1))','g') 64 | endfun 65 | 66 | fun! s:Case.lower(word) abort 67 | return tolower(a:word) 68 | endfun 69 | 70 | fun! s:Case.upper(word) abort 71 | return toupper(a:word) 72 | endfun 73 | 74 | fun! s:Case.capitalize(word) abort 75 | return toupper(a:word[0:0]) . tolower(a:word[1:]) 76 | endfun 77 | 78 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 79 | 80 | fun! s:Case.menu() abort 81 | if get(g:, 'VM_verbose_commands', 0) 82 | echohl WarningMsg | echo "\tCase Conversion\n---------------------------------" 83 | echohl WarningMsg | echo "u " | echohl Type | echon "lowercase" | echohl None 84 | echohl WarningMsg | echo "U " | echohl Type | echon "UPPERCASE" | echohl None 85 | echohl WarningMsg | echo "C " | echohl Type | echon "Captialize" | echohl None 86 | echohl WarningMsg | echo "t " | echohl Type | echon "Title Case" | echohl None 87 | echohl WarningMsg | echo "c " | echohl Type | echon "camelCase" | echohl None 88 | echohl WarningMsg | echo "P " | echohl Type | echon "PascalCase" | echohl None 89 | echohl WarningMsg | echo "s " | echohl Type | echon "snake_case" | echohl None 90 | echohl WarningMsg | echo "S " | echohl Type | echon "SNAKE_UPPERCASE" | echohl None 91 | echohl WarningMsg | echo "- " | echohl Type | echon "dash-case" | echohl None 92 | echohl WarningMsg | echo ". " | echohl Type | echon "dot.case" | echohl None 93 | echohl WarningMsg | echo " " | echohl Type | echon "space case" | echohl None 94 | echohl WarningMsg | echo "---------------------------------" 95 | echohl Directory | echo "Enter an option: " | echohl None 96 | else 97 | echohl Constant | echo "Case conversion: " | echohl None | echon '(u/U/C/t/c/P/s/S/-/./ )' 98 | endif 99 | let c = nr2char(getchar()) 100 | let case = { 101 | \ "u": 'lower', "U": 'upper', 102 | \ "C": 'capitalize', "t": 'title', 103 | \ "c": 'camel', "P": 'pascal', 104 | \ "s": 'snake', "S": 'snake_upper', 105 | \ "-": 'dash', "k": 'remove', 106 | \ ".": 'dot', " ": 'space', 107 | \} 108 | if has_key(case, c) 109 | call self.convert(case[c]) 110 | endif 111 | if get(g:, 'VM_verbose_commands', 0) 112 | call feedkeys("\", 'n') 113 | endif 114 | endfun 115 | 116 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 117 | 118 | fun! s:Case.convert(type) abort 119 | if !len(s:R()) | return | endif 120 | if !s:X() 121 | call vm#operators#select(1, 'iw') 122 | endif 123 | 124 | let text = [] | let g:Vm.registers['"'] = text 125 | for r in s:R() 126 | call add(text, eval("self.".a:type."(r.txt)")) 127 | endfor 128 | call b:VM_Selection.Edit.paste(1, 0, 1, '"') 129 | endfun 130 | " vim: et ts=2 sw=2 sts=2 : 131 | -------------------------------------------------------------------------------- /autoload/vm/themes.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | "Set up highlighting 3 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | let s:Themes = {} 6 | 7 | augroup VM_reset_theme 8 | au! 9 | au ColorScheme * call vm#themes#init() 10 | augroup END 11 | 12 | 13 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 14 | 15 | fun! vm#themes#init() abort 16 | if !exists('g:Vm') | return | endif 17 | 18 | if !empty(g:VM_highlight_matches) 19 | let out = execute('highlight Search') 20 | if match(out, ' links to ') >= 0 21 | let hi = substitute(out, '^.*links to ', '', '') 22 | let g:Vm.search_hi = "link Search " . hi 23 | else 24 | let hi = strtrans(substitute(out, '^.*xxx ', '', '')) 25 | let hi = substitute(hi, '\^.', '', 'g') 26 | let g:Vm.search_hi = "Search " . hi 27 | endif 28 | 29 | call vm#themes#search_highlight() 30 | endif 31 | 32 | let theme = get(g:, 'VM_theme', '') 33 | 34 | if theme == 'default' 35 | hi! link VM_Mono ErrorMsg 36 | hi! link VM_Cursor Visual 37 | hi! link VM_Extend PmenuSel 38 | hi! link VM_Insert DiffChange 39 | hi! link MultiCursor VM_Cursor 40 | 41 | elseif has_key(s:Themes, theme) 42 | call s:Themes[theme]() 43 | endif 44 | endfun 45 | 46 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 47 | 48 | fun! vm#themes#search_highlight() abort 49 | " Init Search highlight. 50 | let hl = g:VM_highlight_matches 51 | let g:Vm.Search = hl == 'underline' ? 'Search term=underline cterm=underline gui=underline' : 52 | \ hl == 'red' ? 'Search ctermfg=196 guifg=#ff0000' : 53 | \ hl =~ '^hi!\? ' ? substitute(g:VM_highlight_matches, '^hi!\?', '', '') 54 | \ : 'Search term=underline cterm=underline gui=underline' 55 | endfun 56 | 57 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 58 | 59 | fun! vm#themes#load(theme) abort 60 | " Load a theme or set default. 61 | if empty(a:theme) || a:theme == 'default' 62 | let g:VM_theme = 'default' 63 | elseif index(keys(s:Themes), a:theme) < 0 64 | echo "No such theme." 65 | return 66 | else 67 | let g:VM_theme = a:theme 68 | endif 69 | call vm#themes#init() 70 | endfun 71 | 72 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 73 | 74 | fun! vm#themes#complete(A, L, P) abort 75 | let valid = &background == 'light' ? s:Themes._light : s:Themes._dark 76 | return filter(sort(copy(valid)), 'v:val=~#a:A') 77 | endfun 78 | 79 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 80 | 81 | fun! vm#themes#statusline() abort 82 | if !exists('b:visual_multi') 83 | return '' 84 | endif 85 | let v = b:VM_Selection.Vars 86 | let vm = VMInfos() 87 | let color = '%#VM_Extend#' 88 | let single = b:VM_Selection.Vars.single_region ? '%#VM_Mono# SINGLE ' : '' 89 | try 90 | if v.insert 91 | if b:VM_Selection.Insert.replace 92 | let [ mode, color ] = [ 'V-R', '%#VM_Mono#' ] 93 | else 94 | let [ mode, color ] = [ 'V-I', '%#VM_Cursor#' ] 95 | endif 96 | else 97 | let mode = { 'n': 'V-M', 'v': 'V', 'V': 'V-L', "\": 'V-B' }[mode()] 98 | endif 99 | catch 100 | let mode = 'V-M' 101 | endtry 102 | let mode = exists('v.statusline_mode') ? v.statusline_mode : mode 103 | let patterns = string(vm.patterns)[:(winwidth(0)-30)] 104 | return printf("%s %s %s %s %s%s %s %%=%%l:%%c %s %s", 105 | \ color, mode, '%#VM_Insert#', vm.ratio, single, '%#TabLine#', 106 | \ patterns, color, vm.status . ' ') 107 | endfun 108 | 109 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 110 | 111 | let s:Themes._light = ['sand', 'paper', 'lightblue1', 'lightblue2', 'lightpurple1', 'lightpurple2'] 112 | let s:Themes._dark = ['iceblue', 'ocean', 'neon', 'purplegray', 'nord', 'codedark', 'spacegray', 'olive', 'sand'] 113 | 114 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 115 | 116 | fun! s:Themes.iceblue() 117 | hi! VM_Extend ctermbg=24 guibg=#005f87 118 | hi! VM_Cursor ctermbg=31 ctermfg=237 guibg=#0087af guifg=#87dfff 119 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 120 | hi! VM_Mono ctermbg=180 ctermfg=235 guibg=#dfaf87 guifg=#262626 121 | endfun 122 | 123 | fun! s:Themes.ocean() 124 | hi! VM_Extend ctermbg=25 guibg=#005faf 125 | hi! VM_Cursor ctermbg=39 ctermfg=239 guibg=#87afff guifg=#4e4e4e 126 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 127 | hi! VM_Mono ctermbg=186 ctermfg=239 guibg=#dfdf87 guifg=#4e4e4e 128 | endfun 129 | 130 | fun! s:Themes.neon() 131 | hi! VM_Extend ctermbg=26 ctermfg=109 guibg=#005fdf guifg=#89afaf 132 | hi! VM_Cursor ctermbg=39 ctermfg=239 guibg=#00afff guifg=#4e4e4e 133 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 134 | hi! VM_Mono ctermbg=221 ctermfg=239 guibg=#ffdf5f guifg=#4e4e4e 135 | endfun 136 | 137 | fun! s:Themes.lightblue1() 138 | hi! VM_Extend ctermbg=153 guibg=#afdfff 139 | hi! VM_Cursor ctermbg=111 ctermfg=239 guibg=#87afff guifg=#4e4e4e 140 | hi! VM_Insert ctermbg=180 ctermfg=235 guibg=#dfaf87 guifg=#262626 141 | hi! VM_Mono ctermbg=167 ctermfg=253 guibg=#df5f5f guifg=#dadada cterm=bold term=bold gui=bold 142 | endfun 143 | 144 | fun! s:Themes.lightblue2() 145 | hi! VM_Extend ctermbg=117 guibg=#87dfff 146 | hi! VM_Cursor ctermbg=111 ctermfg=239 guibg=#87afff guifg=#4e4e4e 147 | hi! VM_Insert ctermbg=180 ctermfg=235 guibg=#dfaf87 guifg=#262626 148 | hi! VM_Mono ctermbg=167 ctermfg=253 guibg=#df5f5f guifg=#dadada cterm=bold term=bold gui=bold 149 | endfun 150 | 151 | fun! s:Themes.purplegray() 152 | hi! VM_Extend ctermbg=60 guibg=#544a65 153 | hi! VM_Cursor ctermbg=103 ctermfg=54 guibg=#8787af guifg=#5f0087 154 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 155 | hi! VM_Mono ctermbg=141 ctermfg=235 guibg=#af87ff guifg=#262626 156 | endfun 157 | 158 | fun! s:Themes.nord() 159 | hi! VM_Extend ctermbg=239 guibg=#434C5E 160 | hi! VM_Cursor ctermbg=245 ctermfg=24 guibg=#8a8a8a guifg=#005f87 161 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 162 | hi! VM_Mono ctermbg=131 ctermfg=235 guibg=#AF5F5F guifg=#262626 163 | endfun 164 | 165 | fun! s:Themes.codedark() 166 | hi! VM_Extend ctermbg=242 guibg=#264F78 167 | hi! VM_Cursor ctermbg=239 ctermfg=252 guibg=#6A7D89 guifg=#C5D4DD 168 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 169 | hi! VM_Mono ctermbg=131 ctermfg=235 guibg=#AF5F5F guifg=#262626 170 | endfun 171 | 172 | fun! s:Themes.spacegray() 173 | hi! VM_Extend ctermbg=237 guibg=#404040 174 | hi! VM_Cursor ctermbg=242 ctermfg=239 guibg=Grey50 guifg=#4e4e4e 175 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 176 | hi! VM_Mono ctermbg=131 ctermfg=235 guibg=#AF5F5F guifg=#262626 177 | endfun 178 | 179 | fun! s:Themes.sand() 180 | hi! VM_Extend ctermbg=143 ctermfg=0 guibg=darkkhaki guifg=black 181 | hi! VM_Cursor ctermbg=64 ctermfg=186 guibg=olivedrab guifg=khaki 182 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 183 | hi! VM_Mono ctermbg=131 ctermfg=235 guibg=#AF5F5F guifg=#262626 184 | endfun 185 | 186 | fun! s:Themes.paper() 187 | hi! VM_Extend ctermbg=250 ctermfg=16 guibg=#bfbcaf guifg=black 188 | hi! VM_Cursor ctermbg=239 ctermfg=188 guibg=#4c4e50 guifg=#d8d5c7 189 | hi! VM_Insert ctermbg=167 ctermfg=253 guibg=#df5f5f guifg=#dadada cterm=bold term=bold gui=bold 190 | hi! VM_Mono ctermbg=16 ctermfg=188 guibg=#000000 guifg=#d8d5c7 191 | endfun 192 | 193 | fun! s:Themes.olive() 194 | hi! VM_Extend ctermbg=3 ctermfg=0 guibg=olive guifg=black 195 | hi! VM_Cursor ctermbg=64 ctermfg=186 guibg=olivedrab guifg=khaki 196 | hi! VM_Insert ctermbg=239 guibg=#4c4e50 197 | hi! VM_Mono ctermbg=131 ctermfg=235 guibg=#AF5F5F guifg=#262626 198 | endfun 199 | 200 | fun! s:Themes.lightpurple1() 201 | hi! VM_Extend ctermbg=225 guibg=#ffdfff 202 | hi! VM_Cursor ctermbg=183 ctermfg=54 guibg=#dfafff guifg=#5f0087 cterm=bold term=bold gui=bold 203 | hi! VM_Insert ctermbg=146 ctermfg=235 guibg=#afafdf guifg=#262626 204 | hi! VM_Mono ctermbg=135 ctermfg=225 guibg=#af5fff guifg=#ffdfff cterm=bold term=bold gui=bold 205 | endfun 206 | 207 | fun! s:Themes.lightpurple2() 208 | hi! VM_Extend ctermbg=189 guibg=#dfdfff 209 | hi! VM_Cursor ctermbg=183 ctermfg=54 guibg=#dfafff guifg=#5f0087 cterm=bold term=bold gui=bold 210 | hi! VM_Insert ctermbg=225 ctermfg=235 guibg=#ffdfff guifg=#262626 211 | hi! VM_Mono ctermbg=135 ctermfg=225 guibg=#af5fff guifg=#ffdfff cterm=bold term=bold gui=bold 212 | endfun 213 | 214 | " vim: et ts=2 sw=2 sts=2 : 215 | -------------------------------------------------------------------------------- /autoload/vm/variables.vim: -------------------------------------------------------------------------------- 1 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Set vim variable to VM compatible values 3 | 4 | fun! vm#variables#set() abort 5 | let F = b:VM_Selection.Funcs 6 | let v = b:VM_Selection.Vars 7 | 8 | " disable folding, but keep winline 9 | if &foldenable 10 | call F.Scroll.get(1) 11 | let v.oldfold = 1 12 | set nofoldenable 13 | call F.Scroll.restore() 14 | endif 15 | 16 | if g:VM_case_setting ==? 'smart' 17 | set smartcase 18 | set ignorecase 19 | elseif g:VM_case_setting ==? 'sensitive' 20 | set nosmartcase 21 | set noignorecase 22 | elseif g:VM_case_setting ==? 'ignore' 23 | set nosmartcase 24 | set ignorecase 25 | endif 26 | 27 | "force default register 28 | set clipboard= 29 | 30 | "disable conceal 31 | let &l:conceallevel = vm#comp#conceallevel() 32 | set concealcursor= 33 | 34 | set virtualedit=onemore 35 | set ww=h,l,<,> 36 | set lz 37 | 38 | if get(g:, 'VM_cmdheight', 1) > 1 39 | let &ch = g:VM_cmdheight 40 | endif 41 | endfun 42 | 43 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 44 | " Init VM variables 45 | 46 | fun! vm#variables#init() abort 47 | let F = b:VM_Selection.Funcs 48 | let v = b:VM_Selection.Vars 49 | 50 | "init search 51 | let v.def_reg = F.default_reg() 52 | let v.oldreg = F.get_reg() 53 | let v.oldregs_1_9 = F.get_regs_1_9() 54 | let v.oldsearch = [getreg("/"), getregtype("/")] 55 | let v.noh = !v:hlsearch ? 'noh|' : '' 56 | 57 | "store old vars 58 | let v.oldhls = &hlsearch 59 | let v.oldvirtual = &virtualedit 60 | let v.oldwhichwrap = &whichwrap 61 | let v.oldlz = &lz 62 | let v.oldch = &ch 63 | let v.oldcase = [&smartcase, &ignorecase] 64 | let v.indentkeys = &indentkeys 65 | let v.cinkeys = &cinkeys 66 | let v.synmaxcol = &synmaxcol 67 | let v.oldmatches = getmatches() 68 | let v.clipboard = &clipboard 69 | let v.textwidth = &textwidth 70 | let v.conceallevel = &conceallevel 71 | let v.concealcursor = &concealcursor 72 | let v.softtabstop = &softtabstop 73 | let v.statusline = &statusline 74 | 75 | "init new vars 76 | 77 | let v.search = [] 78 | let v.IDs_list = [] 79 | let v.ID = 0 80 | let v.index = -1 81 | let v.direction = 1 82 | let v.nav_direction = 1 83 | let v.auto = 0 84 | let v.silence = 0 85 | let v.eco = 0 86 | let v.single_region = 0 87 | let v.using_regex = 0 88 | let v.multiline = 0 89 | let v.yanked = 0 90 | let v.merge = 0 91 | let v.insert = 0 92 | let v.whole_word = 0 93 | let v.winline = 0 94 | let v.restore_scroll = 0 95 | let v.find_all_overlap = 0 96 | let v.dot = '' 97 | let v.no_search = 0 98 | let v.visual_regex = 0 99 | let v.use_register = v.def_reg 100 | let v.deleting = 0 101 | let v.vmarks = [getpos("'<"), getpos("'>")] 102 | endfun 103 | 104 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 105 | " Reset vim variables to previous values 106 | 107 | fun! vm#variables#reset() abort 108 | let v = b:VM_Selection.Vars 109 | 110 | if !v.oldhls 111 | set nohlsearch 112 | endif 113 | 114 | let &virtualedit = v.oldvirtual 115 | let &whichwrap = v.oldwhichwrap 116 | let &smartcase = v.oldcase[0] 117 | let &ignorecase = v.oldcase[1] 118 | let &lz = v.oldlz 119 | let &cmdheight = v.oldch 120 | let &clipboard = v.clipboard 121 | 122 | let &l:indentkeys = v.indentkeys 123 | let &l:cinkeys = v.cinkeys 124 | let &l:synmaxcol = v.synmaxcol 125 | let &l:textwidth = v.textwidth 126 | let &l:softtabstop = v.softtabstop 127 | let &l:conceallevel = v.conceallevel 128 | let &l:concealcursor = v.concealcursor 129 | 130 | if get(g:, 'VM_set_statusline', 2) 131 | let &l:statusline = v.statusline 132 | endif 133 | 134 | silent! unlet b:VM_skip_reset_once_on_bufleave 135 | endfun 136 | 137 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 138 | " Reset VM global variables 139 | 140 | fun! vm#variables#reset_globals() 141 | let b:VM_Backup = {} 142 | let b:VM_Selection = {} 143 | let g:Vm.buffer = 0 144 | let g:Vm.extend_mode = 0 145 | let g:Vm.finding = 0 146 | endfun 147 | 148 | " vim: et ts=2 sw=2 sts=2 tw=79 : 149 | -------------------------------------------------------------------------------- /autoload/vm/visual.vim: -------------------------------------------------------------------------------- 1 | "commands to add/subtract regions with visual selection 2 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 3 | 4 | fun! vm#visual#add(mode) abort 5 | " Add visually selected region to current regions. 6 | call s:backup_map() 7 | let pos = getpos('.')[1:2] 8 | 9 | if a:mode ==# 'v' | call s:vchar() 10 | elseif a:mode ==# 'V' | call s:vline() 11 | else | let s:v.direction = s:vblock(1) 12 | endif 13 | 14 | call s:visual_merge() 15 | 16 | if a:mode ==# 'V' 17 | call s:G.split_lines() 18 | call s:G.remove_empty_lines() 19 | elseif a:mode ==# 'v' 20 | for r in s:R() 21 | if r.h | let s:v.multiline = 1 | break | endif 22 | endfor 23 | endif 24 | 25 | call s:G.update_and_select_region(pos) 26 | endfun 27 | 28 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 29 | 30 | fun! vm#visual#subtract(mode) abort 31 | " Subtract visually selected region from current regions. 32 | let X = s:backup_map() 33 | 34 | if a:mode ==# 'v' | call s:vchar() 35 | elseif a:mode ==# 'V' | call s:vline() 36 | else | call s:vblock(1) 37 | endif 38 | 39 | call s:visual_subtract() 40 | call s:G.update_and_select_region({'id': s:v.IDs_list[-1]}) 41 | if X | call s:G.cursor_mode() | endif 42 | endfun 43 | 44 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 45 | 46 | fun! vm#visual#reduce() abort 47 | " Remove regions outside of visual selection. 48 | let X = s:backup_map() 49 | call s:G.rebuild_from_map(s:Bytes, [s:F.pos2byte("'<"), s:F.pos2byte("'>")]) 50 | if X | call s:G.cursor_mode() | endif 51 | call s:G.update_and_select_region() 52 | endfun 53 | 54 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 55 | 56 | fun! vm#visual#cursors(mode) abort 57 | " Create cursors, one for each line of the visual selection. 58 | call s:init() 59 | let [pos, start, end] = [getpos('.')[1:2], 60 | \ getpos("'<")[1:2], getpos("'>")[1:2]] 61 | 62 | call s:create_cursors(start, end) 63 | 64 | if a:mode ==# 'V' && get(g:, 'VM_autoremove_empty_lines', 1) 65 | call s:G.remove_empty_lines() 66 | endif 67 | 68 | call s:G.update_and_select_region(pos) 69 | endfun 70 | 71 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 72 | 73 | fun! vm#visual#split() abort 74 | " Split regions with regex pattern. 75 | call s:init() 76 | if !len(s:R()) | return 77 | elseif !s:X() | return s:F.msg('Not in cursor mode.') | endif 78 | 79 | echohl Type | let pat = input('Pattern to remove > ') | echohl None 80 | if empty(pat) | return s:F.msg('Command aborted.') | endif 81 | 82 | let start = s:R()[0] "first region 83 | let stop = s:R()[-1] "last region 84 | 85 | call s:F.Cursor(start.A) "check for a match first 86 | if !search(pat, 'nczW', stop.L) "search method: accept at cursor position 87 | call s:F.msg("\t\tPattern not found") 88 | return s:G.select_region(s:v.index) 89 | endif 90 | 91 | call s:backup_map() 92 | 93 | "backup old patterns and create new regions 94 | let oldsearch = copy(s:v.search) 95 | call s:V.Search.get_slash_reg(pat) 96 | 97 | call s:G.get_all_regions(start.A, stop.B) 98 | 99 | "subtract regions and rebuild from map 100 | call s:visual_subtract() 101 | call s:V.Search.join(oldsearch) 102 | call s:G.update_and_select_region() 103 | endfun 104 | 105 | 106 | 107 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 108 | " Helpers 109 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 110 | 111 | fun! s:vchar() abort 112 | "characterwise selection 113 | silent keepjumps normal! ``] 114 | call s:G.check_mutliline(0, s:G.new_region()) 115 | endfun 116 | 117 | 118 | fun! s:vline() abort 119 | "linewise selection 120 | silent keepjumps normal! '`] 121 | call s:G.new_region() 122 | endfun 123 | 124 | 125 | fun! s:vblock(extend) abort 126 | "blockwise selection 127 | let start = getpos("'<")[1:2] 128 | let end = getpos("'>")[1:2] 129 | 130 | if ( start[1] > end[1] ) 131 | " swap columns because top-right or bottom-left corner is selected 132 | let temp = start[1] 133 | let start[1] = end[1] 134 | let end[1] = temp 135 | let inverted = line(".") == line("'>") 136 | else 137 | let inverted = line(".") == line("'<") 138 | endif 139 | 140 | let block_width = abs(virtcol("'>") - virtcol("'<")) 141 | 142 | call s:create_cursors(start, end) 143 | 144 | if a:extend && block_width 145 | call vm#commands#motion('l', block_width, 1, 0) 146 | endif 147 | return !inverted 148 | endfun 149 | 150 | 151 | fun! s:backup_map() abort 152 | "use temporary regions, they will be merged later 153 | call s:init() 154 | let X = s:G.extend_mode() 155 | let s:Bytes = copy(s:V.Bytes) 156 | call s:G.erase_regions() 157 | let s:v.no_search = 1 158 | let s:v.eco = 1 159 | return X 160 | endfun 161 | 162 | 163 | fun! s:visual_merge() abort 164 | "merge regions 165 | let new_map = copy(s:V.Bytes) 166 | let s:V.Bytes = s:Bytes 167 | call s:G.merge_maps(new_map) 168 | unlet new_map 169 | endfun 170 | 171 | 172 | fun! s:visual_subtract() abort 173 | "subtract regions 174 | let new_map = copy(s:V.Bytes) 175 | let s:V.Bytes = s:Bytes 176 | call s:G.subtract_maps(new_map) 177 | unlet new_map 178 | endfun 179 | 180 | 181 | fun! s:init() abort 182 | "init script vars 183 | let s:V = b:VM_Selection 184 | let s:v = s:V.Vars 185 | let s:G = s:V.Global 186 | let s:F = s:V.Funcs 187 | endfun 188 | 189 | 190 | fun! s:create_cursors(start, end) abort 191 | "create cursors that span over visual selection 192 | call cursor(a:start) 193 | 194 | if ( a:end[0] > a:start[0] ) 195 | while line('.') < a:end[0] 196 | call vm#commands#add_cursor_down(0, 1) 197 | endwhile 198 | 199 | elseif empty(s:G.region_at_pos()) 200 | " ensure there's at least a cursor 201 | call s:G.new_cursor() 202 | endif 203 | endfun 204 | 205 | 206 | let s:R = { -> s:V.Regions } 207 | let s:X = { -> g:Vm.extend_mode } 208 | 209 | 210 | " vim: et sw=4 ts=4 sts=4 fdm=indent fdn=1 211 | -------------------------------------------------------------------------------- /doc/vm-ex-commands.txt: -------------------------------------------------------------------------------- 1 | *vm-ex-commands.txt* Version 0.5.0 Last change: September 27 2019 2 | 3 | EX COMMANDS *vm-ex-commands* 4 | ============================================================================== 5 | 6 | The following Ex commands can be run at any time: 7 | 8 | |:VMDebug| 9 | |:VMClear| 10 | |:VMRegisters| 11 | |:VMSearch| 12 | |:VMLive| 13 | 14 | The following are only available inside a VM session: 15 | 16 | |:VMSort| 17 | |:VMQfix| 18 | |:VMFilterRegions| 19 | |:VMFilterLines| 20 | |:VMRegionsToBuffer| 21 | |:VMMassTranspose| 22 | 23 | Some of these commands are available in the `tools menu` (default mapping: `\\``). 24 | 25 | ============================================================================== 26 | 27 | VMDebug *:VMDebug* 28 | 29 | Print in the command line any error relative to the current buffer (if some 30 | mappings could not be applied). 31 | 32 | ------------------------------------------------------------------------------ 33 | 34 | VMClear *:VMClear* 35 | 36 | Clear any remnant of VM-specific highlight matches in the current buffer. It 37 | can be necessary if VM exited with errors for some reason. 38 | 39 | ------------------------------------------------------------------------------ 40 | 41 | [range] VMSearch[!] [pattern] *:VMSearch* 42 | 43 | Select all regions in [range] that matches the current search register 44 | |quote/|, or the given [pattern]. If is used, ignore [range], and just 45 | select the next occurrence. Examples: 46 | > 47 | :VMSearch! -> select the next occurrence of @/ 48 | :VMSearch! \ -> select the next occurrence of \ 49 | :VMSearch -> select occurrences of @/ in current line 50 | :%VMSearch \ -> select all occurrences of \ 51 | :'<,'>VMSearch \ -> range is visual selection 52 | 53 | ------------------------------------------------------------------------------ 54 | 55 | VMLive *:VMLive* 56 | 57 | Toggle |g:VM_live_editing|. 58 | 59 | ------------------------------------------------------------------------------ 60 | 61 | VMRegisters[!] [register] *:VMRegisters* 62 | 63 | Show VM registers, or single [register]. With , delete a [register], or 64 | all of them if no register is specified. 65 | 66 | ------------------------------------------------------------------------------ 67 | 68 | VMSort [arg] *:VMSort* 69 | 70 | Sort regions. Optional [arg] is the same as {func} of the |sort()| function. 71 | 72 | ------------------------------------------------------------------------------ 73 | 74 | VMQfix[!] *:VMQfix* 75 | 76 | Populate the |quickfix| window with the regions lines. 77 | If run with , use positions and contents instead. If run from cursor 78 | mode, the text will be empty, but the positions will be valid. 79 | 80 | ------------------------------------------------------------------------------ 81 | 82 | VMFilterRegions[!] [pattern] *:VMFilterRegions* 83 | 84 | Filter regions so that you keep only the ones match [pattern]. 85 | If a is used, keep the regions that don't match [pattern]. 86 | If no [pattern] is given, prompt for a pattern. 87 | 88 | In prompt mode, `Ctrl-x` can change the filter type, cycling among: 89 | > 90 | pattern: keep regions that match pattern 91 | !pattern: keep regions that don't match pattern 92 | expression: keep the regions that match the vim expression 93 | < 94 | ------------------------------------------------------------------------------ 95 | 96 | VMFilterLines *:VMFilterLines* 97 | 98 | Open a new buffer, filling it with all lines that contain at least a region. 99 | When saving (|:write| or |:update|) this temporary buffer, the lines in the 100 | original buffer are replaced with the corresponding lines in this buffer. 101 | If the number of lines doesn't match for some reason, the command fails. 102 | Use this feature only if you know what you're doing. 103 | 104 | ------------------------------------------------------------------------------ 105 | 106 | VMRegionsToBuffer *:VMRegionsToBuffer* 107 | 108 | Open a new buffer, filling it with the contents of each region, one per line. 109 | This works similarly to |:VMFilterLines|, in that it can update the original 110 | buffer on save. Use with caution. 111 | If the number of lines doesn't match the number of regions, the command fails. 112 | 113 | ------------------------------------------------------------------------------ 114 | 115 | VMMassTranspose *:VMMassTranspose* 116 | 117 | If you select two patterns, replace all occurrences of the first pattern with 118 | the second one, and viceversa. 119 | 120 | 121 | 122 | 123 | vim: ft=help et sw=2 ts=2 sts=2 tw=78 124 | 125 | -------------------------------------------------------------------------------- /doc/vm-faq.txt: -------------------------------------------------------------------------------- 1 | *vm-faq.txt* Version 0.3.0 Last change: June 15 2019 2 | 3 | FREQUENTLY ASKED QUESTIONS *vm-faq* 4 | =============================================================================== 5 | 6 | *vm-faq-errors* 7 | VM has quit with errors and I can't clear VM highlight matches.~ 8 | 9 | Run |:VMClear|. 10 | 11 | ------------------------------------------------------------------------------- 12 | *vm-faq-search* 13 | How to start VM looking for a pattern? ~ 14 | 15 | Run |:VMSearch|. It also accepts a pattern and a |range| (% finds all matches). 16 | 17 | ------------------------------------------------------------------------------- 18 | *vm-faq-mappings* 19 | I want to remap the main VM mappings.~ 20 | 21 | You have to initialize the mappings dictionary and replace individual mappings 22 | with your own. Below there are some examples. 23 | 24 | For a full list of mappings and other informations, see |vm-mappings.txt|. 25 | > 26 | let g:VM_maps = {} 27 | let g:VM_maps["Exit"] = '' " quit VM 28 | let g:VM_maps['Find Under'] = '' " replace C-n 29 | let g:VM_maps['Find Subword Under'] = '' " replace visual C-n 30 | let g:VM_maps["Add Cursor Down"] = '' " new cursor down 31 | let g:VM_maps["Add Cursor Up"] = '' " new cursor up 32 | let g:VM_maps["Toggle Mappings"] = '' " toggle VM buffer mappings 33 | 34 | To disable a specific mapping, set it to an empty string: 35 | > 36 | let g:VM_maps["Select Operator"] = '' 37 | < 38 | ------------------------------------------------------------------------------- 39 | *vm-faq-disable-mappings* 40 | How can I temporarily disable VM mappings? ~ 41 | 42 | VM has a for that. Normally it is bound to |VM_leader| + , but 43 | unless you use as |VM_leader|, you may prefer an easier mapping: 44 | > 45 | let g:VM_maps["Toggle Mappings"] = '' 46 | < 47 | 48 | ------------------------------------------------------------------------------- 49 | *vm-faq-functions* 50 | How can I do something before VM starts, or after VM ends? ~ 51 | 52 | You can create these functions: > 53 | function! VM_Start() 54 | function! VM_Exit() 55 | < 56 | Or you can rely on a user autocommand: > 57 | autocmd User visual_multi_start call MyVmStart() 58 | autocmd User visual_multi_exit call MyVmExit() 59 | < 60 | If you want to override some VM mapping (and you know what you're doing), this 61 | autocommand is triggered after mappings have been set: 62 | > 63 | autocmd User visual_multi_mappings call MyVmMappings() 64 | 65 | If you need to perform some action before/after `each` VM command is run: > 66 | autocmd User visual_multi_before_cmd call MyFunc1() 67 | autocmd User visual_multi_after_cmd call MyFunc2() 68 | 69 | ------------------------------------------------------------------------------- 70 | *vm-faq-remap* 71 | How can I remap x in VM? ~ 72 | 73 | There are several ways: either do a remap using the functions described above, 74 | e.g.: 75 | > 76 | function! VM_Start() 77 | nmap 78 | imap 79 | endfunction 80 | 81 | function! VM_Exit() 82 | nunmap 83 | iunmap 84 | endfunction 85 | < 86 | or check one of the following settings: 87 | 88 | |g:VM_custom_remaps| 89 | |g:VM_custom_noremaps| 90 | |g:VM_custom_motions| 91 | 92 | 93 | ------------------------------------------------------------------------------- 94 | *vm-faq-custom-mappings* 95 | Can I have a mapping for...? ~ 96 | 97 | If you find yourself repeating a certain action, you can create mappings like: 98 | > 99 | nmap cp vip(VM-Visual-Cursors) 100 | < 101 | This would create a column of cursors that spans over the current inner 102 | paragraph. 103 | > 104 | Obviously you can just use keys rather than plugs: 105 | > 106 | nmap cp vip\\c 107 | < 108 | 109 | vim: ft=help et sw=2 ts=2 sts=2 tw=79 110 | -------------------------------------------------------------------------------- /doc/vm-mappings.txt: -------------------------------------------------------------------------------- 1 | *vm-mappings.txt* Version 0.3.0 Last change: June 11 2019 2 | 3 | MAPPINGS *vm-mappings-all* 4 | =============================================================================== 5 | *g:VM_maps* 6 | To change any mapping you must first initialize the variable: 7 | > 8 | let g:VM_maps = {} 9 | < 10 | Then you can assign it to a new key: 11 | > 12 | let g:VM_maps["Select Operator"] = 'gs' 13 | < 14 | To disable a specific mapping, set it to an empty string: 15 | > 16 | let g:VM_maps["Select Operator"] = '' 17 | < 18 | To enable undo/redo (still experimental): 19 | > 20 | let g:VM_maps["Undo"] = 'u' 21 | let g:VM_maps["Redo"] = '' 22 | 23 | Example of SublimeText-like mappings: 24 | > 25 | let g:VM_maps['Find Under'] = '' 26 | let g:VM_maps['Find Subword Under'] = '' 27 | let g:VM_maps["Select Cursor Down"] = '' 28 | let g:VM_maps["Select Cursor Up"] = '' 29 | < 30 | Example of |vim-multiple-cursors| -like mappings: 31 | > 32 | let g:VM_maps['Select All'] = '' 33 | let g:VM_maps['Visual All'] = '' 34 | let g:VM_maps['Skip Region'] = '' 35 | let g:VM_maps['Increase'] = '+' 36 | let g:VM_maps['Decrease'] = '-' 37 | < 38 | For Colemak users, see |vm-colemak|. 39 | 40 | 41 | ------------------------------------------------------------------------------- 42 | DEFAULT MAPPINGS *vm-mappings-default* 43 | *g:VM_default_mappings* 44 | 45 | Default mappings are `permanent`, that is, always available, and applied when 46 | Vim starts. Buffer mappings instead are applied per-buffer, when VM is started. 47 | 48 | Permanent mappings, except , can be disabled with: 49 | > 50 | let g:VM_default_mappings = 0 51 | < *g:VM_mouse_mappings* 52 | Mouse mappings (also permanent) can be enabled with: 53 | > 54 | let g:VM_mouse_mappings = 1 55 | < 56 | *g:VM_leader-dict* 57 | Mappings preceded by \\ are meant prefixed with |g:VM_leader|. 58 | 59 | Some of the permanent/visual mappings use the |g:VM_leader| as well, and you 60 | could want to use a different one for them. In this case you can define the 61 | leader as a dictionary: 62 | > 63 | let g:VM_leader = {'default': '\', 'visual': '\', 'buffer': 'z'} 64 | 65 | That is, the VM leader used for default (permanent) normal mode mappings, 66 | visual mappings, and buffer mappings. 67 | 68 | ------------------------------------------------------------------------------- 69 | Name Keys Notes ~ 70 | ------------------------------------------------------------------------------- 71 | *vm-mappings-qr* 72 | Exit quit VM 73 | Find Under select the word under cursor 74 | Find Subword Under from visual mode, without word boundaries 75 | Add Cursor Down create cursors vertically 76 | Add Cursor Up ,, ,, ,, 77 | Select All \\A select all occurrences of a word 78 | Start Regex Search \\/ create a selection with regex search 79 | Add Cursor At Pos \\\ add a single cursor at current position 80 | Reselect Last \\gS reselect set of regions of last VM session 81 | 82 | Mouse Cursor create a cursor where clicked 83 | Mouse Word select a word where clicked 84 | Mouse Column create a column, from current cursor to 85 | clicked position 86 | 87 | 88 | ------------------------------------------------------------------------------- 89 | VISUAL MODE MAPPINGS *vm-mappings-visual* 90 | 91 | Of these, `Visual Subtract` and `Visual Reduce` are buffer mappings. 92 | 93 | ------------------------------------------------------------------------------- 94 | Name Keys Notes ~ 95 | ------------------------------------------------------------------------------- 96 | 97 | Visual All \\A select all occurrences of visual selection 98 | Visual Regex \\/ find a pattern in visual selection 99 | Visual Find \\f find all patterns ( or @/ ) from visual mode 100 | Visual Cursors \\c create a column of cursors from visual mode 101 | Visual Add \\a create a region from visual mode 102 | Visual Subtract \\s remove regions in current visual selection 103 | Visual Reduce \\r remove regions outside of visual selection 104 | 105 | 106 | 107 | ------------------------------------------------------------------------------- 108 | BUFFER MAPPINGS *vm-mappings-buffer* 109 | 110 | Not included are the mappings that mimic default vim commands (motions, several 111 | text objects), unless specified. 112 | 113 | ------------------------------------------------------------------------------- 114 | Name Keys Notes ~ 115 | ------------------------------------------------------------------------------- 116 | 117 | Find Next n find next occurrence 118 | Find Prev N find previous occurrence 119 | Goto Next ] go to next selected region 120 | Goto Prev [ go to previous selected region 121 | Seek Next fast go to next (from next page) 122 | Seek Prev fast go to previous (from previous page) 123 | Skip Region q skip and find to next 124 | Remove Region Q remove region under cursor 125 | Slash Search g/ extend/move cursors with / 126 | Replace R replace in regions, or start replace mode 127 | Toggle Multiline M see |vm-multiline-mode| 128 | 129 | The following are valid in extend-mode: ~ 130 | 131 | Surround S requires |vim-surround| plugin 132 | Move Right move all selections to the right 133 | Move Left ,, ,, to the left 134 | 135 | The following are valid in insert-mode and single-region-mode: ~ 136 | 137 | I Next move to next cursor 138 | I Prev move to previous cursor 139 | 140 | Operators: ~ 141 | 142 | Select Operator s see |vm-select-operator| 143 | Find Operator m see |vm-find-operator| 144 | 145 | Special commands: ~ 146 | 147 | Increase increase numbers (same as vim) 148 | Decrease decrease numbers (same as vim) 149 | gIncrease g progressively increase numbers (like |v_g_CTRL-A|) 150 | gDecrease g progressively decrease numbers (like |v_g_CTRL-X|) 151 | Alpha-Increase \\ same but +alpha (see |'nrformats'|) 152 | Alpha-Decrease \\ ,, 153 | 154 | Commands:~ 155 | 156 | Transpose \\t transpose 157 | Align \\a align regions 158 | Align Char \\< align by character 159 | Align Regex \\> align by regex 160 | Split Regions \\s subtract pattern from regions 161 | Filter Regions \\f filter regions by pattern/expression 162 | Transform Regions \\e transform regions with expression 163 | Rewrite Last Search \\r rewrite last pattern to match current region 164 | Merge Regions \\m merge overlapping regions 165 | Duplicate \\d duplicate regions 166 | Shrink \\- reduce regions from the sides 167 | Enlarge \\+ enlarge regions from the sides 168 | One Per Line \\L keep at most one region per line 169 | Numbers \\n see |vm-numbering| 170 | Numbers Append \\N ,, 171 | 172 | Run Normal \\z Run Normal 173 | Run Visual \\v Run Visual 174 | Run Ex \\x Run Ex 175 | Run Last Normal \\Z Run Last Normal 176 | Run Last Visual \\V Run Last Visual 177 | Run Last Ex \\X Run Last Ex 178 | Run Macro \\@ Run Macro 179 | 180 | Options and menus: ~ 181 | 182 | Tools Menu \\` filter lines to buffer, etc 183 | Case Conversion Menu \\C works better in extend mode 184 | Show Registers \\" show VM registers in the command line 185 | Toggle Whole Word \\w toggle whole word search 186 | Case Setting \\c cycle case setting ('scs' -> 'noic' -> 'ic') 187 | Toggle Mappings \\ toggle VM mappings 188 | Toggle Single Region \\ toggle single region mode 189 | 190 | 191 | ------------------------------------------------------------------------------- 192 | COLEMAK MAPPINGS *vm-colemak* 193 | 194 | For Colemak users or more extensive remappings: this mapping system wasn't 195 | really designed for that. Take into account that when you assign keys this way, 196 | you assign a `VM ` to a key, you aren't remapping a key to another key. 197 | For example if you do: 198 | > 199 | let g:VM_maps['i'] = 'a' 200 | let g:VM_maps['I'] = 'A' 201 | let g:VM_maps['a'] = 'o' 202 | let g:VM_maps['A'] = 'O' 203 | 204 | You aren't remapping `i` to `a`, but the other way around. This translates to: 205 | > 206 | nmap a (VM-i) 207 | nmap A (VM-I) 208 | nmap o (VM-a) 209 | nmap O (VM-A) 210 | 211 | And the VM plugs have a fixed meaning: if you remapped `i`, it's not that 212 | `(VM-i)` means something different, it still means "start insert mode 213 | before the cursor". 214 | 215 | This means that you have to take other VM mappings into account, that could 216 | overwrite your mappings. In this case you would also need to map something to 217 | overtake `o` and `O` functions, so this would work: 218 | > 219 | let g:VM_maps['i'] = 'a' 220 | let g:VM_maps['I'] = 'A' 221 | let g:VM_maps['a'] = 'o' 222 | let g:VM_maps['A'] = 'O' 223 | let g:VM_maps['o'] = 'i' 224 | let g:VM_maps['O'] = 'I' 225 | < 226 | In general if you switch `A` and `B`, writing 227 | > 228 | map[A] = B 229 | map[B] = A 230 | 231 | is enough, but if you want to change more keys, you have to remap all 232 | combinations to avoid that some of the mappings won't work. 233 | 234 | 235 | vim: ft=help et sw=2 ts=2 sts=2 tw=79 236 | -------------------------------------------------------------------------------- /doc/vm-settings.txt: -------------------------------------------------------------------------------- 1 | *vm-settings.txt* Version 0.3.0 Last change: June 13 2019 2 | 3 | Settings |vm-settings| 4 | Highlight |vm-highlight| 5 | 6 | 7 | 8 | SETTINGS *vm-settings* 9 | =============================================================================== 10 | 11 | Hopefully you won't need to alter any of these. 12 | 13 | 14 | *g:VM_highlight_matches* Default: 'underline' 15 | 16 | Controls VM default highlighting style for patterns matched, but not 17 | selected. Possible useful values are 'underline' and 'red'. 18 | Otherwise an empty string if you want the normal |Search| highlight, or 19 | a full highlight command (help |:hi|), e.g.: > 20 | let g:VM_highlight_matches = 'hi Search ctermfg=228 cterm=underline' 21 | let g:VM_highlight_matches = 'hi! link Search PmenuSel' 22 | < 23 | 24 | *g:VM_set_statusline* Default: 2 25 | 26 | Enable statusline when VM is active. 27 | With a value of 1, the statusline will be set once, on VM start. 28 | With a value of 2, the statusline will be refreshed on |CursorHold| event. 29 | With a value of 3, also on |CursorMoved| event. 30 | 31 | 32 | *g:VM_silent_exit* Default: 0 33 | 34 | Don't display a message when exiting VM. You may prefer it if you already set 35 | up statusline integration. 36 | 37 | 38 | *g:VM_quit_after_leaving_insert_mode* Default: 0 39 | 40 | So that you don't have to type twice. If you set this to 1, maybe you 41 | can remap `Reselect Last`, so that you can quickly restart VM with your last 42 | selection. See |vm-quick-reference|. 43 | 44 | 45 | *g:VM_add_cursor_at_pos_no_mappings* Default: 0 46 | 47 | When starting VM by adding a single cursor at position, don't enable buffer 48 | mappings, so that you can keep moving freely the cursor to add more cursors 49 | elsewhere. 50 | 51 | *g:VM_show_warnings* Default: 1 52 | 53 | When entering VM and there are mapping conflicts, a warning is displayed. Set 54 | to 0 to disable this warning. You can still run |:VMDebug| to see if there 55 | are conflicts. 56 | 57 | 58 | *g:VM_verbose_commands* Default: 0 59 | 60 | Set to 1 if you want command prompts to be more informative, rather than as 61 | minimal as possible. 62 | 63 | 64 | *g:VM_skip_shorter_lines* Default: 1 65 | 66 | When adding cursors up/down, skip shorter lines. 67 | 68 | 69 | *g:VM_skip_empty_lines* Default: 0 70 | 71 | When adding cursors up/down, skip empty lines. 72 | 73 | 74 | *g:VM_live_editing* Default: 1 75 | 76 | Controls how often text is updated in insert mode. 77 | 78 | 79 | *g:VM_reselect_first* Default: 0 80 | 81 | The first region will be reselected after most commands, if set to 1. 82 | 83 | 84 | *g:VM_case_setting* Default: '' 85 | 86 | Possible values: 'smart', 'sensitive', 'ignore'. 87 | Starting case matching for patterns. Can be switched inside VM. 88 | Leave empty to use your current setting. 89 | 90 | 91 | *g:VM_disable_syntax_in_imode* Default: 0 92 | 93 | You could want to do it for performance reasons. 94 | 95 | 96 | *VM_recursive_operations_at_cursors* Default: 1 97 | 98 | When executing normal commands in cursor mode (`dw` and similar), by default 99 | recursive mappings are used, so that user text object can be used as well. 100 | Set to 0 if you always want commands in cursor mode to be non-recursive. 101 | 102 | 103 | *g:VM_custom_remaps* Default: {} 104 | 105 | To remap VM mappings to other VM mappings. 106 | Example: 107 | > 108 | " also use to select previous and to skip current 109 | let g:VM_custom_remaps = {'': 'N', '': 'q'} 110 | < 111 | 112 | *g:VM_custom_noremaps* Default: {} 113 | 114 | To remap any key to normal! commands. Example: 115 | > 116 | let g:VM_custom_noremaps = {'==': '==', '<<': '<<', '>>': '>>'} 117 | < 118 | would create commands that will work on all cursors inside VM, for each 119 | key-value pair. It's a way to remap built-in vim commands, without having 120 | too many default mappings that can cause conflicts with other plugins. They 121 | only work in (and enforce) cursor mode. 122 | 123 | 124 | *g:VM_custom_motions* Default: {} 125 | 126 | To remap any standard motion (h,j,k,l,f...) commands. For example this 127 | inverts 'h' and 'l' motions: 128 | > 129 | let g:VM_custom_motions = {'h': 'l', 'l': 'h'} 130 | < 131 | It can be useful if you use keyboard layouts other than QWERTY. Valid motions 132 | that you can remap are: 133 | > 134 | h j k l w W b B e E ge gE , ; $ 0 ^ % \| f F t T 135 | < 136 | 137 | *g:VM_user_operators* Default: [] 138 | 139 | Cursor mode only. The elements of the list can be simple strings (then 140 | any text object can be accepted) or a dictionary with the operator as key, 141 | and the number of characters to be typed as value. Example: 142 | > 143 | let VM_user_operators = ['yd', 'cx', {'cs': 2}] 144 | < 145 | This will accept commands like `ydE`, `cxiw`, or `cs` followed by two 146 | characters, for example `cs{[`. These operators can be either vim or 147 | plugin operators, mappings are passed recursively. 148 | 149 | Note: |vim-surround| and |vim-abolish| have built-in support, this isn't 150 | needed for them to work. 151 | 152 | 153 | *g:VM_use_first_cursor_in_line* Default: 0 154 | 155 | In insert mode, the active cursor is normally the last selected one. Set this 156 | option to `1` to always use the first cursor in the line. 157 | 158 | 159 | *g:VM_insert_special_keys* Default: ['c-v'] 160 | 161 | Some keys in insert mode can have a different behaviour, compared to vim 162 | defaults. Possible values: 163 | 164 | `c-a` go to the beginning of the line, at indent level 165 | `c-e` go to the end of the line 166 | `c-v` paste from VM unnamed register 167 | 168 | 169 | *g:VM_single_mode_maps* Default: 1 170 | 171 | Set to 0 to disable entirely insert mode mappings to cycle cursors in 172 | |vm-single-mode|. If you only want to change the default mappings, see 173 | |vm-mappings-buffer|. 174 | 175 | 176 | *g:VM_single_mode_auto_reset* Default: 1 177 | 178 | If insert mode is entered while |vm-single-mode| is enabled, it will be reset 179 | automatically when exiting insert mode, unless this value is 0. 180 | 181 | 182 | *g:VM_filesize_limit* Default: 0 (disabled) 183 | 184 | VM won't start if buffer size is greater than this. 185 | 186 | 187 | *g:VM_persistent_registers* Default: 0 188 | 189 | If true VM registers will be stored in the |viminfo|. The 'viminfo' option 190 | must include !, for this to work. Also see |:VMRegisters|. 191 | 192 | 193 | *g:VM_reindent_filetypes* Default: [] 194 | 195 | Autoindentation (via |indentkeys|) is temporarily disabled in insert mode, 196 | and you have to reindent edited lines yoursef. For filetypes included in this 197 | list, edited lines are automatically reindented when exiting insert mode. 198 | 199 | 200 | *g:VM_plugins_compatibilty* Default: {} 201 | 202 | Used for plugins compatibility, see |vm-compatibility|. 203 | 204 | 205 | 206 | 207 | HIGHLIGHT *vm-highlight* 208 | =============================================================================== 209 | 210 | 211 | VM default theme is based on your color scheme, if you don't like it you can: 212 | 213 | * select a theme 214 | * relink highlight groups 215 | *g:VM_theme* 216 | You can load a theme by default by defining: > 217 | let g:VM_theme = 'your_chosen_theme' 218 | < *:VMTheme* 219 | If you want to change theme, run: > 220 | :VMTheme 221 | < 222 | These are the default VM highlight groups: > 223 | 224 | hi! link VM_Mono DiffText 225 | hi! link VM_Extend DiffAdd 226 | hi! link VM_Cursor Visual 227 | hi! link VM_Insert DiffChange 228 | < 229 | *VM_Mono* is the highlight in cursor mode 230 | *VM_Extend* ,, in extend mode (the selections) 231 | *VM_Cursor* ,, in extend mode (the cursors) 232 | *VM_Insert* ,, in insert mode (the virtual cursors) 233 | 234 | *vm-colorschemes* 235 | 236 | If you want to load a theme inside a colorscheme, just put in the colorscheme 237 | script: 238 | > 239 | silent! VMTheme theme_name 240 | < 241 | You can also link (or redefine) the highlight groups directly. 242 | 243 | 244 | 245 | vim: ft=help et sw=2 ts=2 sts=2 tw=79 246 | -------------------------------------------------------------------------------- /doc/vm-troubleshooting.txt: -------------------------------------------------------------------------------- 1 | *vm-troubleshooting.txt* Version 0.3.0 Last change: June 13 2019 2 | 3 | Known issues |vm-bugs| 4 | Plugins compatibility |vm-compatibility| 5 | 6 | 7 | 8 | TROUBLESHOOTING *vm-troubleshooting* 9 | =============================================================================== 10 | 11 | First stop for troubleshooting is |vm-faq|. 12 | 13 | 14 | 15 | 16 | 17 | KNOWN ISSUES *vm-bugs* 18 | ============================================================================== 19 | 20 | VM relies mostly on autocommands, especially in insert mode. Plugins and 21 | autocommands defined by the user could cause issues, if they do something 22 | intrusive while VM is updating text. Snippets plugins won't work for this 23 | reason, but |abbreviations| work. For example this won't work either: 24 | > 25 | au InsertLeave * silent! update 26 | < *b:visual_multi* 27 | In this case you may want to check that VM is not active, before performing an 28 | action, you could write it as: 29 | > 30 | au InsertLeave * if !exists('b:visual_multi') | silent! update | endif 31 | < 32 | {https://github.com/mg979/vim-visual-multi/issues/91} 33 | 34 | ------------------------------------------------------------------------------ 35 | *vm-autocompletion* 36 | 37 | Some autocompletion plugins can cause trouble if the active cursor is not the 38 | first cursor in the line. In this case this can help: 39 | > 40 | let g:VM_use_first_cursor_in_line = 1 41 | 42 | 43 | ------------------------------------------------------------------------------ 44 | 45 | In |vm-single-region| mode, some insert mode mappings (, , ) are 46 | disabled because they don't behave well. and are not disabled, but 47 | they cause issues when deleting other cursors, so be careful. They are not 48 | disabled because it shouldn't happen in normal use cases for this feature, that 49 | should be used when you want to use cursors as 'fields' to edit sequentially. 50 | 51 | 52 | 53 | 54 | 55 | PLUGINS COMPATIBILITY *vm-compatibility* 56 | ============================================================================== 57 | 58 | As mentioned above, snippets plugins cannot work with VM. Other plugins can 59 | have incompatibilities, especially if they apply buffer mappings. In this 60 | case, VM will not replace the conflicting keys, but this can result in missing 61 | VM functionalities or even render VM unusable. If you notice some mappings 62 | don't work in VM, run |:VMDebug| to see if a plugin is the cause. 63 | 64 | However, VM will always overwrite insert mode mappings. If this happens, you 65 | can also read it in the |:VMDebug| output. In this case, it's not VM that 66 | breaks, it's the conflicting plugin. 67 | 68 | Some plugins have built-in compatibility: 69 | 70 | - jiangmiao/auto-pairs 71 | - AndrewRadev/tagalong.vim 72 | - dyng/ctrlsf.vim 73 | - Shougo/deoplete.nvim 74 | - ncmw/ncm2 75 | 76 | If you want to run some code before/after VM starts/exits, see 77 | |vm-faq-functions|. 78 | 79 | If you make a plugin and want to test if VM is active, perform a check on the 80 | |b:visual_multi| variable. 81 | 82 | If a plugin needs to be deactivated inside VM, you could also use the 83 | |g:VM_plugins_compatibilty| dictionary, with this structure: 84 | > 85 | let g:VM_plugins_compatibilty = { 86 | \'plugin_name': { 87 | \ 'test': { -> exists('plugin_is_enabled') }, 88 | \ 'enable': ':PluginEnableCommand', 89 | \ 'disable': ':PluginDisableCommand'}, 90 | \} 91 | < 92 | If you would like a plugin to have built-in compatibility, open an issue about 93 | it on the VM GitHub repo and/or make a pull request. 94 | 95 | 96 | 97 | 98 | vim: ft=help et sw=2 ts=2 sts=2 tw=78 99 | -------------------------------------------------------------------------------- /doc/vm-tutorial: -------------------------------------------------------------------------------- 1 | =================================================================================== 2 | _ _ __ ____ _~ 3 | _ __(_)___ ___ _ __(_)______ ______ _/ / ____ ___ __ __ / / /_(_)~ 4 | | | / / / __ `__ \_____| | / / / ___/ / / / __ `/ /_____/ __ `__ \/ / / // / __/ /~ 5 | | |/ / / / / / / /_____/ |/ / (__ ) /_/ / /_/ / /_____/ / / / / / /_/ // / /_/ / ~ 6 | |___/_/_/ /_/ /_/ |___/_/____/\__,_/\__,_/_/ /_/ /_/ /_/\__,_//_/\__/_/ ~ 7 | 8 | =================================================================================== 9 | _____ _ _ _~ 10 | |_ _| _| |_ ___ _ __(_) __ _| |~ 11 | | || | | | __/ _ \| '__| |/ _` | |~ 12 | | || |_| | || (_) | | | | (_| | |~ 13 | |_| \__,_|\__\___/|_| |_|\__,_|_|~ 14 | 15 | =================================================================================== 16 | 17 | Hi and thanks for trying this plugin. Let's start! 18 | 19 | First and foremost, this mini vimrc will use the following non-default settings, so 20 | if you try the plugin with your vimrc, and something doesn't work, this could be the 21 | reason. These settings enable a theme, the mouse mappings, and the Undo/Redo 22 | functionality. 23 | > 24 | let g:VM_mouse_mappings = 1 25 | let g:VM_theme = 'iceblue' 26 | 27 | let g:VM_maps = {} 28 | let g:VM_maps["Undo"] = 'u' 29 | let g:VM_maps["Redo"] = '' 30 | < 31 | Also note that I'm using the default g:VM_leader, that is '\\' (two backslashes). 32 | Where you see two backslashes being used, that should be replaced if you're setting 33 | a different g:VM_leader in your vimrc. 34 | 35 | 36 | Create cursors ~ 37 | ------------------------------------------------------------------------------------ 38 | 39 | I'll reuse the block above for the first test. 40 | 41 | let g:VM_mouse_mappings = 1 42 | let g:VM_theme = 'iceblue' 43 | let g:VM_highlight_matches = 'underline' 44 | 45 | let g:VM_maps = {} 46 | let g:VM_maps["Undo"] = 'u' 47 | let g:VM_maps["Redo"] = '' 48 | 49 | Move the cursor to the first 'let' word, on the first letter. 50 | Press `mm`, to set a mark, since you'll go back there more than once. 51 | Then press |z| to scroll the window. 52 | 53 | Now press 5 times : you see that you've created cursors vertically, skipping 54 | the empty line. Press to exit VM, then `m to go back to the mark. 55 | 56 | Press 5: see what happened? [count] works just the same. 57 | 58 | You are now in `cursor mode`: you have a bunch of cursors and the commands you issue 59 | will be run as if you were in normal mode. 60 | 61 | What can you do with these cursors? 62 | 63 | - you can move them: press wwbb, then WWBB to get an idea. 64 | - you can issue VM specific commands that you'll learn later. 65 | - you can issue normal mode commands: press `dw`, then `cW`, and start editing. 66 | - you can also enter insert mode with i, a, I, A 67 | 68 | 69 | 70 | Extend selections ~ 71 | ------------------------------------------------------------------------------------ 72 | 73 | Quit VM, undo changes if you made any, press `m, and 5. 74 | 75 | Now press : what happened? You switched to `extend mode`: you can think of it 76 | as the 'visual' mode in VM. You are not working with cursors anymore, you are 77 | working with selections. As in visual mode, you can extend these selections with 78 | motions, and pressing 'c' will delete their content, and start editing. 79 | 80 | Note: vim registers will be restored after exiting VM. Inside VM, `change/x` commands 81 | are redirected to the black hole register (`_`), differently from vim. 82 | 83 | 84 | 85 | Select words ~ 86 | ------------------------------------------------------------------------------------ 87 | 88 | Quit VM, undo changes, move below and set a new mark, then |z|. 89 | 90 | let g:VM_moLET_mappings = 1 91 | Let g:VM_theme = 'iceblue' 92 | lEt g:VM_highlight_matches = 'underline' 93 | 94 | Let g:VM_maps = {} 95 | let g:VM_maps["Undo"] = 'u' 96 | let g:VM_maps["Redo"] = '' 97 | 98 | Now press : this is the default mapping to select words under the cursor. If 99 | your search settings are set to 'smartcase', you'll see that all following 'let' 100 | words are underlined, and will be selected if you keep pressing . 101 | 102 | Quit VM and move to the second line. 103 | 104 | Press : if smartcase is active, only one other match will be underlined. 105 | Press `\\c`: this allows you to cycle the case setting of the current pattern. Press 106 | it until it becomes case insensitive. 107 | Press `q`: the current match will be skipped. Alternate and `q`. 108 | 109 | selects words with word boundaries. What if you want to select a part of a 110 | word? You have three options. Go back with `m after every attempt: 111 | 112 | 1. press 'vl', then . You started VM from visual mode. 113 | 2. press : you started VM by creating a selection, one character at a time. 114 | 3. press , then `\\w`: you selected a word, then converted its pattern to one 115 | that doesn't check for word boundaries. 116 | 117 | Let's focus on the second option, since it's probably the easiest. So you press 118 | once and select the first two letters. How do you select the next 119 | occurrence? is an obvious choice: it will create a pattern from those letters, 120 | and will select the next occurrence. 121 | 122 | But you can also use `n` and `N`: select again the first two letters with , 123 | then press `nnn`. Try it: add a pattern with `n`, skip one with `q`, go back with 124 | `N`, skip again with `q`. 125 | 126 | The keys `n` and `N` stand for `find next/previous`, they don't add new patterns, 127 | unless there is none set yet: since doesn't add a pattern, `n` and `N` 128 | will do it. 129 | 130 | Now press `Q`: you didn't skip a selection moving to the next one: you removed it. 131 | Note that you can also remove cursors with `Q`, not only selections. 132 | 133 | 134 | 135 | Some commands ~ 136 | ------------------------------------------------------------------------------------ 137 | 138 | Set a new mark here below and let's try some other commands. 139 | 140 | let g:VM_mouse_mappings = 1 " some text 141 | let g:VM_theme = 'iceblue' " some text 142 | let g:VM_highlight_matches = 'underline' " some text 143 | 144 | let g:VM_maps = {} " some text 145 | let g:VM_maps["Undo"] = 'u' " some text 146 | let g:VM_maps["Redo"] = '' " some text 147 | 148 | 149 | Alignment ~ 150 | 151 | Press `v2ap`, then `\\c`: from visual mode you created a column of cursors. 152 | Press `f=`: all cursors will move to the `=` character. 153 | Press `\\a`: all cursors will be aligned. 154 | 155 | Tip: make a macro or paste this in your command line, then execute the macro: 156 | > 157 | let @q = '`mv6j\\c' 158 | 159 | Let's do it differently: undo changes, move on top, type `v6j\\c`, then press 160 | `\\<`, and you'll be asked for a character to align. 161 | 162 | If you press `=` you'll align equal signs as before. Press `'` instead: you'll see 163 | that where it can't find the apostrophe, no alignment is performed, and the cursor 164 | is removed. 165 | 166 | Anyway you'll see that the `"` column is now misaligned. You can do `f"\\a`. 167 | Undo changes and press again `v6j\\c.` 168 | 169 | This time try `2\\<="`. You're saying: align two characters, that are = and ". 170 | You can then press `12X` to reduce that empty space. 171 | 172 | Note: the two characters could have been the same (as in 2\\<==). 173 | Note: you can also align with regex, using `\\>` instead of `\\<`. 174 | 175 | 176 | Dot ~ 177 | 178 | Pressing the dot (.) can be a very fast way to apply a change at cursors. 179 | 180 | 1. Press `o` right here, start inserting some text, then exit insert mode 181 | 2. Undo the change, then press `m to go back 182 | 3. Press 5 183 | 4. Finally press `.` 184 | 185 | Now try this, after having undone the last changes: 186 | 187 | 1. Go back with `m, and press 5 188 | 2. Press `dw`, then `.` several times 189 | 190 | You see that also this kind of operation at cursors is repeatable. 191 | Just don't expect *everything* to be repeatable, though! 192 | 193 | 194 | Replace in regions, Select Operator ~ 195 | 196 | Say you want to remove underscores from the quoted text below. 197 | Move to the start of the block, then: 198 | 199 | 1. Press `f"l` to go inside the quotes 200 | 2. Press `3` to create cursors 201 | 3. Press `` to select words 202 | 4. Press `R`: this is the command to replace a pattern in each region. 203 | 5. Enter `_`, then a single space as replacement. 204 | > 205 | param_table.AddNumber("num_control_buffers", options_.num_control_buffers); 206 | param_table.AddNumber("control_buffer_size", options_.control_buffer_size); 207 | param_table.AddNumber("num_payload_buffers", options_.num_payload_buffers); 208 | param_table.AddNumber("payload_buffer_size", options_.payload_buffer_size); 209 | 210 | You can also do the opposite: we'll use the `select operator` to do that. 211 | Go again to the start of the block, then: 212 | 213 | 1. Press `3` to create cursors 214 | 2. Press `f"` to go to the quotes 215 | 3. Press `si"` to select inside the quotes 216 | 4. Press `R`, then a single space followed by 217 | 5. Enter `_` as replacement, and again 218 | > 219 | param_table.AddNumber("num control buffers", options_.num_control_buffers); 220 | param_table.AddNumber("control buffer size", options_.control_buffer_size); 221 | param_table.AddNumber("num payload buffers", options_.num_payload_buffers); 222 | param_table.AddNumber("payload buffer size", options_.payload_buffer_size); 223 | 224 | 225 | Some experiments ~ 226 | ------------------------------------------------------------------------------------ 227 | > 228 | let g:VM_mouse_mappings = 1 " some text 229 | let g:VM_theme = 'iceblue' " some text 230 | let g:VM_highlight_matches = 'underline' " some text 231 | 232 | let g:VM_maps = {} " some text 233 | let g:VM_maps["Select l"] = '' " some text 234 | let g:VM_maps["Select h"] = '' " some text 235 | 236 | You could try some of the following, after setting a new mark and creating a 237 | column of cursors with 5. Do the following in a single VM session: 238 | 239 | 1. `~` (toggle case for character under cursors) 240 | 2. `r` (replace character under cursors) 241 | 3. to select words under current cursors, then repeat `~` and `r` 242 | 4. press `\\N`, then enter `,` in the prompt (numbering with separator) 243 | 5. now press `dWWP`, then `A` 244 | 6. exit insert mode, then press `0`, then , then 245 | 7. still in VM, press `u` until you can, do the same with , then again `u` 246 | 247 | About in insert mode: it's a special VM command that will paste the content of 248 | the unnamed VM register, if this has been filled with something. 249 | If you pressed {register}, this would still work, but the pasted content would 250 | be the same for all cursors, since it would use vim (and not VM) registers. 251 | 252 | About undo/redo, you can see that you are limited to the changes you did inside VM, 253 | and that your selections were restored as well: you can't undo changes that were 254 | done before entering VM. This will only work if you enable the undo/redo mappings, 255 | and they are disabled by default. 256 | 257 | 258 | 259 | vim: ft=help tw=84 cole=2 260 | -------------------------------------------------------------------------------- /plugin/visual-multi.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " File: visual-multi.vim 3 | " Description: multiple selections in vim 4 | " Mantainer: Gianmaria Bajo 5 | " Url: https://github.com/mg979/vim-visual-multi 6 | " Licence: The MIT License (MIT) 7 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 8 | 9 | " Guard {{{ 10 | if v:version < 800 11 | echomsg '[vim-visual-multi] Vim version 8 is required' 12 | finish 13 | endif 14 | 15 | if exists("g:loaded_visual_multi") 16 | finish 17 | endif 18 | let g:loaded_visual_multi = 1 19 | 20 | let s:save_cpo = &cpo 21 | set cpo&vim 22 | "}}} 23 | 24 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 25 | 26 | com! -nargs=? -complete=customlist,vm#themes#complete VMTheme call vm#themes#load() 27 | 28 | com! -bar VMDebug call vm#special#commands#debug() 29 | com! -bar VMClear call vm#hard_reset() 30 | com! -bar VMLive call vm#special#commands#live() 31 | 32 | com! -bang -nargs=? VMRegisters call vm#special#commands#show_registers(0, ) 33 | com! -range -bang -nargs=? VMSearch call vm#special#commands#search(0, , , ) 34 | 35 | " Deprecated commands {{{1 36 | com! -bang VMFromSearch call vm#special#commands#deprecated('VMFromSearch') 37 | "}}} 38 | 39 | hi default link VM_Mono IncSearch 40 | hi default link VM_Cursor Visual 41 | hi default link VM_Extend PmenuSel 42 | hi default link VM_Insert DiffChange 43 | hi link MultiCursor VM_Cursor 44 | 45 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 46 | 47 | let g:Vm = { 'hi' : {}, 48 | \ 'buffer' : 0, 49 | \ 'extend_mode' : 0, 50 | \ 'finding' : 0, 51 | \ 'mappings_enabled' : 0, 52 | \ 'last_ex' : '', 53 | \ 'last_normal' : '', 54 | \ 'last_visual' : '', 55 | \ 'registers' : {'"': [], '-': []}, 56 | \ 'oldupdate' : exists("##TextYankPost") ? 0 : &updatetime, 57 | \} 58 | 59 | let g:VM_highlight_matches = get(g:, 'VM_highlight_matches', 'underline') 60 | 61 | 62 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 63 | " Global mappings 64 | 65 | call vm#plugs#permanent() 66 | call vm#maps#default() 67 | 68 | 69 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 70 | " Registers 71 | 72 | let g:VM_persistent_registers = get(g:, 'VM_persistent_registers', 0) 73 | 74 | fun! s:vm_registers() 75 | if exists('g:VM_PERSIST') && !g:VM_persistent_registers 76 | unlet g:VM_PERSIST 77 | elseif exists('g:VM_PERSIST') 78 | let g:Vm.registers = deepcopy(g:VM_PERSIST) 79 | endif 80 | endfun 81 | 82 | fun! s:vm_persist() 83 | if exists('g:VM_PERSIST') && !g:VM_persistent_registers 84 | unlet g:VM_PERSIST 85 | elseif g:VM_persistent_registers 86 | let g:VM_PERSIST = deepcopy(g:Vm.registers) 87 | endif 88 | endfun 89 | 90 | 91 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 92 | " Autocommands 93 | 94 | augroup VM_start 95 | au! 96 | au VimEnter * call s:vm_registers() 97 | au VimLeavePre * call s:vm_persist() 98 | augroup END 99 | 100 | 101 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 102 | 103 | fun! VMInfos() abort 104 | if !exists('b:VM_Selection') || empty(b:VM_Selection) 105 | return {} 106 | endif 107 | 108 | let infos = {} 109 | let VM = b:VM_Selection 110 | 111 | let m = g:Vm.mappings_enabled ? 'M' : 'm' 112 | let s = VM.Vars.single_region ? 'S' : 's' 113 | let l = VM.Vars.multiline ? 'V' : 'v' 114 | 115 | let infos.current = VM.Vars.index + 1 116 | let infos.total = len(VM.Regions) 117 | let infos.ratio = infos.current . ' / ' . infos.total 118 | let infos.patterns = VM.Vars.search 119 | let infos.status = m.s.l 120 | return infos 121 | endfun 122 | 123 | let &cpo = s:save_cpo 124 | unlet s:save_cpo 125 | " vim: ft=vim et sw=2 ts=2 sts=2 fdm=marker 126 | -------------------------------------------------------------------------------- /python/vm.py: -------------------------------------------------------------------------------- 1 | import vim 2 | 3 | #------------------------------------------------------------------------------ 4 | 5 | def py_rebuild_from_map(): 6 | """Rebuild regions from bytes map.""" 7 | 8 | bmap = ev('l:dict') 9 | Range = ev('l:range') 10 | bys = sorted([int(b) for b in bmap.keys()]) 11 | if Range: 12 | A, B = int(Range[0]), int(Range[1]) 13 | bys = [b for b in bys if b >= A and b <= B] 14 | 15 | start, end = bys[0], bys[0] 16 | vim.command('call b:VM_Selection.Global.erase_regions()') 17 | 18 | for i in bys[1:]: 19 | if i == end + 1: 20 | end = i 21 | else: 22 | vim.command('call vm#region#new(0, %d, %d)' % (start, end)) 23 | start, end = i, i 24 | 25 | vim.command('call vm#region#new(0, %d, %d)' % (start, end)) 26 | 27 | #------------------------------------------------------------------------------ 28 | 29 | def py_lines_with_regions(): 30 | """Find lines with regions.""" 31 | 32 | lines, regions = {}, ev('s:R()') 33 | specific_line, rev = evint('l:specific_line'), evint('a:reverse') 34 | 35 | for r in regions: 36 | line = int(r['l']) 37 | #called for a specific line 38 | if specific_line and line != specific_line: 39 | continue 40 | #add region index to indices for that line 41 | lines.setdefault(line, []) 42 | lines[line].append(int(r['index'])) 43 | 44 | for line in lines: 45 | #sort list so that lower indices are put farther in the list 46 | if len(lines[line]) > 1: 47 | lines[line].sort(reverse=rev) 48 | 49 | let('lines', lines) 50 | 51 | 52 | 53 | #------------------------------------------------------------------------------ 54 | # Helpers 55 | #------------------------------------------------------------------------------ 56 | 57 | def evint(exp): 58 | """Eval a vim expression as integer.""" 59 | return int(vim.eval(exp)) 60 | 61 | 62 | def ev(exp): 63 | """Eval a vim expression.""" 64 | return vim.eval(exp) 65 | 66 | def let(name, value): 67 | """Let variable through vim command.""" 68 | vim.command('let %s = %s' % (name, str(value))) 69 | 70 | -------------------------------------------------------------------------------- /run_tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd test 4 | 5 | [ -e test.py ] || exit 6 | 7 | chmod u+x test.py 8 | 9 | if [ $# -gt 0 ]; then 10 | ./test.py "$@" 11 | else 12 | ./test.py 13 | fi 14 | 15 | chmod u-x test.py 16 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # How to Run: 2 | ## run all tests: 3 | ./test.py 4 | 5 | ## run 1 test: 6 | ./test.py [test] 7 | 8 | ## list all tests 9 | ./test.py -l 10 | 11 | ## use a different key interval (default 0.1s) 12 | ./test.py -t 0.3 13 | 14 | ## show a diff for failed tests 15 | ./test.py -d 16 | 17 | ## run with `g:VM_live_editing` disabled 18 | ./test.py -L 19 | 20 | # Add a Test 21 | ## create a directory in tests/ then add the following files: 22 | - input_file.txt 23 | - commands.py 24 | - a literal backslash is written `r'\\'` 25 | - a literal double quote is written `r'\"'` 26 | - key notation have to be escaped `r'\'` 27 | - expected_output_file.txt 28 | 29 | You can call the following function to create a template: 30 | 31 | :call vm#special#commands#new_test() 32 | 33 | ## (optional) if you don't want to use default/vimrc.vim, add: 34 | - vimrc.vim 35 | 36 | ## (optional) if you want to add extra constraints, add: 37 | - config.json 38 | ```json 39 | { 40 | "max_cpu_time": 2.7 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /test/default/vimrc.vim: -------------------------------------------------------------------------------- 1 | " needed by vimrunner 2 | function! VimrunnerPyEvaluateCommandOutput(command) 3 | return execute(a:command) 4 | endfunction 5 | 6 | let g:loaded_remote_plugins = 1 7 | 8 | set runtimepath=$VIMRUNTIME 9 | set packpath= 10 | set nocompatible 11 | set runtimepath^=.. 12 | set ignorecase smartcase 13 | set noswapfile 14 | source ../plugin/visual-multi.vim 15 | -------------------------------------------------------------------------------- /test/requirements.txt: -------------------------------------------------------------------------------- 1 | vimrunner==1.0.3 2 | pynvim==0.3.1 3 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | from pathlib import Path, PurePath 5 | import os 6 | import sys 7 | import json 8 | import shutil 9 | import filecmp 10 | import vimrunner 11 | import time 12 | import multiprocessing 13 | import subprocess 14 | from pynvim import attach 15 | 16 | 17 | # ------------------------------------------------------------- 18 | # global varialbes 19 | # ------------------------------------------------------------- 20 | 21 | class bcolors: 22 | HEADER = '\033[95m' 23 | OKBLUE = '\033[94m' 24 | OKGREEN = '\033[92m' 25 | WARNING = '\033[93m' 26 | FAIL = '\033[91m' 27 | ENDC = '\033[0m' 28 | BOLD = '\033[1m' 29 | UNDERLINE = '\033[4m' 30 | 31 | 32 | SUCCESS_STR = "{}SUCCESS{}".format(bcolors.OKGREEN, bcolors.ENDC) 33 | FAIL_STR = "{}FAIL{}".format(bcolors.FAIL, bcolors.ENDC) 34 | CLIENT = None 35 | 36 | 37 | # ------------------------------------------------------------- 38 | # functions 39 | # ------------------------------------------------------------- 40 | def log(string, f=None): 41 | """Log to terminal and to file.""" 42 | print(string) 43 | if f is not None: 44 | f.write(string + "\n") 45 | 46 | 47 | def print_banner(string, f=None): 48 | """Print a banner with the result of the test.""" 49 | log("\n╒═" + 77*"═" + "\n│ %s" % string + "\n╘═" + 77*"═" + "\n", f) 50 | 51 | 52 | def get_vimrc(test, f): 53 | """Check if test-specific vimrc is present, or use default one.""" 54 | try: 55 | return Path('tests/', test, 'vimrc.vim').resolve(strict=True) 56 | except FileNotFoundError: 57 | return DEFAULT_VIMRC 58 | 59 | 60 | def get_test_description(test): 61 | """Get test description if present.""" 62 | commands = Path('tests/', test, 'commands.py').resolve(strict=True) 63 | with open(commands) as file: 64 | desc = file.readline() 65 | if desc[0] == '#': 66 | return (desc[2:-1]) 67 | else: 68 | return ('') 69 | 70 | 71 | def get_test_info(test, nvim, vimrc): 72 | """Generate line for test logging.""" 73 | desc = get_test_description(test) 74 | return test.ljust(20) + desc.ljust(40) 75 | 76 | 77 | def print_tests_list(tests, f): 78 | """Print the list of available tests, with their descriptions.""" 79 | log("\n" + '-' * 60) 80 | for t in tests: 81 | desc = get_test_description(t) 82 | log(t.ljust(20) + "\t" + desc) 83 | log('-' * 60) 84 | 85 | 86 | def get_paths(test, f): 87 | """Create the dictionary with the relevant file paths.""" 88 | paths = {} 89 | paths["vimrc"] = get_vimrc(test, f) 90 | paths["command"] = Path('tests/', test, 'commands.py').resolve(strict=True) 91 | paths["in_file"] = Path('tests/', test, 'input_file.txt').resolve(strict=True) 92 | paths["config"] = Path('tests/', test, 'config.json').resolve() 93 | paths["exp_out_file"] = Path('tests/', test, 'expected_output_file.txt').resolve(strict=True) 94 | paths["gen_out_file"] = Path('tests/', test, 'generated_output_file.txt').resolve() 95 | paths["socket"] = Path('socket_' + test).resolve() 96 | return paths 97 | 98 | 99 | def keys_nvim(key_str): 100 | """nvim implementation of keys()""" 101 | key_str = key_str.replace(r'\<', '<') 102 | key_str = key_str.replace(r'\"', r'"') 103 | key_str = key_str.replace('\\\\', '\\') 104 | CLIENT.input(key_str) 105 | time.sleep(KEY_PRESS_INTERVAL) 106 | 107 | 108 | def keys_vim(key_str): 109 | """vim implementation of keys()""" 110 | CLIENT.feedkeys(key_str) 111 | time.sleep(KEY_PRESS_INTERVAL) 112 | 113 | 114 | def run_core(paths, nvim=False): 115 | """Start the test and return commands_cpu_time.""" 116 | global CLIENT 117 | if nvim: 118 | # client/server connection 119 | server = multiprocessing.Process( 120 | target=subprocess.call, 121 | args=(VIM + " -u " + str(paths["vimrc"]) + ' --listen ' + str(paths["socket"]),), 122 | kwargs={'shell': True} 123 | ) 124 | server.start() 125 | time.sleep(1) 126 | CLIENT = attach('socket', path=str(paths["socket"])) 127 | # run test 128 | CLIENT.command('e %s' % paths["in_file"]) 129 | keys = keys_nvim 130 | start_time = time.process_time() 131 | commands = open(paths["command"]).read() 132 | if not LIVE_EDITING: 133 | commands = r'keys(r":let g:VM_live_editing = 0\")\n' + commands 134 | exec(commands) 135 | end_time = time.process_time() 136 | CLIENT.command(':w! %s' % paths["gen_out_file"]) 137 | CLIENT.quit() 138 | else: 139 | vim = vimrunner.Server(noplugin=False, vimrc=paths["vimrc"], executable=VIM) 140 | CLIENT = vim.start() 141 | CLIENT.edit(paths["in_file"]) 142 | keys = keys_vim 143 | start_time = time.process_time() 144 | exec(open(paths["command"]).read()) 145 | end_time = time.process_time() 146 | CLIENT.feedkeys(r'\') 147 | CLIENT.feedkeys(r':wq! %s\' % paths["gen_out_file"]) 148 | return end_time - start_time 149 | 150 | 151 | def run_one_test(test, f=None, nvim=False): 152 | """Run a single test.""" 153 | # input/output files 154 | paths = get_paths(test, f) 155 | info = get_test_info(test, nvim, paths['vimrc']) 156 | config = {} 157 | if os.path.exists(paths["config"]): 158 | config = json.load(open(paths["config"])) 159 | # remove previously generated file 160 | if os.path.exists(paths["gen_out_file"]): 161 | os.remove(paths["gen_out_file"]) 162 | # run test 163 | time.sleep(.5) 164 | commands_cpu_time = run_core(paths, nvim) 165 | time.sleep(.5) 166 | # check results 167 | time_str = "(took {:.3f} sec)".format(commands_cpu_time) 168 | if filecmp.cmp(paths["exp_out_file"], paths["gen_out_file"]): 169 | if "max_cpu_time" in config and config["max_cpu_time"] < commands_cpu_time: 170 | log("{} {} {}[slow]{} {}".format(info, FAIL_STR, 171 | bcolors.WARNING, bcolors.ENDC, time_str), f) 172 | return False 173 | log("{} {} {}".format(info, SUCCESS_STR, time_str), f) 174 | return True 175 | else: 176 | log("{} {} {}[mismatch]{} {}".format(info, FAIL_STR, 177 | bcolors.WARNING, bcolors.ENDC, time_str), f) 178 | return False 179 | 180 | 181 | def main(): 182 | """Main function.""" 183 | # arg parsing 184 | parser = argparse.ArgumentParser(description='Run test suite. See README.') 185 | parser.add_argument('test', nargs='?', help='run only instead of running all tests') 186 | parser.add_argument('-t', '--time', nargs=1, type=float, default=[0.1], help='set key delay in seconds (default 0.1)') 187 | parser.add_argument('-n', '--nvim', action='store_true', help='run in neovim instead of vim') 188 | parser.add_argument('-l', '--list', action='store_true', help='list all tests') 189 | parser.add_argument('-L', '--nolive', action='store_false', help='disable live editing') 190 | parser.add_argument('-d', '--diff', action='store_true', help='diff falied tests') 191 | args = parser.parse_args() 192 | 193 | # clear vim environmental variable in case tests are run from within (n)vim 194 | os.environ.pop('VIMRUNTIME', None) 195 | os.environ.pop('VIM', None) 196 | 197 | # vim version and default vimrc 198 | global VIM, DEFAULT_VIMRC, KEY_PRESS_INTERVAL, LIVE_EDITING, DIFF_FAILED 199 | VIM = shutil.which('vim' if not args.nvim else 'nvim') 200 | DEFAULT_VIMRC = Path('default/', 'vimrc.vim').resolve(strict=True) 201 | KEY_PRESS_INTERVAL = args.time[0] 202 | LIVE_EDITING = args.nolive 203 | DIFF_FAILED = args.diff 204 | 205 | # execution 206 | failing_tests = [] 207 | f = open('test.log', 'w') 208 | tests = sorted([PurePath(str(p)).name for p in Path('tests').glob('*')]) 209 | if args.list: 210 | print_tests_list(tests, f) 211 | else: 212 | print_banner("Starting vim-visual-multi tests", f) 213 | tests = tests if args.test is None else [args.test] 214 | for t in tests: 215 | if run_one_test(t, f, args.nvim) is not True: 216 | failing_tests.append(t) 217 | if failing_tests == []: 218 | print_banner("summary: " + SUCCESS_STR, f) 219 | else: 220 | print_banner("summary: " + FAIL_STR, f) 221 | log("the following tests failed:", f) 222 | log("\n".join(failing_tests), f) 223 | if DIFF_FAILED: 224 | for t in failing_tests: 225 | print_banner(t) 226 | exp = 'tests/' + t + '/expected_output_file.txt' 227 | gen = 'tests/' + t + '/generated_output_file.txt' 228 | subprocess.run('diff --color=always ' + exp + ' ' + gen, shell=True) 229 | f.close() 230 | if failing_tests != []: 231 | sys.exit(1) 232 | 233 | 234 | # ------------------------------------------------------------- 235 | # execution 236 | # ------------------------------------------------------------- 237 | if __name__ == '__main__': 238 | main() 239 | -------------------------------------------------------------------------------- /test/tests/abbrev/commands.py: -------------------------------------------------------------------------------- 1 | # abbreviations in insert mode 2 | 3 | keys(r':inoreabbrev rr return\') 4 | 5 | keys(r':VMLive\') 6 | keys(r'4\') 7 | keys('c') 8 | keys('rr') 9 | keys(r'\') 10 | keys(r'\') 11 | 12 | # the same, but with inverted g:VM_live_editing 13 | keys('6gg0') 14 | keys(r':VMLive\') 15 | keys(r'4\') 16 | keys('c') 17 | keys('rr') 18 | keys(r'\') 19 | keys(r'\') 20 | 21 | # repeat both with replace mode 22 | keys('11gg0') 23 | keys(r':VMLive\') 24 | keys(r'3\') 25 | keys('R') 26 | keys('rr') 27 | keys(r'\') 28 | keys(r'\') 29 | 30 | keys('16gg0') 31 | keys(r':VMLive\') 32 | keys(r'3\') 33 | keys('R') 34 | keys('rr') 35 | keys(r'\') 36 | keys(r'\') 37 | -------------------------------------------------------------------------------- /test/tests/abbrev/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | return string 2 | return string 3 | return string 4 | return string 5 | 6 | return string 7 | return string 8 | return string 9 | return string 10 | 11 | returntring 12 | returntring 13 | returntring 14 | returntring 15 | 16 | returntring 17 | returntring 18 | returntring 19 | returntring 20 | -------------------------------------------------------------------------------- /test/tests/abbrev/input_file.txt: -------------------------------------------------------------------------------- 1 | test string 2 | test string 3 | test string 4 | test string 5 | 6 | test string 7 | test string 8 | test string 9 | test string 10 | 11 | test string 12 | test string 13 | test string 14 | test string 15 | 16 | test string 17 | test string 18 | test string 19 | test string 20 | -------------------------------------------------------------------------------- /test/tests/alignment/commands.py: -------------------------------------------------------------------------------- 1 | # test alignment feature 2 | keys('\\\\\\\\A') 3 | keys('\\\\\\\\a') 4 | -------------------------------------------------------------------------------- /test/tests/alignment/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | foo 2 | foo 3 | foo 4 | foo 5 | foo 6 | -------------------------------------------------------------------------------- /test/tests/alignment/input_file.txt: -------------------------------------------------------------------------------- 1 | foo 2 | foo 3 | foo 4 | foo 5 | foo 6 | -------------------------------------------------------------------------------- /test/tests/backspace/commands.py: -------------------------------------------------------------------------------- 1 | # backspace and multibyte chars 2 | 3 | keys(r'2fs') 4 | keys(r'3\') 5 | keys(r']') 6 | keys(r'\\\\') 7 | keys(r'f§') 8 | keys(r'\\\\') 9 | keys(r'3\') 10 | keys(r'a') 11 | keys(r'\') 12 | keys(r'\') 13 | keys(r'\') 14 | keys(r'\') 15 | keys(r'\') 16 | keys(r'\') 17 | keys(r'\') 18 | keys(r'\') 19 | keys(r'\') 20 | keys(r'\') 21 | keys(r'\') 22 | keys(r'\') 23 | keys(r'\') 24 | keys(r'\') 25 | keys(r'\') 26 | keys(r'\') 27 | keys(r'\') 28 | keys(r'\') 29 | keys(r'\') 30 | keys(r'\') 31 | keys(r'\') 32 | keys(r'\') 33 | -------------------------------------------------------------------------------- /test/tests/backspace/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | Loi adipiscing elit, sed do eiusmod tempor 2 | ineua. Ut enim ad minim veniam, quis 3 | nou ut aliquip ex ea commodo consequat. 4 | Du voluptate velit esse cillum dolore eu 5 | fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 6 | culpa qui officia deserunt mollit anim id est laborum. 7 | -------------------------------------------------------------------------------- /test/tests/backspace/input_file.txt: -------------------------------------------------------------------------------- 1 | Lorem §§ ipsum dolor sit amet, consectetur§ adipiscing elit, sed do eiusmod tempor 2 | incididunt ut labore§ et dolore magna ali§qua. Ut enim ad minim veniam, quis 3 | nostrud §exercitation ullamco laboris §nisi ut aliquip ex ea commodo consequat. 4 | Duis aute irure§ dolor in reprehe§nderit in voluptate velit esse cillum dolore eu 5 | fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 6 | culpa qui officia deserunt mollit anim id est laborum. 7 | -------------------------------------------------------------------------------- /test/tests/change/commands.py: -------------------------------------------------------------------------------- 1 | # various change commands 2 | L = '\\\\\\\\' 3 | 4 | keys(':set tw=79\') 5 | keys(':set autoindent\') 6 | keys('2\') 7 | keys('I') 8 | keys('back_delete_test\') 9 | keys('\') 10 | keys('gciw') 11 | keys('smart case') 12 | keys('\') 13 | keys('ea') 14 | keys('_text') 15 | keys('\') 16 | keys('wTxcT_') 17 | keys('TEX') 18 | keys('\') 19 | keys('f(gcib') 20 | keys('gcib') 21 | keys('\') 22 | keys('\}j$2\ciw') 23 | keys('CIW') 24 | keys('\') 25 | keys('\j2\') 26 | keys('c$') 27 | keys('_c$') 28 | keys('\') 29 | keys('\j03\') 30 | keys('cc') 31 | keys('keep indent please') 32 | keys('\') 33 | keys('\gg0') 34 | keys('2\wcl') 35 | keys('C') 36 | keys('\') 37 | keys('$h2cl') 38 | keys('2CL') 39 | keys('\') 40 | keys('\') 41 | -------------------------------------------------------------------------------- /test/tests/change/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | Smart Case ipsum_TEXt dolor sit amet, consectetur (GCIB) elit, sed do eiusmod temp2CL 2 | smart Case ut_TEXt labore et dolore magna (Gcib). Ut enim ad minim veniam, qu2CL 3 | smart Case exercitation_TEXt ullamco laboris (gcib) ut aliquip ex ea commodo consequa2CL 4 | 5 | Duis aute irure dolor CIW 6 | reprehenderit in CIW 7 | velit esse cillum CIW eu 8 | fugiat nulla pariatu_c$ 9 | Excepteur sint occae_c$ 10 | cupidatat non proide_c$ 11 | keep indent please 12 | keep indent please 13 | keep indent please 14 | keep indent please 15 | -------------------------------------------------------------------------------- /test/tests/change/input_file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur (ADIPISICING) elit, sed do eiusmod tempor 2 | incididunt ut labore et dolore magna (Aliqua). Ut enim ad minim veniam, quis 3 | nostrud exercitation ullamco laboris (nisi) ut aliquip ex ea commodo consequat. 4 | 5 | Duis aute irure dolor in 6 | reprehenderit in voluptate 7 | velit esse cillum dolore eu 8 | fugiat nulla pariatur. 9 | Excepteur sint occaecat 10 | cupidatat non proident, sunt 11 | in culpa 12 | qui officia deserunt 13 | mollit 14 | anim id est laborum. 15 | -------------------------------------------------------------------------------- /test/tests/change2/commands.py: -------------------------------------------------------------------------------- 1 | # change commands #2 2 | keys(r'2\') 3 | keys(r'f(') 4 | keys(r'gcib') 5 | keys(r'gcib') 6 | keys(r'\') 7 | keys(r'\') 8 | keys(r'gg0$') 9 | keys(r'\\') 10 | keys(r'\') 11 | keys(r'\') 12 | keys(r'$ciw') 13 | keys(r'CIW') 14 | keys(r'\') 15 | keys(r'bbb') 16 | keys(r'c2aw') 17 | keys(r'c2aw ') 18 | keys(r'\') 19 | -------------------------------------------------------------------------------- /test/tests/change2/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit (gcib) amet, sed c2aw CIW 2 | incididunt ut labore (GCIB) et c2aw CIW 3 | nostrud exercitation (gcib) c2aw consequatCIW 4 | -------------------------------------------------------------------------------- /test/tests/change2/input_file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit (text) amet, sed do eiusmod tempor 2 | incididunt ut labore (TEXT) et dolore quis tempor 3 | nostrud exercitation () ullamco commodo consequat. 4 | -------------------------------------------------------------------------------- /test/tests/cquote/commands.py: -------------------------------------------------------------------------------- 1 | # change inside/around double quotes 2 | keys(r'fh') 3 | keys(r'\\') 4 | keys(r'ci\"') 5 | keys(r'test') 6 | keys(r'\\') 7 | keys(r'uugg0') 8 | keys(r'fh') 9 | keys(r'\\') 10 | keys(r'ca\"') 11 | keys(r'test') 12 | keys(r'\\') 13 | 14 | -------------------------------------------------------------------------------- /test/tests/cquote/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | {test1} 2 | {test2} 3 | {test3} 4 | -------------------------------------------------------------------------------- /test/tests/cquote/input_file.txt: -------------------------------------------------------------------------------- 1 | {"hello 1" 1} 2 | {"hello 2" 2} 3 | {"hello 3" 3} 4 | -------------------------------------------------------------------------------- /test/tests/curs2/commands.py: -------------------------------------------------------------------------------- 1 | # operations at cursors #2 2 | L = '\\\\\\\\' 3 | 4 | keys('\wwD\') 5 | keys('jj^\C') 6 | keys('\') 7 | keys('\') 8 | keys('\') 9 | keys('\Bba') 10 | keys('\') 11 | keys('\') 12 | keys('\') 13 | keys('\') 14 | keys('\') 15 | 16 | keys('jj^') 17 | keys(L+'/c..') 18 | keys('\') 19 | keys('n'+L+'n') 20 | keys(' ') 21 | keys('\') 22 | keys('\') 23 | 24 | keys('jj^\fd') 25 | keys('sEE~o\wC') 26 | keys('ciao') 27 | keys('\') 28 | keys('\') 29 | 30 | keys('jj0$h') 31 | keys('\\9h') 32 | keys('c2E') 33 | keys(' c2E') 34 | keys('\') 35 | keys('\') 36 | -------------------------------------------------------------------------------- /test/tests/curs2/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | fun! 2 | let 3 | 4 | s:d_cursors reg, 5 | M = a:M | '"'.a 6 | 7 | "reorder com 1mand; DD = 'dd' 8 | let [S, N, DD] = s:reorder_cmd 2(M, r, a:n, 'd') 9 | 10 | "for D, D$, DD: ciao 11 | if (S == '$' || S == 'D') | ciao 12 | 13 | "no matter the entered register, we're using defau c2E 14 | "we're passing the register in the options diction c2E 15 | "fill_register function will be called and take ca c2E it, if appropriate 16 | call s:V.Edit.run_normal('"'.s:v.def_reg.'d'.S, {'count': N, 'store': a:reg}) 17 | call s:G.merge_regions() 18 | endfun 19 | -------------------------------------------------------------------------------- /test/tests/curs2/input_file.txt: -------------------------------------------------------------------------------- 1 | fun! s:d_cursors(M, reg, n) 2 | let M = a:M | let r = '"'.a:reg 3 | 4 | "ds surround 5 | if M[:1] ==# 'ds' | return s:V.Edit.run_normal(M, {'maps': 0}) | endif 6 | 7 | "reorder command; DD = 'dd' 8 | let [S, N, DD] = s:reorder_cmd(M, r, a:n, 'd') 9 | 10 | "for D, d$, dd: ensure there is only one region per line 11 | if (S == '$' || S == 'd') | call s:G.one_region_per_line() | endif 12 | 13 | "no matter the entered register, we're using default register 14 | "we're passing the register in the options dictionary instead 15 | "fill_register function will be called and take care of it, if appropriate 16 | call s:V.Edit.run_normal('"'.s:v.def_reg.'d'.S, {'count': N, 'store': a:reg}) 17 | call s:G.merge_regions() 18 | endfun 19 | -------------------------------------------------------------------------------- /test/tests/curs_del/commands.py: -------------------------------------------------------------------------------- 1 | # delete operations at cursors 2 | L = '\\\\\\\\' 3 | 4 | keys('j0') 5 | keys('5\ww') 6 | keys('dWbP') 7 | keys('5W'+L+'a') 8 | keys('wllb'+L+'a') 9 | keys('Bd2l') 10 | keys('d4h') 11 | keys('\') 12 | -------------------------------------------------------------------------------- /test/tests/curs_del/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | if v:version >= 800 2 | s:R let = { V.Regions } 3 | s:X let = { VM.extend_mode } 4 | s:back let = { index(split('FTlhbB0nN^{(', '\zs'), c[0]) >= 0 } 5 | s:ia let = { index(['i', 'a'], c) >= 0 } 6 | s:single let = { index(split('hljkwebWEB$^0{}()%nN', '\zs'), c) >= 0 } 7 | s:double let = { index(split('iafFtTg', '\zs'), c) >= 0 } 8 | finish 9 | endif 10 | -------------------------------------------------------------------------------- /test/tests/curs_del/input_file.txt: -------------------------------------------------------------------------------- 1 | if v:version >= 800 2 | let s:R = { -> s:V.Regions } 3 | let s:X = { -> g:VM.extend_mode } 4 | let s:back = { c -> index(split('FTlhbB0nN^{(', '\zs'), c[0]) >= 0 } 5 | let s:ia = { c -> index(['i', 'a'], c) >= 0 } 6 | let s:single = { c -> index(split('hljkwebWEB$^0{}()%nN', '\zs'), c) >= 0 } 7 | let s:double = { c -> index(split('iafFtTg', '\zs'), c) >= 0 } 8 | finish 9 | endif 10 | -------------------------------------------------------------------------------- /test/tests/dot/commands.py: -------------------------------------------------------------------------------- 1 | # dot/select operator si' 2 | keys('\\\\') 3 | keys('c') 4 | keys('changed_word') 5 | keys('\') 6 | keys('\') 7 | keys('ggA\') 8 | keys('text') 9 | keys('\') 10 | keys('0j\\') 11 | keys('.') 12 | keys('0dw.~...') 13 | keys('\') 14 | 15 | keys("6Gf'l") 16 | keys('\\') 17 | keys("si'") 18 | keys('c') 19 | keys('change') 20 | keys('\') 21 | keys('\') 22 | -------------------------------------------------------------------------------- /test/tests/dot/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | changed_word word text 2 | TEXT 3 | TEXT 4 | TEXT 5 | 6 | let g:VM_maps['change'] = 'g/' 7 | let g:VM_maps['change'] = 'A' 8 | -------------------------------------------------------------------------------- /test/tests/dot/input_file.txt: -------------------------------------------------------------------------------- 1 | Word word 2 | word Word 3 | Word word 4 | word Word 5 | 6 | let g:VM_maps['Visual Regex'] = 'g/' 7 | let g:VM_maps['Visual All'] = 'A' 8 | -------------------------------------------------------------------------------- /test/tests/example/commands.py: -------------------------------------------------------------------------------- 1 | # 3x C-Down, i 2 | keys('\\\') 3 | keys('i') 4 | keys('Hello') 5 | keys('\') 6 | -------------------------------------------------------------------------------- /test/tests/example/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | Helloa 2 | Hellob 3 | Helloc 4 | Hellod 5 | -------------------------------------------------------------------------------- /test/tests/example/input_file.txt: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | d 5 | -------------------------------------------------------------------------------- /test/tests/example2/commands.py: -------------------------------------------------------------------------------- 1 | # 2x C-n, c 2 | keys('\') 3 | keys('\') 4 | keys('C') 5 | keys('hello') 6 | keys('\') 7 | -------------------------------------------------------------------------------- /test/tests/example2/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | hello 2 | madec 3 | hello 4 | -------------------------------------------------------------------------------- /test/tests/example2/input_file.txt: -------------------------------------------------------------------------------- 1 | antoine 2 | madec 3 | antoine 4 | -------------------------------------------------------------------------------- /test/tests/getcc/commands.py: -------------------------------------------------------------------------------- 1 | # visual select, cc 2 | L = '\\\\\\\\' 3 | keys('ve') 4 | keys(L + 'A') 5 | keys('\') 6 | keys('cc') 7 | keys('test') 8 | keys('\\') 9 | -------------------------------------------------------------------------------- /test/tests/getcc/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | test 2 | test 3 | test 4 | test 5 | test 6 | -------------------------------------------------------------------------------- /test/tests/getcc/input_file.txt: -------------------------------------------------------------------------------- 1 | miao miao 2 | miao miao 3 | miao miao 4 | miao miao 5 | miao miao 6 | -------------------------------------------------------------------------------- /test/tests/oO/commands.py: -------------------------------------------------------------------------------- 1 | # insert CR, insert line above 2 | 3 | keys(':setf vim\jw') 4 | keys('4\') 5 | keys('Ea') 6 | keys('\') 7 | keys('CARRYING OVER ') 8 | keys('\A') 9 | keys('\') 10 | keys('CR at EOL') 11 | keys('\k') 12 | keys('O') 13 | keys('above CR') 14 | keys('\\') 15 | -------------------------------------------------------------------------------- /test/tests/oO/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | fun! vm#icmds#return() 2 | "invert 3 | above CR 4 | CARRYING OVER regions order, so that they are processed from bottom to top 5 | CR at EOL 6 | let 7 | above CR 8 | CARRYING OVER s:V.Regions = reverse(s:R()) 9 | CR at EOL 10 | 11 | for 12 | above CR 13 | CARRYING OVER r in s:R() 14 | CR at EOL 15 | call 16 | above CR 17 | CARRYING OVER cursor(r.l, r.a) 18 | CR at EOL 19 | 20 | "if 21 | above CR 22 | CARRYING OVER not at eol, CR will cut the line and carry over the remaining text 23 | CR at EOL 24 | let at_eol = (r.a >= col([r.l, '$']) - 1) 25 | 26 | "if carrying over some text, delete it now, before finding the indent 27 | if !at_eol 28 | normal! d$ 29 | endif 30 | -------------------------------------------------------------------------------- /test/tests/oO/input_file.txt: -------------------------------------------------------------------------------- 1 | fun! vm#icmds#return() 2 | "invert regions order, so that they are processed from bottom to top 3 | let s:V.Regions = reverse(s:R()) 4 | 5 | for r in s:R() 6 | call cursor(r.l, r.a) 7 | 8 | "if not at eol, CR will cut the line and carry over the remaining text 9 | let at_eol = (r.a >= col([r.l, '$']) - 1) 10 | 11 | "if carrying over some text, delete it now, before finding the indent 12 | if !at_eol 13 | normal! d$ 14 | endif 15 | -------------------------------------------------------------------------------- /test/tests/oO/vimrc.vim: -------------------------------------------------------------------------------- 1 | " needed by vimrunner 2 | function! VimrunnerPyEvaluateCommandOutput(command) 3 | return execute(a:command) 4 | endfunction 5 | 6 | let g:loaded_remote_plugins = 1 7 | 8 | set runtimepath=$VIMRUNTIME 9 | set packpath= 10 | set nocompatible 11 | set runtimepath^=.. 12 | source ../plugin/visual-multi.vim 13 | 14 | filetype plugin indent on 15 | syntax enable 16 | set et ts=2 sts=2 sw=2 17 | -------------------------------------------------------------------------------- /test/tests/pasteatcur/commands.py: -------------------------------------------------------------------------------- 1 | # paste at cursor position 2 | L = '\\\\\\\\' 3 | 4 | keys('vip\$y') 5 | keys('vip' + L + 'c') 6 | keys('f_p') 7 | keys('a') 8 | keys('Hello') 9 | keys('\') 10 | -------------------------------------------------------------------------------- /test/tests/pasteatcur/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | test_test_aHelloa 2 | test_test_bHellob 3 | test_test_cHelloc 4 | test_test_dHellod 5 | -------------------------------------------------------------------------------- /test/tests/pasteatcur/input_file.txt: -------------------------------------------------------------------------------- 1 | test_a 2 | test_b 3 | test_c 4 | test_d 5 | -------------------------------------------------------------------------------- /test/tests/regex/commands.py: -------------------------------------------------------------------------------- 1 | # regex, find operator, select all 2 | 3 | keys('\\\\\\\\/') 4 | keys('Word\') 5 | keys('\\\') 6 | keys('c') 7 | keys('regex') 8 | keys('\') 9 | keys('\') 10 | keys('}j0') 11 | 12 | keys('V3j') 13 | keys('\\\\\\\\/') 14 | keys('Word\') 15 | keys('c') 16 | keys('visual_regex') 17 | keys('\') 18 | keys('\') 19 | keys('}j0') 20 | 21 | keys('\mip') 22 | keys('c') 23 | keys('find_operator') 24 | keys('\') 25 | keys('\') 26 | keys('}j0') 27 | 28 | keys('\') 29 | keys('vip') 30 | keys('\\\\\\\\f') 31 | keys('c') 32 | keys('visual_find') 33 | keys('\') 34 | keys('\') 35 | 36 | keys('0\\\\\\\\A') 37 | keys('c') 38 | keys('change_all') 39 | keys('\') 40 | keys('\') 41 | 42 | keys('0f_l') 43 | keys('ve') 44 | keys('\\\\\\\\A') 45 | keys('c') 46 | keys('this') 47 | keys('\') 48 | keys('\') 49 | 50 | -------------------------------------------------------------------------------- /test/tests/regex/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | 2 | regex change_this 3 | change_this regex 4 | regex change_this 5 | change_this regex 6 | 7 | visual_regex change_this 8 | change_this visual_regex 9 | visual_regex change_this 10 | change_this visual_regex 11 | 12 | find_operator change_this 13 | change_this find_operator 14 | find_operator change_this 15 | change_this find_operator 16 | 17 | visual_find change_this 18 | change_this visual_find 19 | visual_find change_this 20 | change_this visual_find 21 | -------------------------------------------------------------------------------- /test/tests/regex/input_file.txt: -------------------------------------------------------------------------------- 1 | 2 | Word word 3 | word Word 4 | Word word 5 | word Word 6 | 7 | Word word 8 | word Word 9 | Word word 10 | word Word 11 | 12 | Word word 13 | word Word 14 | Word word 15 | word Word 16 | 17 | Word word 18 | word Word 19 | Word word 20 | word Word 21 | -------------------------------------------------------------------------------- /test/tests/repl/commands.py: -------------------------------------------------------------------------------- 1 | # replace mode 2 | 3 | keys(r'3\') 4 | keys('R') 5 | keys('testòlè') 6 | keys(r'\') 7 | keys(r'\') 8 | 9 | # the same, but from second column 10 | keys('6gg0') 11 | keys(r'3\') 12 | keys('rtlR') 13 | keys('estòlè') 14 | keys(r'\') 15 | keys(r'\') 16 | 17 | # from second column, and multibyte in first one 18 | keys('11gg0l') 19 | keys(r'3\') 20 | keys('R') 21 | keys('estòlè') 22 | keys(r'\') 23 | keys(r'\') 24 | 25 | # backspace 26 | keys(r':set list\') 27 | keys('16gg0') 28 | keys(r'3\') 29 | keys('R') 30 | keys('festòlè') 31 | keys(r'\') 32 | keys(r'\') 33 | keys(r'\') 34 | keys(r'\') 35 | keys(r'\') 36 | keys(r'\') 37 | keys(r'\') 38 | keys(r'\') 39 | -------------------------------------------------------------------------------- /test/tests/repl/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | testòlè 2 | testòlè 3 | testòlè 4 | testòlè 5 | 6 | testòlè 7 | testòlè 8 | testòlè 9 | testòlè 10 | 11 | -estòlè 12 | -estòlè 13 | àestòlè 14 | -estòlè 15 | 16 | f------ 17 | fà--è-- 18 | f-ìè--- 19 | f--ù--- 20 | -------------------------------------------------------------------------------- /test/tests/repl/input_file.txt: -------------------------------------------------------------------------------- 1 | ------- 2 | -à--è-- 3 | à-ìè--- 4 | ---ù--- 5 | 6 | ------- 7 | -à--è-- 8 | à-ìè--- 9 | ---ù--- 10 | 11 | ------- 12 | -à--è-- 13 | à-ìè--- 14 | ---ù--- 15 | 16 | ------- 17 | -à--è-- 18 | à-ìè--- 19 | ---ù--- 20 | -------------------------------------------------------------------------------- /test/tests/trans/commands.py: -------------------------------------------------------------------------------- 1 | # transpositions and region splitting 2 | L = '\\\\\\\\' 3 | 4 | keys(':let @/ = \\"\\"\') 5 | keys('\\\') 6 | keys('\') 7 | keys('v\\\') 8 | keys(L + 'c') 9 | keys('\') 10 | keys(L + 't') 11 | keys('\}j0') 12 | 13 | keys('V3j') 14 | keys(L + 'a') 15 | keys(L + 's') 16 | keys('\\') 17 | keys(L + 't') 18 | keys('\') 19 | 20 | keys('/cat\\\\|bat\') 21 | keys('ggV3j') 22 | keys(L + 'f') 23 | keys(L + 't') 24 | keys('\') 25 | 26 | -------------------------------------------------------------------------------- /test/tests/trans/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | bat dog cat 2 | bat dog cat 3 | bat dog cat 4 | bat dog cat 5 | 6 | bat dog cat 7 | bat dog cat 8 | bat dog cat 9 | bat dog cat 10 | 11 | -------------------------------------------------------------------------------- /test/tests/trans/input_file.txt: -------------------------------------------------------------------------------- 1 | dog cat bat 2 | dog cat bat 3 | dog cat bat 4 | dog cat bat 5 | 6 | dog cat bat 7 | dog cat bat 8 | dog cat bat 9 | dog cat bat 10 | 11 | -------------------------------------------------------------------------------- /test/tests/vmsearch/commands.py: -------------------------------------------------------------------------------- 1 | # VMSearch test 2 | 3 | keys(r':VMSearch magna\') 4 | keys(r'c') 5 | keys(r'MAGNA') 6 | keys(r'\') 7 | keys(r'\') 8 | 9 | # BUGGER... this took me a hour, and it still fails in vim 10 | keys(r':%VMSearch \\') 11 | keys(r'dolor\>\') 12 | keys(r'c') 13 | keys(r'DOLOR') 14 | keys(r'\') 15 | keys(r'\') 16 | 17 | keys(r'ggVj') 18 | keys(r':VMSearch dolor\') 19 | keys(r'a') 20 | keys(r'---') 21 | keys(r'\') 22 | 23 | keys(r':%VMSearch lab\') 24 | keys(r'a') 25 | keys(r'LAB') 26 | keys(r'\') 27 | keys(r'\') 28 | 29 | -------------------------------------------------------------------------------- /test/tests/vmsearch/expected_output_file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum DOLORLAB--- sit amet, consectetur adipiscing elit, sed do eiusmod tempor 2 | incididunt ut labLABore et dolorLAB---e MAGNA aliqua. Ut enim ad minim veniam, quis 3 | nostrud exercitation ullamco labLABoris nisi ut aliquip ex ea commodo consequat. 4 | Duis aute irure DOLORLAB in reprehenderit in voluptate velit esse cillum dolorLABe eu 5 | fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 6 | culpa qui officia deserunt mollit anim id est labLABorum. 7 | -------------------------------------------------------------------------------- /test/tests/vmsearch/input_file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 2 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 3 | nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 4 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 5 | fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 6 | culpa qui officia deserunt mollit anim id est laborum. 7 | -------------------------------------------------------------------------------- /tutorialrc: -------------------------------------------------------------------------------- 1 | if &compatible 2 | set nocompatible 3 | endif 4 | 5 | set runtimepath=$VIMRUNTIME 6 | cd `=expand(':p:h')` 7 | set runtimepath^=. 8 | silent! set packpath= 9 | 10 | syntax enable 11 | 12 | if $COLORTERM == ( 'truecolor' || 'gnome-terminal' || 'rxvt-xpm' ) 13 | set t_Co=256 14 | endif 15 | 16 | if has('gui_running') 17 | colorscheme darkblue 18 | hi! link Tabline StatusLine 19 | else 20 | colorscheme industry 21 | hi Search ctermbg=236 ctermfg=12 22 | hi PreProc ctermfg=174 23 | hi Special ctermfg=223 24 | hi ColorColumn ctermbg=236 25 | hi Tabline cterm=NONE 26 | hi! link StatusLine Tabline 27 | if has('patch-8.0.0616') || has('nvim') 28 | hi Normal ctermbg=235 29 | endif 30 | endif 31 | set ttimeout timeoutlen=3000 ttimeoutlen=10 32 | silent e ./doc/vm-tutorial 33 | setfiletype help 34 | setlocal bt=nofile bh=wipe noswf nobl 35 | nnoremap ZZ :quit! 36 | set mouse=a 37 | set t_ut= 38 | set expandtab 39 | set laststatus=2 40 | set hlsearch 41 | 42 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 43 | " vim-visual-multi configuration {{{1 44 | 45 | let g:VM_mouse_mappings = 1 46 | let g:VM_theme = 'iceblue' 47 | 48 | let g:VM_maps = {} 49 | let g:VM_maps["Undo"] = 'u' 50 | let g:VM_maps["Redo"] = '' 51 | 52 | 53 | " vim: ft=vim 54 | --------------------------------------------------------------------------------