├── .gitignore ├── Makefile ├── plugin ├── lib │ ├── string_util.rb │ ├── context_renderer.rb │ └── failure_renderer.rb ├── vim-rspec.rb └── vim-rspec.vim ├── test └── test_spec.rb ├── syntax └── vim-rspec.vim └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = `git tag | sort -r| head -n1` 2 | 3 | zip: 4 | git archive --format=zip HEAD plugin/* syntax/* > vim-rspec-$(VERSION).zip 5 | install: 6 | cp -vR plugin/* ~/.vim/plugin/ 7 | cp -v syntax/* ~/.vim/syntax/ 8 | -------------------------------------------------------------------------------- /plugin/lib/string_util.rb: -------------------------------------------------------------------------------- 1 | module StringUtil 2 | def strip_html_spans(code) 3 | code.gsub(/]+>/,'').gsub(/<\/span>/,'') 4 | end 5 | 6 | def unescape(html) 7 | CGI.unescapeHTML(html) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class TestVimRspec 3 | def testme 4 | true 5 | end 6 | end 7 | describe "vim-rspec" do 8 | it "shows failures in red" do 9 | false.should == TestVimRspec.new.testme 10 | end 11 | 12 | it "shows successes in green" do 13 | true.should be_true 14 | end 15 | 16 | xit "shows pending specs in yellow" 17 | end 18 | -------------------------------------------------------------------------------- /syntax/vim-rspec.vim: -------------------------------------------------------------------------------- 1 | syntax match rspecHeader /^*.*/ 2 | syntax match rspecTitle /^\[.\+/ 3 | syntax match rspecOk /^+.\+/ 4 | syntax match rspecOk /PASS.\+/ 5 | syntax match rspecError /^-.\+/ 6 | syntax match rspecError /FAIL.\+/ 7 | syntax match rspecError /^|.\+/ 8 | syntax match rspecErrorDetail /^ \w.\+/ 9 | syntax match rspecErrorURL /^ \/.\+/ 10 | syntax match rspecNotImplemented /^#.\+/ 11 | syntax match rspecCode /^ \d\+:/ 12 | syntax match rspecNotImplemented /Example disabled.*/ 13 | 14 | highlight link rspecHeader Identifier 15 | highlight link rspecTitle Identifier 16 | highlight link rspecOk Statement 17 | highlight link rspecError Error 18 | highlight link rspecErrorDetail Constant 19 | highlight link rspecErrorURL PreProc 20 | highlight link rspecNotImplemented Todo 21 | highlight link rspecCode Type 22 | -------------------------------------------------------------------------------- /plugin/lib/context_renderer.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class RSpecContextRenderer 3 | # context: an html representation of an rspec context from rspec output 4 | # counts: a hash with :passed, :failed, :not_implemented counters 5 | def initialize(context, counts) 6 | @context=context 7 | @counts=counts 8 | @classes = {"passed"=>"+","failed"=>"-","not_implemented"=>"#"} 9 | render_context_header 10 | render_specs 11 | puts " " 12 | end 13 | 14 | private 15 | def render_context_header 16 | puts "[#{(@context/"dl/dt").inner_html}]" 17 | end 18 | 19 | def render_specs 20 | (@context/"dd").each do |dd| 21 | render_spec_descriptor(dd) 22 | FailureRenderer.new(dd/"div[@class~='failure']") if dd[:class] =~ /failed/ 23 | end 24 | end 25 | 26 | def render_spec_descriptor(dd) 27 | txt = (dd/"span:first").inner_html 28 | clazz = dd[:class].gsub(/(?:example|spec) /,'') 29 | puts "#{@classes[clazz]} #{txt}" 30 | outcome = clazz.to_sym 31 | @counts[outcome] += 1 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /plugin/lib/failure_renderer.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class FailureRenderer 3 | include StringUtil 4 | 5 | def initialize(failure) 6 | @failure = failure 7 | puts failure_message 8 | puts failure_location 9 | puts backtrace_details 10 | end 11 | 12 | private 13 | 14 | def indent(msg) 15 | " #{msg}" 16 | end 17 | 18 | def failure_location 19 | unescape( 20 | (@failure/"div[@class='backtrace']/pre").inner_html.split("\n").map { |line| "#{indent(line.strip)}" }.join("\n") 21 | ) 22 | end 23 | 24 | def failure_message 25 | indent(unescape((@failure/"div[@class~='message']/pre").inner_html.gsub(/\n/,'').gsub(/\s+/,' '))) 26 | end 27 | 28 | def backtrace_details 29 | unescape( 30 | backtrace_lines.map do |elem| 31 | linenum = elem[1] 32 | code = elem[3].chomp 33 | code = strip_html_spans(code) 34 | " #{linenum}: #{code}\n" 35 | end.join 36 | ) 37 | end 38 | 39 | def backtrace_lines 40 | (@failure/"pre[@class='ruby']/code").inner_html.scan(/()(\d+)(<\/span>)(.*)/).reject { |line| line[3] =~ ignore_line_if_matches } 41 | end 42 | 43 | def ignore_line_if_matches 44 | /install syntax to get syntax highlighting/ 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /plugin/vim-rspec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require "rubygems" 3 | require "hpricot" 4 | require 'cgi' 5 | require "#{File.join(File.dirname(__FILE__), "lib/string_util")}" 6 | require "#{File.join(File.dirname(__FILE__), "lib/failure_renderer")}" 7 | require "#{File.join(File.dirname(__FILE__), "lib/context_renderer")}" 8 | 9 | class RSpecOutputHandler 10 | 11 | def initialize(doc) 12 | @doc=doc 13 | @counts={ 14 | :passed => 0, 15 | :failed => 0, 16 | :not_implemented => 0 17 | } 18 | render_header 19 | render_examples 20 | end 21 | 22 | private 23 | 24 | def render_header 25 | stats = (@doc/"script").select {|script| script.innerHTML =~ /duration|totals/ } 26 | stats.map! do |script| 27 | script.inner_html.scan(/".*"/).first.gsub(/<\/?strong>/,"").gsub(/\"/,'') 28 | end 29 | # results in ["Finished in 0.00482 seconds", "2 examples, 1 failure"] 30 | failure_success_messages,other_stats = stats.partition {|stat| stat =~ /failure/} 31 | render_red_green_header(failure_success_messages.first) 32 | other_stats.each do |stat| 33 | puts stat 34 | end 35 | puts " " 36 | end 37 | 38 | def render_red_green_header(failure_success_messages) 39 | total_count = failure_success_messages.match(/(\d+) example/)[1].to_i rescue 0 40 | fail_count = failure_success_messages.match(/(\d+) failure/)[1].to_i rescue 0 41 | pending_count = failure_success_messages.match(/(\d+) pending/)[1].to_i rescue 0 42 | 43 | if fail_count > 0 44 | puts "------------------------------" 45 | puts " FAIL: #{fail_count} PASS: #{total_count - (fail_count + pending_count)} PENDING: #{pending_count}" 46 | puts "------------------------------" 47 | else 48 | puts "++++++++++++++++++++++++++++++" 49 | puts "+ PASS: All #{total_count} Specs Pass!" 50 | puts "++++++++++++++++++++++++++++++" 51 | end 52 | 53 | end 54 | 55 | def render_examples 56 | (@doc/"div[@class~='example_group']").each do |context| 57 | RSpecContextRenderer.new(context, @counts) 58 | end 59 | end 60 | 61 | end 62 | 63 | renderer = RSpecOutputHandler.new(Hpricot(STDIN.read)) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vim-rspec 2 | 3 | ABOUT 4 | ------ 5 | Beautiful, colorized RSpec tests in Vim with direct access to 6 | the line where the error occurred. 7 | 8 | ![GreenScreenshot](http://i.imgur.com/PWutK.png) 9 | 10 | HISTORY 11 | ------- 12 | * Original by Eustáquio Rangel (https://github.com/taq), development ended 11/2010 13 | * Modularized rewrite by Yan Pritzker (http://github.com/skwp) 12/2011. I am actively maintaining this fork. 14 | 15 | INSTALL 16 | ------- 17 | * Requires: gem install hpricot 18 | * Install with pathogen: clone/submodule into vim/bundle 19 | * **rbenv** users will need to install hpricot using system ruby `RBENV_VERSION=system sudo gem install hpricot` 20 | 21 | USAGE 22 | ----- 23 | * :RunSpec for current file 24 | * :RunSpecLine for current line (current 'it' block) 25 | * :RunSpecs for all files in spec dir 26 | * A split will open vertically on the right (if you prefer vertical, let g:RspecSplitHorizontal=0) 27 | * You can hit 'n' to go to the next error, or navigate to it and hit Enter to go to the line in the file. 28 | 29 | Enhancements 30 | ----- 31 | * Run rspec on current line (execute a single 'it' block) 32 | * Failures and Success is now displayed prominently at the top in green or red 33 | * Improved colors (for Solarized, specifically) 34 | * Run in same window, do not create a new window for every run 35 | * When browsing errors in rspec window, hitting enter takes you to the code in other split (do not create new window) 36 | * Ability to hit 'n' in the rspec output to go to the next error (and the corresponding code in the split) 37 | * Unescape html so that brackets in stacktraces are correctly displayed 38 | * Took out xslt support to focus the project on a ruby-based formatter 39 | * Default to horizontal split, use "let g:RspecSplitHorizontal=0" in vimrc to split vertical 40 | * Support for RSpec1 and RSpec2 (@thenoseman) 41 | * Automatically find the window with the spec (@thenoseman) 42 | 43 | Suggested Key Mappings 44 | ----- 45 | By default you get these keymappings. If you don't want them, turn them off with: 46 | 47 | let g:RspecKeymap=0 48 | 49 | Run using Cmd-Shift-R: 50 | 51 | map :RunSpec 52 | 53 | Run on current line (current 'it' block) Cmd-Shift-L: 54 | 55 | map :RunSpecLine 56 | 57 | TODO 58 | ------- 59 | * Further refactoring to improve maintainability 60 | * Custom paths for RunSpecs (e.g. fast_specs dir) 61 | * Support for other testing frameworks (test/unit, shoulda), maybe 62 | 63 | NOTE: This version is drastically different from the original taq/vim-rspec 64 | fork due to a large refactoring of the main codebase into a modularized 65 | form. If you have an old fork with custom changes, you may want to look 66 | at what's been done here. 67 | -------------------------------------------------------------------------------- /plugin/vim-rspec.vim: -------------------------------------------------------------------------------- 1 | " 2 | " Vim Rspec 3 | " Last change: March 5 2009 4 | " Version> 0.0.5 5 | " Maintainer: Eustáquio 'TaQ' Rangel 6 | " License: GPL 7 | " URL: git://github.com/taq/vim-rspec 8 | " 9 | " Script to run the spec command inside Vim 10 | " To install, unpack the files on your ~/.vim directory and source it 11 | " 12 | " The following options can be set/overridden in your .vimrc 13 | " * g:RspecRBFilePath :: Path to vim-rspec.rb 14 | " * g:RspecBin :: Rspec binary command (in rspec 2 this is 'rspec') 15 | " * g:RspecOpts :: Opts to send to rspec call 16 | " * g:RspecSplitHorizontal :: Set to 0 to cause Vertical split (default:1) 17 | 18 | let s:hpricot_cmd = "" 19 | let s:hpricot = 0 20 | let s:helper_dir = expand(":h") 21 | if !exists("g:RspecKeymap") 22 | let g:RspecKeymap=1 23 | end 24 | 25 | function! s:find_hpricot() 26 | return system("( gem search -i hpricot &> /dev/null && echo true ) 27 | \ || ( dpkg-query -Wf'${db:Status-abbrev}' ruby-hpricot 2>/dev/null 28 | \ | grep -q '^i' && echo true ) 29 | \ || echo false") 30 | endfunction 31 | 32 | function! s:error_msg(msg) 33 | echohl ErrorMsg 34 | echo a:msg 35 | echohl None 36 | endfunction 37 | 38 | function! s:notice_msg(msg) 39 | echohl MoreMsg 40 | echo a:msg 41 | echohl None 42 | endfunction 43 | 44 | function! s:fetch(varname, default) 45 | if exists("g:".a:varname) 46 | return eval("g:".a:varname) 47 | else 48 | return a:default 49 | endif 50 | endfunction 51 | 52 | function! s:createOutputWin() 53 | if !exists("g:RspecSplitHorizontal") 54 | let g:RspecSplitHorizontal=1 55 | endif 56 | if !exists("g:RspecSplitSize") 57 | let g:RspecSplitSize=15 58 | endif 59 | let splitLocation = "botright " 60 | let splitSize = g:RspecSplitSize 61 | 62 | if bufexists('RSpecOutput') 63 | silent! bw! RSpecOutput 64 | end 65 | 66 | if g:RspecSplitHorizontal == 1 67 | silent! exec splitLocation . ' ' . splitSize . ' new' 68 | else 69 | silent! exec splitLocation . ' ' . splitSize . ' vnew' 70 | end 71 | silent! exec "edit RSpecOutput" 72 | endfunction 73 | 74 | function! s:RunSpecMain(type) 75 | let l:bufn = expand("%:p") 76 | let s:SpecFile = l:bufn 77 | 78 | if len(s:hpricot_cmd)<1 79 | let s:hpricot_cmd = s:find_hpricot() 80 | let s:hpricot = match(s:hpricot_cmd,'true')>=0 81 | end 82 | 83 | if !s:hpricot 84 | call s:error_msg("You need the hpricot gem to run this script.") 85 | return 86 | end 87 | 88 | " find the installed rspec command 89 | let l:default_cmd = "" 90 | if executable("spec")==1 91 | let l:default_cmd = "spec" 92 | elseif executable("rspec")==1 93 | let l:default_cmd = "rspec" 94 | end 95 | 96 | " filter 97 | let l:filter = "ruby ". s:fetch("RspecRBPath", s:helper_dir."/vim-rspec.rb") 98 | 99 | " run just the current file 100 | if a:type=="file" 101 | if match(l:bufn,'_spec.rb')>=0 102 | call s:notice_msg("Running " . s:SpecFile . "...") 103 | let l:spec_bin = s:fetch("RspecBin",l:default_cmd) 104 | let l:spec_opts = s:fetch("RspecOpts", "") 105 | let l:spec = l:spec_bin . " " . l:spec_opts . " -f h " . l:bufn 106 | else 107 | call s:error_msg("Seems ".l:bufn." is not a *_spec.rb file") 108 | return 109 | end 110 | elseif a:type=="line" 111 | if match(l:bufn,'_spec.rb')>=0 112 | let l:current_line = line('.') 113 | 114 | call s:notice_msg("Running Line " . l:current_line . " on " . s:SpecFile . " ") 115 | let l:spec_bin = s:fetch("RspecBin",l:default_cmd) 116 | let l:spec_opts = s:fetch("RspecOpts", "") 117 | let l:spec = l:spec_bin . " " . l:spec_opts . " -l " . l:current_line . " -f h " . l:bufn 118 | else 119 | call s:error_msg("Seems ".l:bufn." is not a *_spec.rb file") 120 | return 121 | end 122 | elseif a:type=="rerun" 123 | if exists("s:spec") 124 | let l:spec = s:spec 125 | else 126 | call s:error_msg("No rspec run to repeat") 127 | return 128 | end 129 | else 130 | let l:dir = expand("%:p:h") 131 | if isdirectory(l:dir."/spec")>0 132 | call s:notice_msg("Running spec on the spec directory ...") 133 | else 134 | " try to find a spec directory on the current path 135 | let l:tokens = split(l:dir,"/") 136 | let l:dir = "" 137 | for l:item in l:tokens 138 | call remove(l:tokens,-1) 139 | let l:path = "/".join(l:tokens,"/")."/spec" 140 | if isdirectory(l:path) 141 | let l:dir = l:path 142 | break 143 | end 144 | endfor 145 | if len(l:dir)>0 146 | call s:notice_msg("Running spec with on the spec directory found (".l:dir.") ...") 147 | else 148 | call s:error_msg("No ".l:dir."/spec directory found") 149 | return 150 | end 151 | end 152 | if isdirectory(l:dir)<0 153 | call s:error_msg("Could not find the ".l:dir." directory.") 154 | return 155 | end 156 | let l:spec = s:fetch("RspecBin", l:default_cmd) . s:fetch("RspecOpts", "") 157 | let l:spec = l:spec . " -f h " . l:dir . " -p **/*_spec.rb" 158 | end 159 | let s:spec = l:spec 160 | 161 | " run the spec command 162 | let s:cmd = l:spec." | ".l:filter 163 | 164 | "put the result on a new buffer 165 | call s:createOutputWin() 166 | setl buftype=nofile 167 | silent exec "r! ".s:cmd 168 | setl syntax=vim-rspec 169 | silent exec "nnoremap :call TryToOpen()" 170 | silent exec 'nnoremap n /\/.*spec.*\::call TryToOpen()' 171 | silent exec 'nnoremap N ?/\/.*spec.*\::call TryToOpen()' 172 | silent exec "nnoremap q :q:wincmd p" 173 | setl nolist 174 | setl foldmethod=expr 175 | setl foldexpr=getline(v:lnum)=~'^\+' 176 | setl foldtext=\"+--\ \".string(v:foldend-v:foldstart+1).\"\ passed\ \" 177 | call cursor(1,1) 178 | 179 | endfunction 180 | 181 | function! s:FindWindowByBufferName(buffername) 182 | let l:windowNumberToBufnameList = map(range(1, winnr('$')), '[v:val, bufname(winbufnr(v:val))]') 183 | let l:arrayIndex = match(l:windowNumberToBufnameList, a:buffername) 184 | let l:windowNumber = windowNumberToBufnameList[l:arrayIndex][0] 185 | return l:windowNumber 186 | endfunction 187 | 188 | function! s:SwitchToWindowNumber(number) 189 | exe a:number . "wincmd w" 190 | endfunction 191 | 192 | function! s:SwitchToWindowByName(buffername) 193 | let l:windowNumber = s:FindWindowByBufferName(a:buffername) 194 | call s:SwitchToWindowNumber(l:windowNumber) 195 | endfunction 196 | 197 | function! s:TryToOpen() 198 | " Search up to find '*_spec.rb' 199 | call search("_spec","bcW") 200 | let l:line = getline(".") 201 | if match(l:line,'^ .*_spec.rb')<0 202 | call s:error_msg("No spec file found.") 203 | return 204 | end 205 | let l:tokens = split(l:line,":") 206 | 207 | " move back to the other window, if available 208 | call s:SwitchToWindowByName(s:SpecFile) 209 | 210 | " open the file in question (either in the split) 211 | " that was already open, or in the current win 212 | exec "e ".substitute(l:tokens[0],'/^\s\+',"","") 213 | call cursor(l:tokens[1],1) 214 | endfunction 215 | 216 | function! RunSpec() 217 | call s:RunSpecMain("file") 218 | endfunction 219 | 220 | function! RunSpecLine() 221 | call s:RunSpecMain("line") 222 | endfunction 223 | 224 | function! RunSpecs() 225 | call s:RunSpecMain("dir") 226 | endfunction 227 | 228 | function! RerunSpec() 229 | call s:RunSpecMain("rerun") 230 | endfunction 231 | 232 | command! RunSpec call RunSpec() 233 | command! RerunSpec call RerunSpec() 234 | command! RunSpecs call RunSpecs() 235 | command! RunSpecLine call RunSpecLine() 236 | 237 | if g:RspecKeymap==1 238 | " Cmd-Shift-R for RSpec 239 | nmap :RunSpec 240 | " Cmd-Shift-L for RSpec Current Line 241 | nmap :RunSpecLine 242 | " Cmd-Shift-E for RSpec previous spec 243 | nmap :RerunSpec 244 | endif 245 | --------------------------------------------------------------------------------