├── .github └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.markdown ├── README.markdown ├── doc └── abolish.txt └── plugin └── abolish.vim /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: tpope 2 | custom: ["https://www.paypal.me/vimpope"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/tags 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | See the [contribution guidelines for pathogen.vim](https://github.com/tpope/vim-pathogen/blob/master/CONTRIBUTING.markdown). 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # abolish.vim 2 | 3 | I sat on this plugin for 3 years before releasing it, primarily 4 | because it's so gosh darn hard to explain. It's three superficially 5 | unrelated plugins in one that share a common theme: working with 6 | variants of a word. 7 | 8 | ## Abbreviation 9 | 10 | I know how to spell "separate". I know how to spell "desperate". My 11 | fingers, however, have trouble distinguishing between the two, and I 12 | invariably have a 50 percent chance of typing "seperate" or "desparate" 13 | each time one of these comes up. At first, I tried abbreviations: 14 | 15 | :iabbrev seperate separate 16 | :iabbrev desparate desperate 17 | 18 | But this falls short at the beginning of a sentence. 19 | 20 | :iabbrev Seperate Separate 21 | :iabbrev Desparate Desperate 22 | 23 | To be really thorough, we need uppercase too! 24 | 25 | :iabbrev SEPERATE SEPARATE 26 | :iabbrev DESPARATE DESPERATE 27 | 28 | Oh, but consider the noun form, and the adverb form! 29 | 30 | :iabbrev seperation separation 31 | :iabbrev desparation desperation 32 | :iabbrev seperately separately 33 | :iabbrev desparately desperately 34 | :iabbrev Seperation separation 35 | :iabbrev Desparation Desperation 36 | :iabbrev Seperately Separately 37 | :iabbrev Desparately Desperately 38 | :iabbrev SEPERATION SEPARATION 39 | :iabbrev DESPARATION DESPERATION 40 | :iabbrev SEPERATELY SEPARATELY 41 | :iabbrev DESPARATELY DESPERATELY 42 | 43 | Wait, there's also "separates", "separated", "separating", 44 | "separations", "separator"... 45 | 46 | Abolish.vim provides a simpler way. The following one command produces 47 | 48 abbreviations including all of the above. 48 | 49 | :Abolish {despa,sepe}rat{e,es,ed,ing,ely,ion,ions,or} {despe,sepa}rat{} 50 | 51 | My current configuration has 25 Abolish commands that create hundreds of 52 | corrections my fingers refuse to learn. 53 | 54 | ## Substitution 55 | 56 | One time I had an application with a domain model called 57 | "facility" that needed to be renamed to "building". So, a simple 58 | search and replace, right? 59 | 60 | :%s/facility/building/g 61 | 62 | Oh, but the case variants! 63 | 64 | :%s/Facility/Building/g 65 | :%s/FACILITY/BUILDING/g 66 | 67 | Wait, the plural is more than "s" so we need to get that too! 68 | 69 | :%s/facilities/buildings/g 70 | :%s/Facilities/Buildings/g 71 | :%s/FACILITIES/BUILDINGS/g 72 | 73 | Abolish.vim has your back. One command to do all six, and you can 74 | repeat it with `&` too! 75 | 76 | :%Subvert/facilit{y,ies}/building{,s}/g 77 | 78 | From a conceptual level, one way to think about how this substitution 79 | works is to imagine that in the braces you are declaring the 80 | requirements for turning that word from singular to plural. In 81 | the facility example, the same base letters in both the singular 82 | and plural form of the word are `facilit` To turn "facility" to a 83 | plural word you must change the `y` to `ies` so you specify 84 | `{y,ies}` in the braces. 85 | 86 | To convert the word "building" from singular to plural, again 87 | look at the common letters between the singular and plural forms: 88 | `building`. In this case you do not need to remove any letter 89 | from building to turn it into plural form and you need to 90 | add an `s` so the braces should be `{,s}`. 91 | 92 | A few more examples: 93 | 94 | Address to Reference 95 | 96 | :Subvert/address{,es}/reference{,s}/g 97 | 98 | Blog to Post (you can just do this with a regular :s also) 99 | 100 | :Subvert/blog{,s}/post{,s}/g 101 | 102 | Child to Adult 103 | 104 | :Subvert/child{,ren}/adult{,s}/g 105 | 106 | Be amazed as it correctly turns the word children into the word adults! 107 | 108 | Die to Spinner 109 | 110 | :Subvert/di{e,ce}/spinner{,s}/g 111 | 112 | You can abbreviate it as `:S`, and it accepts the full range of flags 113 | including things like `c` (confirm). 114 | 115 | There's also a variant for searching and a variant for grepping. 116 | 117 | ## Coercion 118 | 119 | Want to turn `fooBar` into `foo_bar`? Press `crs` (coerce to 120 | snake\_case). MixedCase (`crm`), camelCase (`crc`), UPPER\_CASE 121 | (`cru`), dash-case (`cr-`), and dot.case (`cr.`) are all just 3 122 | keystrokes away. 123 | 124 | ## Installation 125 | 126 | Install using your favorite package manager, or use Vim's built-in package 127 | support: 128 | 129 | mkdir -p ~/.vim/pack/tpope/start 130 | cd ~/.vim/pack/tpope/start 131 | git clone https://tpope.io/vim/abolish.git 132 | vim -u NONE -c "helptags abolish/doc" -c q 133 | 134 | ## Self-Promotion 135 | 136 | Like abolish.vim? Follow the repository on 137 | [GitHub](https://github.com/tpope/vim-abolish) and vote for it on 138 | [vim.org](http://www.vim.org/scripts/script.php?script_id=1545). And if 139 | you're feeling especially charitable, follow [tpope](http://tpo.pe/) on 140 | [Twitter](http://twitter.com/tpope) and 141 | [GitHub](https://github.com/tpope). 142 | 143 | ## License 144 | 145 | Copyright (c) Tim Pope. Distributed under the same terms as Vim itself. 146 | See `:help license`. 147 | -------------------------------------------------------------------------------- /doc/abolish.txt: -------------------------------------------------------------------------------- 1 | *abolish.txt* Language friendly searches, substitutions, and abbreviations 2 | 3 | Author: Tim Pope 4 | License: Same terms as Vim itself (see |license|) 5 | 6 | This plugin is only available if 'compatible' is not set. 7 | 8 | INTRODUCTION *abolish* *:Abolish* *:Subvert* 9 | 10 | Abolish lets you quickly find, substitute, and abbreviate several variations 11 | of a word at once. By default, three case variants (foo, Foo, and FOO) are 12 | operated on by every command. 13 | 14 | Two commands are provided. :Abolish is the most general interface. 15 | :Subvert provides an alternative, more concise syntax for searching and 16 | substituting. 17 | > 18 | :Abolish [options] {abbreviation} {replacement} 19 | :Abolish -delete [options] {abbreviation} 20 | 21 | :Abolish -search [options] {pattern} 22 | :Subvert/{pattern}[/flags] 23 | :Abolish!-search [options] {pattern} 24 | :Subvert?{pattern}[?flags] 25 | 26 | :Abolish -search [options] {pattern} {grep-arguments} 27 | :Subvert /{pattern}/[flags] {grep-options} 28 | :Abolish!-search [options] {pattern} {grep-arguments} 29 | :Subvert!/{pattern}/[flags] {grep-options} 30 | 31 | :[range]Abolish -substitute [options] {pattern} {replacement} 32 | :[range]Subvert/{pattern}/{replacement}[/flags] 33 | < 34 | *:S* 35 | In addition to the :Subvert command, a :S synonym is provided if not 36 | already defined. This will be used in examples below. 37 | 38 | PATTERNS *abolish-patterns* 39 | 40 | Patterns can include brace pairs that contain comma separated alternatives: 41 | 42 | box{,es} => box, boxes, Box, Boxes, BOX, BOXES 43 | 44 | For commands with a replacement, corresponding brace pairs are used in both 45 | halves. If the replacement should be identical to the pattern, an empty 46 | brace pair may be used. If fewer replacements are given than were given in 47 | the pattern, they are looped. That is, {a,b} on the replacement side is the 48 | same as {a,b,a,b,a,b,...} repeated indefinitely. 49 | 50 | The following replaces several different misspellings of "necessary": 51 | > 52 | :%S/{,un}nec{ce,ces,e}sar{y,ily}/{}nec{es}sar{}/g 53 | < 54 | ABBREVIATING *abolish-abbrev* 55 | 56 | By default :Abolish creates abbreviations, which replace words automatically 57 | as you type. This is good for words you frequently misspell, or as 58 | shortcuts for longer words. Since these are just Vim abbreviations, only 59 | whole words will match. 60 | > 61 | :Abolish anomol{y,ies} anomal{} 62 | :Abolish {,in}consistant{,ly} {}consistent{} 63 | :Abolish Tqbf The quick, brown fox jumps over the lazy dog 64 | < 65 | Accepts the following options: 66 | 67 | -buffer: buffer local 68 | -cmdline: work in command line in addition to insert mode 69 | 70 | A good place to define abbreviations is "after/plugin/abolish.vim", 71 | relative to ~\vimfiles on Windows and ~/.vim everywhere else. 72 | 73 | With a bang (:Abolish!) the abbreviation is also appended to the file in 74 | g:abolish_save_file. The default is "after/plugin/abolish.vim", relative 75 | to the install directory. 76 | 77 | Abbreviations can be removed with :Abolish -delete: 78 | > 79 | Abolish -delete -buffer -cmdline anomol{y,ies} 80 | < 81 | SEARCHING *abolish-search* 82 | 83 | The -search command does a search in a manner similar to / key. 84 | search. After searching, you can use |n| and |N| as you would with a normal 85 | search. 86 | 87 | The following will search for box, Box, and BOX: 88 | > 89 | :Abolish -search box 90 | < 91 | When given a single word to operate on, :Subvert defaults to doing a 92 | search as well: 93 | > 94 | :S/box/ 95 | < 96 | This one searches for box, boxes, boxed, boxing, Box, Boxes, Boxed, Boxing, 97 | BOX, BOXES, BOXED, and BOXING: 98 | > 99 | :S/box{,es,ed,ing}/ 100 | < 101 | The following syntaxes search in reverse. 102 | > 103 | :Abolish! -search box 104 | :S?box? 105 | < 106 | Flags can be given with the -flags= option to :Abolish, or by appending them 107 | after the separator to :Subvert. The flags trigger the following behaviors: 108 | 109 | I: Disable case variations (box, Box, BOX) 110 | v: Match inside variable names (match my_box, myBox, but not mybox) 111 | w: Match whole words (like surrounding with \< and \>) 112 | 113 | A |search-offset| may follow the flags. 114 | > 115 | :Abolish -search -flags=avs+1 box 116 | :S?box{,es,ed,ing}?we 117 | < 118 | GREPPING *abolish-grep* 119 | 120 | Grepping works similar to searching, and is invoked when additional options 121 | are given. These options are passed directly to the :grep command. 122 | > 123 | :Abolish -search box{,es} 124 | :S /box{,es}/ * 125 | :S /box/aw *.txt *.html 126 | < 127 | The slash delimiters must both be present if used with :Subvert. They may 128 | both be omitted if no flags are used. 129 | 130 | Both an external grepprg and vimgrep (via grepprg=internal) are supported. 131 | With an external grep, the "v" flag behaves less intelligently, due to the 132 | lack of look ahead and look behind support in grep regexps. 133 | 134 | SUBSTITUTING *abolish-substitute* 135 | 136 | Giving a range switches :Subvert into substitute mode. This command will 137 | change box -> bag, boxes -> bags, Box -> Bag, Boxes -> Bags, BOX -> BAG, 138 | BOXES -> BAGS across the entire document: 139 | > 140 | :%Abolish -substitute -flags=g box{,es} bag{,s} 141 | :%S/box{,es}/bag{,s}/g 142 | < 143 | The "c", "e", "g", and "n" flags can be used from the substitute command 144 | |:s_flags|, along with the "a", "I", "v", and "w" flags from searching. 145 | 146 | COERCION *abolish-coercion* *cr* 147 | 148 | Abolish's case mutating algorithms can be applied to the word under the cursor 149 | using the cr mapping (mnemonic: CoeRce) followed by one of the following 150 | characters: 151 | 152 | c: camelCase 153 | p: PascalCase 154 | m: MixedCase (aka PascalCase) 155 | _: snake_case 156 | s: snake_case 157 | u: SNAKE_UPPERCASE 158 | U: SNAKE_UPPERCASE 159 | k: kebab-case (not usually reversible; see |abolish-coercion-reversible|) 160 | -: dash-case (aka kebab-case) 161 | .: dot.case (not usually reversible; see |abolish-coercion-reversible|) 162 | 163 | For example, cru on a lowercase word is a slightly easier to type equivalent 164 | to gUiw. 165 | 166 | COERCION REVERSIBILITY *abolish-coercion-reversible* 167 | 168 | Some separators, such as "-" and ".", are listed as "not usually reversible". 169 | The reason is that these are not "keyword characters", so vim (and 170 | abolish.vim) will treat them as breaking a word. 171 | 172 | For example: "key_word" is a single keyword. The dash-case version, 173 | "key-word", is treated as two keywords, "key" and "word". 174 | 175 | This behaviour is governed by the 'iskeyword' option. If a separator appears 176 | in 'iskeyword', the corresponding coercion will be reversible. For instance, 177 | dash-case is reversible in 'lisp' files, and dot-case is reversible in R 178 | files. 179 | 180 | vim:tw=78:ts=8:ft=help:norl: 181 | -------------------------------------------------------------------------------- /plugin/abolish.vim: -------------------------------------------------------------------------------- 1 | " abolish.vim - Language friendly searches, substitutions, and abbreviations 2 | " Maintainer: Tim Pope 3 | " Version: 1.2 4 | " GetLatestVimScripts: 1545 1 :AutoInstall: abolish.vim 5 | 6 | " Initialization {{{1 7 | 8 | if exists("g:loaded_abolish") || &cp || v:version < 700 9 | finish 10 | endif 11 | let g:loaded_abolish = 1 12 | 13 | if !exists("g:abolish_save_file") 14 | if isdirectory(expand("~/.vim")) 15 | let g:abolish_save_file = expand("~/.vim/after/plugin/abolish.vim") 16 | elseif isdirectory(expand("~/vimfiles")) || has("win32") 17 | let g:abolish_save_file = expand("~/vimfiles/after/plugin/abolish.vim") 18 | else 19 | let g:abolish_save_file = expand("~/.vim/after/plugin/abolish.vim") 20 | endif 21 | endif 22 | 23 | " }}}1 24 | " Utility functions {{{1 25 | 26 | function! s:function(name) abort 27 | return function(substitute(a:name,'^s:',matchstr(expand(''), '.*\zs\d\+_'),'')) 28 | endfunction 29 | 30 | function! s:send(self,func,...) 31 | if type(a:func) == type('') || type(a:func) == type(0) 32 | let l:Func = get(a:self,a:func,'') 33 | else 34 | let l:Func = a:func 35 | endif 36 | let s = type(a:self) == type({}) ? a:self : {} 37 | if type(Func) == type(function('tr')) 38 | return call(Func,a:000,s) 39 | elseif type(Func) == type({}) && has_key(Func,'apply') 40 | return call(Func.apply,a:000,Func) 41 | elseif type(Func) == type({}) && has_key(Func,'call') 42 | return call(Func.call,a:000,s) 43 | elseif type(Func) == type('') && Func == '' && has_key(s,'function missing') 44 | return call('s:send',[s,'function missing',a:func] + a:000) 45 | else 46 | return Func 47 | endif 48 | endfunction 49 | 50 | let s:object = {} 51 | function! s:object.clone(...) 52 | let sub = deepcopy(self) 53 | return a:0 ? extend(sub,a:1) : sub 54 | endfunction 55 | 56 | if !exists("g:Abolish") 57 | let Abolish = {} 58 | endif 59 | call extend(Abolish, s:object, 'force') 60 | call extend(Abolish, {'Coercions': {}}, 'keep') 61 | 62 | function! s:throw(msg) 63 | let v:errmsg = a:msg 64 | throw "Abolish: ".a:msg 65 | endfunction 66 | 67 | function! s:words() 68 | let words = [] 69 | let lnum = line('w0') 70 | while lnum <= line('w$') 71 | let line = getline(lnum) 72 | let col = 0 73 | while match(line,'\<\k\k\+\>',col) != -1 74 | let words += [matchstr(line,'\<\k\k\+\>',col)] 75 | let col = matchend(line,'\<\k\k\+\>',col) 76 | endwhile 77 | let lnum += 1 78 | endwhile 79 | return words 80 | endfunction 81 | 82 | function! s:extractopts(list,opts) 83 | let i = 0 84 | while i < len(a:list) 85 | if a:list[i] =~ '^-[^=]' && has_key(a:opts,matchstr(a:list[i],'-\zs[^=]*')) 86 | let key = matchstr(a:list[i],'-\zs[^=]*') 87 | let value = matchstr(a:list[i],'=\zs.*') 88 | if type(get(a:opts,key)) == type([]) 89 | let a:opts[key] += [value] 90 | elseif type(get(a:opts,key)) == type(0) 91 | let a:opts[key] = 1 92 | else 93 | let a:opts[key] = value 94 | endif 95 | else 96 | let i += 1 97 | continue 98 | endif 99 | call remove(a:list,i) 100 | endwhile 101 | return a:opts 102 | endfunction 103 | 104 | " }}}1 105 | " Dictionary creation {{{1 106 | 107 | function! s:mixedcase(word) 108 | return substitute(s:camelcase(a:word),'^.','\u&','') 109 | endfunction 110 | 111 | function! s:camelcase(word) 112 | let word = substitute(a:word, '-', '_', 'g') 113 | if word !~# '_' && word =~# '\l' 114 | return substitute(word,'^.','\l&','') 115 | else 116 | return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g') 117 | endif 118 | endfunction 119 | 120 | function! s:snakecase(word) 121 | let word = substitute(a:word,'::','/','g') 122 | let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g') 123 | let word = substitute(word,'\(\l\|\d\)\(\u\)','\1_\2','g') 124 | let word = substitute(word,'[.-]','_','g') 125 | let word = tolower(word) 126 | return word 127 | endfunction 128 | 129 | function! s:uppercase(word) 130 | return toupper(s:snakecase(a:word)) 131 | endfunction 132 | 133 | function! s:dashcase(word) 134 | return substitute(s:snakecase(a:word),'_','-','g') 135 | endfunction 136 | 137 | function! s:spacecase(word) 138 | return substitute(s:snakecase(a:word),'_',' ','g') 139 | endfunction 140 | 141 | function! s:dotcase(word) 142 | return substitute(s:snakecase(a:word),'_','.','g') 143 | endfunction 144 | 145 | call extend(Abolish, { 146 | \ 'camelcase': s:function('s:camelcase'), 147 | \ 'mixedcase': s:function('s:mixedcase'), 148 | \ 'snakecase': s:function('s:snakecase'), 149 | \ 'uppercase': s:function('s:uppercase'), 150 | \ 'dashcase': s:function('s:dashcase'), 151 | \ 'dotcase': s:function('s:dotcase'), 152 | \ 'spacecase': s:function('s:spacecase'), 153 | \ }, 'keep') 154 | 155 | function! s:create_dictionary(lhs,rhs,opts) 156 | let dictionary = {} 157 | let i = 0 158 | let expanded = s:expand_braces({a:lhs : a:rhs}) 159 | for [lhs,rhs] in items(expanded) 160 | if get(a:opts,'case',1) 161 | let dictionary[s:mixedcase(lhs)] = s:mixedcase(rhs) 162 | let dictionary[tolower(lhs)] = tolower(rhs) 163 | let dictionary[toupper(lhs)] = toupper(rhs) 164 | endif 165 | let dictionary[lhs] = rhs 166 | endfor 167 | let i += 1 168 | return dictionary 169 | endfunction 170 | 171 | function! s:expand_braces(dict) 172 | let new_dict = {} 173 | for [key,val] in items(a:dict) 174 | if key =~ '{.*}' 175 | let redo = 1 176 | let [all,kbefore,kmiddle,kafter;crap] = matchlist(key,'\(.\{-\}\){\(.\{-\}\)}\(.*\)') 177 | let [all,vbefore,vmiddle,vafter;crap] = matchlist(val,'\(.\{-\}\){\(.\{-\}\)}\(.*\)') + ["","","",""] 178 | if all == "" 179 | let [vbefore,vmiddle,vafter] = [val, ",", ""] 180 | endif 181 | let targets = split(kmiddle,',',1) 182 | let replacements = split(vmiddle,',',1) 183 | if replacements == [""] 184 | let replacements = targets 185 | endif 186 | for i in range(0,len(targets)-1) 187 | let new_dict[kbefore.targets[i].kafter] = vbefore.replacements[i%len(replacements)].vafter 188 | endfor 189 | else 190 | let new_dict[key] = val 191 | endif 192 | endfor 193 | if exists("redo") 194 | return s:expand_braces(new_dict) 195 | else 196 | return new_dict 197 | endif 198 | endfunction 199 | 200 | " }}}1 201 | " Abolish Dispatcher {{{1 202 | 203 | function! s:SubComplete(A,L,P) 204 | if a:A =~ '^[/?]\k\+$' 205 | let char = strpart(a:A,0,1) 206 | return join(map(s:words(),'char . v:val'),"\n") 207 | elseif a:A =~# '^\k\+$' 208 | return join(s:words(),"\n") 209 | endif 210 | endfunction 211 | 212 | function! s:Complete(A,L,P) 213 | " Vim bug: :Abolish - calls this function with a:A equal to 0 214 | if a:A =~# '^[^/?-]' && type(a:A) != type(0) 215 | return join(s:words(),"\n") 216 | elseif a:L =~# '^\w\+\s\+\%(-\w*\)\=$' 217 | return "-search\n-substitute\n-delete\n-buffer\n-cmdline\n" 218 | elseif a:L =~# ' -\%(search\|substitute\)\>' 219 | return "-flags=" 220 | else 221 | return "-buffer\n-cmdline" 222 | endif 223 | endfunction 224 | 225 | let s:commands = {} 226 | let s:commands.abstract = s:object.clone() 227 | 228 | function! s:commands.abstract.dispatch(bang,line1,line2,count,args) 229 | return self.clone().go(a:bang,a:line1,a:line2,a:count,a:args) 230 | endfunction 231 | 232 | function! s:commands.abstract.go(bang,line1,line2,count,args) 233 | let self.bang = a:bang 234 | let self.line1 = a:line1 235 | let self.line2 = a:line2 236 | let self.count = a:count 237 | return self.process(a:bang,a:line1,a:line2,a:count,a:args) 238 | endfunction 239 | 240 | function! s:dispatcher(bang,line1,line2,count,args) 241 | let i = 0 242 | let args = copy(a:args) 243 | let command = s:commands.abbrev 244 | while i < len(args) 245 | if args[i] =~# '^-\w\+$' && has_key(s:commands,matchstr(args[i],'-\zs.*')) 246 | let command = s:commands[matchstr(args[i],'-\zs.*')] 247 | call remove(args,i) 248 | break 249 | endif 250 | let i += 1 251 | endwhile 252 | try 253 | return command.dispatch(a:bang,a:line1,a:line2,a:count,args) 254 | catch /^Abolish: / 255 | echohl ErrorMsg 256 | echo v:errmsg 257 | echohl NONE 258 | return "" 259 | endtry 260 | endfunction 261 | 262 | " }}}1 263 | " Subvert Dispatcher {{{1 264 | 265 | function! s:subvert_dispatcher(bang,line1,line2,count,args) 266 | try 267 | return s:parse_subvert(a:bang,a:line1,a:line2,a:count,a:args) 268 | catch /^Subvert: / 269 | echohl ErrorMsg 270 | echo v:errmsg 271 | echohl NONE 272 | return "" 273 | endtry 274 | endfunction 275 | 276 | function! s:parse_subvert(bang,line1,line2,count,args) 277 | if a:args =~ '^\%(\w\|$\)' 278 | let args = (a:bang ? "!" : "").a:args 279 | else 280 | let args = a:args 281 | endif 282 | let separator = '\v((\\)@= 2 && split[1] =~# '^[A-Za-z]* ' 293 | let flags = matchstr(split[1],'^[A-Za-z]*') 294 | let rest = matchstr(join(split[1:],separator),' \zs.*') 295 | return s:grep_command(rest,a:bang,flags,split[0]) 296 | elseif len(split) >= 2 && separator == ' ' 297 | return s:grep_command(join(split[1:],' '),a:bang,"",split[0]) 298 | else 299 | return s:parse_substitute(a:bang,a:line1,a:line2,a:count,split) 300 | endif 301 | endfunction 302 | 303 | function! s:normalize_options(flags) 304 | if type(a:flags) == type({}) 305 | let opts = a:flags 306 | let flags = get(a:flags,"flags","") 307 | else 308 | let opts = {} 309 | let flags = a:flags 310 | endif 311 | if flags =~# 'w' 312 | let opts.boundaries = 2 313 | elseif flags =~# 'v' 314 | let opts.boundaries = 1 315 | elseif !has_key(opts,'boundaries') 316 | let opts.boundaries = 0 317 | endif 318 | let opts.case = (flags !~# 'I' ? get(opts,'case',1) : 0) 319 | let opts.flags = substitute(flags,'\C[avIiw]','','g') 320 | return opts 321 | endfunction 322 | 323 | " }}}1 324 | " Searching {{{1 325 | 326 | function! s:subesc(pattern) 327 | return substitute(a:pattern,'[][\\/.*+?~%()&]','\\&','g') 328 | endfunction 329 | 330 | function! s:sort(a,b) 331 | if a:a ==? a:b 332 | return a:a == a:b ? 0 : a:a > a:b ? 1 : -1 333 | elseif strlen(a:a) == strlen(a:b) 334 | return a:a >? a:b ? 1 : -1 335 | else 336 | return strlen(a:a) < strlen(a:b) ? 1 : -1 337 | endif 338 | endfunction 339 | 340 | function! s:pattern(dict,boundaries) 341 | if a:boundaries == 2 342 | let a = '<' 343 | let b = '>' 344 | elseif a:boundaries 345 | let a = '%(<|_@<=|[[:lower:]]@<=[[:upper:]]@=)' 346 | let b = '%(>|_@=|[[:lower:]]@<=[[:upper:]]@=)' 347 | else 348 | let a = '' 349 | let b = '' 350 | endif 351 | return '\v\C'.a.'%('.join(map(sort(keys(a:dict),function('s:sort')),'s:subesc(v:val)'),'|').')'.b 352 | endfunction 353 | 354 | function! s:egrep_pattern(dict,boundaries) 355 | if a:boundaries == 2 356 | let a = '\<' 357 | let b = '\>' 358 | elseif a:boundaries 359 | let a = '(\<\|_)' 360 | let b = '(\>\|_\|[[:upper:]][[:lower:]])' 361 | else 362 | let a = '' 363 | let b = '' 364 | endif 365 | return a.'('.join(map(sort(keys(a:dict),function('s:sort')),'s:subesc(v:val)'),'\|').')'.b 366 | endfunction 367 | 368 | function! s:c() 369 | call histdel('search',-1) 370 | return "" 371 | endfunction 372 | 373 | function! s:find_command(cmd,flags,word) 374 | let opts = s:normalize_options(a:flags) 375 | let dict = s:create_dictionary(a:word,"",opts) 376 | " This is tricky. If we use :/pattern, the search drops us at the 377 | " beginning of the line, and we can't use position flags (e.g., /foo/e). 378 | " If we use :norm /pattern, we leave ourselves vulnerable to "press enter" 379 | " prompts (even with :silent). 380 | let cmd = (a:cmd =~ '[?!]$' ? '?' : '/') 381 | let @/ = s:pattern(dict,opts.boundaries) 382 | if opts.flags == "" || !search(@/,'n') 383 | return "norm! ".cmd."\" 384 | elseif opts.flags =~ ';[/?]\@!' 385 | call s:throw("E386: Expected '?' or '/' after ';'") 386 | else 387 | return "exe 'norm! ".cmd.cmd.opts.flags."\'|call histdel('search',-1)" 388 | return "" 389 | endif 390 | endfunction 391 | 392 | function! s:grep_command(args,bang,flags,word) 393 | let opts = s:normalize_options(a:flags) 394 | let dict = s:create_dictionary(a:word,"",opts) 395 | if &grepprg == "internal" 396 | let lhs = "'".s:pattern(dict,opts.boundaries)."'" 397 | elseif &grepprg =~# '^rg\|^ag' 398 | let lhs = "'".s:egrep_pattern(dict,opts.boundaries)."'" 399 | else 400 | let lhs = "-E '".s:egrep_pattern(dict,opts.boundaries)."'" 401 | endif 402 | return "grep".(a:bang ? "!" : "")." ".lhs." ".a:args 403 | endfunction 404 | 405 | let s:commands.search = s:commands.abstract.clone() 406 | let s:commands.search.options = {"word": 0, "variable": 0, "flags": ""} 407 | 408 | function! s:commands.search.process(bang,line1,line2,count,args) 409 | call s:extractopts(a:args,self.options) 410 | if self.options.word 411 | let self.options.flags .= "w" 412 | elseif self.options.variable 413 | let self.options.flags .= "v" 414 | endif 415 | let opts = s:normalize_options(self.options) 416 | if len(a:args) > 1 417 | return s:grep_command(join(a:args[1:]," "),a:bang,opts,a:args[0]) 418 | elseif len(a:args) == 1 419 | return s:find_command(a:bang ? "!" : " ",opts,a:args[0]) 420 | else 421 | call s:throw("E471: Argument required") 422 | endif 423 | endfunction 424 | 425 | " }}}1 426 | " Substitution {{{1 427 | 428 | function! Abolished() 429 | return get(g:abolish_last_dict,submatch(0),submatch(0)) 430 | endfunction 431 | 432 | function! s:substitute_command(cmd,bad,good,flags) 433 | let opts = s:normalize_options(a:flags) 434 | let dict = s:create_dictionary(a:bad,a:good,opts) 435 | let lhs = s:pattern(dict,opts.boundaries) 436 | let g:abolish_last_dict = dict 437 | return a:cmd.'/'.lhs.'/\=Abolished()'."/".opts.flags 438 | endfunction 439 | 440 | function! s:parse_substitute(bang,line1,line2,count,args) 441 | if get(a:args,0,'') =~ '^[/?'']' 442 | let separator = matchstr(a:args[0],'^.') 443 | let args = split(join(a:args,' '),separator,1) 444 | call remove(args,0) 445 | else 446 | let args = a:args 447 | endif 448 | if len(args) < 2 449 | call s:throw("E471: Argument required") 450 | elseif len(args) > 3 451 | call s:throw("E488: Trailing characters") 452 | endif 453 | let [bad,good,flags] = (args + [""])[0:2] 454 | if a:count == 0 455 | let cmd = "substitute" 456 | else 457 | let cmd = a:line1.",".a:line2."substitute" 458 | endif 459 | return s:substitute_command(cmd,bad,good,flags) 460 | endfunction 461 | 462 | let s:commands.substitute = s:commands.abstract.clone() 463 | let s:commands.substitute.options = {"word": 0, "variable": 0, "flags": "g"} 464 | 465 | function! s:commands.substitute.process(bang,line1,line2,count,args) 466 | call s:extractopts(a:args,self.options) 467 | if self.options.word 468 | let self.options.flags .= "w" 469 | elseif self.options.variable 470 | let self.options.flags .= "v" 471 | endif 472 | let opts = s:normalize_options(self.options) 473 | if len(a:args) <= 1 474 | call s:throw("E471: Argument required") 475 | else 476 | let good = join(a:args[1:],"") 477 | let cmd = a:bang ? "." : "%" 478 | return s:substitute_command(cmd,a:args[0],good,self.options) 479 | endif 480 | endfunction 481 | 482 | " }}}1 483 | " Abbreviations {{{1 484 | 485 | function! s:badgood(args) 486 | let words = filter(copy(a:args),'v:val !~ "^-"') 487 | call filter(a:args,'v:val =~ "^-"') 488 | if empty(words) 489 | call s:throw("E471: Argument required") 490 | elseif !empty(a:args) 491 | call s:throw("Unknown argument: ".a:args[0]) 492 | endif 493 | let [bad; words] = words 494 | return [bad, join(words," ")] 495 | endfunction 496 | 497 | function! s:abbreviate_from_dict(cmd,dict) 498 | for [lhs,rhs] in items(a:dict) 499 | exe a:cmd lhs rhs 500 | endfor 501 | endfunction 502 | 503 | let s:commands.abbrev = s:commands.abstract.clone() 504 | let s:commands.abbrev.options = {"buffer":0,"cmdline":0,"delete":0} 505 | function! s:commands.abbrev.process(bang,line1,line2,count,args) 506 | let args = copy(a:args) 507 | call s:extractopts(a:args,self.options) 508 | if self.options.delete 509 | let cmd = "unabbrev" 510 | let good = "" 511 | else 512 | let cmd = "noreabbrev" 513 | endif 514 | if !self.options.cmdline 515 | let cmd = "i" . cmd 516 | endif 517 | if self.options.delete 518 | let cmd = "silent! ".cmd 519 | endif 520 | if self.options.buffer 521 | let cmd = cmd . " " 522 | endif 523 | let [bad, good] = s:badgood(a:args) 524 | if substitute(bad, '[{},]', '', 'g') !~# '^\k*$' 525 | call s:throw("E474: Invalid argument (not a keyword: ".string(bad).")") 526 | endif 527 | if !self.options.delete && good == "" 528 | call s:throw("E471: Argument required".a:args[0]) 529 | endif 530 | let dict = s:create_dictionary(bad,good,self.options) 531 | call s:abbreviate_from_dict(cmd,dict) 532 | if a:bang 533 | let i = 0 534 | let str = "Abolish ".join(args," ") 535 | let file = g:abolish_save_file 536 | if !isdirectory(fnamemodify(file,':h')) 537 | call mkdir(fnamemodify(file,':h'),'p') 538 | endif 539 | 540 | if filereadable(file) 541 | let old = readfile(file) 542 | else 543 | let old = ["\" Exit if :Abolish isn't available.","if !exists(':Abolish')"," finish","endif",""] 544 | endif 545 | call writefile(old + [str],file) 546 | endif 547 | return "" 548 | endfunction 549 | 550 | let s:commands.delete = s:commands.abbrev.clone() 551 | let s:commands.delete.options.delete = 1 552 | 553 | " }}}1 554 | " Maps {{{1 555 | 556 | function! s:unknown_coercion(letter,word) 557 | return a:word 558 | endfunction 559 | 560 | call extend(Abolish.Coercions, { 561 | \ 'c': Abolish.camelcase, 562 | \ 'm': Abolish.mixedcase, 563 | \ 'p': Abolish.mixedcase, 564 | \ 's': Abolish.snakecase, 565 | \ '_': Abolish.snakecase, 566 | \ 'u': Abolish.uppercase, 567 | \ 'U': Abolish.uppercase, 568 | \ '-': Abolish.dashcase, 569 | \ 'k': Abolish.dashcase, 570 | \ '.': Abolish.dotcase, 571 | \ ' ': Abolish.spacecase, 572 | \ "function missing": s:function("s:unknown_coercion") 573 | \}, "keep") 574 | 575 | function! s:coerce(type) abort 576 | if a:type !~# '^\%(line\|char\|block\)' 577 | let s:transformation = a:type 578 | let &opfunc = matchstr(expand(''), '\w*') 579 | return 'g@' 580 | endif 581 | let selection = &selection 582 | let clipboard = &clipboard 583 | try 584 | set selection=inclusive clipboard-=unnamed clipboard-=unnamedplus 585 | let regbody = getreg('"') 586 | let regtype = getregtype('"') 587 | let c = v:count1 588 | let begin = getcurpos() 589 | while c > 0 590 | let c -= 1 591 | if a:type ==# 'line' 592 | let move = "'[V']" 593 | elseif a:type ==# 'block' 594 | let move = "`[\`]" 595 | else 596 | let move = "`[v`]" 597 | endif 598 | silent exe 'normal!' move.'y' 599 | let word = @@ 600 | let @@ = s:send(g:Abolish.Coercions,s:transformation,word) 601 | if word !=# @@ 602 | let changed = 1 603 | exe 'normal!' move.'p' 604 | endif 605 | endwhile 606 | call setreg('"',regbody,regtype) 607 | call setpos("'[",begin) 608 | call setpos(".",begin) 609 | finally 610 | let &selection = selection 611 | let &clipboard = clipboard 612 | endtry 613 | endfunction 614 | 615 | nnoremap (abolish-coerce) coerce(nr2char(getchar())) 616 | vnoremap (abolish-coerce) coerce(nr2char(getchar())) 617 | nnoremap (abolish-coerce-word) coerce(nr2char(getchar())).'iw' 618 | 619 | " }}}1 620 | 621 | if !exists("g:abolish_no_mappings") || ! g:abolish_no_mappings 622 | nmap cr (abolish-coerce-word) 623 | endif 624 | 625 | command! -nargs=+ -bang -bar -range=0 -complete=custom,s:Complete Abolish 626 | \ :exec s:dispatcher(0,,,,[]) 627 | command! -nargs=1 -bang -bar -range=0 -complete=custom,s:SubComplete Subvert 628 | \ :exec s:subvert_dispatcher(0,,,,) 629 | if exists(':S') != 2 630 | command -nargs=1 -bang -bar -range=0 -complete=custom,s:SubComplete S 631 | \ :exec s:subvert_dispatcher(0,,,,) 632 | endif 633 | 634 | " vim:set ft=vim sw=2 sts=2: 635 | --------------------------------------------------------------------------------