├── .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 |
--------------------------------------------------------------------------------