├── .gitattributes ├── .gitignore ├── README.md ├── autoload └── ruby_debugger.vim ├── doc └── ruby_debugger.txt ├── plugin └── ruby_debugger.vim ├── screenshot.png └── src ├── additionals └── autoload │ └── ruby_debugger.vim ├── collector.rb ├── notifier ├── ruby_debugger ├── after.vim ├── before.vim ├── breakpoint.vim ├── commands.vim ├── common.vim ├── exception.vim ├── frame.vim ├── init.vim ├── logger.vim ├── public.vim ├── queue.vim ├── server.vim ├── var.vim ├── var_child.vim ├── var_parent.vim ├── window.vim ├── window_breakpoints.vim ├── window_frames.vim └── window_variables.vim ├── ruby_debugger_autoload_plan.txt ├── ruby_debugger_plugin_plan.txt ├── ruby_test_plan.txt └── tests ├── breakpoint.vim ├── command.vim ├── exceptions.vim ├── frames.vim ├── framework.vim ├── mock.vim ├── server.vim └── variables.vim /.gitattributes: -------------------------------------------------------------------------------- 1 | additionals/autoload/ruby_debugger.vim -diff 2 | vim/autoload/ruby_debugger.vim -diff 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.un~ 3 | tmp 4 | doc/tags 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer # 2 | 3 | This is a new version of the plugin, which uses **debugger-xml** gem, and works only with Ruby >= 1.9. If you want to use **ruby-debug-ide** gem and/or Ruby <= 1.8.7, you should check 'v1.0' branch (http://github.com/astashov/vim-ruby-debugger/tree/v1.0) 4 | 5 | # Description # 6 | 7 | This Vim plugin implements interactive Ruby debugger in Vim. 8 | 9 | This version of the plugin works only with Ruby >= 1.9. It uses [**debugger-xml**](https://rubygems.org/gems/debugger-xml) under the hood, which is just a XML/IDE extension for the [**debugger**](https://rubygems.org/gems/debugger) gem, which supports Ruby 1.9.2 and 1.9.3 out-of-the-box, but doesn't support Ruby <= 1.8.7. 10 | 11 | # Features # 12 | 13 | 1. It can debug any Ruby application (Rails, by default), **debugger-xml** gem 14 | 2. The debugger looks like in any IDE - you can go through the code, watch variables, breakpoints in a separate window, set and remove breakpoints. 15 | 3. It supports execution of commands in the context of stopped line. E.g., you can execute ':RdbEval User.all' in the Vim command line and it will display the results like usual echo Vim command. 16 | 17 | 18 | # Requirements # 19 | 20 | 1. Vim >= 7.0, compiled with +signs, +clientserver and +ruby. You can verify it by VIM command: 21 | 22 | :echo has("signs") && has("clientserver") && has("ruby") && v:version > 700 23 | 24 | It should show result '1'. 25 | 26 | 2. debugger-xml gem. 27 | 3. For OS X: 28 | 29 | The vim that ships with OS X does not use ruby, nor does it support --servername, so MacVim must be used. 30 | 31 | Make sure that both MacVim, and mvim are installed. 32 | 33 | If they are not, you can use homebrew (http://mxcl.github.com/homebrew/): 34 | 35 | brew install macvim 36 | 37 | This will install MacVim, along with the mvim command line utility. 38 | 39 | # Installation # 40 | 41 | 1. Clone the repo 42 | 43 | git clone git://github.com/astashov/vim-ruby-debugger.git 44 | 45 | or just download the archive from here: 46 | 47 | http://github.com/astashov/vim-ruby-debugger/tarball/master 48 | 49 | You will get the 'vim-ruby-debugger' dir with the plugin. 50 | 51 | 2. Copy contents of the 'vim-ruby-debugger' dir to your ~/.vim/ (or to ~/.vim/bundle/vim-ruby-debugger if you use pathogen). 52 | 53 | 3. Generate the local tags file 54 | 55 | :helptags ~/.vim/doc 56 | 57 | Now, you can use 58 | 59 | :help ruby-debugger 60 | 61 | to get help for the ruby-debugger plugin. 62 | 63 | 4. If using MacVim: 64 | 65 | Modify your ~/.vimrc to add the following line: 66 | 67 | ```VimL 68 | let g:ruby_debugger_progname = 'mvim' 69 | ``` 70 | 71 | Windows is not supported, sorry, Windows users. 72 | 73 | 74 | # Using # 75 | 76 | 1. Run Vim. If you use gvim/mvim, it will automatically start the server, but if you use vim, you need to set 77 | servername explicitly, e.g., **vim --servername VIM** 78 | 79 | 2. Go to the directory with some your Rails application. 80 | 81 | :cd ~/projects/rails 82 | 83 | 3. Run Server with Debugger: 84 | 85 | :Rdebugger 86 | 87 | It will run debugger-xml's rdebug-vim executable, create a UNIX socket in tmp directory, 88 | and connect to debugger-xml through it. 89 | 90 | 3. Set a breakpoint somewhere by **<Leader>b** (e.g., '\b'). You should see 'xx' symbol at current line. 91 | 92 | 4. Open a page with the breakpoint in a browser. Vim should automatically set the current line to the breakpoint. 93 | 94 | 5. After this, you can use commands: 95 | 96 | b - set breakpoint at current line 97 | v - open/close window with variables. You can expand/collapse variables by 'o' in normal mode or left-mouse double-click 98 | n - step over 99 | s - step into 100 | c - continue 101 | 102 | 6. You may find useful to override default shortcut commands by F5-F8 shortcuts. Add these to your .vimrc: 103 | 104 | map :call g:RubyDebugger.step() 105 | map :call g:RubyDebugger.next() 106 | map :call g:RubyDebugger.continue() 107 | 108 | # Testing # 109 | 110 | If you want to run tests, replace in /autoload directory ruby_debugger.vim to **ruby_debugger.vim** from additionals/autoload directory. 111 | And then, in command mode execute 112 | 113 | :call g:TU.run() 114 | 115 | 116 | # Screenshot # 117 | 118 | ![Screenshot](https://raw.github.com/astashov/vim-ruby-debugger/master/screenshot.png) 119 | 120 | 121 | # Thanks # 122 | 123 | Special thanks to tpope (for rails.vim) and Marty Grenfell (for NERDTree), mostly, I learn Vim Scripting from their projects. 124 | -------------------------------------------------------------------------------- /autoload/ruby_debugger.vim: -------------------------------------------------------------------------------- 1 | " Init section - set default values, highlight colors 2 | 3 | " like ~/.vim 4 | let s:runtime_dir = expand(':h:h') 5 | " File for communicating between intermediate Ruby script ruby_debugger.rb and 6 | " this plugin 7 | let s:tmp_file = s:runtime_dir . '/tmp/ruby_debugger' 8 | let s:logger_file = s:runtime_dir . '/tmp/ruby_debugger_log' 9 | let s:server_output_file = s:runtime_dir . '/tmp/ruby_debugger_output' 10 | " Default id for sign of current line 11 | let s:current_line_sign_id = 120 12 | let s:separator = "++vim-ruby-debugger-separator++" 13 | let s:sign_id = 0 14 | let s:rdebug_pid = "" 15 | 16 | " Create tmp directory if it doesn't exist 17 | if !isdirectory(s:runtime_dir . '/tmp') 18 | call mkdir(s:runtime_dir . '/tmp') 19 | endif 20 | 21 | " Init breakpoint signs 22 | hi def link Breakpoint Error 23 | sign define breakpoint linehl=Breakpoint text=xx 24 | 25 | " Init current line signs 26 | hi def link CurrentLine DiffAdd 27 | sign define current_line linehl=CurrentLine text=>> 28 | 29 | " Loads this file. Required for autoloading the code for this plugin 30 | fun! ruby_debugger#load_debugger() 31 | if !s:check_prerequisites() 32 | finish 33 | endif 34 | endf 35 | 36 | fun! ruby_debugger#statusline() 37 | let is_running = g:RubyDebugger.is_running() 38 | if is_running == 0 39 | return '' 40 | endif 41 | return '[ruby debugger running]' 42 | endfunction 43 | 44 | " Check all requirements for the current plugin 45 | fun! s:check_prerequisites() 46 | let problems = [] 47 | if v:version < 700 48 | call add(problems, "RubyDebugger: This plugin requires Vim >= 7.") 49 | endif 50 | if !has("clientserver") 51 | call add(problems, "RubyDebugger: This plugin requires +clientserver option") 52 | endif 53 | if !has("ruby") 54 | call add(problems, "RubyDebugger: This plugin requires +ruby option.") 55 | end 56 | if empty(problems) 57 | return 1 58 | else 59 | for p in problems 60 | echoerr p 61 | endfor 62 | return 0 63 | endif 64 | endf 65 | 66 | 67 | " End of init section 68 | 69 | 70 | " *** Common (global) functions 71 | 72 | " Split string of tags to List. E.g., 73 | " 74 | " will be splitted to 75 | " [ '', '' ] 76 | function! s:get_tags(cmd) 77 | let tags = [] 78 | let cmd = a:cmd 79 | " Remove wrap tags 80 | let inner_tags_match = s:get_inner_tags(cmd) 81 | if !empty(inner_tags_match) 82 | " Then find every tag and remove it from source string 83 | let pattern = '<.\{-}\/>' 84 | let inner_tags = inner_tags_match[1] 85 | let tagmatch = matchlist(inner_tags, pattern) 86 | while empty(tagmatch) == 0 87 | call add(tags, tagmatch[0]) 88 | " These symbols are interpretated as special, we need to escape them 89 | let tagmatch[0] = escape(tagmatch[0], '[]~*\') 90 | " Remove it from source string 91 | let inner_tags = substitute(inner_tags, tagmatch[0], '', '') 92 | " Find next tag 93 | let tagmatch = matchlist(inner_tags, pattern) 94 | endwhile 95 | endif 96 | return tags 97 | endfunction 98 | 99 | 100 | " Converts command with relative path to absolute path. If given command 101 | " contains relative path, it will try to use 'which' on it first, and if 102 | " 'which' returns nothing, it will add current dir path to given command 103 | function! s:get_escaped_absolute_path(command) 104 | " Remove leading and trailing quotes 105 | let given_path = a:command 106 | let given_path = substitute(given_path, '"', '\"', "g") 107 | let given_path = substitute(given_path, "^'", '', "g") 108 | let given_path = substitute(given_path, "'$", '', "g") 109 | if given_path[0] == '/' 110 | let absolute_path = given_path 111 | else 112 | let parts = split(given_path) 113 | let relative_command = remove(parts, 0) 114 | let arguments = join(parts) 115 | let absolute_command = "" 116 | " I don't know Windows analogue for 'which', if you know - feel free to add it here 117 | if !(has("win32") || has("win64")) 118 | let absolute_command = s:strip(system('which ' . relative_command)) 119 | endif 120 | if absolute_command[0] != '/' 121 | let absolute_command = getcwd() . '/' . relative_command 122 | endif 123 | let absolute_path = "\"'" . absolute_command . "' " . arguments . '"' 124 | endif 125 | return absolute_path 126 | endfunction 127 | 128 | 129 | " Return a string without leading and trailing spaces and linebreaks. 130 | function! s:strip(input_string) 131 | return substitute(substitute(a:input_string, "\n", '', 'g'), '(\s*\(.\{-}\)\s*', '\1', 'g') 132 | endfunction 133 | 134 | 135 | " Shortcut for g:RubyDebugger.logger.debug 136 | function! s:log(string) 137 | call g:RubyDebugger.logger.put(a:string) 138 | endfunction 139 | 140 | 141 | " Return match of inner tags without wrap tags. E.g.: 142 | " mathes only 143 | function! s:get_inner_tags(cmd) 144 | return matchlist(a:cmd, '^<.\{-}>\(.\{-}\)<\/.\{-}>$') 145 | endfunction 146 | 147 | 148 | " Return Dict of attributes. 149 | " E.g., from it returns 150 | " {'name' : 'a', 'value' : 'b'} 151 | function! s:get_tag_attributes(cmd) 152 | let attributes = {} 153 | let cmd = a:cmd 154 | " Find type of used quotes (" or ') 155 | let quote_match = matchlist(cmd, "\\w\\+=\\(.\\)") 156 | let quote = empty(quote_match) ? "\"" : escape(quote_match[1], "'\"") 157 | let pattern = "\\(\\w\\+\\)=" . quote . "\\(.\\{-}\\)" . quote 158 | " Find every attribute and remove it from source string 159 | let attrmatch = matchlist(cmd, pattern) 160 | while !empty(attrmatch) 161 | " Values of attributes can be escaped by HTML entities, unescape them 162 | let attributes[attrmatch[1]] = s:unescape_html(attrmatch[2]) 163 | " These symbols are interpretated as special, we need to escape them 164 | let attrmatch[0] = escape(attrmatch[0], '[]~*\') 165 | " Remove it from source string 166 | let cmd = substitute(cmd, attrmatch[0], '', '') 167 | " Find next attribute 168 | let attrmatch = matchlist(cmd, pattern) 169 | endwhile 170 | return attributes 171 | endfunction 172 | 173 | 174 | " Unescape HTML entities 175 | function! s:unescape_html(html) 176 | let result = substitute(a:html, "&", "\\&", "g") 177 | let result = substitute(result, """, "\"", "g") 178 | let result = substitute(result, "<", "<", "g") 179 | let result = substitute(result, ">", ">", "g") 180 | return result 181 | endfunction 182 | 183 | 184 | function! s:quotify(exp) 185 | let quoted = a:exp 186 | let quoted = substitute(quoted, "\"", "\\\\\"", 'g') 187 | return quoted 188 | endfunction 189 | 190 | 191 | " Get filename of current buffer 192 | function! s:get_filename() 193 | return expand("%:p") 194 | endfunction 195 | 196 | 197 | " Send message to debugger. This function should never be used explicitly, 198 | " only through g:RubyDebugger.send_command function 199 | function! s:send_message_to_debugger(message) 200 | call s:log("Sending a message to ruby_debugger.rb: '" . a:message . "'") 201 | ruby << RUBY 202 | require 'socket' 203 | @vim_ruby_debugger_socket ||= UNIXSocket.open(VIM.evaluate("s:socket_file")) 204 | message = VIM.evaluate("a:message").gsub("\\\"", '"') 205 | begin 206 | @vim_ruby_debugger_socket.puts(message) 207 | rescue Errno::EPIPE 208 | VIM.message("Debugger is not running") 209 | end 210 | RUBY 211 | endfunction 212 | 213 | 214 | function! s:unplace_sign_of_current_line() 215 | if has("signs") 216 | exe ":sign unplace " . s:current_line_sign_id 217 | endif 218 | endfunction 219 | 220 | 221 | " Remove all variables of current line, remove current line sign. Usually it 222 | " is needed before next/step/cont commands 223 | function! s:clear_current_state() 224 | call s:unplace_sign_of_current_line() 225 | let g:RubyDebugger.variables = {} 226 | let g:RubyDebugger.frames = [] 227 | " Clear variables and frames window (just show our empty variables Dict) 228 | if s:variables_window.is_open() 229 | call s:variables_window.open() 230 | endif 231 | if s:frames_window.is_open() 232 | call s:frames_window.open() 233 | endif 234 | endfunction 235 | 236 | 237 | " Open given file and jump to given line 238 | " (stolen from NERDTree) 239 | function! s:jump_to_file(file, line) 240 | "if the file is already open in this tab then just stick the cursor in it 241 | let window_number = bufwinnr('^' . a:file . '$') 242 | if window_number != -1 243 | exe window_number . "wincmd w" 244 | else 245 | " Check if last accessed window is usable to use it 246 | " Usable window - not quickfix, explorer, modified, etc 247 | if !s:is_window_usable(winnr("#")) 248 | exe s:first_normal_window() . "wincmd w" 249 | else 250 | " If it is usable, jump to it 251 | exe 'wincmd p' 252 | endif 253 | exe "edit " . a:file 254 | endif 255 | exe "normal " . a:line . "G" 256 | endfunction 257 | 258 | 259 | " Return 1 if window is usable (not quickfix, explorer, modified, only one 260 | " window, ...) 261 | function! s:is_window_usable(winnumber) 262 | "If there is only one window (winnr("$") - windows count) 263 | if winnr("$") ==# 1 264 | return 0 265 | endif 266 | 267 | " Current window number 268 | let oldwinnr = winnr() 269 | 270 | " Switch to given window and check it 271 | exe a:winnumber . "wincmd p" 272 | let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') 273 | let modified = &modified 274 | 275 | exe oldwinnr . "wincmd p" 276 | 277 | "if it is a special window, e.g. quickfix or another explorer plugin 278 | if specialWindow 279 | return 0 280 | endif 281 | 282 | if &hidden 283 | return 1 284 | endif 285 | 286 | " If this window is modified, but there is another opened window with 287 | " current file, return 1. Otherwise - 0 288 | return !modified || s:buf_in_windows(winbufnr(a:winnumber)) >= 2 289 | endfunction 290 | 291 | 292 | " Determine the number of windows open to this buffer number. 293 | function! s:buf_in_windows(buffer_number) 294 | let count = 0 295 | let window_number = 1 296 | while 1 297 | let buffer_number = winbufnr(window_number) 298 | if buffer_number < 0 299 | break 300 | endif 301 | if buffer_number ==# a:buffer_number 302 | let count = count + 1 303 | endif 304 | let window_number = window_number + 1 305 | endwhile 306 | 307 | return count 308 | endfunction 309 | 310 | 311 | " Find first 'normal' window (not quickfix, explorer, etc) 312 | function! s:first_normal_window() 313 | let i = 1 314 | while i <= winnr("$") 315 | let bnum = winbufnr(i) 316 | if bnum != -1 && getbufvar(bnum, '&buftype') ==# '' && !getwinvar(i, '&previewwindow') 317 | return i 318 | endif 319 | let i += 1 320 | endwhile 321 | return -1 322 | endfunction 323 | 324 | " *** Queue class (start) 325 | 326 | let s:Queue = {} 327 | 328 | " ** Public methods 329 | 330 | " Constructor of new queue. 331 | function! s:Queue.new() dict 332 | let var = copy(self) 333 | let var.queue = [] 334 | let var.after = "" 335 | return var 336 | endfunction 337 | 338 | 339 | " Execute next command in the queue and remove it from queue 340 | function! s:Queue.execute() dict 341 | if !empty(self.queue) 342 | call s:log("Executing queue") 343 | let message = join(self.queue, ';') 344 | call self.empty() 345 | call g:RubyDebugger.send_command(message) 346 | endif 347 | endfunction 348 | 349 | 350 | " Execute 'after' hook only if queue is empty 351 | function! s:Queue.after_hook() dict 352 | if self.after != "" && empty(self.queue) 353 | call self.after() 354 | endif 355 | endfunction 356 | 357 | 358 | function! s:Queue.add(element) dict 359 | call s:log("Adding '" . a:element . "' to queue") 360 | call add(self.queue, a:element) 361 | endfunction 362 | 363 | 364 | function! s:Queue.empty() dict 365 | let self.queue = [] 366 | endfunction 367 | 368 | 369 | " *** Queue class (end) 370 | 371 | 372 | 373 | 374 | " *** Public interface (start) 375 | 376 | let RubyDebugger = { 'commands': {}, 'variables': {}, 'settings': {}, 'breakpoints': [], 'frames': [], 'exceptions': [] } 377 | let g:RubyDebugger.queue = s:Queue.new() 378 | 379 | 380 | " Run debugger server. It takes one optional argument with path to debugged 381 | " ruby script ('script/server webrick' by default) 382 | function! RubyDebugger.start(...) dict 383 | call s:log("Executing :Rdebugger...") 384 | let g:RubyDebugger.server = s:Server.new() 385 | let script_string = a:0 && !empty(a:1) ? a:1 : g:ruby_debugger_default_script 386 | let params = a:0 && a:0 > 1 && !empty(a:2) ? a:2 : [] 387 | echo "Loading debugger..." 388 | call g:RubyDebugger.server.start(s:get_escaped_absolute_path(script_string), params) 389 | let g:RubyDebugger.exceptions = [] 390 | endfunction 391 | 392 | 393 | " Stop running server. 394 | function! RubyDebugger.stop() dict 395 | if has_key(g:RubyDebugger, 'server') 396 | call g:RubyDebugger.server.stop() 397 | endif 398 | endfunction 399 | 400 | function! RubyDebugger.is_running() 401 | if has_key(g:RubyDebugger, 'server') 402 | return g:RubyDebugger.server.is_running() 403 | endif 404 | return 0 405 | endfunction 406 | 407 | 408 | function! RubyDebugger.establish_connection() 409 | for breakpoint in g:RubyDebugger.breakpoints 410 | call g:RubyDebugger.queue.add(breakpoint.command()) 411 | endfor 412 | call g:RubyDebugger.queue.add('start') 413 | call g:RubyDebugger.queue.execute() 414 | echo "Debugger started" 415 | call s:log("Debugger is successfully started") 416 | endfunction 417 | 418 | 419 | " This function receives commands from the debugger. When ruby_debugger.rb 420 | " gets output from rdebug-ide, it writes it to the special file and 'kick' 421 | " the plugin by remotely calling RubyDebugger.receive_command(), e.g.: 422 | " vim --servername VIM --remote-send 'call RubyDebugger.receive_command()' 423 | " That's why +clientserver is required 424 | " This function analyzes the special file and gives handling to right command 425 | function! RubyDebugger.receive_command() dict 426 | let file_contents = join(readfile(s:tmp_file), "") 427 | call s:log("Received command: " . file_contents) 428 | let commands = split(file_contents, s:separator) 429 | for cmd in commands 430 | if !empty(cmd) 431 | if match(cmd, '') != -1 442 | call g:RubyDebugger.commands.set_variables(cmd) 443 | elseif match(cmd, '') != -1 444 | call g:RubyDebugger.commands.error(cmd) 445 | elseif match(cmd, '') != -1 446 | call g:RubyDebugger.commands.message(cmd) 447 | elseif match(cmd, '') != -1 452 | call g:RubyDebugger.commands.trace(cmd) 453 | endif 454 | endif 455 | endfor 456 | call g:RubyDebugger.queue.after_hook() 457 | call g:RubyDebugger.queue.execute() 458 | endfunction 459 | 460 | 461 | function! RubyDebugger.send_command_wrapper(command) 462 | call g:RubyDebugger.send_command(a:command) 463 | endfunction 464 | 465 | " We set function this way, because we want have possibility to mock it by 466 | " other function in tests 467 | let RubyDebugger.send_command = function("send_message_to_debugger") 468 | 469 | 470 | " Open variables window 471 | function! RubyDebugger.open_variables() dict 472 | call s:variables_window.toggle() 473 | call s:log("Opened variables window") 474 | call g:RubyDebugger.queue.execute() 475 | endfunction 476 | 477 | 478 | " Open breakpoints window 479 | function! RubyDebugger.open_breakpoints() dict 480 | call s:breakpoints_window.toggle() 481 | call s:log("Opened breakpoints window") 482 | call g:RubyDebugger.queue.execute() 483 | endfunction 484 | 485 | 486 | " Open frames window 487 | function! RubyDebugger.open_frames() dict 488 | call s:frames_window.toggle() 489 | call s:log("Opened frames window") 490 | call g:RubyDebugger.queue.execute() 491 | endfunction 492 | 493 | 494 | " Set/remove breakpoint at current position. If argument 495 | " is given, it will set conditional breakpoint (argument is condition) 496 | function! RubyDebugger.toggle_breakpoint(...) dict 497 | let line = line(".") 498 | let file = s:get_filename() 499 | call s:log("Trying to toggle a breakpoint in the file " . file . ":" . line) 500 | let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') 501 | " If breakpoint with current file/line doesn't exist, create it. Otherwise - 502 | " remove it 503 | if empty(existed_breakpoints) 504 | call s:log("There is no already set breakpoint, so create new one") 505 | let breakpoint = s:Breakpoint.new(file, line) 506 | call add(g:RubyDebugger.breakpoints, breakpoint) 507 | call s:log("Added Breakpoint object to RubyDebugger.breakpoints array") 508 | call breakpoint.send_to_debugger() 509 | else 510 | call s:log("There is already set breakpoint presented, so delete it") 511 | let breakpoint = existed_breakpoints[0] 512 | call filter(g:RubyDebugger.breakpoints, 'v:val.id != ' . breakpoint.id) 513 | call s:log("Removed Breakpoint object from RubyDebugger.breakpoints array") 514 | call breakpoint.delete() 515 | endif 516 | " Update info in Breakpoints window 517 | if s:breakpoints_window.is_open() 518 | call s:breakpoints_window.open() 519 | exe "wincmd p" 520 | endif 521 | call g:RubyDebugger.queue.execute() 522 | endfunction 523 | 524 | 525 | " Remove all breakpoints 526 | function! RubyDebugger.remove_breakpoints() dict 527 | for breakpoint in g:RubyDebugger.breakpoints 528 | call breakpoint.delete() 529 | endfor 530 | let g:RubyDebugger.breakpoints = [] 531 | call g:RubyDebugger.queue.execute() 532 | endfunction 533 | 534 | 535 | " Eval the passed in expression 536 | function! RubyDebugger.eval(exp) dict 537 | let quoted = s:quotify(a:exp) 538 | call g:RubyDebugger.queue.add("eval " . quoted) 539 | call g:RubyDebugger.queue.execute() 540 | endfunction 541 | 542 | 543 | " Sets conditional breakpoint where cursor is placed 544 | function! RubyDebugger.conditional_breakpoint(exp) dict 545 | let line = line(".") 546 | let file = s:get_filename() 547 | let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') 548 | " If breakpoint with current file/line doesn't exist, create it. Otherwise - 549 | " remove it 550 | if empty(existed_breakpoints) 551 | echo "You can set condition only to already set breakpoints. Move cursor to set breakpoint and add condition" 552 | else 553 | let breakpoint = existed_breakpoints[0] 554 | let quoted = s:quotify(a:exp) 555 | call breakpoint.add_condition(quoted) 556 | " Update info in Breakpoints window 557 | if s:breakpoints_window.is_open() 558 | call s:breakpoints_window.open() 559 | exe "wincmd p" 560 | endif 561 | call g:RubyDebugger.queue.execute() 562 | endif 563 | endfunction 564 | 565 | 566 | " Catch all exceptions with given name 567 | function! RubyDebugger.catch_exception(exp) dict 568 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() 569 | let quoted = s:quotify(a:exp) 570 | let exception = s:Exception.new(quoted) 571 | call add(g:RubyDebugger.exceptions, exception) 572 | if s:breakpoints_window.is_open() 573 | call s:breakpoints_window.open() 574 | exe "wincmd p" 575 | endif 576 | call g:RubyDebugger.queue.execute() 577 | else 578 | echo "Sorry, but you can set Exceptional Breakpoints only with running debugger" 579 | endif 580 | endfunction 581 | 582 | 583 | " Next 584 | function! RubyDebugger.next() dict 585 | call g:RubyDebugger.queue.add("next") 586 | call s:clear_current_state() 587 | call s:log("Step over") 588 | call g:RubyDebugger.queue.execute() 589 | endfunction 590 | 591 | 592 | " Step 593 | function! RubyDebugger.step() dict 594 | call g:RubyDebugger.queue.add("step") 595 | call s:clear_current_state() 596 | call s:log("Step into") 597 | call g:RubyDebugger.queue.execute() 598 | endfunction 599 | 600 | 601 | " Finish 602 | function! RubyDebugger.finish() dict 603 | call g:RubyDebugger.queue.add("finish") 604 | call s:clear_current_state() 605 | call s:log("Step out") 606 | call g:RubyDebugger.queue.execute() 607 | endfunction 608 | 609 | 610 | " Continue 611 | function! RubyDebugger.continue() dict 612 | call g:RubyDebugger.queue.add("cont") 613 | call s:clear_current_state() 614 | call s:log("Continue") 615 | call g:RubyDebugger.queue.execute() 616 | endfunction 617 | 618 | 619 | " Exit 620 | function! RubyDebugger.exit() dict 621 | call g:RubyDebugger.queue.add("exit") 622 | call s:clear_current_state() 623 | call g:RubyDebugger.queue.execute() 624 | endfunction 625 | 626 | 627 | " Show output log of Ruby script 628 | function! RubyDebugger.show_log() dict 629 | exe "view " . s:server_output_file 630 | setlocal autoread 631 | " Per gorkunov's request 632 | setlocal wrap 633 | setlocal nonumber 634 | if exists(":AnsiEsc") 635 | exec ":AnsiEsc" 636 | endif 637 | endfunction 638 | 639 | 640 | " Debug current opened test 641 | function! RubyDebugger.run_test(...) dict 642 | let file = s:get_filename() 643 | if file =~ '_spec\.rb$' 644 | let line = a:0 && a:0 > 0 && !empty(a:1) ? a:1 : " " 645 | call g:RubyDebugger.start(g:ruby_debugger_spec_path . ' ' . file . line) 646 | elseif file =~ '\.feature$' 647 | call g:RubyDebugger.start(g:ruby_debugger_cucumber_path . ' ' . file) 648 | elseif file =~ '_test\.rb$' 649 | call g:RubyDebugger.start(file, ['-Itest']) 650 | endif 651 | endfunction 652 | 653 | 654 | " *** Public interface (end) 655 | 656 | 657 | 658 | 659 | " *** RubyDebugger Commands (what debugger returns) 660 | 661 | 662 | " 663 | " 664 | " Jump to file/line where execution was suspended, set current line sign and get local variables 665 | function! RubyDebugger.commands.jump_to_breakpoint(cmd) dict 666 | let attrs = s:get_tag_attributes(a:cmd) 667 | call s:jump_to_file(attrs.file, attrs.line) 668 | call s:log("Jumped to breakpoint " . attrs.file . ":" . attrs.line) 669 | 670 | if has("signs") 671 | exe ":sign place " . s:current_line_sign_id . " line=" . attrs.line . " name=current_line file=" . attrs.file 672 | endif 673 | endfunction 674 | 675 | 676 | " 677 | " Show message error and jump to given file/line 678 | function! RubyDebugger.commands.handle_exception(cmd) dict 679 | let message_match = matchlist(a:cmd, 'message="\(.\{-}\)"') 680 | call g:RubyDebugger.commands.jump_to_breakpoint(a:cmd) 681 | echo "Exception message: " . s:unescape_html(message_match[1]) 682 | endfunction 683 | 684 | 685 | " 686 | " Confirm setting of exception catcher 687 | function! RubyDebugger.commands.set_exception(cmd) dict 688 | let attrs = s:get_tag_attributes(a:cmd) 689 | call s:log("Exception successfully set: " . attrs.exception) 690 | endfunction 691 | 692 | 693 | " 694 | " Add debugger info to breakpoints (pid of debugger, debugger breakpoint's id) 695 | " Assign rest breakpoints to debugger recursively, if there are breakpoints 696 | " from old server runnings or not assigned breakpoints (e.g., if you at first 697 | " set some breakpoints, and then run the debugger by :Rdebugger) 698 | function! RubyDebugger.commands.set_breakpoint(cmd) 699 | call s:log("Received the breakpoint message, will add PID and number of breakpoint to the Breakpoint object") 700 | let attrs = s:get_tag_attributes(a:cmd) 701 | let file_match = matchlist(attrs.location, '\(.*\):\(.*\)') 702 | 703 | " Find added breakpoint in array and assign debugger's info to it 704 | for breakpoint in g:RubyDebugger.breakpoints 705 | if expand(breakpoint.file) == expand(file_match[1]) && expand(breakpoint.line) == expand(file_match[2]) 706 | call s:log("Found the Breakpoint object for " . breakpoint.file . ":" . breakpoint.line) 707 | let breakpoint.debugger_id = attrs.no 708 | let breakpoint.rdebug_pid = s:rdebug_pid 709 | call s:log("Added id: " . breakpoint.debugger_id . ", PID:" . breakpoint.rdebug_pid . " to Breakpoint") 710 | if has_key(breakpoint, 'condition') 711 | call breakpoint.add_condition(breakpoint.condition) 712 | endif 713 | endif 714 | endfor 715 | 716 | call s:log("Breakpoint is set: " . file_match[1] . ":" . file_match[2]) 717 | call g:RubyDebugger.queue.execute() 718 | endfunction 719 | 720 | 721 | " 722 | " 723 | " 724 | " Assign list of got variables to parent variable and (optionally) show them 725 | function! RubyDebugger.commands.set_variables(cmd) 726 | let tags = s:get_tags(a:cmd) 727 | let list_of_variables = [] 728 | 729 | " Create hash from list of tags 730 | for tag in tags 731 | let attrs = s:get_tag_attributes(tag) 732 | let variable = s:Var.new(attrs) 733 | call add(list_of_variables, variable) 734 | endfor 735 | 736 | " If there is no variables, create unnamed root variable. Local variables 737 | " will be chilren of this variable 738 | if g:RubyDebugger.variables == {} 739 | let g:RubyDebugger.variables = s:VarParent.new({'hasChildren': 'true'}) 740 | let g:RubyDebugger.variables.is_open = 1 741 | let g:RubyDebugger.variables.children = [] 742 | endif 743 | 744 | " If g:RubyDebugger.current_variable exists, then it contains parent 745 | " variable of got subvariables. Assign them to it. 746 | if has_key(g:RubyDebugger, 'current_variable') 747 | let variable = g:RubyDebugger.current_variable 748 | if variable != {} 749 | call variable.add_childs(list_of_variables) 750 | call s:log("Opening child variable: " . variable.attributes.objectId) 751 | " Variables Window is always open if we got subvariables 752 | call s:variables_window.open() 753 | else 754 | call s:log("Can't found variable") 755 | endif 756 | unlet g:RubyDebugger.current_variable 757 | else 758 | " Otherwise, assign them to unnamed root variable 759 | if g:RubyDebugger.variables.children == [] 760 | call g:RubyDebugger.variables.add_childs(list_of_variables) 761 | call s:log("Initializing local variables") 762 | if s:variables_window.is_open() 763 | " show variables only if Variables Window is open 764 | call s:variables_window.open() 765 | endif 766 | endif 767 | endif 768 | 769 | endfunction 770 | 771 | 772 | " 773 | " Just show result of evaluation 774 | function! RubyDebugger.commands.eval(cmd) 775 | " rdebug-ide-gem doesn't escape attributes of tag properly, so we should not 776 | " use usual attribute extractor here... 777 | let match = matchlist(a:cmd, "") 778 | echo "Evaluated expression:\n" . s:unescape_html(match[1]) ."\nResulted value is:\n" . s:unescape_html(match[2]) . "\n" 779 | endfunction 780 | 781 | 782 | " 783 | " Just show exception message 784 | function! RubyDebugger.commands.processing_exception(cmd) 785 | let attrs = s:get_tag_attributes(a:cmd) 786 | let message = "RubyDebugger Exception, type: " . attrs.type . ", message: " . attrs.message 787 | echo message 788 | call s:log(message) 789 | endfunction 790 | 791 | 792 | " 793 | " 794 | " 795 | " 796 | " Assign all frames, fill Frames window by them 797 | function! RubyDebugger.commands.trace(cmd) 798 | let tags = s:get_tags(a:cmd) 799 | let list_of_frames = [] 800 | 801 | " Create hash from list of tags 802 | for tag in tags 803 | let attrs = s:get_tag_attributes(tag) 804 | let frame = s:Frame.new(attrs) 805 | call add(list_of_frames, frame) 806 | endfor 807 | 808 | let g:RubyDebugger.frames = list_of_frames 809 | 810 | if s:frames_window.is_open() 811 | " show backtrace only if Backtrace Window is open 812 | call s:frames_window.open() 813 | endif 814 | endfunction 815 | 816 | 817 | " Error 818 | " Just show error 819 | function! RubyDebugger.commands.error(cmd) 820 | let error_match = s:get_inner_tags(a:cmd) 821 | if !empty(error_match) 822 | let error = error_match[1] 823 | echo "RubyDebugger Error: " . error 824 | call s:log("Got error: " . error) 825 | endif 826 | endfunction 827 | 828 | 829 | " Message 830 | " Just show message 831 | function! RubyDebugger.commands.message(cmd) 832 | let message_match = s:get_inner_tags(a:cmd) 833 | if !empty(message_match) 834 | let message = message_match[1] 835 | echo "RubyDebugger Message: " . message 836 | call s:log("Got message: " . message) 837 | endif 838 | endfunction 839 | 840 | 841 | " *** End of debugger Commands 842 | 843 | 844 | 845 | " *** Window class (start). Abstract Class for creating window. 846 | " Must be inherited. Mostly, stolen from the NERDTree. 847 | 848 | let s:Window = {} 849 | let s:Window['next_buffer_number'] = 1 850 | let s:Window['position'] = 'botright' 851 | let s:Window['size'] = 10 852 | 853 | " ** Public methods 854 | 855 | " Constructs new window 856 | function! s:Window.new(name, title) dict 857 | let new_variable = copy(self) 858 | let new_variable.name = a:name 859 | let new_variable.title = a:title 860 | return new_variable 861 | endfunction 862 | 863 | 864 | " Clear all data from window 865 | function! s:Window.clear() dict 866 | silent 1,$delete _ 867 | endfunction 868 | 869 | 870 | " Close window 871 | function! s:Window.close() dict 872 | if !self.is_open() 873 | throw "RubyDebug: Window " . self.name . " is not open" 874 | endif 875 | 876 | if winnr("$") != 1 877 | call self.focus() 878 | close 879 | exe "wincmd p" 880 | else 881 | " If this is only one window, just quit 882 | :q 883 | endif 884 | call s:log("Closed window with name: " . self.name) 885 | endfunction 886 | 887 | 888 | " Get window number 889 | function! s:Window.get_number() dict 890 | if self._exist_for_tab() 891 | return bufwinnr(self._buf_name()) 892 | else 893 | return -1 894 | endif 895 | endfunction 896 | 897 | 898 | " Display data to the window 899 | function! s:Window.display() 900 | call s:log("Start displaying data in window with name: " . self.name) 901 | call self.focus() 902 | setlocal modifiable 903 | 904 | let current_line = line(".") 905 | let current_column = col(".") 906 | let top_line = line("w0") 907 | 908 | call self.clear() 909 | 910 | call self._insert_data() 911 | call self._restore_view(top_line, current_line, current_column) 912 | 913 | setlocal nomodifiable 914 | call s:log("Complete displaying data in window with name: " . self.name) 915 | endfunction 916 | 917 | 918 | " Put cursor to the window 919 | function! s:Window.focus() dict 920 | exe self.get_number() . " wincmd w" 921 | call s:log("Set focus to window with name: " . self.name) 922 | endfunction 923 | 924 | 925 | " Return 1 if window is opened 926 | function! s:Window.is_open() dict 927 | return self.get_number() != -1 928 | endfunction 929 | 930 | 931 | " Open window and display data (stolen from NERDTree) 932 | function! s:Window.open() dict 933 | if !self.is_open() 934 | " create the window 935 | silent exec self.position . ' ' . self.size . ' new' 936 | 937 | if !self._exist_for_tab() 938 | " If the window is not opened/exists, create new 939 | call self._set_buf_name(self._next_buffer_name()) 940 | silent! exec "edit " . self._buf_name() 941 | " This function does not exist in Window class and should be declared in 942 | " descendants 943 | call self.bind_mappings() 944 | else 945 | " Or just jump to opened buffer 946 | silent! exec "buffer " . self._buf_name() 947 | endif 948 | 949 | " set buffer options 950 | setlocal winfixheight 951 | setlocal noswapfile 952 | setlocal buftype=nofile 953 | setlocal nowrap 954 | setlocal foldcolumn=0 955 | setlocal nobuflisted 956 | setlocal nospell 957 | setlocal nolist 958 | iabc 959 | setlocal cursorline 960 | setfiletype ruby_debugger_window 961 | call s:log("Opened window with name: " . self.name) 962 | endif 963 | 964 | if has("syntax") && exists("g:syntax_on") && !has("syntax_items") 965 | call self.setup_syntax_highlighting() 966 | endif 967 | 968 | call self.display() 969 | endfunction 970 | 971 | 972 | " Open/close window 973 | function! s:Window.toggle() dict 974 | call s:log("Toggling window with name: " . self.name) 975 | if self._exist_for_tab() && self.is_open() 976 | call self.close() 977 | else 978 | call self.open() 979 | end 980 | endfunction 981 | 982 | 983 | " ** Private methods 984 | 985 | 986 | " Return buffer name, that is stored in tab variable 987 | function! s:Window._buf_name() dict 988 | return t:window_{self.name}_buf_name 989 | endfunction 990 | 991 | 992 | " Return 1 if the window exists in current tab 993 | function! s:Window._exist_for_tab() dict 994 | return exists("t:window_" . self.name . "_buf_name") 995 | endfunction 996 | 997 | 998 | " Insert data to the window 999 | function! s:Window._insert_data() dict 1000 | let old_p = @p 1001 | " Put data to the register and then show it by 'put' command 1002 | let @p = self.render() 1003 | silent exe "normal \"pP" 1004 | let @p = old_p 1005 | call s:log("Inserted data to window with name: " . self.name) 1006 | endfunction 1007 | 1008 | 1009 | " Calculate correct name for the window 1010 | function! s:Window._next_buffer_name() dict 1011 | let name = self.name . s:Window.next_buffer_number 1012 | let s:Window.next_buffer_number += 1 1013 | return name 1014 | endfunction 1015 | 1016 | 1017 | " Restore the view 1018 | function! s:Window._restore_view(top_line, current_line, current_column) dict 1019 | let old_scrolloff=&scrolloff 1020 | let &scrolloff=0 1021 | call cursor(a:top_line, 1) 1022 | normal! zt 1023 | call cursor(a:current_line, a:current_column) 1024 | let &scrolloff = old_scrolloff 1025 | call s:log("Restored view of window with name: " . self.name) 1026 | endfunction 1027 | 1028 | 1029 | function! s:Window._set_buf_name(name) dict 1030 | let t:window_{self.name}_buf_name = a:name 1031 | endfunction 1032 | 1033 | 1034 | " *** Window class (end) 1035 | 1036 | 1037 | " *** WindowVariables class (start) 1038 | 1039 | " Inherits variables window from abstract window class 1040 | let s:WindowVariables = copy(s:Window) 1041 | 1042 | " ** Public methods 1043 | 1044 | function! s:WindowVariables.bind_mappings() 1045 | nnoremap <2-leftmouse> :call window_variables_activate_node() 1046 | nnoremap o :call window_variables_activate_node()" 1047 | endfunction 1048 | 1049 | 1050 | " Returns string that contains all variables (for Window.display()) 1051 | function! s:WindowVariables.render() dict 1052 | let variables = self.title . "\n" 1053 | let variables .= (g:RubyDebugger.variables == {} ? '' : g:RubyDebugger.variables.render()) 1054 | return variables 1055 | endfunction 1056 | 1057 | 1058 | " TODO: Is there some way to call s:WindowVariables.activate_node from mapping 1059 | " command? 1060 | " Expand/collapse variable under cursor 1061 | function! s:window_variables_activate_node() 1062 | let variable = s:Var.get_selected() 1063 | if variable != {} && variable.type == "VarParent" 1064 | if variable.is_open 1065 | call variable.close() 1066 | else 1067 | call variable.open() 1068 | endif 1069 | endif 1070 | call g:RubyDebugger.queue.execute() 1071 | endfunction 1072 | 1073 | 1074 | " Add syntax highlighting 1075 | function! s:WindowVariables.setup_syntax_highlighting() 1076 | execute "syn match rdebugTitle #" . self.title . "#" 1077 | 1078 | syn match rdebugPart #[| `]\+# 1079 | syn match rdebugPartFile #[| `]\+-# contains=rdebugPart nextgroup=rdebugChild contained 1080 | syn match rdebugChild #.\{-}\t# nextgroup=rdebugType contained 1081 | 1082 | syn match rdebugClosable #[| `]\+\~# contains=rdebugPart nextgroup=rdebugParent contained 1083 | syn match rdebugOpenable #[| `]\++# contains=rdebugPart nextgroup=rdebugParent contained 1084 | syn match rdebugParent #.\{-}\t# nextgroup=rdebugType contained 1085 | 1086 | syn match rdebugType #.\{-}\t# nextgroup=rdebugValue contained 1087 | syn match rdebugValue #.*\t#he=e-1 nextgroup=rdebugId contained 1088 | syn match rdebugId #.*# contained 1089 | 1090 | syn match rdebugParentLine '[| `]\+[+\~].*' contains=rdebugClosable,rdebugOpenable transparent 1091 | syn match rdebugChildLine '[| `]\+-.*' contains=rdebugPartFile transparent 1092 | 1093 | hi def link rdebugTitle Identifier 1094 | hi def link rdebugClosable Type 1095 | hi def link rdebugOpenable Title 1096 | hi def link rdebugPart Special 1097 | hi def link rdebugPartFile Type 1098 | hi def link rdebugChild Normal 1099 | hi def link rdebugParent Directory 1100 | hi def link rdebugType Type 1101 | hi def link rdebugValue Special 1102 | hi def link rdebugId Ignore 1103 | endfunction 1104 | 1105 | 1106 | " *** WindowVariables class (end) 1107 | 1108 | 1109 | 1110 | " *** WindowBreakpoints class (start) 1111 | 1112 | " Inherits WindowBreakpoints from Window 1113 | let s:WindowBreakpoints = copy(s:Window) 1114 | 1115 | " ** Public methods 1116 | 1117 | function! s:WindowBreakpoints.bind_mappings() 1118 | nnoremap <2-leftmouse> :call window_breakpoints_activate_node() 1119 | nnoremap o :call window_breakpoints_activate_node() 1120 | nnoremap d :call window_breakpoints_delete_node() 1121 | endfunction 1122 | 1123 | 1124 | " Returns string that contains all breakpoints (for Window.display()) 1125 | function! s:WindowBreakpoints.render() dict 1126 | let breakpoints = "" 1127 | let breakpoints .= self.title . "\n" 1128 | for breakpoint in g:RubyDebugger.breakpoints 1129 | let breakpoints .= breakpoint.render() 1130 | endfor 1131 | let exceptions = map(copy(g:RubyDebugger.exceptions), 'v:val.render()') 1132 | let breakpoints .= "\nException breakpoints: " . join(exceptions, ", ") 1133 | return breakpoints 1134 | endfunction 1135 | 1136 | 1137 | " TODO: Is there some way to call s:WindowBreakpoints.activate_node from mapping 1138 | " command? 1139 | " Open breakpoint under cursor 1140 | function! s:window_breakpoints_activate_node() 1141 | let breakpoint = s:Breakpoint.get_selected() 1142 | if breakpoint != {} 1143 | call breakpoint.open() 1144 | endif 1145 | endfunction 1146 | 1147 | 1148 | " Delete breakpoint under cursor 1149 | function! s:window_breakpoints_delete_node() 1150 | let breakpoint = s:Breakpoint.get_selected() 1151 | if breakpoint != {} 1152 | call breakpoint.delete() 1153 | call filter(g:RubyDebugger.breakpoints, "v:val.id != " . breakpoint.id) 1154 | call s:breakpoints_window.open() 1155 | endif 1156 | endfunction 1157 | 1158 | 1159 | " Add syntax highlighting 1160 | function! s:WindowBreakpoints.setup_syntax_highlighting() dict 1161 | execute "syn match rdebugTitle #" . self.title . "#" 1162 | 1163 | syn match rdebugId "^\d\+\s" contained nextgroup=rdebugDebuggerId 1164 | syn match rdebugDebuggerId "\d*\s" contained nextgroup=rdebugFile 1165 | syn match rdebugFile ".*:" contained nextgroup=rdebugLine 1166 | syn match rdebugLine "\d\+" contained 1167 | 1168 | syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent 1169 | 1170 | hi def link rdebugId Directory 1171 | hi def link rdebugDebuggerId Type 1172 | hi def link rdebugFile Normal 1173 | hi def link rdebugLine Special 1174 | endfunction 1175 | 1176 | 1177 | " *** WindowBreakpoints class (end) 1178 | 1179 | 1180 | 1181 | " *** WindowFrames class (start) 1182 | 1183 | " Inherits WindowFrames from Window 1184 | let s:WindowFrames = copy(s:Window) 1185 | 1186 | " ** Public methods 1187 | 1188 | function! s:WindowFrames.bind_mappings() 1189 | nnoremap <2-leftmouse> :call window_frames_activate_node() 1190 | nnoremap o :call window_frames_activate_node() 1191 | endfunction 1192 | 1193 | 1194 | " Returns string that contains all frames (for Window.display()) 1195 | function! s:WindowFrames.render() dict 1196 | let frames = "" 1197 | let frames .= self.title . "\n" 1198 | for frame in g:RubyDebugger.frames 1199 | let frames .= frame.render() 1200 | endfor 1201 | return frames 1202 | endfunction 1203 | 1204 | 1205 | " Open frame under cursor 1206 | function! s:window_frames_activate_node() 1207 | let frame = s:Frame.get_selected() 1208 | if frame != {} 1209 | call frame.open() 1210 | endif 1211 | endfunction 1212 | 1213 | 1214 | " Add syntax highlighting 1215 | function! s:WindowFrames.setup_syntax_highlighting() dict 1216 | execute "syn match rdebugTitle #" . self.title . "#" 1217 | 1218 | syn match rdebugId "^\d\+\s" contained nextgroup=rdebugFile 1219 | syn match rdebugFile ".*:" contained nextgroup=rdebugLine 1220 | syn match rdebugLine "\d\+" contained 1221 | 1222 | syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent 1223 | 1224 | hi def link rdebugId Directory 1225 | hi def link rdebugFile Normal 1226 | hi def link rdebugLine Special 1227 | endfunction 1228 | 1229 | 1230 | " *** WindowFrames class (end) 1231 | 1232 | 1233 | 1234 | 1235 | " *** Var proxy class (start) 1236 | 1237 | let s:Var = { 'id' : 0 } 1238 | 1239 | " ** Public methods 1240 | 1241 | " This is a proxy method for creating new variable 1242 | function! s:Var.new(attrs) 1243 | if has_key(a:attrs, 'hasChildren') && a:attrs['hasChildren'] == 'true' 1244 | return s:VarParent.new(a:attrs) 1245 | else 1246 | return s:VarChild.new(a:attrs) 1247 | end 1248 | endfunction 1249 | 1250 | 1251 | " Get variable under cursor 1252 | function! s:Var.get_selected() 1253 | let line = getline(".") 1254 | " Get its id - it is last in the string 1255 | let match = matchlist(line, '.*\t\(\d\+\)$') 1256 | let id = get(match, 1) 1257 | if id 1258 | let variable = g:RubyDebugger.variables.find_variable({'id' : id}) 1259 | return variable 1260 | else 1261 | return {} 1262 | endif 1263 | endfunction 1264 | 1265 | 1266 | " *** Var proxy class (end) 1267 | 1268 | 1269 | 1270 | " *** VarChild class (start) 1271 | 1272 | let s:VarChild = {} 1273 | 1274 | " ** Public methods 1275 | 1276 | " Constructs new variable without childs 1277 | function! s:VarChild.new(attrs) 1278 | let new_variable = copy(self) 1279 | let new_variable.attributes = a:attrs 1280 | let new_variable.parent = {} 1281 | let new_variable.level = 0 1282 | let new_variable.type = "VarChild" 1283 | let s:Var.id += 1 1284 | let new_variable.id = s:Var.id 1285 | return new_variable 1286 | endfunction 1287 | 1288 | 1289 | " Renders data of the variable 1290 | function! s:VarChild.render() 1291 | return self._render(0, 0, [], len(self.parent.children) ==# 1) 1292 | endfunction 1293 | 1294 | 1295 | " VarChild can't be opened because it can't have children. But VarParent can 1296 | function! s:VarChild.open() 1297 | return 0 1298 | endfunction 1299 | 1300 | 1301 | " VarChild can't be closed because it can't have children. But VarParent can 1302 | function! s:VarChild.close() 1303 | return 0 1304 | endfunction 1305 | 1306 | 1307 | " VarChild can't be parent. But VarParent can. If Var have hasChildren == 1308 | " true, then it is parent 1309 | function! s:VarChild.is_parent() 1310 | return has_key(self.attributes, 'hasChildren') && get(self.attributes, 'hasChildren') ==# 'true' 1311 | endfunction 1312 | 1313 | 1314 | " Output format for Variables Window 1315 | function! s:VarChild.to_s() 1316 | return get(self.attributes, "name", "undefined") . "\t" . get(self.attributes, "type", "undefined") . "\t" . get(self.attributes, "value", "undefined") . "\t" . get(self, "id", "0") 1317 | endfunction 1318 | 1319 | 1320 | " Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} 1321 | function! s:VarChild.find_variable(attrs) 1322 | if self._match_attributes(a:attrs) 1323 | return self 1324 | else 1325 | return {} 1326 | endif 1327 | endfunction 1328 | 1329 | 1330 | " Find and return array of variables that match given Dict of attrs 1331 | function! s:VarChild.find_variables(attrs) 1332 | let variables = [] 1333 | if self._match_attributes(a:attrs) 1334 | call add(variables, self) 1335 | endif 1336 | return variables 1337 | endfunction 1338 | 1339 | 1340 | " ** Private methods 1341 | 1342 | 1343 | " Recursive function, that renders Variable and all its childs (if they are 1344 | " presented). Stolen from NERDTree 1345 | function! s:VarChild._render(depth, draw_text, vertical_map, is_last_child) 1346 | let output = "" 1347 | if a:draw_text ==# 1 1348 | let tree_parts = '' 1349 | 1350 | " get all the leading spaces and vertical tree parts for this line 1351 | if a:depth > 1 1352 | for j in a:vertical_map[0:-2] 1353 | if j ==# 1 1354 | let tree_parts = tree_parts . '| ' 1355 | else 1356 | let tree_parts = tree_parts . ' ' 1357 | endif 1358 | endfor 1359 | endif 1360 | 1361 | " get the last vertical tree part for this line which will be different 1362 | " if this node is the last child of its parent 1363 | if a:is_last_child 1364 | let tree_parts = tree_parts . '`' 1365 | else 1366 | let tree_parts = tree_parts . '|' 1367 | endif 1368 | 1369 | " smack the appropriate dir/file symbol on the line before the file/dir 1370 | " name itself 1371 | if self.is_parent() 1372 | if self.is_open 1373 | let tree_parts = tree_parts . '~' 1374 | else 1375 | let tree_parts = tree_parts . '+' 1376 | endif 1377 | else 1378 | let tree_parts = tree_parts . '-' 1379 | endif 1380 | let line = tree_parts . self.to_s() 1381 | let output = output . line . "\n" 1382 | 1383 | endif 1384 | 1385 | if self.is_parent() && self.is_open 1386 | if len(self.children) > 0 1387 | 1388 | " draw all the nodes children except the last 1389 | let last_index = len(self.children) - 1 1390 | if last_index > 0 1391 | for i in self.children[0:last_index - 1] 1392 | let output = output . i._render(a:depth + 1, 1, add(copy(a:vertical_map), 1), 0) 1393 | endfor 1394 | endif 1395 | 1396 | " draw the last child, indicating that it IS the last 1397 | let output = output . self.children[last_index]._render(a:depth + 1, 1, add(copy(a:vertical_map), 0), 1) 1398 | 1399 | endif 1400 | endif 1401 | 1402 | return output 1403 | 1404 | endfunction 1405 | 1406 | 1407 | " Return 1 if *all* given attributes (pairs key/value) match to current 1408 | " variable 1409 | function! s:VarChild._match_attributes(attrs) 1410 | let conditions = 1 1411 | for attr in keys(a:attrs) 1412 | if has_key(self.attributes, attr) 1413 | " If current key is contained in attributes of variable (they were 1414 | " attributes in tag, then trying to match there. 1415 | let conditions = conditions && self.attributes[attr] == a:attrs[attr] 1416 | elseif has_key(self, attr) 1417 | " Otherwise, if current key is contained in auxiliary attributes of the 1418 | " variable, trying to match there 1419 | let conditions = conditions && self[attr] == a:attrs[attr] 1420 | else 1421 | " Otherwise, this variable is not match 1422 | let conditions = 0 1423 | break 1424 | endif 1425 | endfor 1426 | return conditions 1427 | endfunction 1428 | 1429 | 1430 | " *** VarChild class (end) 1431 | 1432 | 1433 | 1434 | 1435 | " *** VarParent class (start) 1436 | 1437 | " Inherits VarParent from VarChild 1438 | let s:VarParent = copy(s:VarChild) 1439 | 1440 | " ** Public methods 1441 | 1442 | 1443 | " Initializes new variable with childs 1444 | function! s:VarParent.new(attrs) 1445 | if !has_key(a:attrs, 'hasChildren') || a:attrs['hasChildren'] != 'true' 1446 | throw "RubyDebug: VarParent must be initialized with hasChildren = true" 1447 | endif 1448 | let new_variable = copy(self) 1449 | let new_variable.attributes = a:attrs 1450 | let new_variable.parent = {} 1451 | let new_variable.is_open = 0 1452 | let new_variable.level = 0 1453 | let new_variable.children = [] 1454 | let new_variable.type = "VarParent" 1455 | let s:Var.id += 1 1456 | let new_variable.id = s:Var.id 1457 | return new_variable 1458 | endfunction 1459 | 1460 | 1461 | " Open variable, init its children and display them 1462 | function! s:VarParent.open() 1463 | let self.is_open = 1 1464 | call self._init_children() 1465 | return 0 1466 | endfunction 1467 | 1468 | 1469 | " Close variable and display it 1470 | function! s:VarParent.close() 1471 | let self.is_open = 0 1472 | call s:variables_window.display() 1473 | if has_key(g:RubyDebugger, "current_variable") 1474 | unlet g:RubyDebugger.current_variable 1475 | endif 1476 | return 0 1477 | endfunction 1478 | 1479 | 1480 | " Renders data of the variable 1481 | function! s:VarParent.render() 1482 | return self._render(0, 0, [], len(self.children) ==# 1) 1483 | endfunction 1484 | 1485 | 1486 | 1487 | " Add childs to the variable. You always should use this method instead of 1488 | " explicit assigning to children property (like 'add(self.children, variables)') 1489 | function! s:VarParent.add_childs(childs) 1490 | " If children are given by array, extend self.children by this array 1491 | if type(a:childs) == type([]) 1492 | for child in a:childs 1493 | let child.parent = self 1494 | let child.level = self.level + 1 1495 | endfor 1496 | call extend(self.children, a:childs) 1497 | else 1498 | " Otherwise, add child to self.children 1499 | let a:childs.parent = self 1500 | let child.level = self.level + 1 1501 | call add(self.children, a:childs) 1502 | end 1503 | endfunction 1504 | 1505 | 1506 | " Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} 1507 | " If current variable doesn't match these attributes, try to find in children 1508 | function! s:VarParent.find_variable(attrs) 1509 | if self._match_attributes(a:attrs) 1510 | return self 1511 | else 1512 | for child in self.children 1513 | let result = child.find_variable(a:attrs) 1514 | if result != {} 1515 | return result 1516 | endif 1517 | endfor 1518 | endif 1519 | return {} 1520 | endfunction 1521 | 1522 | 1523 | " Find and return array of variables that match given Dict of attrs. 1524 | " Try to match current variable and its children 1525 | function! s:VarParent.find_variables(attrs) 1526 | let variables = [] 1527 | if self._match_attributes(a:attrs) 1528 | call add(variables, self) 1529 | endif 1530 | for child in self.children 1531 | call extend(variables, child.find_variables(a:attrs)) 1532 | endfor 1533 | return variables 1534 | endfunction 1535 | 1536 | 1537 | " ** Private methods 1538 | 1539 | 1540 | " Update children of the variable 1541 | function! s:VarParent._init_children() 1542 | " Remove all the current child nodes 1543 | let self.children = [] 1544 | 1545 | " Get children 1546 | if has_key(self.attributes, 'objectId') 1547 | let g:RubyDebugger.current_variable = self 1548 | call g:RubyDebugger.queue.add('var instance ' . self.attributes.objectId) 1549 | endif 1550 | 1551 | endfunction 1552 | 1553 | 1554 | " *** VarParent class (end) 1555 | 1556 | 1557 | 1558 | " *** Logger class (start) 1559 | 1560 | let s:Logger = {} 1561 | 1562 | function! s:Logger.new(file) 1563 | let new_variable = copy(self) 1564 | let new_variable.file = a:file 1565 | call writefile([], new_variable.file) 1566 | return new_variable 1567 | endfunction 1568 | 1569 | 1570 | " Log datetime and then message. It logs only if debug mode is enabled 1571 | " TODO It outputs a bunch of spaces at the front of the entry - fix that. 1572 | function! s:Logger.put(string) dict 1573 | if g:ruby_debugger_debug_mode 1574 | let string = 'Vim plugin, ' . strftime("%H:%M:%S") . ': ' . a:string 1575 | exec 'redir >> ' . g:RubyDebugger.logger.file 1576 | silent call s:Logger.silent_echo(s:strip(string)) 1577 | exec 'redir END' 1578 | endif 1579 | endfunction 1580 | 1581 | function! s:Logger.silent_echo(string) 1582 | echo a:string 1583 | endfunction 1584 | 1585 | " *** Logger class (end) 1586 | " 1587 | " 1588 | 1589 | " *** Breakpoint class (start) 1590 | 1591 | let s:Breakpoint = { 'id': 0 } 1592 | 1593 | " ** Public methods 1594 | 1595 | " Constructor of new brekpoint. Create new breakpoint and set sign. 1596 | function! s:Breakpoint.new(file, line) 1597 | let var = copy(self) 1598 | let var.file = a:file 1599 | let var.line = a:line 1600 | let s:Breakpoint.id += 1 1601 | let var.id = s:Breakpoint.id 1602 | 1603 | call var._set_sign() 1604 | call s:log("Set breakpoint to: " . var.file . ":" . var.line) 1605 | return var 1606 | endfunction 1607 | 1608 | 1609 | " Destroyer of the breakpoint. It just sends commands to debugger and destroys 1610 | " sign, but you should manually remove it from breakpoints array 1611 | function! s:Breakpoint.delete() dict 1612 | call self._unset_sign() 1613 | call self._send_delete_to_debugger() 1614 | endfunction 1615 | 1616 | 1617 | " Add condition to breakpoint. If server is not running, just store it, it 1618 | " will be evaluated after starting the server 1619 | function! s:Breakpoint.add_condition(condition) dict 1620 | let self.condition = a:condition 1621 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() && has_key(self, 'debugger_id') 1622 | call g:RubyDebugger.queue.add(self.condition_command()) 1623 | endif 1624 | endfunction 1625 | 1626 | 1627 | 1628 | " Send adding breakpoint message to debugger, if it is run 1629 | function! s:Breakpoint.send_to_debugger() dict 1630 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() 1631 | call s:log("Server is running, so add command to Queue") 1632 | call g:RubyDebugger.queue.add(self.command()) 1633 | endif 1634 | endfunction 1635 | 1636 | 1637 | " Command for setting breakpoint (e.g.: 'break /path/to/file:23') 1638 | function! s:Breakpoint.command() dict 1639 | return 'break ' . self.file . ':' . self.line 1640 | endfunction 1641 | 1642 | 1643 | " Command for adding condition to breakpoin (e.g.: 'condition 1 x>5') 1644 | function! s:Breakpoint.condition_command() dict 1645 | return 'condition ' . self.debugger_id . ' ' . self.condition 1646 | endfunction 1647 | 1648 | 1649 | " Find and return breakpoint under cursor 1650 | function! s:Breakpoint.get_selected() dict 1651 | let line = getline(".") 1652 | let match = matchlist(line, '^\(\d\+\)') 1653 | let id = get(match, 1) 1654 | let breakpoints = filter(copy(g:RubyDebugger.breakpoints), "v:val.id == " . id) 1655 | if !empty(breakpoints) 1656 | return breakpoints[0] 1657 | else 1658 | return {} 1659 | endif 1660 | endfunction 1661 | 1662 | 1663 | " Output format for Breakpoints Window 1664 | function! s:Breakpoint.render() dict 1665 | let output = self.id . " " . (exists("self.debugger_id") ? self.debugger_id : '') . " " . self.file . ":" . self.line 1666 | if exists("self.condition") 1667 | let output .= " " . self.condition 1668 | endif 1669 | return output . "\n" 1670 | endfunction 1671 | 1672 | 1673 | " Open breakpoint in existed/new window 1674 | function! s:Breakpoint.open() dict 1675 | call s:jump_to_file(self.file, self.line) 1676 | endfunction 1677 | 1678 | 1679 | " ** Private methods 1680 | 1681 | 1682 | function! s:Breakpoint._set_sign() dict 1683 | if has("signs") 1684 | exe ":sign place " . self.id . " line=" . self.line . " name=breakpoint file=" . self.file 1685 | endif 1686 | endfunction 1687 | 1688 | 1689 | function! s:Breakpoint._unset_sign() dict 1690 | if has("signs") 1691 | exe ":sign unplace " . self.id 1692 | endif 1693 | endfunction 1694 | 1695 | 1696 | " Send deleting breakpoint message to debugger, if it is run 1697 | " (e.g.: 'delete 5') 1698 | function! s:Breakpoint._send_delete_to_debugger() dict 1699 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() && has_key(self, 'debugger_id') 1700 | let message = 'delete ' . self.debugger_id 1701 | call g:RubyDebugger.queue.add(message) 1702 | endif 1703 | endfunction 1704 | 1705 | 1706 | " *** Breakpoint class (end) 1707 | 1708 | " *** Exception class (start) 1709 | " These are ruby exceptions we catch with 'catch Exception' command 1710 | " (:RdbCatch) 1711 | 1712 | let s:Exception = { } 1713 | 1714 | " ** Public methods 1715 | 1716 | " Constructor of new exception. 1717 | function! s:Exception.new(name) 1718 | let var = copy(self) 1719 | let var.name = a:name 1720 | call s:log("Trying to set exception: " . var.name) 1721 | call g:RubyDebugger.queue.add(var.command()) 1722 | return var 1723 | endfunction 1724 | 1725 | 1726 | " Command for setting exception (e.g.: 'catch NameError') 1727 | function! s:Exception.command() dict 1728 | return 'catch ' . self.name 1729 | endfunction 1730 | 1731 | 1732 | " Output format for Breakpoints Window 1733 | function! s:Exception.render() dict 1734 | return self.name 1735 | endfunction 1736 | 1737 | " *** Exception class (end) 1738 | 1739 | 1740 | 1741 | 1742 | " *** Frame class (start) 1743 | 1744 | let s:Frame = { } 1745 | 1746 | " ** Public methods 1747 | 1748 | " Constructor of new frame. 1749 | " Create new frame and set sign to it. 1750 | function! s:Frame.new(attrs) 1751 | let var = copy(self) 1752 | let var.no = a:attrs.no 1753 | let var.file = a:attrs.file 1754 | let var.line = a:attrs.line 1755 | if has_key(a:attrs, 'current') 1756 | let var.current = (a:attrs.current == 'true') 1757 | else 1758 | let var.current = 0 1759 | endif 1760 | "let s:sign_id += 1 1761 | "let var.sign_id = s:sign_id 1762 | "call var._set_sign() 1763 | return var 1764 | endfunction 1765 | 1766 | 1767 | " Find and return frame under cursor 1768 | function! s:Frame.get_selected() dict 1769 | let line = getline(".") 1770 | let match = matchlist(line, '^\(\d\+\)') 1771 | let no = get(match, 1) 1772 | let frames = filter(copy(g:RubyDebugger.frames), "v:val.no == " . no) 1773 | if !empty(frames) 1774 | return frames[0] 1775 | else 1776 | return {} 1777 | endif 1778 | endfunction 1779 | 1780 | 1781 | " Output format for Frame Window 1782 | function! s:Frame.render() dict 1783 | return self.no . (self.current ? ' Current' : ''). " " . self.file . ":" . self.line . "\n" 1784 | endfunction 1785 | 1786 | 1787 | " Open frame in existed/new window 1788 | function! s:Frame.open() dict 1789 | call s:jump_to_file(self.file, self.line) 1790 | endfunction 1791 | 1792 | 1793 | " ** Private methods 1794 | 1795 | function! s:Frame._set_sign() dict 1796 | if has("signs") 1797 | exe ":sign place " . self.sign_id . " line=" . self.line . " name=frame file=" . self.file 1798 | endif 1799 | endfunction 1800 | 1801 | 1802 | function! s:Frame._unset_sign() dict 1803 | if has("signs") 1804 | exe ":sign unplace " . self.sign_id 1805 | endif 1806 | endfunction 1807 | 1808 | 1809 | " *** Frame class (end) 1810 | 1811 | 1812 | 1813 | " *** Server class (start) 1814 | 1815 | let s:Server = {} 1816 | 1817 | " ** Public methods 1818 | 1819 | " Constructor of new server. Just inits it, not runs 1820 | function! s:Server.new() dict 1821 | let var = copy(self) 1822 | call s:log("Initializing Server object") 1823 | return var 1824 | endfunction 1825 | 1826 | 1827 | " Start the server. It will kill any listeners on given ports before. 1828 | function! s:Server.start(script, params) dict 1829 | call self.stop() 1830 | call s:log("Starting Server, command: " . a:script) 1831 | " Remove leading and trailing quotes 1832 | let script_name = substitute(a:script, "\\(^['\"]\\|['\"]$\\)", '', 'g') 1833 | let s:socket_file = tempname() 1834 | let cmd = g:ruby_debugger_executable . ' --file ' . s:tmp_file . ' --output ' . s:server_output_file . ' --socket ' . s:socket_file . ' --logger_file ' . s:logger_file . ' --debug_mode ' . g:ruby_debugger_debug_mode . ' --vim_executable ' . g:ruby_debugger_progname . ' --vim_servername ' . v:servername . ' --separator ' . s:separator . ' -- ' . script_name 1835 | call s:log("Executing command: ". cmd) 1836 | let s:rdebug_pid = split(system(cmd), "\n")[-1] 1837 | call s:log("PID: " . s:rdebug_pid) 1838 | call s:log("Waiting for starting debugger...") 1839 | endfunction 1840 | 1841 | 1842 | " Kill servers and empty PIDs 1843 | function! s:Server.stop() dict 1844 | ruby << RUBY 1845 | if @vim_ruby_debugger_socket 1846 | @vim_ruby_debugger_socket.close 1847 | @vim_ruby_debugger_socket = nil 1848 | end 1849 | RUBY 1850 | call s:log("Stopping, pid is: " . s:rdebug_pid) 1851 | if s:rdebug_pid =~ '^\d\+$' 1852 | call self._kill_process(s:rdebug_pid) 1853 | endif 1854 | let s:rdebug_pid = "" 1855 | endfunction 1856 | 1857 | 1858 | " Return 1 if processes with set PID exist. 1859 | function! s:Server.is_running() dict 1860 | return !empty(s:rdebug_pid) 1861 | endfunction 1862 | 1863 | 1864 | " Kill process with given PID 1865 | function! s:Server._kill_process(pid) dict 1866 | let message = "Killing server with pid " . a:pid 1867 | call s:log(message) 1868 | echo message 1869 | let cmd = "ruby -e 'Process.kill(9," . a:pid . ")'" 1870 | call s:log("Executing command: " . cmd) 1871 | call system(cmd) 1872 | call s:log("Killed server with pid: " . a:pid) 1873 | endfunction 1874 | 1875 | 1876 | " *** Server class (end) 1877 | 1878 | 1879 | 1880 | " *** Creating instances (start) 1881 | 1882 | if !exists("g:ruby_debugger_debug_mode") 1883 | let g:ruby_debugger_debug_mode = 0 1884 | endif 1885 | if !exists("g:ruby_debugger_executable") 1886 | let g:ruby_debugger_executable = "rdebug-vim" 1887 | endif 1888 | if !exists("g:ruby_debugger_spec_path") 1889 | let g:ruby_debugger_spec_path = 'rspec' 1890 | endif 1891 | if !exists("g:ruby_debugger_cucumber_path") 1892 | let g:ruby_debugger_cucumber_path = 'cucumber' 1893 | endif 1894 | if !exists("g:ruby_debugger_progname") 1895 | let g:ruby_debugger_progname = v:progname 1896 | endif 1897 | if !exists("g:ruby_debugger_default_script") 1898 | let g:ruby_debugger_default_script = 'script/rails server' 1899 | endif 1900 | if !exists("g:ruby_debugger_no_maps") 1901 | let g:ruby_debugger_no_maps = 0 1902 | endif 1903 | 1904 | " Creating windows 1905 | let s:variables_window = s:WindowVariables.new("variables", "Variables_Window") 1906 | let s:breakpoints_window = s:WindowBreakpoints.new("breakpoints", "Breakpoints_Window") 1907 | let s:frames_window = s:WindowFrames.new("frames", "Backtrace_Window") 1908 | 1909 | " Init logger. The plugin logs all its actions. If you have some troubles, 1910 | " this file can help 1911 | let RubyDebugger.logger = s:Logger.new(s:logger_file) 1912 | let s:variables_window.logger = RubyDebugger.logger 1913 | let s:breakpoints_window.logger = RubyDebugger.logger 1914 | let s:frames_window.logger = RubyDebugger.logger 1915 | 1916 | autocmd VimLeavePre * :call RubyDebugger.stop() 1917 | 1918 | " *** Creating instances (end) 1919 | 1920 | -------------------------------------------------------------------------------- /doc/ruby_debugger.txt: -------------------------------------------------------------------------------- 1 | *ruby_debugger.txt* Plugin for debugging Ruby applications 2 | 3 | Author: Anton Astashov 4 | |ruby-debugger-plugin-author| 5 | 6 | |ruby-debugger-introduction| Introduction and Feature Summary 7 | |ruby-debugger-installation| Installation 8 | |ruby-debugger-quickstart| QuickStart 9 | |ruby-debugger-details| Some additional details about the plugin 10 | |ruby-debugger-tests| Debugging of tests 11 | |ruby-debugger-issues| Troubleshooting 12 | |ruby-debugger-bugs| Bugreporting 13 | |ruby-debugger-about| About 14 | 15 | This plugin is only available if 'compatible' is not set. 16 | The plugin requires Vim to be compiled with +signs, +clientserver and +ruby 17 | and Vim version >= 7. To check it, run > 18 | :echo has("signs") && has("clientserver") && has("ruby") && v:version > 700 19 | Result should be equal to 1 20 | 21 | Also, it requires debugger-xml gem. To install it, run > 22 | gem install debugger-xml 23 | 24 | or just add it to your Gemfile: > 25 | gem 'debugger-xml' 26 | 27 | Please make sure, that vim/gvim/mvim, rdebug-vim and ruby directories are set 28 | in your $PATH variable. 29 | 30 | This version of vim-ruby-debugger doesn't work with Ruby <= 1.8.7. If you need 31 | to debug that version of Ruby, you may try old vim-ruby-debugger, in the "v1.0" 32 | branch (http://github.com/astashov/vim-ruby-debugger/tree/v1.0) 33 | 34 | Windows is not supported. 35 | 36 | {Vi does not have any of this} 37 | 38 | ============================================================================== 39 | INTRODUCTION *ruby-debugger-introduction* *ruby-debugger* 40 | 41 | This plugin implements interactive Ruby debugger in Vim. 42 | 43 | 1. It can debug any Ruby application (Rails, by default), using debugger-xml 44 | gem 45 | 46 | 2. The debugger looks like in any IDE - you can go through the code, watch 47 | variables, breakpoints in a separate window, set and remove breakpoints. 48 | 49 | 3. It supports execution of commands in the context of stopped line. E.g., you 50 | can execute > 51 | :RdbEval User.all 52 | in the Vim command line and it will display the results like usual echo Vim 53 | command 54 | 55 | ============================================================================== 56 | INSTALLATION *ruby-debugger-installation* 57 | 58 | Clone current version of the repo from GitHub: > 59 | git clone git://github.com/astashov/vim-ruby-debugger.git 60 | or if you don't have Git, download the archive from here: > 61 | http://github.com/astashov/vim-ruby-debugger/tarball/master 62 | 63 | Then, copy the vim-ruby-debugger dir to the vim directory (~/.vim)). Or, if you 64 | use pathogen, copy the vim-ruby-debugger dir to ~/.vim/bundle/vim-ruby-debugger. 65 | 66 | Then, run: > 67 | :helptags ~/.vim/doc 68 | for generating the local tags file. 69 | 70 | Now you can use the > 71 | :help ruby-debugger 72 | and watch the help file you just added. 73 | 74 | ============================================================================== 75 | QUICKSTART *ruby-debugger-quickstart* 76 | 77 | 1. Run Vim. If you use mvim/gvim, it will automatically start the server, but if 78 | you use vim, you need to set servername explicitly, e.g., > 79 | vim --servername VIM 80 | 81 | 2. Go to the directory with some your Rails 2 application. > 82 | :cd ~/projects/rails 83 | 84 | 3. Run Server with Debugger: > 85 | :Rdebugger 86 | 87 | It will run debugger-xml's rdebug-vim executable, create a UNIX socket in tmp 88 | directory, and connect to debugger-xml through it. 89 | 90 | 3. Set breakpoint somewhere by b (usually, '\b'). You should see 91 | "xx" symbol at the current line. 92 | 93 | 4. Open page with the breakpoint in the browser. Vim should automatically set 94 | its current line to breakpoint. 95 | 96 | 5. After this, you can use commands: 97 | * b - set breakpoint at current line 98 | * v - open/close window with variables. You can expand/collapse 99 | variables by 'o' in normal mode or left-mouse double-click 100 | * m - open/close window with breakpoints. You can open file with 101 | breakpoint by pressing 'o' or left-mouse double-click on it, 102 | or delete the breakpoint by pressing 'd' on it. 103 | * t - open/close window with backtrace. You can open file/line in 104 | this window by pressing 'o' or left-mouse double-click on it 105 | * n - step over 106 | * s - step into 107 | * f - step out 108 | * c - continue 109 | * d - remove all breakpoints 110 | 111 | 6. To see when the ruby debugger is running, you can add the following function call 112 | to your status line: 113 | 114 | set statusline=%{ruby_debugger#statusline()} 115 | 116 | When the debugger is running you'll see '[ruby debugger running]' 117 | 118 | ============================================================================== 119 | DETAILS *ruby-debugger-details* 120 | 121 | * Of course, you can set your own mappings instead of mine. For this, just 122 | add this to your .vimrc and change keys for mapping: 123 | > 124 | map b :call g:RubyDebugger.toggle_breakpoint() 125 | map v :call g:RubyDebugger.open_variables() 126 | map m :call g:RubyDebugger.open_breakpoints() 127 | map t :call g:RubyDebugger.open_frames() 128 | map s :call g:RubyDebugger.step() 129 | map f :call g:RubyDebugger.finish() 130 | map n :call g:RubyDebugger.next() 131 | map c :call g:RubyDebugger.continue() 132 | map e :call g:RubyDebugger.exit() 133 | map d :call g:RubyDebugger.remove_breakpoints() 134 | > 135 | * Standard output and errors (STDOUT and STDERR) of running script is 136 | redirected to ~/.vim/tmp/ruby_debugger_output file 137 | 138 | * To run some other Ruby application (not Rails), you should specify its 139 | path as argument of Rdebugger command. E.g. > 140 | :Rdebugger bla.rb 141 | 142 | If your script receives arguments, quote them into single quotes: > 143 | :Rdebugger '/path/to/bla.rb 1234 bla_bla' 144 | 145 | * You can specify default script which will be run when you specify 146 | :Rdebugger without arguments. By default it is 'script/rails server', 147 | but you can change it by adding such line to your .vimrc: > 148 | let g:ruby_debugger_default_script = 'rackup -p 4567' 149 | 150 | * To run some rdebug command, use :RdbCommand. E.g.: > 151 | :RdbCommand where 152 | 153 | * To eval some code, use :RdbEval. E.g.: > 154 | :RdbEval u.name 155 | :RdbEval app_config['settings'].map { |s| s.capitalize } 156 | 157 | * To add condition to some breakpoint, you can move cursor on the breakpoint, 158 | and type command: > 159 | :RdbCond condition 160 | E.g.: > 161 | :RdbCond current_user.name == "John" 162 | Then, execution will be stopped on the breakpoint only if condition is true 163 | 164 | * To stop running server, you can use :RdbStop command: > 165 | :RdbStop 166 | 167 | * For communicating with the rdebug the plugin uses temp file: 168 | ~/.vim/tmp/ruby_debugger. debugger-xml writes some response to this file, 169 | "kicks" the plugin remotely calling RubyDebugger.receive_command() by Vim's 170 | client-server functionality and the plugin make actions. For this reason, 171 | you need Vim compiled with +clientserver. 172 | 173 | * The plugin will log all its actions to ~/.vim/tmp/ruby_debugger_log, if you 174 | set g:ruby_debugger_debug_mode in your .vimrc: > 175 | let g:ruby_debugger_debug_mode = 1 176 | 177 | * You also can run Unit tests for the plugin. For this, copy to 178 | ~/.vim/autoload/ ruby_debugger.vim from additionals/autoload (instead of vim/autoload). 179 | It has the same functionality, but with unit tests at end of the file. 180 | To run unit tests, change current directory to some rails project and run > 181 | call g:TU.run() 182 | 183 | * To watch standard output of executing of the Ruby script, you can use > 184 | :RdbLog 185 | 186 | It actually just opens ~/.vim/tmp/ruby_debugger_output, with options: > 187 | setlocal autoread 188 | setlocal wrap 189 | setlocal nonumber 190 | 191 | Also, if plugin AnsiEsc is installed 192 | (http://www.vim.org/scripts/script.php?script_id=302, (it colorizes ANSI escape 193 | sequences, they are used heavily by e.g. ActiveRecord logging)), it will be run 194 | automatically after :RdbLog call to colorize ruby_debugger_output. 195 | 196 | 197 | ============================================================================== 198 | DEBUGGING OF TESTS *ruby-debugger-tests* 199 | 200 | The plugin supports debugging of Test::Unit tests, RSpec specs and Cucumber 201 | features by :RdbTest command. Just open file with the test, set some 202 | breakpoints and type: > 203 | :RdbTest 204 | 205 | It equals to running > 206 | :Rdebugger /path/to/some_test.rb " for Test::Unit tests 207 | :Rdebugger 'rspec /path/to/some_spec.rb' " for RSpec 208 | :Rdebugger 'cucumber /path/to/some.feature' " for Cucumber feautres 209 | 210 | For debugging Cucumber features, you should set breakpoints in step 211 | definitions file (e.g., user_steps.rb), but start debugger by :RdbTest command 212 | in blabla.feature file. You can't set breakpoints in .feature file (I mean you 213 | can, but they will be ignored), because... well, it is a just plain text! :) 214 | 215 | If you store rspec or cucumber executables in some different place, not in 216 | $PATH, you should set path to them explicitly. 217 | 218 | For this, set some variables in your .vimrc. E.g.: > 219 | let g:ruby_debugger_spec_path = '/path/to/rspec' " set Rspec path 220 | let g:ruby_debugger_cucumber_path = '/path/to/cucumber' " set Cucumber path 221 | 222 | A single rspec test can be invoked by RdbTestSingle which will invoke rspec 223 | and pass the current line to rspec via "-l" argument to rspec. For Cucumber 224 | and Test::Unit all tests/features are run. 225 | 226 | ============================================================================== 227 | TROUBLESHOOTING *ruby-debugger-issues* 228 | 229 | 1. Sometimes (e.g., if you use Mac OS and mvim), you can notice strange and 230 | not correct behavior of the plugin (only a couple commands work, you can't see 231 | variables list, next/step commands don't work). Make sure variable 232 | 'g:ruby_debugger_progname' contains proper name of Vim's executable (mvim 233 | if you run mvim, gvim for gvim, vim for vim): > 234 | :echo g:ruby_debugger_progname 235 | 236 | If it contains some incorrect value, set it in your .vimrc. E.g. for mvim: > 237 | let g:ruby_debugger_progname = 'mvim' 238 | 239 | If Vim's executable directory is not in your PATH environment variable, set 240 | full path to executable: > 241 | let g:ruby_debugger_progname = '/opt/local/bin/mvim' 242 | 243 | 2. If rdebug-vim (executable of debugger-xml) is not in your $PATH, you can 244 | specify where is it explicitly: > 245 | let g:ruby_debugger_executable = "bundle exec rdebug-vim" 246 | 247 | 4. If you still can't fix the issue, you can enable debug mode by: > 248 | let g:ruby_debugger_debug_mode = 1 249 | in your .vimrc, and then open the new issue in Github Issue Tracker 250 | (or write email to me) and attach ~/.vim/tmp/ruby_debugger_log file. 251 | 252 | 5. Sometimes the default key bindings can conflict with the key bindings of 253 | other plugins. In this case you may want to disable the default bindings of 254 | vim-ruby-debugger and assign your own bindings. For that, use this: > 255 | let g:ruby_debugger_no_maps = 1 256 | in your .vimrc, and then you can specify your own bindings 257 | 258 | ============================================================================== 259 | BUGS *ruby-debugger-bugs* 260 | 261 | If you meet any bug (even small), please, report about it. You can write email 262 | to me (|ruby-debugger-plugin-author|), or even better - write about your issue 263 | here: 264 | > 265 | http://github.com/astashov/vim-ruby-debugger/issues 266 | > 267 | Also, any feedback is highly desired. Please, send all comments, complaints 268 | and compliments to the author. 269 | Thanks! 270 | 271 | ============================================================================== 272 | ABOUT *ruby-debugger-about* *ruby-debugger-plugin-author* 273 | 274 | This plugin was written by Anton Astashov. 275 | Email: anton (dot) astashov (at) gmail.com 276 | Website: astashov.net 277 | 278 | The latest version of plugin can be found at: 279 | http://github.com/astashov/vim-ruby-debugger 280 | 281 | This plugin is distributable under the same terms as Vim itself. See 282 | |license|. No warranties, expressed or implied. 283 | 284 | ============================================================================== 285 | vim:tw=78:ts=8:ft=help:norl: 286 | -------------------------------------------------------------------------------- /plugin/ruby_debugger.vim: -------------------------------------------------------------------------------- 1 | if exists("g:ruby_debugger_loaded") 2 | finish 3 | endif 4 | 5 | if !exists("g:ruby_debugger_no_maps") || !g:ruby_debugger_no_maps 6 | noremap b :call ruby_debugger#load_debugger() call g:RubyDebugger.toggle_breakpoint() 7 | noremap v :call ruby_debugger#load_debugger() call g:RubyDebugger.open_variables() 8 | noremap m :call ruby_debugger#load_debugger() call g:RubyDebugger.open_breakpoints() 9 | noremap t :call ruby_debugger#load_debugger() call g:RubyDebugger.open_frames() 10 | noremap s :call ruby_debugger#load_debugger() call g:RubyDebugger.step() 11 | noremap f :call ruby_debugger#load_debugger() call g:RubyDebugger.finish() 12 | noremap n :call ruby_debugger#load_debugger() call g:RubyDebugger.next() 13 | noremap c :call ruby_debugger#load_debugger() call g:RubyDebugger.continue() 14 | noremap e :call ruby_debugger#load_debugger() call g:RubyDebugger.exit() 15 | noremap d :call ruby_debugger#load_debugger() call g:RubyDebugger.remove_breakpoints() 16 | endif 17 | 18 | command! -nargs=* -complete=file Rdebugger call ruby_debugger#load_debugger() | call g:RubyDebugger.start() 19 | command! -nargs=0 RdbStop call g:RubyDebugger.stop() 20 | command! -nargs=1 RdbCommand call g:RubyDebugger.send_command_wrapper() 21 | command! -nargs=0 RdbTest call g:RubyDebugger.run_test() 22 | command! -nargs=0 RdbTestSingle call g:RubyDebugger.run_test(" -l " . line(".")) 23 | command! -nargs=1 RdbEval call g:RubyDebugger.eval() 24 | command! -nargs=1 RdbCond call g:RubyDebugger.conditional_breakpoint() 25 | command! -nargs=1 RdbCatch call g:RubyDebugger.catch_exception() 26 | command! -nargs=0 RdbLog call ruby_debugger#load_debugger() | call g:RubyDebugger.show_log() 27 | 28 | let g:ruby_debugger_loaded = 1 29 | 30 | 31 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astashov/vim-ruby-debugger/1ccb0831cfe7bba14400fa81a051938e975c3b38/screenshot.png -------------------------------------------------------------------------------- /src/collector.rb: -------------------------------------------------------------------------------- 1 | class Collector 2 | 3 | def initialize(input, output) 4 | @path = File.dirname(__FILE__) 5 | @input_files = input.map{|i| @path + '/' + i} 6 | @output_file = File.expand_path(@path + '/' + output) 7 | @file = '' 8 | end 9 | 10 | def accumulate! 11 | read_file 12 | save_file 13 | end 14 | 15 | 16 | protected 17 | 18 | def read_file 19 | @file = "" 20 | plan = @input_files.inject([]) {|sum, i| sum += File.read(i).split("\n") } 21 | plan.each do |line| 22 | @file += File.read(@path + '/' + line) 23 | @file += "\n" 24 | end 25 | @file 26 | end 27 | 28 | 29 | def save_file 30 | File.open(@output_file, 'w') do |file| 31 | file.write(@file) 32 | end 33 | end 34 | 35 | end 36 | 37 | 38 | plugin = Collector.new(['ruby_debugger_plugin_plan.txt'], '../plugin/ruby_debugger.vim') 39 | plugin.accumulate! 40 | 41 | auto_load = Collector.new(['ruby_debugger_autoload_plan.txt'], '../autoload/ruby_debugger.vim') 42 | auto_load.accumulate! 43 | 44 | with_tests = Collector.new(['ruby_debugger_autoload_plan.txt', 'ruby_test_plan.txt'], 'additionals/autoload/ruby_debugger.vim') 45 | with_tests.accumulate! 46 | -------------------------------------------------------------------------------- /src/notifier: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | while inotifywait -r -e create,modify,delete ruby_debugger/*.vim tests/*.vim; do 3 | sleep 0.3 4 | ruby collector.rb 5 | done 6 | -------------------------------------------------------------------------------- /src/ruby_debugger/after.vim: -------------------------------------------------------------------------------- 1 | " *** Creating instances (start) 2 | 3 | if !exists("g:ruby_debugger_debug_mode") 4 | let g:ruby_debugger_debug_mode = 0 5 | endif 6 | if !exists("g:ruby_debugger_executable") 7 | let g:ruby_debugger_executable = "rdebug-vim" 8 | endif 9 | if !exists("g:ruby_debugger_spec_path") 10 | let g:ruby_debugger_spec_path = 'rspec' 11 | endif 12 | if !exists("g:ruby_debugger_cucumber_path") 13 | let g:ruby_debugger_cucumber_path = 'cucumber' 14 | endif 15 | if !exists("g:ruby_debugger_progname") 16 | let g:ruby_debugger_progname = v:progname 17 | endif 18 | if !exists("g:ruby_debugger_default_script") 19 | let g:ruby_debugger_default_script = 'script/rails server' 20 | endif 21 | if !exists("g:ruby_debugger_no_maps") 22 | let g:ruby_debugger_no_maps = 0 23 | endif 24 | 25 | " Creating windows 26 | let s:variables_window = s:WindowVariables.new("variables", "Variables_Window") 27 | let s:breakpoints_window = s:WindowBreakpoints.new("breakpoints", "Breakpoints_Window") 28 | let s:frames_window = s:WindowFrames.new("frames", "Backtrace_Window") 29 | 30 | " Init logger. The plugin logs all its actions. If you have some troubles, 31 | " this file can help 32 | let RubyDebugger.logger = s:Logger.new(s:logger_file) 33 | let s:variables_window.logger = RubyDebugger.logger 34 | let s:breakpoints_window.logger = RubyDebugger.logger 35 | let s:frames_window.logger = RubyDebugger.logger 36 | 37 | autocmd VimLeavePre * :call RubyDebugger.stop() 38 | 39 | " *** Creating instances (end) 40 | -------------------------------------------------------------------------------- /src/ruby_debugger/before.vim: -------------------------------------------------------------------------------- 1 | " Init section - set default values, highlight colors 2 | 3 | " like ~/.vim 4 | let s:runtime_dir = expand(':h:h') 5 | " File for communicating between intermediate Ruby script ruby_debugger.rb and 6 | " this plugin 7 | let s:tmp_file = s:runtime_dir . '/tmp/ruby_debugger' 8 | let s:logger_file = s:runtime_dir . '/tmp/ruby_debugger_log' 9 | let s:server_output_file = s:runtime_dir . '/tmp/ruby_debugger_output' 10 | " Default id for sign of current line 11 | let s:current_line_sign_id = 120 12 | let s:separator = "++vim-ruby-debugger-separator++" 13 | let s:sign_id = 0 14 | let s:rdebug_pid = "" 15 | 16 | " Create tmp directory if it doesn't exist 17 | if !isdirectory(s:runtime_dir . '/tmp') 18 | call mkdir(s:runtime_dir . '/tmp') 19 | endif 20 | 21 | " Init breakpoint signs 22 | hi def link Breakpoint Error 23 | sign define breakpoint linehl=Breakpoint text=xx 24 | 25 | " Init current line signs 26 | hi def link CurrentLine DiffAdd 27 | sign define current_line linehl=CurrentLine text=>> 28 | 29 | " Loads this file. Required for autoloading the code for this plugin 30 | fun! ruby_debugger#load_debugger() 31 | if !s:check_prerequisites() 32 | finish 33 | endif 34 | endf 35 | 36 | fun! ruby_debugger#statusline() 37 | let is_running = g:RubyDebugger.is_running() 38 | if is_running == 0 39 | return '' 40 | endif 41 | return '[ruby debugger running]' 42 | endfunction 43 | 44 | " Check all requirements for the current plugin 45 | fun! s:check_prerequisites() 46 | let problems = [] 47 | if v:version < 700 48 | call add(problems, "RubyDebugger: This plugin requires Vim >= 7.") 49 | endif 50 | if !has("clientserver") 51 | call add(problems, "RubyDebugger: This plugin requires +clientserver option") 52 | endif 53 | if !has("ruby") 54 | call add(problems, "RubyDebugger: This plugin requires +ruby option.") 55 | end 56 | if empty(problems) 57 | return 1 58 | else 59 | for p in problems 60 | echoerr p 61 | endfor 62 | return 0 63 | endif 64 | endf 65 | 66 | 67 | " End of init section 68 | 69 | -------------------------------------------------------------------------------- /src/ruby_debugger/breakpoint.vim: -------------------------------------------------------------------------------- 1 | " *** Breakpoint class (start) 2 | 3 | let s:Breakpoint = { 'id': 0 } 4 | 5 | " ** Public methods 6 | 7 | " Constructor of new brekpoint. Create new breakpoint and set sign. 8 | function! s:Breakpoint.new(file, line) 9 | let var = copy(self) 10 | let var.file = a:file 11 | let var.line = a:line 12 | let s:Breakpoint.id += 1 13 | let var.id = s:Breakpoint.id 14 | 15 | call var._set_sign() 16 | call s:log("Set breakpoint to: " . var.file . ":" . var.line) 17 | return var 18 | endfunction 19 | 20 | 21 | " Destroyer of the breakpoint. It just sends commands to debugger and destroys 22 | " sign, but you should manually remove it from breakpoints array 23 | function! s:Breakpoint.delete() dict 24 | call self._unset_sign() 25 | call self._send_delete_to_debugger() 26 | endfunction 27 | 28 | 29 | " Add condition to breakpoint. If server is not running, just store it, it 30 | " will be evaluated after starting the server 31 | function! s:Breakpoint.add_condition(condition) dict 32 | let self.condition = a:condition 33 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() && has_key(self, 'debugger_id') 34 | call g:RubyDebugger.queue.add(self.condition_command()) 35 | endif 36 | endfunction 37 | 38 | 39 | 40 | " Send adding breakpoint message to debugger, if it is run 41 | function! s:Breakpoint.send_to_debugger() dict 42 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() 43 | call s:log("Server is running, so add command to Queue") 44 | call g:RubyDebugger.queue.add(self.command()) 45 | endif 46 | endfunction 47 | 48 | 49 | " Command for setting breakpoint (e.g.: 'break /path/to/file:23') 50 | function! s:Breakpoint.command() dict 51 | return 'break ' . self.file . ':' . self.line 52 | endfunction 53 | 54 | 55 | " Command for adding condition to breakpoin (e.g.: 'condition 1 x>5') 56 | function! s:Breakpoint.condition_command() dict 57 | return 'condition ' . self.debugger_id . ' ' . self.condition 58 | endfunction 59 | 60 | 61 | " Find and return breakpoint under cursor 62 | function! s:Breakpoint.get_selected() dict 63 | let line = getline(".") 64 | let match = matchlist(line, '^\(\d\+\)') 65 | let id = get(match, 1) 66 | let breakpoints = filter(copy(g:RubyDebugger.breakpoints), "v:val.id == " . id) 67 | if !empty(breakpoints) 68 | return breakpoints[0] 69 | else 70 | return {} 71 | endif 72 | endfunction 73 | 74 | 75 | " Output format for Breakpoints Window 76 | function! s:Breakpoint.render() dict 77 | let output = self.id . " " . (exists("self.debugger_id") ? self.debugger_id : '') . " " . self.file . ":" . self.line 78 | if exists("self.condition") 79 | let output .= " " . self.condition 80 | endif 81 | return output . "\n" 82 | endfunction 83 | 84 | 85 | " Open breakpoint in existed/new window 86 | function! s:Breakpoint.open() dict 87 | call s:jump_to_file(self.file, self.line) 88 | endfunction 89 | 90 | 91 | " ** Private methods 92 | 93 | 94 | function! s:Breakpoint._set_sign() dict 95 | if has("signs") 96 | exe ":sign place " . self.id . " line=" . self.line . " name=breakpoint file=" . self.file 97 | endif 98 | endfunction 99 | 100 | 101 | function! s:Breakpoint._unset_sign() dict 102 | if has("signs") 103 | exe ":sign unplace " . self.id 104 | endif 105 | endfunction 106 | 107 | 108 | " Send deleting breakpoint message to debugger, if it is run 109 | " (e.g.: 'delete 5') 110 | function! s:Breakpoint._send_delete_to_debugger() dict 111 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() && has_key(self, 'debugger_id') 112 | let message = 'delete ' . self.debugger_id 113 | call g:RubyDebugger.queue.add(message) 114 | endif 115 | endfunction 116 | 117 | 118 | " *** Breakpoint class (end) 119 | -------------------------------------------------------------------------------- /src/ruby_debugger/commands.vim: -------------------------------------------------------------------------------- 1 | " *** RubyDebugger Commands (what debugger returns) 2 | 3 | 4 | " 5 | " 6 | " Jump to file/line where execution was suspended, set current line sign and get local variables 7 | function! RubyDebugger.commands.jump_to_breakpoint(cmd) dict 8 | let attrs = s:get_tag_attributes(a:cmd) 9 | call s:jump_to_file(attrs.file, attrs.line) 10 | call s:log("Jumped to breakpoint " . attrs.file . ":" . attrs.line) 11 | 12 | if has("signs") 13 | exe ":sign place " . s:current_line_sign_id . " line=" . attrs.line . " name=current_line file=" . attrs.file 14 | endif 15 | endfunction 16 | 17 | 18 | " 19 | " Show message error and jump to given file/line 20 | function! RubyDebugger.commands.handle_exception(cmd) dict 21 | let message_match = matchlist(a:cmd, 'message="\(.\{-}\)"') 22 | call g:RubyDebugger.commands.jump_to_breakpoint(a:cmd) 23 | echo "Exception message: " . s:unescape_html(message_match[1]) 24 | endfunction 25 | 26 | 27 | " 28 | " Confirm setting of exception catcher 29 | function! RubyDebugger.commands.set_exception(cmd) dict 30 | let attrs = s:get_tag_attributes(a:cmd) 31 | call s:log("Exception successfully set: " . attrs.exception) 32 | endfunction 33 | 34 | 35 | " 36 | " Add debugger info to breakpoints (pid of debugger, debugger breakpoint's id) 37 | " Assign rest breakpoints to debugger recursively, if there are breakpoints 38 | " from old server runnings or not assigned breakpoints (e.g., if you at first 39 | " set some breakpoints, and then run the debugger by :Rdebugger) 40 | function! RubyDebugger.commands.set_breakpoint(cmd) 41 | call s:log("Received the breakpoint message, will add PID and number of breakpoint to the Breakpoint object") 42 | let attrs = s:get_tag_attributes(a:cmd) 43 | let file_match = matchlist(attrs.location, '\(.*\):\(.*\)') 44 | 45 | " Find added breakpoint in array and assign debugger's info to it 46 | for breakpoint in g:RubyDebugger.breakpoints 47 | if expand(breakpoint.file) == expand(file_match[1]) && expand(breakpoint.line) == expand(file_match[2]) 48 | call s:log("Found the Breakpoint object for " . breakpoint.file . ":" . breakpoint.line) 49 | let breakpoint.debugger_id = attrs.no 50 | let breakpoint.rdebug_pid = s:rdebug_pid 51 | call s:log("Added id: " . breakpoint.debugger_id . ", PID:" . breakpoint.rdebug_pid . " to Breakpoint") 52 | if has_key(breakpoint, 'condition') 53 | call breakpoint.add_condition(breakpoint.condition) 54 | endif 55 | endif 56 | endfor 57 | 58 | call s:log("Breakpoint is set: " . file_match[1] . ":" . file_match[2]) 59 | call g:RubyDebugger.queue.execute() 60 | endfunction 61 | 62 | 63 | " 64 | " 65 | " 66 | " Assign list of got variables to parent variable and (optionally) show them 67 | function! RubyDebugger.commands.set_variables(cmd) 68 | let tags = s:get_tags(a:cmd) 69 | let list_of_variables = [] 70 | 71 | " Create hash from list of tags 72 | for tag in tags 73 | let attrs = s:get_tag_attributes(tag) 74 | let variable = s:Var.new(attrs) 75 | call add(list_of_variables, variable) 76 | endfor 77 | 78 | " If there is no variables, create unnamed root variable. Local variables 79 | " will be chilren of this variable 80 | if g:RubyDebugger.variables == {} 81 | let g:RubyDebugger.variables = s:VarParent.new({'hasChildren': 'true'}) 82 | let g:RubyDebugger.variables.is_open = 1 83 | let g:RubyDebugger.variables.children = [] 84 | endif 85 | 86 | " If g:RubyDebugger.current_variable exists, then it contains parent 87 | " variable of got subvariables. Assign them to it. 88 | if has_key(g:RubyDebugger, 'current_variable') 89 | let variable = g:RubyDebugger.current_variable 90 | if variable != {} 91 | call variable.add_childs(list_of_variables) 92 | call s:log("Opening child variable: " . variable.attributes.objectId) 93 | " Variables Window is always open if we got subvariables 94 | call s:variables_window.open() 95 | else 96 | call s:log("Can't found variable") 97 | endif 98 | unlet g:RubyDebugger.current_variable 99 | else 100 | " Otherwise, assign them to unnamed root variable 101 | if g:RubyDebugger.variables.children == [] 102 | call g:RubyDebugger.variables.add_childs(list_of_variables) 103 | call s:log("Initializing local variables") 104 | if s:variables_window.is_open() 105 | " show variables only if Variables Window is open 106 | call s:variables_window.open() 107 | endif 108 | endif 109 | endif 110 | 111 | endfunction 112 | 113 | 114 | " 115 | " Just show result of evaluation 116 | function! RubyDebugger.commands.eval(cmd) 117 | " rdebug-ide-gem doesn't escape attributes of tag properly, so we should not 118 | " use usual attribute extractor here... 119 | let match = matchlist(a:cmd, "") 120 | echo "Evaluated expression:\n" . s:unescape_html(match[1]) ."\nResulted value is:\n" . s:unescape_html(match[2]) . "\n" 121 | endfunction 122 | 123 | 124 | " 125 | " Just show exception message 126 | function! RubyDebugger.commands.processing_exception(cmd) 127 | let attrs = s:get_tag_attributes(a:cmd) 128 | let message = "RubyDebugger Exception, type: " . attrs.type . ", message: " . attrs.message 129 | echo message 130 | call s:log(message) 131 | endfunction 132 | 133 | 134 | " 135 | " 136 | " 137 | " 138 | " Assign all frames, fill Frames window by them 139 | function! RubyDebugger.commands.trace(cmd) 140 | let tags = s:get_tags(a:cmd) 141 | let list_of_frames = [] 142 | 143 | " Create hash from list of tags 144 | for tag in tags 145 | let attrs = s:get_tag_attributes(tag) 146 | let frame = s:Frame.new(attrs) 147 | call add(list_of_frames, frame) 148 | endfor 149 | 150 | let g:RubyDebugger.frames = list_of_frames 151 | 152 | if s:frames_window.is_open() 153 | " show backtrace only if Backtrace Window is open 154 | call s:frames_window.open() 155 | endif 156 | endfunction 157 | 158 | 159 | " Error 160 | " Just show error 161 | function! RubyDebugger.commands.error(cmd) 162 | let error_match = s:get_inner_tags(a:cmd) 163 | if !empty(error_match) 164 | let error = error_match[1] 165 | echo "RubyDebugger Error: " . error 166 | call s:log("Got error: " . error) 167 | endif 168 | endfunction 169 | 170 | 171 | " Message 172 | " Just show message 173 | function! RubyDebugger.commands.message(cmd) 174 | let message_match = s:get_inner_tags(a:cmd) 175 | if !empty(message_match) 176 | let message = message_match[1] 177 | echo "RubyDebugger Message: " . message 178 | call s:log("Got message: " . message) 179 | endif 180 | endfunction 181 | 182 | 183 | " *** End of debugger Commands 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/ruby_debugger/common.vim: -------------------------------------------------------------------------------- 1 | " *** Common (global) functions 2 | 3 | " Split string of tags to List. E.g., 4 | " 5 | " will be splitted to 6 | " [ '', '' ] 7 | function! s:get_tags(cmd) 8 | let tags = [] 9 | let cmd = a:cmd 10 | " Remove wrap tags 11 | let inner_tags_match = s:get_inner_tags(cmd) 12 | if !empty(inner_tags_match) 13 | " Then find every tag and remove it from source string 14 | let pattern = '<.\{-}\/>' 15 | let inner_tags = inner_tags_match[1] 16 | let tagmatch = matchlist(inner_tags, pattern) 17 | while empty(tagmatch) == 0 18 | call add(tags, tagmatch[0]) 19 | " These symbols are interpretated as special, we need to escape them 20 | let tagmatch[0] = escape(tagmatch[0], '[]~*\') 21 | " Remove it from source string 22 | let inner_tags = substitute(inner_tags, tagmatch[0], '', '') 23 | " Find next tag 24 | let tagmatch = matchlist(inner_tags, pattern) 25 | endwhile 26 | endif 27 | return tags 28 | endfunction 29 | 30 | 31 | " Converts command with relative path to absolute path. If given command 32 | " contains relative path, it will try to use 'which' on it first, and if 33 | " 'which' returns nothing, it will add current dir path to given command 34 | function! s:get_escaped_absolute_path(command) 35 | " Remove leading and trailing quotes 36 | let given_path = a:command 37 | let given_path = substitute(given_path, '"', '\"', "g") 38 | let given_path = substitute(given_path, "^'", '', "g") 39 | let given_path = substitute(given_path, "'$", '', "g") 40 | if given_path[0] == '/' 41 | let absolute_path = given_path 42 | else 43 | let parts = split(given_path) 44 | let relative_command = remove(parts, 0) 45 | let arguments = join(parts) 46 | let absolute_command = "" 47 | " I don't know Windows analogue for 'which', if you know - feel free to add it here 48 | if !(has("win32") || has("win64")) 49 | let absolute_command = s:strip(system('which ' . relative_command)) 50 | endif 51 | if absolute_command[0] != '/' 52 | let absolute_command = getcwd() . '/' . relative_command 53 | endif 54 | let absolute_path = "\"'" . absolute_command . "' " . arguments . '"' 55 | endif 56 | return absolute_path 57 | endfunction 58 | 59 | 60 | " Return a string without leading and trailing spaces and linebreaks. 61 | function! s:strip(input_string) 62 | return substitute(substitute(a:input_string, "\n", '', 'g'), '(\s*\(.\{-}\)\s*', '\1', 'g') 63 | endfunction 64 | 65 | 66 | " Shortcut for g:RubyDebugger.logger.debug 67 | function! s:log(string) 68 | call g:RubyDebugger.logger.put(a:string) 69 | endfunction 70 | 71 | 72 | " Return match of inner tags without wrap tags. E.g.: 73 | " mathes only 74 | function! s:get_inner_tags(cmd) 75 | return matchlist(a:cmd, '^<.\{-}>\(.\{-}\)<\/.\{-}>$') 76 | endfunction 77 | 78 | 79 | " Return Dict of attributes. 80 | " E.g., from it returns 81 | " {'name' : 'a', 'value' : 'b'} 82 | function! s:get_tag_attributes(cmd) 83 | let attributes = {} 84 | let cmd = a:cmd 85 | " Find type of used quotes (" or ') 86 | let quote_match = matchlist(cmd, "\\w\\+=\\(.\\)") 87 | let quote = empty(quote_match) ? "\"" : escape(quote_match[1], "'\"") 88 | let pattern = "\\(\\w\\+\\)=" . quote . "\\(.\\{-}\\)" . quote 89 | " Find every attribute and remove it from source string 90 | let attrmatch = matchlist(cmd, pattern) 91 | while !empty(attrmatch) 92 | " Values of attributes can be escaped by HTML entities, unescape them 93 | let attributes[attrmatch[1]] = s:unescape_html(attrmatch[2]) 94 | " These symbols are interpretated as special, we need to escape them 95 | let attrmatch[0] = escape(attrmatch[0], '[]~*\') 96 | " Remove it from source string 97 | let cmd = substitute(cmd, attrmatch[0], '', '') 98 | " Find next attribute 99 | let attrmatch = matchlist(cmd, pattern) 100 | endwhile 101 | return attributes 102 | endfunction 103 | 104 | 105 | " Unescape HTML entities 106 | function! s:unescape_html(html) 107 | let result = substitute(a:html, "&", "\\&", "g") 108 | let result = substitute(result, """, "\"", "g") 109 | let result = substitute(result, "<", "<", "g") 110 | let result = substitute(result, ">", ">", "g") 111 | return result 112 | endfunction 113 | 114 | 115 | function! s:quotify(exp) 116 | let quoted = a:exp 117 | let quoted = substitute(quoted, "\"", "\\\\\"", 'g') 118 | return quoted 119 | endfunction 120 | 121 | 122 | " Get filename of current buffer 123 | function! s:get_filename() 124 | return expand("%:p") 125 | endfunction 126 | 127 | 128 | " Send message to debugger. This function should never be used explicitly, 129 | " only through g:RubyDebugger.send_command function 130 | function! s:send_message_to_debugger(message) 131 | call s:log("Sending a message to ruby_debugger.rb: '" . a:message . "'") 132 | ruby << RUBY 133 | require 'socket' 134 | @vim_ruby_debugger_socket ||= UNIXSocket.open(VIM.evaluate("s:socket_file")) 135 | message = VIM.evaluate("a:message").gsub("\\\"", '"') 136 | begin 137 | @vim_ruby_debugger_socket.puts(message) 138 | rescue Errno::EPIPE 139 | VIM.message("Debugger is not running") 140 | end 141 | RUBY 142 | endfunction 143 | 144 | 145 | function! s:unplace_sign_of_current_line() 146 | if has("signs") 147 | exe ":sign unplace " . s:current_line_sign_id 148 | endif 149 | endfunction 150 | 151 | 152 | " Remove all variables of current line, remove current line sign. Usually it 153 | " is needed before next/step/cont commands 154 | function! s:clear_current_state() 155 | call s:unplace_sign_of_current_line() 156 | let g:RubyDebugger.variables = {} 157 | let g:RubyDebugger.frames = [] 158 | " Clear variables and frames window (just show our empty variables Dict) 159 | if s:variables_window.is_open() 160 | call s:variables_window.open() 161 | endif 162 | if s:frames_window.is_open() 163 | call s:frames_window.open() 164 | endif 165 | endfunction 166 | 167 | 168 | " Open given file and jump to given line 169 | " (stolen from NERDTree) 170 | function! s:jump_to_file(file, line) 171 | "if the file is already open in this tab then just stick the cursor in it 172 | let window_number = bufwinnr('^' . a:file . '$') 173 | if window_number != -1 174 | exe window_number . "wincmd w" 175 | else 176 | " Check if last accessed window is usable to use it 177 | " Usable window - not quickfix, explorer, modified, etc 178 | if !s:is_window_usable(winnr("#")) 179 | exe s:first_normal_window() . "wincmd w" 180 | else 181 | " If it is usable, jump to it 182 | exe 'wincmd p' 183 | endif 184 | exe "edit " . a:file 185 | endif 186 | exe "normal " . a:line . "G" 187 | endfunction 188 | 189 | 190 | " Return 1 if window is usable (not quickfix, explorer, modified, only one 191 | " window, ...) 192 | function! s:is_window_usable(winnumber) 193 | "If there is only one window (winnr("$") - windows count) 194 | if winnr("$") ==# 1 195 | return 0 196 | endif 197 | 198 | " Current window number 199 | let oldwinnr = winnr() 200 | 201 | " Switch to given window and check it 202 | exe a:winnumber . "wincmd p" 203 | let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') 204 | let modified = &modified 205 | 206 | exe oldwinnr . "wincmd p" 207 | 208 | "if it is a special window, e.g. quickfix or another explorer plugin 209 | if specialWindow 210 | return 0 211 | endif 212 | 213 | if &hidden 214 | return 1 215 | endif 216 | 217 | " If this window is modified, but there is another opened window with 218 | " current file, return 1. Otherwise - 0 219 | return !modified || s:buf_in_windows(winbufnr(a:winnumber)) >= 2 220 | endfunction 221 | 222 | 223 | " Determine the number of windows open to this buffer number. 224 | function! s:buf_in_windows(buffer_number) 225 | let count = 0 226 | let window_number = 1 227 | while 1 228 | let buffer_number = winbufnr(window_number) 229 | if buffer_number < 0 230 | break 231 | endif 232 | if buffer_number ==# a:buffer_number 233 | let count = count + 1 234 | endif 235 | let window_number = window_number + 1 236 | endwhile 237 | 238 | return count 239 | endfunction 240 | 241 | 242 | " Find first 'normal' window (not quickfix, explorer, etc) 243 | function! s:first_normal_window() 244 | let i = 1 245 | while i <= winnr("$") 246 | let bnum = winbufnr(i) 247 | if bnum != -1 && getbufvar(bnum, '&buftype') ==# '' && !getwinvar(i, '&previewwindow') 248 | return i 249 | endif 250 | let i += 1 251 | endwhile 252 | return -1 253 | endfunction 254 | -------------------------------------------------------------------------------- /src/ruby_debugger/exception.vim: -------------------------------------------------------------------------------- 1 | " *** Exception class (start) 2 | " These are ruby exceptions we catch with 'catch Exception' command 3 | " (:RdbCatch) 4 | 5 | let s:Exception = { } 6 | 7 | " ** Public methods 8 | 9 | " Constructor of new exception. 10 | function! s:Exception.new(name) 11 | let var = copy(self) 12 | let var.name = a:name 13 | call s:log("Trying to set exception: " . var.name) 14 | call g:RubyDebugger.queue.add(var.command()) 15 | return var 16 | endfunction 17 | 18 | 19 | " Command for setting exception (e.g.: 'catch NameError') 20 | function! s:Exception.command() dict 21 | return 'catch ' . self.name 22 | endfunction 23 | 24 | 25 | " Output format for Breakpoints Window 26 | function! s:Exception.render() dict 27 | return self.name 28 | endfunction 29 | 30 | " *** Exception class (end) 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/ruby_debugger/frame.vim: -------------------------------------------------------------------------------- 1 | " *** Frame class (start) 2 | 3 | let s:Frame = { } 4 | 5 | " ** Public methods 6 | 7 | " Constructor of new frame. 8 | " Create new frame and set sign to it. 9 | function! s:Frame.new(attrs) 10 | let var = copy(self) 11 | let var.no = a:attrs.no 12 | let var.file = a:attrs.file 13 | let var.line = a:attrs.line 14 | if has_key(a:attrs, 'current') 15 | let var.current = (a:attrs.current == 'true') 16 | else 17 | let var.current = 0 18 | endif 19 | "let s:sign_id += 1 20 | "let var.sign_id = s:sign_id 21 | "call var._set_sign() 22 | return var 23 | endfunction 24 | 25 | 26 | " Find and return frame under cursor 27 | function! s:Frame.get_selected() dict 28 | let line = getline(".") 29 | let match = matchlist(line, '^\(\d\+\)') 30 | let no = get(match, 1) 31 | let frames = filter(copy(g:RubyDebugger.frames), "v:val.no == " . no) 32 | if !empty(frames) 33 | return frames[0] 34 | else 35 | return {} 36 | endif 37 | endfunction 38 | 39 | 40 | " Output format for Frame Window 41 | function! s:Frame.render() dict 42 | return self.no . (self.current ? ' Current' : ''). " " . self.file . ":" . self.line . "\n" 43 | endfunction 44 | 45 | 46 | " Open frame in existed/new window 47 | function! s:Frame.open() dict 48 | call s:jump_to_file(self.file, self.line) 49 | endfunction 50 | 51 | 52 | " ** Private methods 53 | 54 | function! s:Frame._set_sign() dict 55 | if has("signs") 56 | exe ":sign place " . self.sign_id . " line=" . self.line . " name=frame file=" . self.file 57 | endif 58 | endfunction 59 | 60 | 61 | function! s:Frame._unset_sign() dict 62 | if has("signs") 63 | exe ":sign unplace " . self.sign_id 64 | endif 65 | endfunction 66 | 67 | 68 | " *** Frame class (end) 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/ruby_debugger/init.vim: -------------------------------------------------------------------------------- 1 | if exists("g:ruby_debugger_loaded") 2 | finish 3 | endif 4 | 5 | if !exists("g:ruby_debugger_no_maps") || !g:ruby_debugger_no_maps 6 | noremap b :call ruby_debugger#load_debugger() call g:RubyDebugger.toggle_breakpoint() 7 | noremap v :call ruby_debugger#load_debugger() call g:RubyDebugger.open_variables() 8 | noremap m :call ruby_debugger#load_debugger() call g:RubyDebugger.open_breakpoints() 9 | noremap t :call ruby_debugger#load_debugger() call g:RubyDebugger.open_frames() 10 | noremap s :call ruby_debugger#load_debugger() call g:RubyDebugger.step() 11 | noremap f :call ruby_debugger#load_debugger() call g:RubyDebugger.finish() 12 | noremap n :call ruby_debugger#load_debugger() call g:RubyDebugger.next() 13 | noremap c :call ruby_debugger#load_debugger() call g:RubyDebugger.continue() 14 | noremap e :call ruby_debugger#load_debugger() call g:RubyDebugger.exit() 15 | noremap d :call ruby_debugger#load_debugger() call g:RubyDebugger.remove_breakpoints() 16 | endif 17 | 18 | command! -nargs=* -complete=file Rdebugger call ruby_debugger#load_debugger() | call g:RubyDebugger.start() 19 | command! -nargs=0 RdbStop call g:RubyDebugger.stop() 20 | command! -nargs=1 RdbCommand call g:RubyDebugger.send_command_wrapper() 21 | command! -nargs=0 RdbTest call g:RubyDebugger.run_test() 22 | command! -nargs=0 RdbTestSingle call g:RubyDebugger.run_test(" -l " . line(".")) 23 | command! -nargs=1 RdbEval call g:RubyDebugger.eval() 24 | command! -nargs=1 RdbCond call g:RubyDebugger.conditional_breakpoint() 25 | command! -nargs=1 RdbCatch call g:RubyDebugger.catch_exception() 26 | command! -nargs=0 RdbLog call ruby_debugger#load_debugger() | call g:RubyDebugger.show_log() 27 | 28 | let g:ruby_debugger_loaded = 1 29 | 30 | -------------------------------------------------------------------------------- /src/ruby_debugger/logger.vim: -------------------------------------------------------------------------------- 1 | " *** Logger class (start) 2 | 3 | let s:Logger = {} 4 | 5 | function! s:Logger.new(file) 6 | let new_variable = copy(self) 7 | let new_variable.file = a:file 8 | call writefile([], new_variable.file) 9 | return new_variable 10 | endfunction 11 | 12 | 13 | " Log datetime and then message. It logs only if debug mode is enabled 14 | " TODO It outputs a bunch of spaces at the front of the entry - fix that. 15 | function! s:Logger.put(string) dict 16 | if g:ruby_debugger_debug_mode 17 | let string = 'Vim plugin, ' . strftime("%H:%M:%S") . ': ' . a:string 18 | exec 'redir >> ' . g:RubyDebugger.logger.file 19 | silent call s:Logger.silent_echo(s:strip(string)) 20 | exec 'redir END' 21 | endif 22 | endfunction 23 | 24 | function! s:Logger.silent_echo(string) 25 | echo a:string 26 | endfunction 27 | 28 | " *** Logger class (end) 29 | " 30 | " 31 | -------------------------------------------------------------------------------- /src/ruby_debugger/public.vim: -------------------------------------------------------------------------------- 1 | " *** Public interface (start) 2 | 3 | let RubyDebugger = { 'commands': {}, 'variables': {}, 'settings': {}, 'breakpoints': [], 'frames': [], 'exceptions': [] } 4 | let g:RubyDebugger.queue = s:Queue.new() 5 | 6 | 7 | " Run debugger server. It takes one optional argument with path to debugged 8 | " ruby script ('script/server webrick' by default) 9 | function! RubyDebugger.start(...) dict 10 | call s:log("Executing :Rdebugger...") 11 | let g:RubyDebugger.server = s:Server.new() 12 | let script_string = a:0 && !empty(a:1) ? a:1 : g:ruby_debugger_default_script 13 | let params = a:0 && a:0 > 1 && !empty(a:2) ? a:2 : [] 14 | echo "Loading debugger..." 15 | call g:RubyDebugger.server.start(s:get_escaped_absolute_path(script_string), params) 16 | let g:RubyDebugger.exceptions = [] 17 | endfunction 18 | 19 | 20 | " Stop running server. 21 | function! RubyDebugger.stop() dict 22 | if has_key(g:RubyDebugger, 'server') 23 | call g:RubyDebugger.server.stop() 24 | endif 25 | endfunction 26 | 27 | function! RubyDebugger.is_running() 28 | if has_key(g:RubyDebugger, 'server') 29 | return g:RubyDebugger.server.is_running() 30 | endif 31 | return 0 32 | endfunction 33 | 34 | 35 | function! RubyDebugger.establish_connection() 36 | for breakpoint in g:RubyDebugger.breakpoints 37 | call g:RubyDebugger.queue.add(breakpoint.command()) 38 | endfor 39 | call g:RubyDebugger.queue.add('start') 40 | call g:RubyDebugger.queue.execute() 41 | echo "Debugger started" 42 | call s:log("Debugger is successfully started") 43 | endfunction 44 | 45 | 46 | " This function receives commands from the debugger. When ruby_debugger.rb 47 | " gets output from rdebug-ide, it writes it to the special file and 'kick' 48 | " the plugin by remotely calling RubyDebugger.receive_command(), e.g.: 49 | " vim --servername VIM --remote-send 'call RubyDebugger.receive_command()' 50 | " That's why +clientserver is required 51 | " This function analyzes the special file and gives handling to right command 52 | function! RubyDebugger.receive_command() dict 53 | let file_contents = join(readfile(s:tmp_file), "") 54 | call s:log("Received command: " . file_contents) 55 | let commands = split(file_contents, s:separator) 56 | for cmd in commands 57 | if !empty(cmd) 58 | if match(cmd, '') != -1 69 | call g:RubyDebugger.commands.set_variables(cmd) 70 | elseif match(cmd, '') != -1 71 | call g:RubyDebugger.commands.error(cmd) 72 | elseif match(cmd, '') != -1 73 | call g:RubyDebugger.commands.message(cmd) 74 | elseif match(cmd, '') != -1 79 | call g:RubyDebugger.commands.trace(cmd) 80 | endif 81 | endif 82 | endfor 83 | call g:RubyDebugger.queue.after_hook() 84 | call g:RubyDebugger.queue.execute() 85 | endfunction 86 | 87 | 88 | function! RubyDebugger.send_command_wrapper(command) 89 | call g:RubyDebugger.send_command(a:command) 90 | endfunction 91 | 92 | " We set function this way, because we want have possibility to mock it by 93 | " other function in tests 94 | let RubyDebugger.send_command = function("send_message_to_debugger") 95 | 96 | 97 | " Open variables window 98 | function! RubyDebugger.open_variables() dict 99 | call s:variables_window.toggle() 100 | call s:log("Opened variables window") 101 | call g:RubyDebugger.queue.execute() 102 | endfunction 103 | 104 | 105 | " Open breakpoints window 106 | function! RubyDebugger.open_breakpoints() dict 107 | call s:breakpoints_window.toggle() 108 | call s:log("Opened breakpoints window") 109 | call g:RubyDebugger.queue.execute() 110 | endfunction 111 | 112 | 113 | " Open frames window 114 | function! RubyDebugger.open_frames() dict 115 | call s:frames_window.toggle() 116 | call s:log("Opened frames window") 117 | call g:RubyDebugger.queue.execute() 118 | endfunction 119 | 120 | 121 | " Set/remove breakpoint at current position. If argument 122 | " is given, it will set conditional breakpoint (argument is condition) 123 | function! RubyDebugger.toggle_breakpoint(...) dict 124 | let line = line(".") 125 | let file = s:get_filename() 126 | call s:log("Trying to toggle a breakpoint in the file " . file . ":" . line) 127 | let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') 128 | " If breakpoint with current file/line doesn't exist, create it. Otherwise - 129 | " remove it 130 | if empty(existed_breakpoints) 131 | call s:log("There is no already set breakpoint, so create new one") 132 | let breakpoint = s:Breakpoint.new(file, line) 133 | call add(g:RubyDebugger.breakpoints, breakpoint) 134 | call s:log("Added Breakpoint object to RubyDebugger.breakpoints array") 135 | call breakpoint.send_to_debugger() 136 | else 137 | call s:log("There is already set breakpoint presented, so delete it") 138 | let breakpoint = existed_breakpoints[0] 139 | call filter(g:RubyDebugger.breakpoints, 'v:val.id != ' . breakpoint.id) 140 | call s:log("Removed Breakpoint object from RubyDebugger.breakpoints array") 141 | call breakpoint.delete() 142 | endif 143 | " Update info in Breakpoints window 144 | if s:breakpoints_window.is_open() 145 | call s:breakpoints_window.open() 146 | exe "wincmd p" 147 | endif 148 | call g:RubyDebugger.queue.execute() 149 | endfunction 150 | 151 | 152 | " Remove all breakpoints 153 | function! RubyDebugger.remove_breakpoints() dict 154 | for breakpoint in g:RubyDebugger.breakpoints 155 | call breakpoint.delete() 156 | endfor 157 | let g:RubyDebugger.breakpoints = [] 158 | call g:RubyDebugger.queue.execute() 159 | endfunction 160 | 161 | 162 | " Eval the passed in expression 163 | function! RubyDebugger.eval(exp) dict 164 | let quoted = s:quotify(a:exp) 165 | call g:RubyDebugger.queue.add("eval " . quoted) 166 | call g:RubyDebugger.queue.execute() 167 | endfunction 168 | 169 | 170 | " Sets conditional breakpoint where cursor is placed 171 | function! RubyDebugger.conditional_breakpoint(exp) dict 172 | let line = line(".") 173 | let file = s:get_filename() 174 | let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') 175 | " If breakpoint with current file/line doesn't exist, create it. Otherwise - 176 | " remove it 177 | if empty(existed_breakpoints) 178 | echo "You can set condition only to already set breakpoints. Move cursor to set breakpoint and add condition" 179 | else 180 | let breakpoint = existed_breakpoints[0] 181 | let quoted = s:quotify(a:exp) 182 | call breakpoint.add_condition(quoted) 183 | " Update info in Breakpoints window 184 | if s:breakpoints_window.is_open() 185 | call s:breakpoints_window.open() 186 | exe "wincmd p" 187 | endif 188 | call g:RubyDebugger.queue.execute() 189 | endif 190 | endfunction 191 | 192 | 193 | " Catch all exceptions with given name 194 | function! RubyDebugger.catch_exception(exp) dict 195 | if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() 196 | let quoted = s:quotify(a:exp) 197 | let exception = s:Exception.new(quoted) 198 | call add(g:RubyDebugger.exceptions, exception) 199 | if s:breakpoints_window.is_open() 200 | call s:breakpoints_window.open() 201 | exe "wincmd p" 202 | endif 203 | call g:RubyDebugger.queue.execute() 204 | else 205 | echo "Sorry, but you can set Exceptional Breakpoints only with running debugger" 206 | endif 207 | endfunction 208 | 209 | 210 | " Next 211 | function! RubyDebugger.next() dict 212 | call g:RubyDebugger.queue.add("next") 213 | call s:clear_current_state() 214 | call s:log("Step over") 215 | call g:RubyDebugger.queue.execute() 216 | endfunction 217 | 218 | 219 | " Step 220 | function! RubyDebugger.step() dict 221 | call g:RubyDebugger.queue.add("step") 222 | call s:clear_current_state() 223 | call s:log("Step into") 224 | call g:RubyDebugger.queue.execute() 225 | endfunction 226 | 227 | 228 | " Finish 229 | function! RubyDebugger.finish() dict 230 | call g:RubyDebugger.queue.add("finish") 231 | call s:clear_current_state() 232 | call s:log("Step out") 233 | call g:RubyDebugger.queue.execute() 234 | endfunction 235 | 236 | 237 | " Continue 238 | function! RubyDebugger.continue() dict 239 | call g:RubyDebugger.queue.add("cont") 240 | call s:clear_current_state() 241 | call s:log("Continue") 242 | call g:RubyDebugger.queue.execute() 243 | endfunction 244 | 245 | 246 | " Exit 247 | function! RubyDebugger.exit() dict 248 | call g:RubyDebugger.queue.add("exit") 249 | call s:clear_current_state() 250 | call g:RubyDebugger.queue.execute() 251 | endfunction 252 | 253 | 254 | " Show output log of Ruby script 255 | function! RubyDebugger.show_log() dict 256 | exe "view " . s:server_output_file 257 | setlocal autoread 258 | " Per gorkunov's request 259 | setlocal wrap 260 | setlocal nonumber 261 | if exists(":AnsiEsc") 262 | exec ":AnsiEsc" 263 | endif 264 | endfunction 265 | 266 | 267 | " Debug current opened test 268 | function! RubyDebugger.run_test(...) dict 269 | let file = s:get_filename() 270 | if file =~ '_spec\.rb$' 271 | let line = a:0 && a:0 > 0 && !empty(a:1) ? a:1 : " " 272 | call g:RubyDebugger.start(g:ruby_debugger_spec_path . ' ' . file . line) 273 | elseif file =~ '\.feature$' 274 | call g:RubyDebugger.start(g:ruby_debugger_cucumber_path . ' ' . file) 275 | elseif file =~ '_test\.rb$' 276 | call g:RubyDebugger.start(file, ['-Itest']) 277 | endif 278 | endfunction 279 | 280 | 281 | " *** Public interface (end) 282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /src/ruby_debugger/queue.vim: -------------------------------------------------------------------------------- 1 | " *** Queue class (start) 2 | 3 | let s:Queue = {} 4 | 5 | " ** Public methods 6 | 7 | " Constructor of new queue. 8 | function! s:Queue.new() dict 9 | let var = copy(self) 10 | let var.queue = [] 11 | let var.after = "" 12 | return var 13 | endfunction 14 | 15 | 16 | " Execute next command in the queue and remove it from queue 17 | function! s:Queue.execute() dict 18 | if !empty(self.queue) 19 | call s:log("Executing queue") 20 | let message = join(self.queue, ';') 21 | call self.empty() 22 | call g:RubyDebugger.send_command(message) 23 | endif 24 | endfunction 25 | 26 | 27 | " Execute 'after' hook only if queue is empty 28 | function! s:Queue.after_hook() dict 29 | if self.after != "" && empty(self.queue) 30 | call self.after() 31 | endif 32 | endfunction 33 | 34 | 35 | function! s:Queue.add(element) dict 36 | call s:log("Adding '" . a:element . "' to queue") 37 | call add(self.queue, a:element) 38 | endfunction 39 | 40 | 41 | function! s:Queue.empty() dict 42 | let self.queue = [] 43 | endfunction 44 | 45 | 46 | " *** Queue class (end) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/ruby_debugger/server.vim: -------------------------------------------------------------------------------- 1 | " *** Server class (start) 2 | 3 | let s:Server = {} 4 | 5 | " ** Public methods 6 | 7 | " Constructor of new server. Just inits it, not runs 8 | function! s:Server.new() dict 9 | let var = copy(self) 10 | call s:log("Initializing Server object") 11 | return var 12 | endfunction 13 | 14 | 15 | " Start the server. It will kill any listeners on given ports before. 16 | function! s:Server.start(script, params) dict 17 | call self.stop() 18 | call s:log("Starting Server, command: " . a:script) 19 | " Remove leading and trailing quotes 20 | let script_name = substitute(a:script, "\\(^['\"]\\|['\"]$\\)", '', 'g') 21 | let s:socket_file = tempname() 22 | let cmd = g:ruby_debugger_executable . ' --file ' . s:tmp_file . ' --output ' . s:server_output_file . ' --socket ' . s:socket_file . ' --logger_file ' . s:logger_file . ' --debug_mode ' . g:ruby_debugger_debug_mode . ' --vim_executable ' . g:ruby_debugger_progname . ' --vim_servername ' . v:servername . ' --separator ' . s:separator . ' -- ' . script_name 23 | call s:log("Executing command: ". cmd) 24 | let s:rdebug_pid = split(system(cmd), "\n")[-1] 25 | call s:log("PID: " . s:rdebug_pid) 26 | call s:log("Waiting for starting debugger...") 27 | endfunction 28 | 29 | 30 | " Kill servers and empty PIDs 31 | function! s:Server.stop() dict 32 | ruby << RUBY 33 | if @vim_ruby_debugger_socket 34 | @vim_ruby_debugger_socket.close 35 | @vim_ruby_debugger_socket = nil 36 | end 37 | RUBY 38 | call s:log("Stopping, pid is: " . s:rdebug_pid) 39 | if s:rdebug_pid =~ '^\d\+$' 40 | call self._kill_process(s:rdebug_pid) 41 | endif 42 | let s:rdebug_pid = "" 43 | endfunction 44 | 45 | 46 | " Return 1 if processes with set PID exist. 47 | function! s:Server.is_running() dict 48 | return !empty(s:rdebug_pid) 49 | endfunction 50 | 51 | 52 | " Kill process with given PID 53 | function! s:Server._kill_process(pid) dict 54 | let message = "Killing server with pid " . a:pid 55 | call s:log(message) 56 | echo message 57 | let cmd = "ruby -e 'Process.kill(9," . a:pid . ")'" 58 | call s:log("Executing command: " . cmd) 59 | call system(cmd) 60 | call s:log("Killed server with pid: " . a:pid) 61 | endfunction 62 | 63 | 64 | " *** Server class (end) 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/ruby_debugger/var.vim: -------------------------------------------------------------------------------- 1 | " *** Var proxy class (start) 2 | 3 | let s:Var = { 'id' : 0 } 4 | 5 | " ** Public methods 6 | 7 | " This is a proxy method for creating new variable 8 | function! s:Var.new(attrs) 9 | if has_key(a:attrs, 'hasChildren') && a:attrs['hasChildren'] == 'true' 10 | return s:VarParent.new(a:attrs) 11 | else 12 | return s:VarChild.new(a:attrs) 13 | end 14 | endfunction 15 | 16 | 17 | " Get variable under cursor 18 | function! s:Var.get_selected() 19 | let line = getline(".") 20 | " Get its id - it is last in the string 21 | let match = matchlist(line, '.*\t\(\d\+\)$') 22 | let id = get(match, 1) 23 | if id 24 | let variable = g:RubyDebugger.variables.find_variable({'id' : id}) 25 | return variable 26 | else 27 | return {} 28 | endif 29 | endfunction 30 | 31 | 32 | " *** Var proxy class (end) 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/ruby_debugger/var_child.vim: -------------------------------------------------------------------------------- 1 | " *** VarChild class (start) 2 | 3 | let s:VarChild = {} 4 | 5 | " ** Public methods 6 | 7 | " Constructs new variable without childs 8 | function! s:VarChild.new(attrs) 9 | let new_variable = copy(self) 10 | let new_variable.attributes = a:attrs 11 | let new_variable.parent = {} 12 | let new_variable.level = 0 13 | let new_variable.type = "VarChild" 14 | let s:Var.id += 1 15 | let new_variable.id = s:Var.id 16 | return new_variable 17 | endfunction 18 | 19 | 20 | " Renders data of the variable 21 | function! s:VarChild.render() 22 | return self._render(0, 0, [], len(self.parent.children) ==# 1) 23 | endfunction 24 | 25 | 26 | " VarChild can't be opened because it can't have children. But VarParent can 27 | function! s:VarChild.open() 28 | return 0 29 | endfunction 30 | 31 | 32 | " VarChild can't be closed because it can't have children. But VarParent can 33 | function! s:VarChild.close() 34 | return 0 35 | endfunction 36 | 37 | 38 | " VarChild can't be parent. But VarParent can. If Var have hasChildren == 39 | " true, then it is parent 40 | function! s:VarChild.is_parent() 41 | return has_key(self.attributes, 'hasChildren') && get(self.attributes, 'hasChildren') ==# 'true' 42 | endfunction 43 | 44 | 45 | " Output format for Variables Window 46 | function! s:VarChild.to_s() 47 | return get(self.attributes, "name", "undefined") . "\t" . get(self.attributes, "type", "undefined") . "\t" . get(self.attributes, "value", "undefined") . "\t" . get(self, "id", "0") 48 | endfunction 49 | 50 | 51 | " Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} 52 | function! s:VarChild.find_variable(attrs) 53 | if self._match_attributes(a:attrs) 54 | return self 55 | else 56 | return {} 57 | endif 58 | endfunction 59 | 60 | 61 | " Find and return array of variables that match given Dict of attrs 62 | function! s:VarChild.find_variables(attrs) 63 | let variables = [] 64 | if self._match_attributes(a:attrs) 65 | call add(variables, self) 66 | endif 67 | return variables 68 | endfunction 69 | 70 | 71 | " ** Private methods 72 | 73 | 74 | " Recursive function, that renders Variable and all its childs (if they are 75 | " presented). Stolen from NERDTree 76 | function! s:VarChild._render(depth, draw_text, vertical_map, is_last_child) 77 | let output = "" 78 | if a:draw_text ==# 1 79 | let tree_parts = '' 80 | 81 | " get all the leading spaces and vertical tree parts for this line 82 | if a:depth > 1 83 | for j in a:vertical_map[0:-2] 84 | if j ==# 1 85 | let tree_parts = tree_parts . '| ' 86 | else 87 | let tree_parts = tree_parts . ' ' 88 | endif 89 | endfor 90 | endif 91 | 92 | " get the last vertical tree part for this line which will be different 93 | " if this node is the last child of its parent 94 | if a:is_last_child 95 | let tree_parts = tree_parts . '`' 96 | else 97 | let tree_parts = tree_parts . '|' 98 | endif 99 | 100 | " smack the appropriate dir/file symbol on the line before the file/dir 101 | " name itself 102 | if self.is_parent() 103 | if self.is_open 104 | let tree_parts = tree_parts . '~' 105 | else 106 | let tree_parts = tree_parts . '+' 107 | endif 108 | else 109 | let tree_parts = tree_parts . '-' 110 | endif 111 | let line = tree_parts . self.to_s() 112 | let output = output . line . "\n" 113 | 114 | endif 115 | 116 | if self.is_parent() && self.is_open 117 | if len(self.children) > 0 118 | 119 | " draw all the nodes children except the last 120 | let last_index = len(self.children) - 1 121 | if last_index > 0 122 | for i in self.children[0:last_index - 1] 123 | let output = output . i._render(a:depth + 1, 1, add(copy(a:vertical_map), 1), 0) 124 | endfor 125 | endif 126 | 127 | " draw the last child, indicating that it IS the last 128 | let output = output . self.children[last_index]._render(a:depth + 1, 1, add(copy(a:vertical_map), 0), 1) 129 | 130 | endif 131 | endif 132 | 133 | return output 134 | 135 | endfunction 136 | 137 | 138 | " Return 1 if *all* given attributes (pairs key/value) match to current 139 | " variable 140 | function! s:VarChild._match_attributes(attrs) 141 | let conditions = 1 142 | for attr in keys(a:attrs) 143 | if has_key(self.attributes, attr) 144 | " If current key is contained in attributes of variable (they were 145 | " attributes in tag, then trying to match there. 146 | let conditions = conditions && self.attributes[attr] == a:attrs[attr] 147 | elseif has_key(self, attr) 148 | " Otherwise, if current key is contained in auxiliary attributes of the 149 | " variable, trying to match there 150 | let conditions = conditions && self[attr] == a:attrs[attr] 151 | else 152 | " Otherwise, this variable is not match 153 | let conditions = 0 154 | break 155 | endif 156 | endfor 157 | return conditions 158 | endfunction 159 | 160 | 161 | " *** VarChild class (end) 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/ruby_debugger/var_parent.vim: -------------------------------------------------------------------------------- 1 | " *** VarParent class (start) 2 | 3 | " Inherits VarParent from VarChild 4 | let s:VarParent = copy(s:VarChild) 5 | 6 | " ** Public methods 7 | 8 | 9 | " Initializes new variable with childs 10 | function! s:VarParent.new(attrs) 11 | if !has_key(a:attrs, 'hasChildren') || a:attrs['hasChildren'] != 'true' 12 | throw "RubyDebug: VarParent must be initialized with hasChildren = true" 13 | endif 14 | let new_variable = copy(self) 15 | let new_variable.attributes = a:attrs 16 | let new_variable.parent = {} 17 | let new_variable.is_open = 0 18 | let new_variable.level = 0 19 | let new_variable.children = [] 20 | let new_variable.type = "VarParent" 21 | let s:Var.id += 1 22 | let new_variable.id = s:Var.id 23 | return new_variable 24 | endfunction 25 | 26 | 27 | " Open variable, init its children and display them 28 | function! s:VarParent.open() 29 | let self.is_open = 1 30 | call self._init_children() 31 | return 0 32 | endfunction 33 | 34 | 35 | " Close variable and display it 36 | function! s:VarParent.close() 37 | let self.is_open = 0 38 | call s:variables_window.display() 39 | if has_key(g:RubyDebugger, "current_variable") 40 | unlet g:RubyDebugger.current_variable 41 | endif 42 | return 0 43 | endfunction 44 | 45 | 46 | " Renders data of the variable 47 | function! s:VarParent.render() 48 | return self._render(0, 0, [], len(self.children) ==# 1) 49 | endfunction 50 | 51 | 52 | 53 | " Add childs to the variable. You always should use this method instead of 54 | " explicit assigning to children property (like 'add(self.children, variables)') 55 | function! s:VarParent.add_childs(childs) 56 | " If children are given by array, extend self.children by this array 57 | if type(a:childs) == type([]) 58 | for child in a:childs 59 | let child.parent = self 60 | let child.level = self.level + 1 61 | endfor 62 | call extend(self.children, a:childs) 63 | else 64 | " Otherwise, add child to self.children 65 | let a:childs.parent = self 66 | let child.level = self.level + 1 67 | call add(self.children, a:childs) 68 | end 69 | endfunction 70 | 71 | 72 | " Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} 73 | " If current variable doesn't match these attributes, try to find in children 74 | function! s:VarParent.find_variable(attrs) 75 | if self._match_attributes(a:attrs) 76 | return self 77 | else 78 | for child in self.children 79 | let result = child.find_variable(a:attrs) 80 | if result != {} 81 | return result 82 | endif 83 | endfor 84 | endif 85 | return {} 86 | endfunction 87 | 88 | 89 | " Find and return array of variables that match given Dict of attrs. 90 | " Try to match current variable and its children 91 | function! s:VarParent.find_variables(attrs) 92 | let variables = [] 93 | if self._match_attributes(a:attrs) 94 | call add(variables, self) 95 | endif 96 | for child in self.children 97 | call extend(variables, child.find_variables(a:attrs)) 98 | endfor 99 | return variables 100 | endfunction 101 | 102 | 103 | " ** Private methods 104 | 105 | 106 | " Update children of the variable 107 | function! s:VarParent._init_children() 108 | " Remove all the current child nodes 109 | let self.children = [] 110 | 111 | " Get children 112 | if has_key(self.attributes, 'objectId') 113 | let g:RubyDebugger.current_variable = self 114 | call g:RubyDebugger.queue.add('var instance ' . self.attributes.objectId) 115 | endif 116 | 117 | endfunction 118 | 119 | 120 | " *** VarParent class (end) 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/ruby_debugger/window.vim: -------------------------------------------------------------------------------- 1 | " *** Window class (start). Abstract Class for creating window. 2 | " Must be inherited. Mostly, stolen from the NERDTree. 3 | 4 | let s:Window = {} 5 | let s:Window['next_buffer_number'] = 1 6 | let s:Window['position'] = 'botright' 7 | let s:Window['size'] = 10 8 | 9 | " ** Public methods 10 | 11 | " Constructs new window 12 | function! s:Window.new(name, title) dict 13 | let new_variable = copy(self) 14 | let new_variable.name = a:name 15 | let new_variable.title = a:title 16 | return new_variable 17 | endfunction 18 | 19 | 20 | " Clear all data from window 21 | function! s:Window.clear() dict 22 | silent 1,$delete _ 23 | endfunction 24 | 25 | 26 | " Close window 27 | function! s:Window.close() dict 28 | if !self.is_open() 29 | throw "RubyDebug: Window " . self.name . " is not open" 30 | endif 31 | 32 | if winnr("$") != 1 33 | call self.focus() 34 | close 35 | exe "wincmd p" 36 | else 37 | " If this is only one window, just quit 38 | :q 39 | endif 40 | call s:log("Closed window with name: " . self.name) 41 | endfunction 42 | 43 | 44 | " Get window number 45 | function! s:Window.get_number() dict 46 | if self._exist_for_tab() 47 | return bufwinnr(self._buf_name()) 48 | else 49 | return -1 50 | endif 51 | endfunction 52 | 53 | 54 | " Display data to the window 55 | function! s:Window.display() 56 | call s:log("Start displaying data in window with name: " . self.name) 57 | call self.focus() 58 | setlocal modifiable 59 | 60 | let current_line = line(".") 61 | let current_column = col(".") 62 | let top_line = line("w0") 63 | 64 | call self.clear() 65 | 66 | call self._insert_data() 67 | call self._restore_view(top_line, current_line, current_column) 68 | 69 | setlocal nomodifiable 70 | call s:log("Complete displaying data in window with name: " . self.name) 71 | endfunction 72 | 73 | 74 | " Put cursor to the window 75 | function! s:Window.focus() dict 76 | exe self.get_number() . " wincmd w" 77 | call s:log("Set focus to window with name: " . self.name) 78 | endfunction 79 | 80 | 81 | " Return 1 if window is opened 82 | function! s:Window.is_open() dict 83 | return self.get_number() != -1 84 | endfunction 85 | 86 | 87 | " Open window and display data (stolen from NERDTree) 88 | function! s:Window.open() dict 89 | if !self.is_open() 90 | " create the window 91 | silent exec self.position . ' ' . self.size . ' new' 92 | 93 | if !self._exist_for_tab() 94 | " If the window is not opened/exists, create new 95 | call self._set_buf_name(self._next_buffer_name()) 96 | silent! exec "edit " . self._buf_name() 97 | " This function does not exist in Window class and should be declared in 98 | " descendants 99 | call self.bind_mappings() 100 | else 101 | " Or just jump to opened buffer 102 | silent! exec "buffer " . self._buf_name() 103 | endif 104 | 105 | " set buffer options 106 | setlocal winfixheight 107 | setlocal noswapfile 108 | setlocal buftype=nofile 109 | setlocal nowrap 110 | setlocal foldcolumn=0 111 | setlocal nobuflisted 112 | setlocal nospell 113 | setlocal nolist 114 | iabc 115 | setlocal cursorline 116 | setfiletype ruby_debugger_window 117 | call s:log("Opened window with name: " . self.name) 118 | endif 119 | 120 | if has("syntax") && exists("g:syntax_on") && !has("syntax_items") 121 | call self.setup_syntax_highlighting() 122 | endif 123 | 124 | call self.display() 125 | endfunction 126 | 127 | 128 | " Open/close window 129 | function! s:Window.toggle() dict 130 | call s:log("Toggling window with name: " . self.name) 131 | if self._exist_for_tab() && self.is_open() 132 | call self.close() 133 | else 134 | call self.open() 135 | end 136 | endfunction 137 | 138 | 139 | " ** Private methods 140 | 141 | 142 | " Return buffer name, that is stored in tab variable 143 | function! s:Window._buf_name() dict 144 | return t:window_{self.name}_buf_name 145 | endfunction 146 | 147 | 148 | " Return 1 if the window exists in current tab 149 | function! s:Window._exist_for_tab() dict 150 | return exists("t:window_" . self.name . "_buf_name") 151 | endfunction 152 | 153 | 154 | " Insert data to the window 155 | function! s:Window._insert_data() dict 156 | let old_p = @p 157 | " Put data to the register and then show it by 'put' command 158 | let @p = self.render() 159 | silent exe "normal \"pP" 160 | let @p = old_p 161 | call s:log("Inserted data to window with name: " . self.name) 162 | endfunction 163 | 164 | 165 | " Calculate correct name for the window 166 | function! s:Window._next_buffer_name() dict 167 | let name = self.name . s:Window.next_buffer_number 168 | let s:Window.next_buffer_number += 1 169 | return name 170 | endfunction 171 | 172 | 173 | " Restore the view 174 | function! s:Window._restore_view(top_line, current_line, current_column) dict 175 | let old_scrolloff=&scrolloff 176 | let &scrolloff=0 177 | call cursor(a:top_line, 1) 178 | normal! zt 179 | call cursor(a:current_line, a:current_column) 180 | let &scrolloff = old_scrolloff 181 | call s:log("Restored view of window with name: " . self.name) 182 | endfunction 183 | 184 | 185 | function! s:Window._set_buf_name(name) dict 186 | let t:window_{self.name}_buf_name = a:name 187 | endfunction 188 | 189 | 190 | " *** Window class (end) 191 | 192 | -------------------------------------------------------------------------------- /src/ruby_debugger/window_breakpoints.vim: -------------------------------------------------------------------------------- 1 | " *** WindowBreakpoints class (start) 2 | 3 | " Inherits WindowBreakpoints from Window 4 | let s:WindowBreakpoints = copy(s:Window) 5 | 6 | " ** Public methods 7 | 8 | function! s:WindowBreakpoints.bind_mappings() 9 | nnoremap <2-leftmouse> :call window_breakpoints_activate_node() 10 | nnoremap o :call window_breakpoints_activate_node() 11 | nnoremap d :call window_breakpoints_delete_node() 12 | endfunction 13 | 14 | 15 | " Returns string that contains all breakpoints (for Window.display()) 16 | function! s:WindowBreakpoints.render() dict 17 | let breakpoints = "" 18 | let breakpoints .= self.title . "\n" 19 | for breakpoint in g:RubyDebugger.breakpoints 20 | let breakpoints .= breakpoint.render() 21 | endfor 22 | let exceptions = map(copy(g:RubyDebugger.exceptions), 'v:val.render()') 23 | let breakpoints .= "\nException breakpoints: " . join(exceptions, ", ") 24 | return breakpoints 25 | endfunction 26 | 27 | 28 | " TODO: Is there some way to call s:WindowBreakpoints.activate_node from mapping 29 | " command? 30 | " Open breakpoint under cursor 31 | function! s:window_breakpoints_activate_node() 32 | let breakpoint = s:Breakpoint.get_selected() 33 | if breakpoint != {} 34 | call breakpoint.open() 35 | endif 36 | endfunction 37 | 38 | 39 | " Delete breakpoint under cursor 40 | function! s:window_breakpoints_delete_node() 41 | let breakpoint = s:Breakpoint.get_selected() 42 | if breakpoint != {} 43 | call breakpoint.delete() 44 | call filter(g:RubyDebugger.breakpoints, "v:val.id != " . breakpoint.id) 45 | call s:breakpoints_window.open() 46 | endif 47 | endfunction 48 | 49 | 50 | " Add syntax highlighting 51 | function! s:WindowBreakpoints.setup_syntax_highlighting() dict 52 | execute "syn match rdebugTitle #" . self.title . "#" 53 | 54 | syn match rdebugId "^\d\+\s" contained nextgroup=rdebugDebuggerId 55 | syn match rdebugDebuggerId "\d*\s" contained nextgroup=rdebugFile 56 | syn match rdebugFile ".*:" contained nextgroup=rdebugLine 57 | syn match rdebugLine "\d\+" contained 58 | 59 | syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent 60 | 61 | hi def link rdebugId Directory 62 | hi def link rdebugDebuggerId Type 63 | hi def link rdebugFile Normal 64 | hi def link rdebugLine Special 65 | endfunction 66 | 67 | 68 | " *** WindowBreakpoints class (end) 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/ruby_debugger/window_frames.vim: -------------------------------------------------------------------------------- 1 | " *** WindowFrames class (start) 2 | 3 | " Inherits WindowFrames from Window 4 | let s:WindowFrames = copy(s:Window) 5 | 6 | " ** Public methods 7 | 8 | function! s:WindowFrames.bind_mappings() 9 | nnoremap <2-leftmouse> :call window_frames_activate_node() 10 | nnoremap o :call window_frames_activate_node() 11 | endfunction 12 | 13 | 14 | " Returns string that contains all frames (for Window.display()) 15 | function! s:WindowFrames.render() dict 16 | let frames = "" 17 | let frames .= self.title . "\n" 18 | for frame in g:RubyDebugger.frames 19 | let frames .= frame.render() 20 | endfor 21 | return frames 22 | endfunction 23 | 24 | 25 | " Open frame under cursor 26 | function! s:window_frames_activate_node() 27 | let frame = s:Frame.get_selected() 28 | if frame != {} 29 | call frame.open() 30 | endif 31 | endfunction 32 | 33 | 34 | " Add syntax highlighting 35 | function! s:WindowFrames.setup_syntax_highlighting() dict 36 | execute "syn match rdebugTitle #" . self.title . "#" 37 | 38 | syn match rdebugId "^\d\+\s" contained nextgroup=rdebugFile 39 | syn match rdebugFile ".*:" contained nextgroup=rdebugLine 40 | syn match rdebugLine "\d\+" contained 41 | 42 | syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent 43 | 44 | hi def link rdebugId Directory 45 | hi def link rdebugFile Normal 46 | hi def link rdebugLine Special 47 | endfunction 48 | 49 | 50 | " *** WindowFrames class (end) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/ruby_debugger/window_variables.vim: -------------------------------------------------------------------------------- 1 | " *** WindowVariables class (start) 2 | 3 | " Inherits variables window from abstract window class 4 | let s:WindowVariables = copy(s:Window) 5 | 6 | " ** Public methods 7 | 8 | function! s:WindowVariables.bind_mappings() 9 | nnoremap <2-leftmouse> :call window_variables_activate_node() 10 | nnoremap o :call window_variables_activate_node()" 11 | endfunction 12 | 13 | 14 | " Returns string that contains all variables (for Window.display()) 15 | function! s:WindowVariables.render() dict 16 | let variables = self.title . "\n" 17 | let variables .= (g:RubyDebugger.variables == {} ? '' : g:RubyDebugger.variables.render()) 18 | return variables 19 | endfunction 20 | 21 | 22 | " TODO: Is there some way to call s:WindowVariables.activate_node from mapping 23 | " command? 24 | " Expand/collapse variable under cursor 25 | function! s:window_variables_activate_node() 26 | let variable = s:Var.get_selected() 27 | if variable != {} && variable.type == "VarParent" 28 | if variable.is_open 29 | call variable.close() 30 | else 31 | call variable.open() 32 | endif 33 | endif 34 | call g:RubyDebugger.queue.execute() 35 | endfunction 36 | 37 | 38 | " Add syntax highlighting 39 | function! s:WindowVariables.setup_syntax_highlighting() 40 | execute "syn match rdebugTitle #" . self.title . "#" 41 | 42 | syn match rdebugPart #[| `]\+# 43 | syn match rdebugPartFile #[| `]\+-# contains=rdebugPart nextgroup=rdebugChild contained 44 | syn match rdebugChild #.\{-}\t# nextgroup=rdebugType contained 45 | 46 | syn match rdebugClosable #[| `]\+\~# contains=rdebugPart nextgroup=rdebugParent contained 47 | syn match rdebugOpenable #[| `]\++# contains=rdebugPart nextgroup=rdebugParent contained 48 | syn match rdebugParent #.\{-}\t# nextgroup=rdebugType contained 49 | 50 | syn match rdebugType #.\{-}\t# nextgroup=rdebugValue contained 51 | syn match rdebugValue #.*\t#he=e-1 nextgroup=rdebugId contained 52 | syn match rdebugId #.*# contained 53 | 54 | syn match rdebugParentLine '[| `]\+[+\~].*' contains=rdebugClosable,rdebugOpenable transparent 55 | syn match rdebugChildLine '[| `]\+-.*' contains=rdebugPartFile transparent 56 | 57 | hi def link rdebugTitle Identifier 58 | hi def link rdebugClosable Type 59 | hi def link rdebugOpenable Title 60 | hi def link rdebugPart Special 61 | hi def link rdebugPartFile Type 62 | hi def link rdebugChild Normal 63 | hi def link rdebugParent Directory 64 | hi def link rdebugType Type 65 | hi def link rdebugValue Special 66 | hi def link rdebugId Ignore 67 | endfunction 68 | 69 | 70 | " *** WindowVariables class (end) 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/ruby_debugger_autoload_plan.txt: -------------------------------------------------------------------------------- 1 | ruby_debugger/before.vim 2 | ruby_debugger/common.vim 3 | ruby_debugger/queue.vim 4 | ruby_debugger/public.vim 5 | ruby_debugger/commands.vim 6 | ruby_debugger/window.vim 7 | ruby_debugger/window_variables.vim 8 | ruby_debugger/window_breakpoints.vim 9 | ruby_debugger/window_frames.vim 10 | ruby_debugger/var.vim 11 | ruby_debugger/var_child.vim 12 | ruby_debugger/var_parent.vim 13 | ruby_debugger/logger.vim 14 | ruby_debugger/breakpoint.vim 15 | ruby_debugger/exception.vim 16 | ruby_debugger/frame.vim 17 | ruby_debugger/server.vim 18 | ruby_debugger/after.vim 19 | -------------------------------------------------------------------------------- /src/ruby_debugger_plugin_plan.txt: -------------------------------------------------------------------------------- 1 | ruby_debugger/init.vim 2 | -------------------------------------------------------------------------------- /src/ruby_test_plan.txt: -------------------------------------------------------------------------------- 1 | tests/framework.vim 2 | tests/mock.vim 3 | tests/server.vim 4 | tests/breakpoint.vim 5 | tests/exceptions.vim 6 | tests/frames.vim 7 | tests/variables.vim 8 | tests/command.vim 9 | -------------------------------------------------------------------------------- /src/tests/breakpoint.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.breakpoint = {} 2 | 3 | function! s:Tests.breakpoint.before_all() 4 | call s:Mock.mock_debugger() 5 | endfunction 6 | 7 | 8 | function! s:Tests.breakpoint.after_all() 9 | call s:Mock.unmock_debugger() 10 | endfunction 11 | 12 | 13 | function! s:Tests.breakpoint.before() 14 | let s:Breakpoint.id = 0 15 | let g:RubyDebugger.frames = [] 16 | let g:RubyDebugger.exceptions = [] 17 | let g:RubyDebugger.breakpoints = [] 18 | let g:RubyDebugger.variables = {} 19 | call g:RubyDebugger.queue.empty() 20 | call s:Server._stop_server(s:rdebug_port) 21 | call s:Server._stop_server(s:debugger_port) 22 | silent exe "only" 23 | endfunction 24 | 25 | 26 | function! s:Tests.breakpoint.test_should_set_breakpoint(test) 27 | exe "Rdebugger" 28 | let filename = s:Mock.mock_file() 29 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 30 | 31 | call g:RubyDebugger.toggle_breakpoint() 32 | let breakpoint = get(g:RubyDebugger.breakpoints, 0) 33 | call g:TU.equal(1, breakpoint.id, "Id of first breakpoint should == 1", a:test) 34 | call g:TU.match(breakpoint.file, file_pattern, "File should be set right", a:test) 35 | call g:TU.equal(1, breakpoint.line, "Line should be set right", a:test) 36 | " TODO: Find way to test sign 37 | call g:TU.equal(g:RubyDebugger.server.rdebug_pid, breakpoint.rdebug_pid, "Breakpoint should be assigned to running server", a:test) 38 | call g:TU.equal(1, breakpoint.debugger_id, "Breakpoint should get number from debugger", a:test) 39 | call s:Mock.unmock_file(filename) 40 | endfunction 41 | 42 | 43 | function! s:Tests.breakpoint.test_should_add_all_unassigned_breakpoints_to_running_server(test) 44 | let filename = s:Mock.mock_file() 45 | " Write 3 lines of text and set 3 breakpoints (on every line) 46 | exe "normal iblablabla" 47 | exe "normal oblabla" 48 | exe "normal obla" 49 | exe "normal gg" 50 | exe "write" 51 | call g:RubyDebugger.toggle_breakpoint() 52 | exe "normal j" 53 | call g:RubyDebugger.toggle_breakpoint() 54 | exe "normal j" 55 | call g:RubyDebugger.toggle_breakpoint() 56 | 57 | " Lets suggest that some breakpoint was assigned to old server 58 | let g:RubyDebugger.breakpoints[1].rdebug_pid = 'bla' 59 | 60 | call g:TU.equal(3, len(g:RubyDebugger.breakpoints), "3 breakpoints should be set", a:test) 61 | exe "Rdebugger" 62 | call g:TU.equal(3, s:Mock.breakpoints, "3 breakpoints should be assigned", a:test) 63 | for breakpoint in g:RubyDebugger.breakpoints 64 | call g:TU.equal(g:RubyDebugger.server.rdebug_pid, breakpoint.rdebug_pid, "Breakpoint should have PID of running server", a:test) 65 | endfor 66 | call s:Mock.unmock_file(filename) 67 | endfunction 68 | 69 | 70 | function! s:Tests.breakpoint.test_should_remove_all_breakpoints(test) 71 | let filename = s:Mock.mock_file() 72 | " Write 3 lines of text and set 3 breakpoints (on every line) 73 | exe "normal iblablabla" 74 | exe "normal oblabla" 75 | exe "normal obla" 76 | exe "normal gg" 77 | exe "write" 78 | call g:RubyDebugger.toggle_breakpoint() 79 | exe "normal j" 80 | call g:RubyDebugger.toggle_breakpoint() 81 | exe "normal j" 82 | call g:RubyDebugger.toggle_breakpoint() 83 | call g:TU.equal(3, len(g:RubyDebugger.breakpoints), "3 breakpoints should be set", a:test) 84 | 85 | call g:RubyDebugger.remove_breakpoints() 86 | 87 | call g:TU.equal(0, len(g:RubyDebugger.breakpoints), "Breakpoints should be removed", a:test) 88 | 89 | call s:Mock.unmock_file(filename) 90 | endfunction 91 | 92 | 93 | function! s:Tests.breakpoint.test_jump_to_breakpoint_by_breakpoint(test) 94 | call s:Tests.breakpoint.jump_to_breakpoint('breakpoint', a:test) 95 | endfunction 96 | 97 | 98 | function! s:Tests.breakpoint.test_jump_to_breakpoint_by_suspended(test) 99 | call s:Tests.breakpoint.jump_to_breakpoint('suspended', a:test) 100 | endfunction 101 | 102 | 103 | function! s:Tests.breakpoint.test_delete_breakpoint(test) 104 | exe "Rdebugger" 105 | let filename = s:Mock.mock_file() 106 | call g:RubyDebugger.toggle_breakpoint() 107 | call g:RubyDebugger.toggle_breakpoint() 108 | 109 | call g:TU.ok(empty(g:RubyDebugger.breakpoints), "Breakpoint should be removed", a:test) 110 | call g:TU.equal(0, s:Mock.breakpoints, "0 breakpoints should be assigned", a:test) 111 | 112 | call s:Mock.unmock_file(filename) 113 | endfunction 114 | 115 | 116 | function! s:Tests.breakpoint.jump_to_breakpoint(cmd, test) 117 | let filename = s:Mock.mock_file() 118 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 119 | 120 | " Write 2 lines and set current line to second line. We will jump to first 121 | " line 122 | exe "normal iblablabla" 123 | exe "normal oblabla" 124 | exe "write" 125 | 126 | call g:TU.equal(2, line("."), "Current line before jumping is second", a:test) 127 | 128 | let cmd = '<' . a:cmd . ' file="' . filename . '" line="1" />' 129 | call writefile([ cmd ], s:tmp_file) 130 | call g:RubyDebugger.receive_command() 131 | 132 | call g:TU.equal(1, line("."), "Current line before jumping is first", a:test) 133 | call g:TU.match(expand("%"), file_pattern, "Jumped to correct file", a:test) 134 | 135 | call s:Mock.unmock_file(filename) 136 | endfunction 137 | 138 | 139 | function! s:Tests.breakpoint.test_should_open_window_without_got_breakpoints(test) 140 | call g:RubyDebugger.open_breakpoints() 141 | 142 | call g:TU.ok(s:breakpoints_window.is_open(), "Breakpoints window should opened", a:test) 143 | call g:TU.equal(bufwinnr("%"), s:breakpoints_window.get_number(), "Focus should be into the breakpoints window", a:test) 144 | call g:TU.equal(getline(1), s:breakpoints_window.title, "First line should be name", a:test) 145 | 146 | exe 'close' 147 | endfunction 148 | 149 | 150 | function! s:Tests.breakpoint.test_should_open_window_and_show_breakpoints(test) 151 | let filename = s:Mock.mock_file() 152 | " Replace all windows separators (\) and POSIX separators (/) to [\/] for 153 | " making it cross-platform 154 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 155 | " Write 2 lines of text and set 2 breakpoints (on every line) 156 | exe "normal iblablabla" 157 | exe "normal oblabla" 158 | exe "normal gg" 159 | exe "write" 160 | call g:RubyDebugger.toggle_breakpoint() 161 | exe "normal j" 162 | call g:RubyDebugger.toggle_breakpoint() 163 | 164 | call s:Mock.unmock_file(filename) 165 | 166 | " Lets suggest that some breakpoint is assigned 167 | let g:RubyDebugger.breakpoints[1].debugger_id = 4 168 | 169 | call g:RubyDebugger.open_breakpoints() 170 | call g:TU.match(getline(2), '1 ' . file_pattern . ':1', "Should show first breakpoint", a:test) 171 | call g:TU.match(getline(3), '2 4 ' . file_pattern . ':2', "Should show second breakpoint", a:test) 172 | 173 | exe 'close' 174 | endfunction 175 | 176 | 177 | function! s:Tests.breakpoint.test_should_open_selected_breakpoint_from_breakpoints_window(test) 178 | let filename = s:Mock.mock_file() 179 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 180 | exe "normal iblablabla" 181 | exe "normal oblabla" 182 | call g:RubyDebugger.toggle_breakpoint() 183 | exe "normal gg" 184 | exe "write" 185 | exe "wincmd w" 186 | exe "new" 187 | 188 | call g:TU.ok(expand("%") != filename, "It should not be within the file with breakpoint", a:test) 189 | call g:RubyDebugger.open_breakpoints() 190 | exe 'normal 2G' 191 | call s:window_breakpoints_activate_node() 192 | call g:TU.match(expand("%"), file_pattern, "It should open file with breakpoint", a:test) 193 | call g:TU.equal(2, line("."), "It should jump to line with breakpoint", a:test) 194 | call g:RubyDebugger.open_breakpoints() 195 | 196 | call s:Mock.unmock_file(filename) 197 | endfunction 198 | 199 | 200 | function! s:Tests.breakpoint.test_should_delete_breakpoint_from_breakpoints_window(test) 201 | let filename = s:Mock.mock_file() 202 | call g:RubyDebugger.toggle_breakpoint() 203 | call s:Mock.unmock_file(filename) 204 | call g:TU.ok(!empty(g:RubyDebugger.breakpoints), "Breakpoint should be set", a:test) 205 | 206 | call g:RubyDebugger.open_breakpoints() 207 | exe 'normal 2G' 208 | call s:window_breakpoints_delete_node() 209 | call g:TU.equal('', getline(2), "Breakpoint should not be shown", a:test) 210 | call g:TU.ok(empty(g:RubyDebugger.breakpoints), "Breakpoint should be destroyed", a:test) 211 | 212 | exe 'close' 213 | endfunction 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/tests/command.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.command = {} 2 | 3 | function! s:Tests.command.before_all() 4 | call s:Mock.mock_debugger() 5 | endfunction 6 | 7 | 8 | function! s:Tests.command.after_all() 9 | call s:Mock.unmock_debugger() 10 | endfunction 11 | 12 | 13 | function! s:Tests.command.test_some_user_command(test) 14 | call g:RubyDebugger.send_command("p \"all users\"") 15 | call g:TU.equal(1, s:Mock.evals, "It should return eval command", a:test) 16 | endfunction 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/tests/exceptions.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.exceptions = {} 2 | 3 | function! s:Tests.exceptions.before_all() 4 | call s:Mock.mock_debugger() 5 | endfunction 6 | 7 | 8 | function! s:Tests.exceptions.after_all() 9 | call s:Mock.unmock_debugger() 10 | endfunction 11 | 12 | 13 | function! s:Tests.exceptions.before() 14 | let s:Breakpoint.id = 0 15 | let g:RubyDebugger.frames = [] 16 | let g:RubyDebugger.exceptions = [] 17 | let g:RubyDebugger.variables = {} 18 | call g:RubyDebugger.queue.empty() 19 | call s:Server._stop_server(s:rdebug_port) 20 | call s:Server._stop_server(s:debugger_port) 21 | endfunction 22 | 23 | 24 | function! s:Tests.exceptions.test_should_not_set_exception_catcher_if_debugger_is_not_running(test) 25 | call g:RubyDebugger.catch_exception("NameError") 26 | call g:TU.equal(0, len(g:RubyDebugger.exceptions), "Exception catcher should not be set", a:test) 27 | endfunction 28 | 29 | 30 | function! s:Tests.exceptions.test_should_clear_exceptions_after_restarting_debugger(test) 31 | exe "Rdebugger" 32 | call g:RubyDebugger.catch_exception("NameError") 33 | call g:TU.equal(1, len(g:RubyDebugger.exceptions), "Exception should be set after starting the server", a:test) 34 | exe "Rdebugger" 35 | call g:TU.equal(0, len(g:RubyDebugger.exceptions), "Exception should be cleared after restarting the server", a:test) 36 | endfunction 37 | 38 | 39 | function! s:Tests.exceptions.test_should_display_exceptions_in_window_breakpoints(test) 40 | exe "Rdebugger" 41 | call g:RubyDebugger.catch_exception("NameError") 42 | call g:RubyDebugger.catch_exception("ArgumentError") 43 | call g:RubyDebugger.open_breakpoints() 44 | call g:TU.match('Exception breakpoints: NameError, ArgumentError', getline(3), "Should show exception breakpoints", a:test) 45 | exe 'close' 46 | endfunction 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/tests/frames.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.frames = {} 2 | 3 | function! s:Tests.frames.before_all() 4 | call s:Mock.mock_debugger() 5 | endfunction 6 | 7 | 8 | function! s:Tests.frames.after_all() 9 | call s:Mock.unmock_debugger() 10 | endfunction 11 | 12 | 13 | function! s:Tests.frames.before() 14 | let s:Breakpoint.id = 0 15 | let g:RubyDebugger.frames = [] 16 | let g:RubyDebugger.variables = {} 17 | call g:RubyDebugger.queue.empty() 18 | call s:Server._stop_server(s:rdebug_port) 19 | call s:Server._stop_server(s:debugger_port) 20 | endfunction 21 | 22 | 23 | function! s:Tests.frames.test_should_display_frames_in_window_frames(test) 24 | let filename = s:Mock.mock_file() 25 | " Replace all windows separators (\) and POSIX separators (/) to [\/] for 26 | " making it cross-platform 27 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 28 | let s:Mock.file = filename 29 | call g:RubyDebugger.send_command('where') 30 | 31 | call g:RubyDebugger.open_frames() 32 | call g:TU.match(getline(2), '1 Current ' . file_pattern . ':2', "Should show first frame", a:test) 33 | call g:TU.match(getline(3), '2 ' . file_pattern . ':3', "Should show second frame", a:test) 34 | 35 | exe 'close' 36 | endfunction 37 | 38 | 39 | function! s:Tests.frames.test_should_open_file_with_frame(test) 40 | let filename = s:Mock.mock_file() 41 | let file_pattern = substitute(filename, '[\/\\]', '[\\\/\\\\]', "g") 42 | let s:Mock.file = filename 43 | " Write 3 lines of text and set 3 frames (on every line) 44 | exe "normal iblablabla" 45 | exe "normal oblabla" 46 | exe "normal obla" 47 | exe "normal gg" 48 | exe "write" 49 | exe "wincmd w" 50 | call g:TU.ok(expand("%") != filename, "It should not be within the file with frame", a:test) 51 | 52 | call g:RubyDebugger.send_command('where') 53 | call g:TU.equal(2, len(g:RubyDebugger.frames), "2 frames should be set", a:test) 54 | 55 | call g:RubyDebugger.open_frames() 56 | exe 'normal 3G' 57 | call s:window_frames_activate_node() 58 | call g:TU.match(expand("%"), file_pattern, "It should open file with frame", a:test) 59 | call g:TU.equal(3, line("."), "It should jump to line with frame", a:test) 60 | call g:RubyDebugger.open_frames() 61 | 62 | call s:Mock.unmock_file(filename) 63 | endfunction 64 | 65 | 66 | function! s:Tests.frames.test_should_clear_frames_after_movement_command(test) 67 | let g:RubyDebugger.frames = [{ 'bla' : 'bla' }] 68 | call g:RubyDebugger.next() 69 | call g:TU.equal([], g:RubyDebugger.frames, "Frames should be cleaned", a:test) 70 | 71 | let g:RubyDebugger.frames = [{ 'bla' : 'bla' }] 72 | call g:RubyDebugger.step() 73 | call g:TU.equal([], g:RubyDebugger.frames, "Frames should be cleaned", a:test) 74 | 75 | let g:RubyDebugger.frames = [{ 'bla' : 'bla' }] 76 | call g:RubyDebugger.continue() 77 | call g:TU.equal([], g:RubyDebugger.frames, "Frames should be cleaned", a:test) 78 | 79 | let g:RubyDebugger.frames = [{ 'bla' : 'bla' }] 80 | call g:RubyDebugger.exit() 81 | call g:TU.equal([], g:RubyDebugger.frames, "Frames should be cleaned", a:test) 82 | endfunction 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/tests/framework.vim: -------------------------------------------------------------------------------- 1 | let TU = { 'output': '', 'errors': '', 'success': ''} 2 | 3 | 4 | function! TU.run(...) 5 | call g:TU.init() 6 | for key in keys(s:Tests) 7 | " Run tests only if function was called without arguments, of argument == 8 | " current tests group. 9 | if !a:0 || a:1 == key 10 | let g:TU.output = g:TU.output . "\n" . key . ":\n" 11 | if has_key(s:Tests[key], 'before_all') 12 | call s:Tests[key].before_all() 13 | endif 14 | for test in keys(s:Tests[key]) 15 | if test =~ '^test_' 16 | if has_key(s:Tests[key], 'before') 17 | call s:Tests[key].before() 18 | endif 19 | call s:Tests[key][test](test) 20 | if has_key(s:Tests[key], 'after') 21 | call s:Tests[key].after() 22 | endif 23 | endif 24 | endfor 25 | if has_key(s:Tests[key], 'after_all') 26 | call s:Tests[key].after_all() 27 | endif 28 | let g:TU.output = g:TU.output . "\n" 29 | endif 30 | endfor 31 | 32 | call g:TU.show_output() 33 | call g:TU.restore() 34 | endfunction 35 | 36 | 37 | function! TU.init() 38 | let g:TU.breakpoint_id = s:Breakpoint.id 39 | let s:Breakpoint.id = 0 40 | 41 | let g:TU.variables = g:RubyDebugger.variables 42 | let g:RubyDebugger.variables = {} 43 | 44 | let g:TU.breakpoints = g:RubyDebugger.breakpoints 45 | let g:RubyDebugger.breakpoints = [] 46 | 47 | let g:TU.var_id = s:Var.id 48 | let s:Var.id = 0 49 | 50 | let s:Mock.breakpoints = 0 51 | let s:Mock.evals = 0 52 | 53 | if s:variables_window.is_open() 54 | call s:variables_window.close() 55 | endif 56 | if s:breakpoints_window.is_open() 57 | call s:breakpoints_window.close() 58 | endif 59 | 60 | let g:TU.output = "" 61 | let g:TU.success = "" 62 | let g:TU.errors = "" 63 | 64 | " For correct closing and deleting test files 65 | let g:TU.hidden = &hidden 66 | set nohidden 67 | endfunction 68 | 69 | 70 | function! TU.restore() 71 | let s:Breakpoint.id = g:TU.breakpoint_id 72 | unlet g:TU.breakpoint_id 73 | 74 | let g:RubyDebugger.variables = g:TU.variables 75 | unlet g:TU.variables 76 | 77 | let g:RubyDebugger.breakpoints = g:TU.breakpoints 78 | unlet g:TU.breakpoints 79 | 80 | let s:Var.id = g:TU.var_id 81 | unlet g:TU.var_id 82 | 83 | let &hidden = g:TU.hidden 84 | endfunction 85 | 86 | 87 | function! TU.show_output() 88 | echo g:TU.output . "\n" . g:TU.errors 89 | endfunction 90 | 91 | 92 | function! TU.ok(condition, description, test) 93 | if a:condition 94 | let g:TU.output = g:TU.output . "." 95 | let g:TU.success = g:TU.success . a:test . ": " . a:description . ", true\n" 96 | else 97 | let g:TU.output = g:TU.output . "F" 98 | let g:TU.errors = g:TU.errors . a:test . ": " . a:description . ", expected true, got false.\n" 99 | endif 100 | endfunction 101 | 102 | 103 | function! TU.equal(expected, actual, description, test) 104 | if a:expected == a:actual 105 | let g:TU.output = g:TU.output . "." 106 | let g:TU.success = g:TU.success . a:test . ": " . a:description . ", equals\n" 107 | else 108 | let g:TU.output = g:TU.output . "F" 109 | let g:TU.errors = g:TU.errors . a:test . ": " . a:description . ", expected " . a:expected . ", got " . a:actual . ".\n" 110 | endif 111 | endfunction 112 | 113 | 114 | function! TU.match(expected, actual, description, test) 115 | if a:expected =~ a:actual 116 | let g:TU.output = g:TU.output . "." 117 | let g:TU.success = g:TU.success . a:test . ": " . a:description . ", match one to other\n" 118 | else 119 | let g:TU.output = g:TU.output . "F" 120 | let g:TU.errors = g:TU.errors . a:test . ": " . a:description . ", expected to match " . a:expected . ", got " . a:actual . ".\n" 121 | endif 122 | endfunction 123 | 124 | 125 | let s:Tests = {} 126 | -------------------------------------------------------------------------------- /src/tests/mock.vim: -------------------------------------------------------------------------------- 1 | let s:Mock = { 'breakpoints': 0, 'evals': 0 } 2 | 3 | function! s:mock_debugger(messages, ...) 4 | let commands = [] 5 | let messages_array = split(a:messages, s:separator) 6 | for message in messages_array 7 | let cmd = "" 8 | if message =~ 'break' 9 | let matches = matchlist(message, 'break \(.*\):\(.*\)') 10 | let cmd = '' 11 | let s:Mock.breakpoints += 1 12 | elseif message =~ 'delete' 13 | let matches = matchlist(message, 'delete \(.*\)') 14 | let cmd = '' 15 | let s:Mock.breakpoints -= 1 16 | elseif message =~ 'var local' 17 | let cmd = '' 18 | let cmd = cmd . '' 19 | let cmd = cmd . '' 20 | let cmd = cmd . '' 21 | let cmd = cmd . '' 22 | let cmd = cmd . '' 23 | let cmd = cmd . '' 24 | elseif message =~ 'var instance -0x2418a904' 25 | let cmd = '' 26 | let cmd = cmd . '' 27 | let cmd = cmd . '' 28 | let cmd = cmd . '' 29 | let cmd = cmd . '' 30 | elseif message =~ 'var instance -0x2418a907' 31 | let cmd = '' 32 | let cmd = cmd . '' 33 | let cmd = cmd . '' 34 | let cmd = cmd . '' 35 | elseif message =~ 'var instance -0x2418a906' 36 | let cmd = '' 37 | let cmd = cmd . '' 38 | let cmd = cmd . '' 39 | let cmd = cmd . '' 40 | elseif message =~ 'var instance -0x2418a914' 41 | let cmd = '' 42 | let cmd = cmd . "" 43 | let cmd = cmd . '' 44 | elseif message =~ 'var instance -0x2418a916' 45 | let cmd = '' 46 | let cmd = cmd . "" 47 | let cmd = cmd . '' 48 | elseif message =~ 'where' 49 | let filename = s:Mock.file 50 | let cmd = '' 51 | let cmd = cmd . "" 52 | let cmd = cmd . "" 53 | let cmd = cmd . '' 54 | elseif message =~ '^p ' 55 | let p = matchlist(message, "^p \\(.*\\)")[1] 56 | let s:Mock.evals += 1 57 | let cmd = '' 58 | endif 59 | if cmd != "" 60 | call add(commands, cmd) 61 | endif 62 | endfor 63 | if !empty(commands) 64 | call writefile([ join(commands, s:separator) ], s:tmp_file) 65 | call g:RubyDebugger.receive_command() 66 | endif 67 | endfunction 68 | 69 | 70 | function! s:Mock.mock_debugger() 71 | let g:RubyDebugger.send_command = function("s:mock_debugger") 72 | endfunction 73 | 74 | 75 | function! s:Mock.unmock_debugger() 76 | let g:RubyDebugger.send_command = function("s:send_message_to_debugger") 77 | endfunction 78 | 79 | 80 | function! s:Mock.mock_file() 81 | let filename = s:runtime_dir . "/tmp/ruby_debugger_test_file" 82 | exe "new " . filename 83 | exe "write" 84 | return filename 85 | endfunction 86 | 87 | 88 | function! s:Mock.unmock_file(filename) 89 | silent exe "close" 90 | call delete(a:filename) 91 | endfunction 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/tests/server.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.server = {} 2 | 3 | function! s:Tests.server.before_all() 4 | let g:RubyDebugger.breakpoints = [] 5 | let g:RubyDebugger.frames = [] 6 | let g:RubyDebugger.variables = {} 7 | endfunction 8 | 9 | function! s:Tests.server.before() 10 | call g:RubyDebugger.queue.empty() 11 | call s:Server._stop_server(s:rdebug_port) 12 | call s:Server._stop_server(s:debugger_port) 13 | endfunction 14 | 15 | function! s:Tests.server.test_should_run_server(test) 16 | exe "Rdebugger" 17 | call g:TU.ok(type(g:RubyDebugger.server) == type({}), "Server should be initialized", a:test) 18 | call g:TU.ok(g:RubyDebugger.server.is_running(), "Server should be run", a:test) 19 | call g:TU.ok(g:RubyDebugger.server.rdebug_pid != "", "Process rdebug-ide should be run", a:test) 20 | call g:TU.ok(g:RubyDebugger.server.debugger_pid != "", "Process debugger.rb should be run", a:test) 21 | endfunction 22 | 23 | 24 | function! s:Tests.server.test_should_stop_server(test) 25 | exe "Rdebugger" 26 | call g:RubyDebugger.server.stop() 27 | call g:TU.ok(!g:RubyDebugger.server.is_running(), "Server should not be run", a:test) 28 | call g:TU.equal("", s:Server._get_pid(s:rdebug_port, 0), "Process rdebug-ide should not exist", a:test) 29 | call g:TU.equal("", s:Server._get_pid(s:debugger_port, 0), "Process debugger.rb should not exist", a:test) 30 | call g:TU.equal("", g:RubyDebugger.server.rdebug_pid, "Pid of rdebug-ide should be nullified", a:test) 31 | call g:TU.equal("", g:RubyDebugger.server.debugger_pid, "Pid of debugger.rb should be nullified", a:test) 32 | endfunction 33 | 34 | 35 | function! s:Tests.server.test_should_kill_old_server_before_starting_new(test) 36 | exe "Rdebugger" 37 | let old_rdebug_pid = g:RubyDebugger.server.rdebug_pid 38 | let old_debugger_pid = g:RubyDebugger.server.debugger_pid 39 | exe "Rdebugger" 40 | call g:TU.ok(g:RubyDebugger.server.is_running(), "Server should be run", a:test) 41 | call g:TU.ok(g:RubyDebugger.server.rdebug_pid != "", "Process rdebug-ide should be run", a:test) 42 | call g:TU.ok(g:RubyDebugger.server.debugger_pid != "", "Process debugger.rb should be run", a:test) 43 | call g:TU.ok(g:RubyDebugger.server.rdebug_pid != old_rdebug_pid, "Rdebug-ide should have new pid", a:test) 44 | call g:TU.ok(g:RubyDebugger.server.debugger_pid != old_debugger_pid, "Debugger.rb should have new pid", a:test) 45 | endfunction 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/tests/variables.vim: -------------------------------------------------------------------------------- 1 | let s:Tests.variables = {} 2 | 3 | function! s:Tests.variables.before_all() 4 | call s:Mock.mock_debugger() 5 | endfunction 6 | 7 | 8 | function! s:Tests.variables.after_all() 9 | call s:Mock.unmock_debugger() 10 | endfunction 11 | 12 | 13 | function! s:Tests.variables.before() 14 | let g:RubyDebugger.breakpoints = [] 15 | let g:RubyDebugger.frames = [] 16 | let g:RubyDebugger.variables = {} 17 | call g:RubyDebugger.queue.empty() 18 | call s:Server._stop_server(s:rdebug_port) 19 | call s:Server._stop_server(s:debugger_port) 20 | endfunction 21 | 22 | 23 | function! s:Tests.variables.test_should_open_window_without_got_variables(test) 24 | call g:RubyDebugger.open_variables() 25 | call g:TU.ok(s:variables_window.is_open(), "Variables window should be opened", a:test) 26 | call g:TU.equal(bufwinnr("%"), s:variables_window.get_number(), "Focus should be into the variables window", a:test) 27 | call g:TU.equal(getline(1), s:variables_window.title, "First line should be name", a:test) 28 | exe 'close' 29 | endfunction 30 | 31 | 32 | " TODO: Now, variables are localized after receiving or 33 | " in ruby_debugger.rb. I don't know how to test them there from here. 34 | "function! s:Tests.variables.test_should_init_variables_after_breakpoint(test) 35 | " let filename = s:Mock.mock_file() 36 | " 37 | " let cmd = '' 38 | " call writefile([ cmd ], s:tmp_file) 39 | " call g:RubyDebugger.receive_command() 40 | " 41 | " call g:TU.equal("VarParent", g:RubyDebugger.variables.type, "Root variable should be initialized", a:test) 42 | " call g:TU.equal(5, len(g:RubyDebugger.variables.children), "4 variables should be initialized", a:test) 43 | " call g:TU.equal(4, len(filter(copy(g:RubyDebugger.variables.children), 'v:val.type == "VarParent"')), "3 Parent variables should be initialized", a:test) 44 | " call g:TU.equal(1, len(filter(copy(g:RubyDebugger.variables.children), 'v:val.type == "VarChild"')), "1 Child variable should be initialized", a:test) 45 | " 46 | " call s:Mock.unmock_file(filename) 47 | "endfunction 48 | 49 | 50 | function! s:Tests.variables.test_should_open_variables_window(test) 51 | call g:RubyDebugger.send_command('var local') 52 | 53 | call g:RubyDebugger.open_variables() 54 | call g:TU.ok(s:variables_window.is_open(), "Variables window should opened", a:test) 55 | call g:TU.equal(bufwinnr("%"), s:variables_window.get_number(), "Focus should be into the variables window", a:test) 56 | call g:TU.equal(getline(1), s:variables_window.title, "First line should be name", a:test) 57 | call g:TU.match(getline(2), '|+self', "Second line should be 'self' variable", a:test) 58 | call g:TU.match(getline(3), '|-some_local', "Third line should be a local variable", a:test) 59 | call g:TU.match(getline(4), '|+array', "4-th line should be an array", a:test) 60 | call g:TU.match(getline(5), '|+quoted_hash', "5-th line should be a hash", a:test) 61 | call g:TU.match(getline(6), '`+hash', "6-th line should be a hash", a:test) 62 | 63 | exe 'close' 64 | endfunction 65 | 66 | 67 | function! s:Tests.variables.test_should_close_variables_window_after_opening(test) 68 | call g:RubyDebugger.send_command('var local') 69 | 70 | call g:RubyDebugger.open_variables() 71 | call g:RubyDebugger.open_variables() 72 | call g:TU.ok(!s:variables_window.is_open(), "Variables window should be closed", a:test) 73 | endfunction 74 | 75 | 76 | function! s:Tests.variables.test_should_open_instance_subvariable(test) 77 | call g:RubyDebugger.send_command('var local') 78 | call g:RubyDebugger.open_variables() 79 | exe 'normal 2G' 80 | 81 | call s:window_variables_activate_node() 82 | call g:TU.ok(s:variables_window.is_open(), "Variables window should opened", a:test) 83 | call g:TU.match(getline(2), '|\~self', "Second line should be opened 'self' variable", a:test) 84 | call g:TU.match(getline(3), '| |+self_array', "Third line should be closed array subvariable", a:test) 85 | call g:TU.match(getline(4), '| |-self_local', "4-th line should be local subvariable", a:test) 86 | call g:TU.match(getline(5), '| `+array', "5-th line should be array", a:test) 87 | call g:TU.match(getline(6), '|-some_local', "6-th line should be local variable", a:test) 88 | 89 | exe 'close' 90 | endfunction 91 | 92 | 93 | function! s:Tests.variables.test_should_open_instance_subvariable_with_quotes(test) 94 | call g:RubyDebugger.send_command('var local') 95 | call g:RubyDebugger.open_variables() 96 | exe 'normal 5G' 97 | 98 | call s:window_variables_activate_node() 99 | call g:TU.ok(s:variables_window.is_open(), "Variables window should opened", a:test) 100 | call g:TU.match(getline(5), '|\~quoted_hash', "5-th line should be hash variable", a:test) 101 | call g:TU.match(getline(6), "| `-'quoted'", "6-th line should be quoted variable", a:test) 102 | 103 | exe 'close' 104 | endfunction 105 | 106 | 107 | function! s:Tests.variables.test_should_close_instance_subvariable(test) 108 | call g:RubyDebugger.send_command('var local') 109 | call g:RubyDebugger.open_variables() 110 | exe 'normal 2G' 111 | 112 | call s:window_variables_activate_node() 113 | call s:window_variables_activate_node() 114 | call g:TU.ok(s:variables_window.is_open(), "Variables window should opened", a:test) 115 | call g:TU.match(getline(2), '|+self', "Second line should be closed 'self' variable", a:test) 116 | 117 | exe 'close' 118 | endfunction 119 | 120 | 121 | function! s:Tests.variables.test_should_open_last_variable_in_list(test) 122 | call g:RubyDebugger.send_command('var local') 123 | call g:RubyDebugger.open_variables() 124 | exe 'normal 6G' 125 | 126 | call s:window_variables_activate_node() 127 | call g:TU.match(getline(6), '`\~hash', "5-th line should be opened hash", a:test) 128 | call g:TU.match(getline(7), ' |-hash_local', "6 line should be local subvariable", a:test) 129 | call g:TU.match(getline(8), ' `+hash_array', "7-th line should be array subvariable", a:test) 130 | 131 | exe 'close' 132 | endfunction 133 | 134 | 135 | function! s:Tests.variables.test_should_open_childs_of_array(test) 136 | call g:RubyDebugger.send_command('var local') 137 | call g:RubyDebugger.open_variables() 138 | exe 'normal 4G' 139 | call s:window_variables_activate_node() 140 | call g:TU.match(getline(4), '|\~array', '4-th line should be opened array', a:test) 141 | call g:TU.match(getline(5), '| |-\[0\]', '5 line should be local subvariable', a:test) 142 | call g:TU.match(getline(6), '| `+\[1\]', '6-th line should be array subvariable', a:test) 143 | 144 | exe 'close' 145 | endfunction 146 | 147 | 148 | function! s:Tests.variables.test_should_clear_variables_after_movement_command(test) 149 | let g:RubyDebugger.variables = { 'bla' : 'bla' } 150 | call g:RubyDebugger.next() 151 | call g:TU.equal({}, g:RubyDebugger.variables, "Variables should be cleaned", a:test) 152 | 153 | let g:RubyDebugger.variables = { 'bla' : 'bla' } 154 | call g:RubyDebugger.step() 155 | call g:TU.equal({}, g:RubyDebugger.variables, "Variables should be cleaned", a:test) 156 | 157 | let g:RubyDebugger.variables = { 'bla' : 'bla' } 158 | call g:RubyDebugger.continue() 159 | call g:TU.equal({}, g:RubyDebugger.variables, "Variables should be cleaned", a:test) 160 | 161 | let g:RubyDebugger.variables = { 'bla' : 'bla' } 162 | call g:RubyDebugger.exit() 163 | call g:TU.equal({}, g:RubyDebugger.variables, "Variables should be cleaned", a:test) 164 | endfunction 165 | 166 | 167 | function! s:Tests.variables.test_should_open_correct_variable_if_variable_has_repeated_name(test) 168 | call g:RubyDebugger.send_command('var local') 169 | call g:RubyDebugger.open_variables() 170 | exe 'normal 2G' 171 | call s:window_variables_activate_node() 172 | exe 'normal 7G' 173 | call s:window_variables_activate_node() 174 | 175 | call g:TU.match(getline(5), '| `+array', "5-th line should be closed array", a:test) 176 | call g:TU.match(getline(6), '|-some_local', "6-th line should be local variable", a:test) 177 | call g:TU.match(getline(7), '|\~array', '7-th line should be opened array', a:test) 178 | call g:TU.match(getline(8), '| |-\[0\]', '8 line should be local subvariable', a:test) 179 | call g:TU.match(getline(9), '| `+\[1\]', '9-th line should be array subvariable', a:test) 180 | 181 | exe 'close' 182 | endfunction 183 | 184 | " Test for issue #6 185 | "function! s:Tests.variables.test_should_update_opened_variables_on_next_suspend(test) 186 | " call g:RubyDebugger.send_command('var local') 187 | " call g:RubyDebugger.open_variables() 188 | " exe 'normal 2G' 189 | " call s:window_variables_activate_node() 190 | " exe 'normal 7G' 191 | " call s:window_variables_activate_node() 192 | " call g:RubyDebugger.next() 193 | " call g:RubyDebugger.open_variables() 194 | " call g:RubyDebugger.open_variables() 195 | " 196 | " call g:TU.equal(7, line("."), "Current line should = 7", a:test) 197 | " call g:TU.match(getline(2), '|\~self', "Second line should be opened 'self' variable", a:test) 198 | " call g:TU.match(getline(3), '| |+self_array', "Third line should be closed array subvariable", a:test) 199 | " call g:TU.match(getline(4), '| |-self_updated', "4-th line should be local subvariable", a:test) 200 | " call g:TU.match(getline(5), '| `+array', "5-th line should be closed array", a:test) 201 | " call g:TU.match(getline(6), '|-some_local', "6-th line should be local variable", a:test) 202 | " call g:TU.match(getline(7), '|\~array', '7-th line should be opened array', a:test) 203 | " call g:TU.match(getline(8), '| `+\[0\]', '9-th line should be array subvariable', a:test) 204 | " call g:TU.match(getline(9), '|+quoted_hash', '9-th line should be array subvariable', a:test) 205 | " 206 | " call g:RubyDebugger.open_variables() 207 | " unlet s:Mock.next 208 | " call s:Mock.unmock_file(s:Mock.file) 209 | " 210 | "endfunction 211 | --------------------------------------------------------------------------------