specky/plugin/specky.vim
branchvim-stuff
changeset 29 a0e6ddfadf82
parent 28 2b198f0a86fe
equal deleted inserted replaced
28:2b198f0a86fe 29:a0e6ddfadf82
     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\ &quote\ 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