|
1 " vim: set noet nosta sw=4 ts=4 fdm=marker : |
|
2 " |
|
3 " Specky! |
|
4 " Mahlon E. Smith <mahlon@martini.nu> |
|
5 " $Id$ |
|
6 " |
|
7 |
|
8 |
|
9 " Hook up the functions to the user supplied key bindings. {{{ |
|
10 " |
|
11 if exists( 'g:speckySpecSwitcherKey' ) |
|
12 execute 'map ' . g:speckySpecSwitcherKey . ' :call <SID>SpecSwitcher()<CR>' |
|
13 " map &g:speckySpecSwitcherKey <SID>SpecSwitcher() |
|
14 endif |
|
15 |
|
16 if exists( 'g:speckyQuoteSwitcherKey' ) |
|
17 execute 'map ' . g:speckyQuoteSwitcherKey . ' :call <SID>QuoteSwitcher()<CR>' |
|
18 endif |
|
19 |
|
20 if exists( 'g:speckyBannerKey' ) |
|
21 execute 'map ' . g:speckyBannerKey . ' :call <SID>MakeBanner()<CR>' |
|
22 endif |
|
23 |
|
24 if exists( 'g:speckyRunSpecKey' ) |
|
25 execute 'map ' . g:speckyRunSpecKey . ' :call <SID>RunSpec()<CR>' |
|
26 endif |
|
27 |
|
28 if exists( 'g:speckyRunRdocKey' ) |
|
29 execute 'map ' . g:speckyRunRdocKey . ' :call <SID>RunRdoc()<CR>' |
|
30 endif |
|
31 |
|
32 if exists( 'specky_loaded' ) |
|
33 finish |
|
34 endif |
|
35 let specky_loaded = '$Rev$' |
|
36 |
|
37 |
|
38 "}}} |
|
39 " Menu configuration {{{ |
|
40 " |
|
41 let s:menuloc = '&Plugin.&specky' |
|
42 execute 'menu ' . s:menuloc . '.&Jump\ to\ code/spec :call <SID>SpecSwitcher()<CR>' |
|
43 execute 'menu ' . s:menuloc . '.Run\ &spec :call <SID>RunSpec()<CR>' |
|
44 execute 'menu ' . s:menuloc . '.&RDoc\ lookup :call <SID>RunRdoc()<CR>' |
|
45 execute 'menu ' . s:menuloc . '.Rotate\ "e\ style :call <SID>QuoteSwitcher()<CR>' |
|
46 execute 'menu ' . s:menuloc . '.Make\ a\ &banner :call <SID>MakeBanner()<CR>' |
|
47 |
|
48 |
|
49 " }}} |
|
50 " SpecSwitcher() {{{ |
|
51 " |
|
52 " When in ruby code or an rspec BDD file, try and search recursively through |
|
53 " the filesystem (within the current working directory) to find the |
|
54 " respectively matching file. (code to spec, spec to code.) |
|
55 " |
|
56 " This operates under the assumption that you've used chdir() to put vim into |
|
57 " the top level directory of your project. |
|
58 " |
|
59 function! <SID>SpecSwitcher() |
|
60 |
|
61 " If we aren't in a ruby or rspec file then we probably don't care |
|
62 " too much about this function. |
|
63 " |
|
64 if &ft != 'ruby' && &ft != 'rspec' |
|
65 call s:err( "Not currently in ruby or rspec mode." ) |
|
66 return |
|
67 endif |
|
68 |
|
69 " Ensure that we can always search recursively for files to open. |
|
70 " |
|
71 let l:orig_path = &path |
|
72 set path=** |
|
73 |
|
74 " Get the current buffer name, and determine if it is a spec file. |
|
75 " |
|
76 " /tmp/something/whatever/rubycode.rb ---> rubycode.rb |
|
77 " A requisite of the specfiles is that they match to the class/code file, |
|
78 " this emulates the eigenclass stuff, but doesn't require the same |
|
79 " directory structures. |
|
80 " |
|
81 " rubycode.rb ---> rubycode_spec.rb |
|
82 " |
|
83 let l:filename = matchstr( bufname('%'), '[0-9A-Za-z_.-]*$' ) |
|
84 let l:is_spec_file = match( l:filename, '_spec.rb$' ) == -1 ? 0 : 1 |
|
85 |
|
86 if l:is_spec_file |
|
87 let l:other_file = substitute( l:filename, '_spec\.rb$', '\.rb', '' ) |
|
88 else |
|
89 let l:other_file = substitute( l:filename, '\.rb$', '_spec\.rb', '' ) |
|
90 endif |
|
91 |
|
92 let l:bufnum = bufnr( l:other_file ) |
|
93 if l:bufnum == -1 |
|
94 " The file isn't currently open, so let's search for it. |
|
95 execute 'find ' . l:other_file |
|
96 else |
|
97 " We've already got an open buffer with this file, just go to it. |
|
98 execute 'buffer' . l:bufnum |
|
99 endif |
|
100 |
|
101 " Restore the original path. |
|
102 execute 'set path=' . l:orig_path |
|
103 endfunction |
|
104 |
|
105 |
|
106 " }}} |
|
107 " QuoteSwitcher() {{{ |
|
108 " |
|
109 " Wrap the word under the cursor in quotes. If in ruby mode, |
|
110 " cycle between quoting styles and symbols. |
|
111 " |
|
112 " variable -> "variable" -> 'variable' -> :variable |
|
113 " |
|
114 function! <SID>QuoteSwitcher() |
|
115 let l:type = strpart( expand("<cWORD>"), 0, 1 ) |
|
116 let l:word = expand("<cword>") |
|
117 |
|
118 if l:type == '"' |
|
119 " Double quote to single |
|
120 execute ":normal viWc'" . l:word . "'" |
|
121 |
|
122 elseif l:type == "'" |
|
123 if &ft == 'ruby' || &ft == 'rspec' |
|
124 " Single quote to symbol |
|
125 execute ':normal viWc:' . l:word |
|
126 else |
|
127 " Single quote to double |
|
128 execute ':normal viWc"' . l:word . '"' |
|
129 end |
|
130 |
|
131 else |
|
132 " Whatever to double quote |
|
133 execute ':normal viWc"' . l:word . '"' |
|
134 endif |
|
135 |
|
136 " Move the cursor back into the cl:word |
|
137 call cursor( 0, getpos('.')[2] - 1 ) |
|
138 endfunction |
|
139 |
|
140 |
|
141 " }}} |
|
142 " MakeBanner() {{{ |
|
143 " |
|
144 " Create a quick banner from the current line's text. |
|
145 " |
|
146 function! <SID>MakeBanner() |
|
147 let l:banner_text = toupper(join( split( getline('.'), '\zs' ), ' ' )) |
|
148 let l:banner_text = substitute( l:banner_text, '^\s\+', '', '' ) |
|
149 let l:sep = repeat( '#', &textwidth == 0 ? 72 : &textwidth ) |
|
150 let l:line = line('.') |
|
151 |
|
152 call setline( l:line, l:sep ) |
|
153 call append( l:line, [ '### ' . l:banner_text, l:sep ] ) |
|
154 execute 'normal 3==' |
|
155 call cursor( l:line + 3, 0 ) |
|
156 endfunction |
|
157 |
|
158 |
|
159 " }}} |
|
160 " RunSpec() {{{ |
|
161 " |
|
162 " Run this function while in a spec file to run the specs within vim. |
|
163 " |
|
164 function! <SID>RunSpec() |
|
165 |
|
166 " If we're in the code instead of the spec, try and switch |
|
167 " before running tests. |
|
168 " |
|
169 let l:filename = matchstr( bufname('%'), '[0-9A-Za-z_.-]*$' ) |
|
170 let l:is_spec_file = match( l:filename, '_spec.rb$' ) == -1 ? 0 : 1 |
|
171 if ( ! l:is_spec_file ) |
|
172 silent call <SID>SpecSwitcher() |
|
173 endif |
|
174 |
|
175 let l:spec = bufname('%') |
|
176 let l:buf = 'specky:specrun' |
|
177 let l:bufnum = bufnr( l:buf ) |
|
178 let l:specver = (exists( 'g:speckySpecVersion') && g:speckySpecVersion == 1) ? 1 : 2 |
|
179 |
|
180 " Squash the old buffer, if it exists. |
|
181 " |
|
182 if buflisted( l:buf ) |
|
183 execute 'bd! ' . l:buf |
|
184 endif |
|
185 |
|
186 execute <SID>NewWindowCmd() . l:buf |
|
187 setlocal buftype=nofile bufhidden=delete noswapfile |
|
188 if ( l:specver == 1 ) |
|
189 setlocal filetype=specrun1 |
|
190 set foldtext='--'.getline(v:foldstart).v:folddashes |
|
191 else |
|
192 setlocal filetype=specrun |
|
193 set foldtext=_formatFoldText() |
|
194 endif |
|
195 |
|
196 " Set up some convenient keybindings. |
|
197 " |
|
198 nnoremap <silent> <buffer> q :close<CR> |
|
199 nnoremap <silent> <buffer> e :call <SID>FindSpecError(1)<CR> |
|
200 nnoremap <silent> <buffer> r :call <SID>FindSpecError(-1)<CR> |
|
201 nnoremap <silent> <buffer> E :call <SID>FindSpecError(0)<CR> |
|
202 nnoremap <silent> <buffer> <C-e> :let b:err_line=1<CR> |
|
203 |
|
204 " Default cmd for spec |
|
205 " |
|
206 if !exists( 'g:speckyRunSpecCmd' ) |
|
207 let g:speckyRunSpecCmd = l:specver == 1 ? 'spec -fs' : 'rspec' |
|
208 endif |
|
209 |
|
210 " Call spec and gather up the output |
|
211 " |
|
212 let l:cmd = g:speckyRunSpecCmd . ' ' . l:spec |
|
213 call append( line('$'), 'Output of: ' . l:cmd ) |
|
214 call append( line('$'), '' ) |
|
215 let l:output = system( l:cmd ) |
|
216 call append( line('$'), split( l:output, "\n" ) ) |
|
217 normal gg |
|
218 |
|
219 " Lockdown the buffer |
|
220 setlocal nomodifiable |
|
221 endfunction |
|
222 |
|
223 |
|
224 " }}} |
|
225 " RunRdoc() {{{ |
|
226 " |
|
227 " Get documentation for the word under the cursor. |
|
228 " |
|
229 function! <SID>RunRdoc() |
|
230 |
|
231 " If we aren't in a ruby file (specs are ruby-mode too) then we probably |
|
232 " don't care too much about this function. |
|
233 " |
|
234 if ( &ft != 'ruby' && &ft != 'rdoc' && &ft != 'rspec' ) |
|
235 call s:err( "Not currently in a rubyish-mode." ) |
|
236 return |
|
237 endif |
|
238 |
|
239 " Set defaults |
|
240 " |
|
241 if !exists( 'g:speckyRunRdocCmd' ) |
|
242 let g:speckyRunRdocCmd = 'ri' |
|
243 endif |
|
244 |
|
245 let l:buf = 'specky:rdoc' |
|
246 let l:bufname = bufname('%') |
|
247 |
|
248 if ( match( l:bufname, l:buf ) != -1 ) |
|
249 " Already in the rdoc buffer. This allows us to lookup |
|
250 " something like Kernel#require. |
|
251 " |
|
252 let l:word = expand('<cWORD>') |
|
253 else |
|
254 " Not in the rdoc buffer. This allows us to lookup |
|
255 " something like 'each' in some_hash.each { ... } |
|
256 " |
|
257 let l:word = expand('<cword>') |
|
258 endif |
|
259 |
|
260 " Squash the old buffer, if it exists. |
|
261 " |
|
262 if buflisted( l:buf ) |
|
263 execute 'bd! ' . l:buf |
|
264 endif |
|
265 |
|
266 " With multiple matches, strip the commas from the cWORD. |
|
267 let l:word = substitute( l:word, ',', '', 'eg' ) |
|
268 |
|
269 execute <SID>NewWindowCmd() . l:buf |
|
270 setlocal buftype=nofile bufhidden=delete noswapfile filetype=rdoc |
|
271 nnoremap <silent> <buffer> q :close<CR> |
|
272 |
|
273 " Call the documentation and gather up the output |
|
274 " |
|
275 let l:cmd = g:speckyRunRdocCmd . ' ' . l:word |
|
276 let l:output = system( l:cmd ) |
|
277 call append( 0, split( l:output, "\n" ) ) |
|
278 execute 'normal gg' |
|
279 |
|
280 " Lockdown the buffer |
|
281 setlocal nomodifiable |
|
282 endfunction |
|
283 |
|
284 |
|
285 " }}} |
|
286 " FindSpecError( detail ) {{{ |
|
287 " |
|
288 " detail: |
|
289 " 1 -- find the next failure |
|
290 " -1 -- find the previous failure |
|
291 " 0 -- expand the current failure's detail |
|
292 " |
|
293 " Convenience searches for jumping to spec failures. |
|
294 " |
|
295 function! <SID>FindSpecError( detail ) |
|
296 |
|
297 let l:specver = (exists( 'g:speckySpecVersion') && g:speckySpecVersion == 1) ? 1 : 2 |
|
298 let l:err_str = l:specver == 1 ? '(FAILED\|ERROR - \d\+)$' : 'FAILED - #\d\+)$' |
|
299 |
|
300 if ( a:detail == 0 ) |
|
301 " Find the detailed failure text for the current failure line, |
|
302 " and unfold it. |
|
303 " |
|
304 let l:orig_so = &so |
|
305 set so=100 |
|
306 if l:specver == 1 |
|
307 call search('^' . matchstr(getline('.'),'\d\+)$') ) |
|
308 else |
|
309 call search('^FAILURE - #' . matchstr(getline('.'),'\d\+)$') ) |
|
310 endif |
|
311 if has('folding') |
|
312 silent! normal za |
|
313 endif |
|
314 execute 'set so=' . l:orig_so |
|
315 |
|
316 else |
|
317 " Find the 'regular' failure line |
|
318 " |
|
319 if exists( 'b:err_line' ) |
|
320 call cursor( b:err_line, a:detail == -1 ? 1 : strlen(getline(b:err_line)) ) |
|
321 endif |
|
322 call search( l:err_str, a:detail == -1 ? 'b' : '' ) |
|
323 let b:err_line = line('.') |
|
324 nohl |
|
325 |
|
326 endif |
|
327 endfunction |
|
328 |
|
329 |
|
330 " }}} |
|
331 " NewWindowCmd() {{{ |
|
332 " |
|
333 " Return the stringified command for a new window, based on user preferences. |
|
334 " |
|
335 function! <SID>NewWindowCmd() |
|
336 if ( ! exists('g:speckyWindowType' ) ) |
|
337 return 'tabnew ' |
|
338 endif |
|
339 |
|
340 if ( g:speckyWindowType == 1 ) |
|
341 return 'new ' |
|
342 elseif ( g:speckyWindowType == 2 ) |
|
343 return 'vert new ' |
|
344 else |
|
345 return 'tabnew ' |
|
346 endif |
|
347 endfunction |
|
348 |
|
349 |
|
350 " }}} |
|
351 " _formatFoldText() {{{ |
|
352 " |
|
353 " Make folded failure detail visually appealing when folded. |
|
354 " |
|
355 function! _formatFoldText() |
|
356 let l:fold = tolower( getline(v:foldstart) ) |
|
357 let l:fold = substitute( l:fold, '-', 'detail', 'e' ) |
|
358 let l:fold = '--[ ' . substitute( l:fold, ')', ' ]', 'e' ) |
|
359 return l:fold |
|
360 endfunction |
|
361 |
|
362 |
|
363 " }}} |
|
364 " s:err( msg ) "{{{ |
|
365 " Notify of problems in a consistent fashion. |
|
366 " |
|
367 function! s:err( msg ) |
|
368 echohl WarningMsg|echomsg 'specky: ' . a:msg|echohl None |
|
369 endfunction " }}} |
|
370 |