diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c256393 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2013-16 Henrik Lissner. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 4927e96..fe37e43 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,51 @@ +# vim-multiedit - Multi-selection and editing in vim + ## About -These are the beginnings of a plugin that brings multi-selection editing (like in Sublime Text) to vim. +Do you envy Sublime Text 2's multiple selection and editing feature? This plugin +tries to fill that multi-caret shaped gap in your heart by letting you +specify "regions" of text and edit them all from one place. + +*(This plugin is based on https://github.com/felixr/vim-multiedit by Felix +Riedel )* + +## Usage + +```vim +" Insert a disposable marker after the cursor +nmap ma :MultieditAddMark a + +" Insert a disposable marker before the cursor +nmap mi :MultieditAddMark i + +" Make a new line and insert a marker +nmap mo o:MultieditAddMark i +nmap mO O:MultieditAddMark i + +" Insert a marker at the end/start of a line +nmap mA $:MultieditAddMark a +nmap mI ^:MultieditAddMark i + +" Make the current selection/word an edit region +vmap m :MultieditAddRegion +nmap mm viw:MultieditAddRegion + +" Restore the regions from a previous edit session +nmap mu :MultieditRestore + +" Move cursor between regions n times +map ]m :MultieditHop 1 +map [m :MultieditHop -1 + +" Start editing! +nmap M :Multiedit + +" Clear the word and start editing +nmap C :Multiedit! -## Status +" Unset the region under the cursor +nmap md :MultieditClear -It's my first plugin and currently it supports only basic editing using insert mode. Current functionality is demoed in the following screencast: http://shelr.tv/records/4fef105a966080237e00005e +" Unset all regions +nmap mr :MultieditReset +``` diff --git a/autoload/multiedit.vim b/autoload/multiedit.vim new file mode 100644 index 0000000..38e6def --- /dev/null +++ b/autoload/multiedit.vim @@ -0,0 +1,510 @@ +" *multiedit.txt* Multi-editing for Vim + +" addRegion() {{ +func! multiedit#addRegion() + if mode() != 'v' + normal! gv + endif + + " Get region parameters + let line = line('.') + let startcol = col('v') + let endcol = col('.')+1 + + " add selection to list + let sel = { + \ 'line': line, + \ 'col': startcol, + \ 'len': endcol-startcol, + \ 'suffix_length': col('$')-endcol + \ } + if !exists("b:regions") + let b:regions = {} + let b:first_region = sel + endif + + if has_key(b:regions, line) + " Check if this overlaps with any other region + let overlapid = multiedit#hasOverlap(sel) + if overlapid == -1 + let b:regions[line] = b:regions[line] + [sel] + else + " If so, change this to the 'main' region + let b:first_region = b:regions[line][overlapid] + let new_first = 1 + endif + else + let b:regions[line] = [sel] + endif + + " Highlight the region + if exists("new_first") + call multiedit#rehighlight() + else + call multiedit#highlight(line, startcol, endcol) + endif + + " Exit visual mode + normal! v +endfunc +" }} + +" addMark() {{ +func! multiedit#addMark(mode) + let mark = g:multiedit_mark_character + + " Insert the marker character and select it + let line = getline('.') + let col = col('.') + + " Insert the markers, pre or post, depending on the mode + let precol = a:mode ==# "i" ? 2 : 1 + let sufcol = a:mode ==# "i" ? 1 : 0 + + let prefix = col > 1 ? line[0:col-precol] : '' + let suffix = line[(col-sufcol):] + call setline(line('.'), prefix.mark.suffix) + if a:mode ==# "a" + normal! l + endif + normal! v + + let line = line('.') + if exists("b:regions") && has_key(b:regions, line) + " Check for regions on the same line that follow this region and shift + " them to the right. + let col = col('.') + for region in b:regions[line] + let offset = strlen(mark) + if region.col > col + let region.col += offset + else + let region.suffix_length += offset + endif + endfor + + call multiedit#rehighlight() + endif + + " ...then make it a region + call multiedit#addRegion() +endfunc +" }} + +" start() {{ +func! multiedit#start(bang, ...) + if !exists("b:regions") + if g:multiedit_auto_restore == 0 || !multiedit#again() + return + endif + endif + + call multiedit#triggerStartEvents() + + let lastcol = b:first_region.col + b:first_region.len + + " If bang exists OR the first region is a marker, then clear it before + " editing mode begins. + if a:bang ==# '!' + " Remove the word and update the highlights + let linetext = getline(b:first_region.line) + if b:first_region.col == 1 + let newline = g:multiedit_mark_character . linetext[(lastcol - 1):] + else + let newline = linetext[0:(b:first_region.col - 2)] . g:multiedit_mark_character . linetext[(lastcol - 1):] + endif + + call setline(b:first_region.line, newline) + call multiedit#update(0) + + " Refresh the lastcol (update() likely changed things!) + let lastcol = b:first_region.col + b:first_region.len + endif + + " Move cursor to the end of the word + call cursor(b:first_region.line, lastcol) + + " Set up some 'abort' mappings, because they can't be accounted for. They + " will unmap themselves once they're pressed. + call multiedit#maps(0) + + " Start insert mode. Since there's no way to mimic 'a' with :normal, we + " have to do it manually: + if col('$') == lastcol + startinsert! + else + startinsert + endif + + " Where the magic happens + augroup multiedit + au! + " Update the highlights as you edit + au CursorMovedI * call multiedit#update(0) + + " Once you leave INSERT, apply changes and delete this augroup + au InsertLeave * call multiedit#update(1) | call multiedit#maps(0) | call multiedit#triggerEndEvents() | au! multiedit + + if g:multiedit_auto_reset == 1 + " Clear all regions once you exit insert mode + au InsertLeave * call multiedit#reset() + endif + augroup END +endfunc +" }} + +" reset() {{ +func! multiedit#reset(...) + if exists("b:regions_last") + unlet b:regions_last + endif + if exists("b:regions") + if a:0 == 0 + let b:regions_last = {} + let b:regions_last["regions"] = b:regions + let b:regions_last["first"] = b:first_region + endif + + unlet b:first_region + unlet b:regions + endif + + syn clear MultieditRegions + syn clear MultieditFirstRegion + + call multiedit#maps(1) + silent! au! multiedit +endfunc +" }} + +" clear() {{ +func! multiedit#clear(...) + if !exists("b:regions") + return + endif + + " The region to delete might have been provided as an argument. + if a:0 == 1 && type(a:1) == 4 + let sel = a:1 + else + let sel = {"col": col('v'), "len": 1, "line":line('.')} + endif + + if !has_key(b:regions, sel.line) + return + endif + + " Iterate through all regions on this line + let i = 0 + for region in b:regions[sel.line] + " Does this cursor overlap with this region? If so, delete it. + if multiedit#isOverlapping(sel, region) + + " If you're deleting a main region, we need to pass on the role to + " another region first! + if region == b:first_region + unlet b:first_region + + " Get the last specified region + let keys = keys(b:regions) + if len(keys) + let b:first_region = b:regions[keys(b:regions)[-1]][-1] + endif + endif + + unlet b:regions[sel.line][i] + endif + let i += 1 + endfor + + " The regions have changed. Update the highlights. + call multiedit#rehighlight() +endfunc +" }} + +" If Multiple_cursors_after function has been defined in ~/.vimrc, call it +" triggerEndEvents() {{ +func! multiedit#triggerEndEvents() + if exists('*Multiple_cursors_after') + exe "call Multiple_cursors_after()" + endif +endfunc +"}} + +" If Multiple_cursors_after function has been defined in ~/.vimrc, call it +" triggerStartEvents() {{ +func! multiedit#triggerStartEvents() + if exists('*Multiple_cursors_before') + exe "call Multiple_cursors_before()" + endif +endfunc +" }} + +" update() {{ +" Update highlights when changes are made +func! multiedit#update(change_mode) + if !exists('b:regions') + return + endif + + " Column offset from start of main edit region to cursor (relevant when + " restoring cursor location post-edit) + let col = col('.') + let cursor_col = col-b:first_region.col + + " Clear highlights so we can make changes + syn clear MultieditRegions + syn clear MultieditFirstRegion + + " Prepare the new, altered line + let linetext = getline(b:first_region.line) + let lineendlen = (len(linetext) - b:first_region.suffix_length) + if lineendlen == 0 + let newtext = "" + else + let newtext = linetext[(b:first_region.col-1): (lineendlen-1)] + endif + + " Iterate through the lines where regions exist. And sort them by + " sequence. + for line in sort(keys(b:regions)) + let regions = copy(b:regions[line]) + let regions = sort(regions, "multiedit#entrySort") + let multiedit#offset = 0 + + " Iterate through each region on this line + for region in regions + if a:change_mode + + let region.col += multiedit#offset + if region.line != b:first_region.line || region.col != b:first_region.col + " Get the old line + let oldline = getline(region.line) + + " ...and assemble a new one + let prefix = '' + if region.col > 1 + let prefix = oldline[0:region.col-2] + endif + let suffix = oldline[(region.col+region.len-1):] + + " Update the line + call setline(region.line, prefix.newtext.suffix) + endif + + if col >= b:first_region.col + let multiedit#offset = multiedit#offset + len(newtext) - region.len + endif + let region.len = len(newtext) + + else + + if region.line == b:first_region.line + + " ...move the highlight offset of regions after it + if region.col >= b:first_region.col + let region.col += multiedit#offset + let multiedit#offset = multiedit#offset + len(newtext) - b:first_region.len + endif + + " ...and update the length of the first_region. + " Remember, we're only affecting the main region and + " regions following it, on the same line + if region.col == b:first_region.col + if newtext ==# "" + if col < b:first_region.col + call multiedit#reset(1) + return + endif + + " If newtext is blank, just make the len 0 (for + " now) otherwise it'll go crazy! + let region.len = 0 + else + let region.len = len(newtext) + endif + endif + + endif + + " Rehighlight the lines + call multiedit#highlight(region.line, region.col, region.col+region.len) + + endif + endfor + endfor + + " Remeasure the strlen + let b:first_region.suffix_length = col([b:first_region.line, '$']) - b:first_region.col - b:first_region.len + + " Restore cursor location + call cursor(b:first_region.line, b:first_region.col + cursor_col) + +endfunc +" }} + +" again() {{ +" Restore last region selections. Returns 1 on success, 0 on failure. +func! multiedit#again() + if !exists("b:regions_last") + echom "No regions to restore!" + return + endif + + let b:first_region = b:regions_last["first"] + let b:regions = b:regions_last["regions"] + + call multiedit#rehighlight() + return 1 +endfunc +" }} + +" jump() {{ +" Hop [-/+]N regions +func! multiedit#jump(n) + let n = str2nr(a:n) + if !exists("b:regions") + echom "There are no regions to jump to." + return + elseif n == 0 + " n == 0 goes nowhere! + return + endif + + " This is the starting point of the search. + let col = col('.') + let line = line('.') + + " Sort the lines so that we can sequentially access them. If the jump is + " going backwards, reverse the resulting keys. + let region_keys = sort(keys(b:regions)) + if n < 0 + call reverse(region_keys) + endif + + let i = 0 + for l in region_keys + " Skip over irrelevant lines (before/after the start point, depending + " on the jump direction) + if (n>0 && lline) + continue + endif + + let regions = sort(copy(b:regions[l]), 'multiedit#entrySort') + if n < 0 + call reverse(regions) + endif + + for region in regions + " If this is the line we're on, skip irrelevant regions + " (before/after the start point, depending on jump direction) + if l == line + if ((n>0) && (region.col<=col)) || ((n<0) && (region.col>=col)) + continue + endif + endif + + " Skip over n-1 matches, then move the cursor on the nth match + let i += a:n > 0 ? 1 : -1 + if n == i + call cursor(l, region.col) + return 1 + endif + endfor + endfor + + echom "No more regions!" + return +endfunc +" }} + +"""""""""""""""""""""""""" +" isOverlapping(selA, selB) {{ +" Checks to see if selA overlaps with selB +func! multiedit#isOverlapping(selA, selB) + " Check for invalid input + if type(a:selA) != 4 || type(a:selB) != 4 + return + endif + + " If they're not on the same line, don't even try. + if a:selA.line != a:selB.line + return + endif + + " Check for overlapping + let selAend = a:selA.col + (a:selA.len - 1) + let selBend = a:selB.col + (a:selB.len - 1) + return a:selA.col == a:selB.col || selAend == selBend + \ || a:selA.col == selBend || selAend == a:selB.col + \ || (a:selA.col > a:selB.col && a:selA.col < selBend) + \ || (selAend < selBend && selAend > a:selB.col) +endfunc +" }} + +" hasOverlap(sel) {{ +" Checks to see if any other regions overlap with this one. Returns -1 if not, +" and the id of it otherwise (e.g. b:regions[line][id]) +func! multiedit#hasOverlap(sel) + if type(a:sel) != 4 || !has_key(b:regions, a:sel.line) + return -1 + endif + + for i in range(len(b:regions[a:sel.line])) + if multiedit#isOverlapping(a:sel, b:regions[a:sel.line][i]) + return i + endif + endfor + return -1 +endfunc +" }} + +" highlight(line, start, end) {{ +func! multiedit#highlight(line, start, end) + if a:start > a:end || a:end < a:start + return + endif + if !exists("b:first_region") || (b:first_region.line == a:line && b:first_region.col == a:start) + let synid = "MultieditFirstRegion" + else + let synid = "MultieditRegions" + endif + execute "syn match ".synid." '\\%".a:line."l\\%".a:start."c\\_.*\\%".a:line."l\\%".a:end."c' containedin=ALL" +endfunc +" }} + +" rehighlight() {{ +func! multiedit#rehighlight() + syn clear MultieditRegions + syn clear MultieditFirstRegion + + " Go through regions and rehighlight them + for line in keys(b:regions) + for sel in b:regions[line] + call multiedit#highlight(line, sel.col, sel.col + sel.len) + endfor + endfor +endfunc +" }} + +" map() {{ +func! multiedit#maps(unmap) + if a:unmap + silent! iunmap + silent! iunmap + silent! iunmap + else + inoremap :call multiedit#maps(1) + inoremap :call multiedit#maps(1) + inoremap :call multiedit#maps(1) + endif +endfunc +" }} + +" entrySort() {{ +func! multiedit#entrySort(a, b) + return a:a.col == a:b.col ? 0 : a:a.col > a:b.col ? 1 : -1 +endfunc +" }} + +" vim: set foldmarker={{,}} foldlevel=0 foldmethod=marker diff --git a/doc/multiedit.txt b/doc/multiedit.txt index f6db6bc..536fb1f 100644 --- a/doc/multiedit.txt +++ b/doc/multiedit.txt @@ -1,39 +1,233 @@ -*multiedit.txt* Multi-editing for Vim - -Version: 0.1.0 -Author : Felix Riedel -License: MIT license {{{ - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -}}} - -CONTENTS *multiedit-contents* - -Introduction |multiedit-introduction| -Changelog |multiedit-changelog| +*multiedit.txt* Multi-editing for Vim *multiedit* + +Author: Henrik Lissner +License: MIT license + +CONTENTS *multiedit-contents* + +Introduction |multiedit-introduction| +Mappings |multiedit-mappings| +Settings |multiedit-settings| +Known Issues |multiedit-issues| +Changelog |multiedit-changelog| + +============================================================================== +INTRODUCTION *multiedit-introduction* + +Do you envy Sublime Text 2's multiple selection and editing feature? This plugin +tries to fill that multi-caret shaped gap in your heart by letting you +specify "regions" of text and edit them all from one place. + +NOTE: This plugin was inspired by https://github.com/felixr/vim-multiedit, by +Felix Riedel . ============================================================================== -INTRODUCTION *multiedit-introduction* +MAPPINGS *multiedit-mappings* +> + " Insert a disposable marker after the cursor + nmap ma :MultieditAddMark a + + " Insert a disposable marker before the cursor + nmap mi :MultieditAddMark i + + " Make a new line and insert a marker + nmap mo o:MultieditAddMark i + nmap mO O:MultieditAddMark i + + " Insert a marker at the end/start of a line + nmap mA $:MultieditAddMark a + nmap mI ^:MultieditAddMark i + " Make the current selection/word an edit region + vmap m :MultieditAddRegion + nmap mm viw:MultieditAddRegion + " Restore the regions from a previous edit session + nmap mu :MultieditRestore + " Move cursor between regions n times + map ]m :MultieditHop 1 + map [m :MultieditHop -1 + + " Start editing! + nmap M :Multiedit + + " Clear the word and start editing + nmap C :Multiedit! + + " Unset the region under the cursor + nmap md :MultieditClear + + " Unset all regions + nmap mr :MultieditReset +< +Here are some keybinds that aren't included in the plugin but may be +useful/interesting to some people: +> + " Make the word under the cursor a region and jump to the next/previous + " occurance of that word + nmap mn mm/=expand("") + nmap mp mm?=expand("") +< + ============================================================================== -CHANGELOG *multiedit-changelog* +SETTINGS *multiedit-settings* +> + " Disable all mappings? + let g:multiedit_no_mappings = 0 + + " Reset all regions on InsertLeave when finished? + let g:multiedit_auto_reset = 1 + + " Disposable marker character (beware characters with strlen > 1 - like + " special symbols). + let g:multiedit_mark_character = '|' + + " If no selections are present and you initiate edit mode, should it + " restore the previous regions? + let g:multiedit_auto_restore = 1 +< + +The highlight regions can be customized, these are the defaults: +> + hi default link MultieditRegions Search + hi default link MultieditFirstRegion IncSearch +< +If other plugins cause odd behaviour during multiple region editing, two +function scan be used to disable/re-enable this behaviour at apprpriate +times. An example showing how to disable NeoComplete during multiple +region editing is below: + + function! Multiple_cursors_before() + exe 'NeoCompleteLock' + echo 'Disabled autocomplete' + endfunction + + function! Multiple_cursors_after() + exe 'NeoCompleteUnlock' + echo 'Enabled autocomplete' + endfunction + +============================================================================== +KNOWN ISSUES *multiedit-issues* + +A few problems exist in this plugin, some that may be addressed in the future +and some that won't. + +1. Multiediting aren't truly like multiple cursors, they can't account for + newlines or deleting past the starting point of the main region. (This + won't be addressed. Macros can easily make up for this. Heck, with mastery + of macros, this entire plugin could be replaced!) +2. g:multiedit_mark_character cannot be a unicode/special character with + a strlen > 1. This may be addressed in a future updated, but is low + priority. +3. It won't make you coffee. This is unacceptable! A THOUSAND YEARS DUNGEON! + +If you encounter any other issues, please report them at +https://github.com/hlissner/vim-multiedit/issues + +Thank you! + +============================================================================== +CHANGELOG *multiedit-changelog* + +2014-11-13 [2.0.3] +- New: added conditional calls to Multiple_cursors_after/Multiple_cursors_before + functions defined in your ~/.vimrc. These functions allow disabling of + NeoComplete while editing multiple regions, for example. Implemenation copied + from terryma/vim-multiple-cursors issue #51 +2013-03-28 [2.0.2] +- Fix: when the region goes crazy when the text is at the start of the line (when + len == 0 it would suddenly expand to the length of the line). +- Fix: when the cursor deletes past its boundaries it will leave multiedit + mode and reset your regions. + +2013-03-26 [2.0.1] +- New: four new keybinds for adding markers to the start/end of a line or on + the next/previous line +- Changed: auto-removing disposable markers where entering insert mode (had + a strange effect when regions were at the start of a line) + +2013-03-25 [2.0.0] +- New: jump between regions with ]m and [m +- Changed: disposable marker will be automatically deleted when entering + insert mode +- Changed: :MultieditAppendMark and :MultieditPrependMark are merged into + :MultieditAddMark [ia] +- Changed: the default highlight color for MultieditRegion is now linked to + Search' hl group +- Changed: in visual mode, mm is not m (it is still mm + in normal mode) +- Removed: ms (sets the region under the cursor to the new + 'main' region). mm and m will do so implicitly if + a pre-existing region is detected under the cursor +- Removed: mn and mp - however, instructions on how to + restore this functionality can be found in |multiedit-settings| +- Fixed: cursor position after editing +- Fixed: old regions weren't saved and couldn't be restored (would throw an + error) +- Fixed: when deleting the main region, the (automatically) assigned new main + region wasn't chosen based on the order in which they were set +- Fixed: errors caused by overlap detection when deleting regions +- General code clean up and refactoring across the board + +2013-03-11 [1.1.3] +- Add :MultieditRestore to restore previous regions (if available). Uses + mu +- Add g:multiedit_auto_restore to control :MultieditRestore being called when + edit mode is initiated without regions specified. + +2013-03-11 [1.1.2] +- Change :Multiedit! functionality to delete word before editing (like + [C]hange command). Use C to initiate this mode. +- Remove mw - now mm does it's job + (viw:MultieditAddRegion) + +2013-03-11 [1.1.1] +- Add :Multiedit! - will move insert caret to the start of the word, instead + of the end. Kep map for this is I +- Fix automatically adding next/prev occurrence when using :MultieditNextMatch + and :MultieditPreviousMatch + +2013-03-10 [1.1.0] +- Fix :MultieditClear "Key not present" bug +- Fix bug with editing consecutive regions on the same line +- Fix region reordering issue caused by clearing individual regions +- Refactor multiedit#update() completely +- Add :MultieditSet and ms - allowing you to change the "editable" + region. + +2013-03-07 [1.0.0] +- Implement next/previous match functionality (like CMD+D in ST2) +- Fix :MultieditAddRegion not accepting ranges + +2013-03-06 +- Move functions to autoload/multiedit.vim +- Remove synchronized editing (caused more problems than helped) +- Highlights keep up with edits +- Remove mouse mappings +- Replace s with :commands, see plugin/multiedit.vim +- Add "abort" keymaps that will disengage edit mode (and insert mode) if + pressed. These are , , - which the plugin can't take into + account. +- Fix bugs with regions on the same line + +2013-03-03 +- New marker system enables you to place disposable markers +- Change setting variable name convention + - Remove g:multedit_auto_update + - Add g:multiedit_mark_character + - Add mouse map +- Highlight first selection differently (though colors need changing) +- Add clear() and addMatch() +- General code cleanup + +2013-02-23 +- Auto-reset on InsertLeave by default (can be disabled) +- Auto-update can be disabled +- Add INSERT, APPEND and CHANGE triggers +- Change default keymaps +- Fix g:multiedit_nomappings setting 2012-07-01 - First experimental version diff --git a/plugin/multiedit.vim b/plugin/multiedit.vim index 783e66b..4a6cfbc 100644 --- a/plugin/multiedit.vim +++ b/plugin/multiedit.vim @@ -1,165 +1,78 @@ -" *multiedit.txt* Multi-editing for Vim -" -" Version: 0.1.0 -" Author : Felix Riedel -" License: MIT license {{{ -" Permission is hereby granted, free of charge, to any person obtaining -" a copy of this software and associated documentation files (the -" "Software"), to deal in the Software without restriction, including -" without limitation the rights to use, copy, modify, merge, publish, -" distribute, sublicense, and/or sell copies of the Software, and to -" permit persons to whom the Software is furnished to do so, subject to -" the following conditions: -" The above copyright notice and this permission notice shall be included -" in all copies or substantial portions of the Software. -" -" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -" }}} - -hi default MultiSelections gui=reverse term=reverse cterm=reverse - -function! s:highlight(line, start, end) - execute "syn match MultiSelections '\\%".a:line."l\\%".a:start."c\\_.*\\%".a:line."l\\%".a:end."c' containedin=ALL" -endfunction - -function! s:addSelection() - - " restore selection - normal! gv - - " get selection parameters - let lnum = line('.') - let startcol = col('v') - let endcol = col('.')+1 - let line_end = col('$') - - " add selection to list - let sel = { 'line': lnum, 'col': startcol, 'end': endcol, 'len': endcol-startcol, 'suffix_length': line_end-endcol } - - if !exists('b:selections') - let b:selections = {} - let b:first_selection = sel - endif - - if has_key(b:selections, lnum) - let b:selections[lnum] = b:selections[lnum] + [sel] - else - let b:selections[lnum] = [sel] - endif - - call s:highlight(lnum, startcol, endcol) - - "exit visual mode - normal! v -endfunction - - -function! s:reset() - if exists('b:selections') - unlet b:selections - endif - if exists('b:first_selection') - unlet b:first_selection - endif - syn clear MultiSelections - au! multiedit -endfunction - - -function! s:startEdit() - if !exists('b:selections') - return - endif - call cursor(b:first_selection.line, (b:first_selection.col + b:first_selection.len)) - augroup multiedit - au! - au CursorMovedI * call s:updateSelections() - " au InsertEnter * call s:updateSelections(1) - au InsertLeave * autocmd! multiedit - augroup END -endfunction - - -function! s:EntrySort(a,b) - return a:a.col == a:b.col ? 0 : a:a.col > a:b.col ? 1 : -1 -endfunction - -function! s:updateSelections() - - " Save cursor position - let b:save_cursor = getpos('.') - " let b:save_col = col(".") - " let b:save_line = line(".") - - if !exists('b:selections') - return - endif - - syn clear MultiSelections - - let editline = getline(b:first_selection.line) - let line_length = len(editline) - let newtext = editline[ (b:first_selection.col-1): (line_length-b:first_selection.suffix_length-1)] - - - for line in sort(keys(b:selections)) - let entries = b:selections[line] - let entries = sort(entries, "s:EntrySort") - let s:offset = 0 - - for entry in entries - " skip the entry of the first selection - if entry.line != b:first_selection.line || entry.col != b:first_selection.col - " selection is moved by offset if this is not - " the first selection in the line - let entry.col = entry.col + s:offset - let oldline = getline(entry.line) - let prefix = '' - if entry.col > 1 - let prefix = oldline[0:entry.col-2] - endif - let suffix = oldline[(entry.col+entry.len-1):] - - " update the line - call setline(entry.line, prefix.newtext.suffix) - endif - - " update the offset for the next selection in this line - let s:offset = s:offset + len(newtext) - entry.len - - " update the length of the selection to fit the new content - let entry.len = len(newtext) +" *multiedit.txt* Multi-editing for Vim +" +" Version: 2.0.2 +" Author: Henrik Lissner +" License: MIT license +" +" Inspired by https://github.com/felixr/vim-multiedit, this plugin hopes to +" fill that multi-cursor-shaped gap in your heart that Sublime Text 2 left you +" with. + +if exists('g:loaded_multiedit') || &cp + finish +endif +let g:loaded_multiedit = 1 - call s:highlight(entry.line, entry.col, entry.col+entry.len) - endfor - endfor - let b:first_selection.suffix_length = col([b:first_selection.line, '$']) - b:first_selection.col - b:first_selection.len +" Settings +if !exists('g:multiedit_no_mappings') + let g:multiedit_no_mappings = 0 +endif - " restore cursor position - " call cursor(s:save_line, s:save_col) - call setpos('.', b:save_cursor) -endfunction +if !exists('g:multiedit_auto_reset') + let g:multiedit_auto_reset = 1 +endif -map (multiedit-add) :call addSelection() -map (multiedit-edit) :call startEdit() -map (multiedit-reset) :call reset() +if !exists('g:multiedit_mark_character') + let g:multiedit_mark_character = '|' +endif +if !exists('g:multiedit_auto_restore') + let g:multiedit_auto_restore = 1 +endif -if !exists('g:multiedit_nomappings') - let g:multiedit_nomappings = 0 +" Color highlights +hi default link MultieditRegions Search +hi default link MultieditFirstRegion IncSearch + + +" Mappings +com! -bar -range MultieditAddRegion call multiedit#addRegion() +com! -bar -nargs=1 MultieditAddMark call multiedit#addMark() +com! -bar -bang Multiedit call multiedit#start() +com! -bar MultieditClear call multiedit#clear() +com! -bar MultieditReset call multiedit#reset() +com! -bar MultieditRestore call multiedit#again() +com! -bar -nargs=1 MultieditHop call multiedit#jump() + +if g:multiedit_no_mappings != 1 + " Insert a disposable marker after the cursor + nmap ma :MultieditAddMark a + " Insert a disposable marker before the cursor + nmap mi :MultieditAddMark i + " Make a new line and insert a marker + nmap mo o:MultieditAddMark i + nmap mO O:MultieditAddMark i + " Insert a marker at the end/start of a line + nmap mA $:MultieditAddMark a + nmap mI ^:MultieditAddMark i + " Make the current selection/word an edit region + vmap m :MultieditAddRegion + nmap mm viw:MultieditAddRegion + " Restore the regions from a previous edit session + nmap mu :MultieditRestore + " Move cursor between regions n times + map ]m :MultieditHop 1 + map [m :MultieditHop -1 + " Start editing! + nmap M :Multiedit + " Clear the word and start editing + nmap C :Multiedit! + " Unset the region under the cursor + nmap md :MultieditClear + " Unset all regions + nmap mr :MultieditReset endif -if g:multiedit_nomappings != 0 - vmap ,f (multiedit-add) - nmap ,f viw,fb - nmap ,i (multiedit-edit)i - map ,r (multiedit-reset) -endif +" vim: set foldmarker={{,}} foldlevel=0 foldmethod=marker