-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathruby-refactoring.vim
More file actions
306 lines (258 loc) · 8.53 KB
/
ruby-refactoring.vim
File metadata and controls
306 lines (258 loc) · 8.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
" Ruby Refactoring in VIM
"
" Author: Enrique Comba Riepenhausen
" Email: enrique@edendevelopment.co.uk
" Email2: ecomba@gmail.com
"
" Acknowledgements:
" Thanks to Gary Bernhardt for the inspiration for this tool and the original
" ExtractVariable() and InlineTemp() functions.
"
" Some support functions borrowed from Luc Hermitte's lh-vim library
"
" Many, many thanks to Paul King for the great effort in writing a lot ot the
" patterns found in this library and the endless nights he stays awake to make
" this happen.
" Support functions
"
" Synopsis:
" Returns the input given by the user
function! s:get_input(message, error_message)
let name = input(a:message)
if name == ''
throw a:error_message
endif
return name
endfunction
" Synopsis:
" Returns the text that was selected when the function was invoked
" without clobbering any registers
function! s:get_visual_selection()
try
let a_save = @a
normal! gv"ay
return @a
finally
let @a = a_save
endtry
endfunction
" Synopsis:
" Loop over the line range given, global replace pattern with replace
function! s:gsub_all_in_range(start_line, end_line, pattern, replace)
let lnum = a:start_line
while lnum <= a:end_line
let oldline = getline(lnum)
let newline = substitute(oldline,a:pattern,a:replace,'g')
call setline(lnum, newline)
let lnum = lnum + 1
endwhile
endfunction!
" Synopsis:
" Find pattern to matching end, flags as per :h search()
function! s:get_range_for_block(pattern_start, flags)
" matchit.vim required
if !exists("g:loaded_matchit")
throw("matchit.vim (http://www.vim.org/scripts/script.php?script_id=39) required for RenameLocalVariable()")
endif
let cursor_position = getpos(".")
" TODO: Need alternative to remove matchit.vim dep - matchpair() ?
let block_start = search(a:pattern_start, a:flags)
normal %
let block_end = line(".")
" Restore the cursor
call setpos(".",cursor_position)
return [block_start, block_end]
endfunction
" Patterns
" Synopsis:
" Adds a parameter (or many separated with commas) to a method
function! AddParameter()
try
let name = s:get_input("Parameter name: ", "No parameter name given!")
catch
echo v:exception
return
endtry
" Save current position
let cursor_position = getpos(".")
" Move backwards to the method definiton if you are not already on the
" correct line
if empty(matchstr(getline("."), '\<def\>'))
exec "?\\<def\\>"
endif
let closing_bracket_index = stridx(getline("."), ")")
if closing_bracket_index == -1
execute "normal A(" . name . ")\<Esc>"
else
exec ':.s/)/, ' . name . ')/'
endif
" Restore caret position
call setpos(".", cursor_position)
endfunction
" Synopsis:
" Extracts the selected scope into a constant at the top of the current
" module or class
function! ExtractConstant()
try
let name = toupper(s:get_input("Constant name: ", "No constant name given!"))
catch
echo v:exception
return
endtry
" Save the scope to register a and then reselect the scope in visual mode
normal! gv
" Replace selected text with the constant's name
exec "normal c" . name
" Find the enclosing class or module
exec "?\\<class\\|module\\>"
" Define the constant inside the class or module
exec "normal! o" . name . " = "
normal! $p
endfunction
" Synopsis:
" Extracts the selected scope to a variable
function! ExtractLocalVariable()
try
let name = s:get_input("Variable name: ", "No variable name given!")
catch
echo v:exception
return
endtry
" Enter visual mode (not sure why this is needed since we're already in
" visual mode anyway)
normal! gv
" Replace selected text with the variable name
exec "normal c" . name
" Define the variable on the line above
exec "normal! O" . name . " = "
" Paste the original selected text to be the variable value
normal! $p
endfunction
" Synopsis:
" Rename the selected instance variable
function! RenameInstanceVariable()
try
let selection = s:get_visual_selection()
" If no @ at the start of selection, then abort
if match( selection, "^@" ) == -1
let left_of_selection = getline(".")[col(".")-2]
if left_of_selection == "@"
let selection = "@".selection
else
throw "Selection '" . selection . "' is not an instance variable"
end
endif
let name = s:get_input("Rename to: @", "No variable name given!" )
catch
echo v:exception
return
endtry
" Assume no prefix given
let name_no_prefix = name
" Add leading @ if none provided
if( match( name, "^@" ) == -1 )
let name = "@" . name
else
" Remove the @ from the no_prefix version
let name_no_prefix = matchstr(name,'^@\zs.*')
endif
" Find the start and end of the current block
" TODO: tidy up if no matching 'def' found (start would be 0 atm)
let [block_start, block_end] = s:get_range_for_block('\<class\>','Wb')
" Rename the variable within the range of the block
call s:gsub_all_in_range(block_start, block_end, selection.'\>\ze\([^\(]\|$\)', name)
" copy with no prefix for the attr_* match
let selection_no_prefix = matchstr( selection, '^@\zs.*' )
" Rename attr_* symbols
call s:gsub_all_in_range(block_start, block_end, '^\s*attr_\(reader\|writer\|accessor\).*\:\zs'.selection_no_prefix, name_no_prefix)
endfunction
" Synopsis:
" Intending to pass all rename variable methods through here, allowing
" various conveniences. Allows normal mode rename of *local* variable under
" the cursor only, for now.
function! RenameVariableProxy()
let selection = s:get_visual_selection()
let left_of_selection = getline(".")[col(".")-2]
if left_of_selection == "@"
throw "Use RenameInstanceVariable() to rename instance variables"
endif
call RenameLocalVariable()
endfunction
" Synopsis:
" Rename the selected local variable
function! RenameLocalVariable()
try
let selection = s:get_visual_selection()
" If @ at the start of selection, then abort
if match( selection, "@" ) != -1
throw "Selection '" . selection . "' is not a local variable"
endif
let name = s:get_input("Rename to: ", "No variable name given!" )
catch
echo v:exception
return
endtry
" Find the start and end of the current block
" TODO: tidy up if no matching 'def' found (start would be 0 atm)
let [block_start, block_end] = s:get_range_for_block('\<def\>','Wb')
" Rename the variable within the range of the block
call s:gsub_all_in_range(block_start, block_end, '[^@]\<\zs'.selection.'\>\ze\([^\(]\|$\)', name)
endfunction
" Synopsis:
" Extracts the selected scope into a method above the scope of the
" current method
function! ExtractMethod() range
try
let name = s:get_input("Method name: ", "No method name given!")
catch
echo v:exception
return
endtry
normal! gv
" Yank & Replace selected range with the method name
exec "normal C" . name
" Mark source position so we can jump back afterwards
" XXX: ideally shouldn't clobber this
normal ma
exec '?\<def\>'
" Mark current position for reindenting the source
exec "normal! O" . "def " . name . "\nend\n"
" Paste yanked range, select entire method & reindent, and jump back to
" starting position
normal kPkV}k=`a
endfunction
" Synopsis:
" Inlines a variable
function! InlineTemp()
" Copy the variable under the cursor into the 'a' register
" XXX: How do I copy into a variable so I don't pollute the registers?
normal "ayiw
" It takes 4 diws to get the variable, equal sign, and surrounding
" whitespace. I'm not sure why. diw is different from dw in this
" respect.
normal 4diw
" Delete the expression into the 'b' register
normal "bd$
" Delete the remnants of the line
normal dd
" Go to the end of the previous line so we can start our search for the
" usage of the variable to replace. Doing '0' instead of 'k$' doesn't
" work; I'm not sure why.
normal k$
" Find the next occurence of the variable
exec '/\<' . @a . '\>'
" Replace that occurence with the text we yanked
exec ':.s/\<' . @a . '\>/' . @b
endfunction
" Mappings:
"
" Default mappings are <leader>r followed by an acronym of the pattern's name
" I.e. Extract Method is mapped to <leader>rem
nnoremap <leader>rap :call AddParameter()<cr>
vnoremap <leader>rec :call ExtractConstant()<cr>
vnoremap <leader>relv :call ExtractLocalVariable()<cr>
vnoremap <leader>rrlv :call RenameLocalVariable()<cr>
nnoremap <leader>rrlv viw:call RenameVariableProxy()<cr>
vnoremap <leader>rriv :call RenameInstanceVariable()<cr>
vnoremap <leader>rem :call ExtractMethod()<cr>
nnoremap <leader>rit :call InlineTemp()<cr>