From 2a11b17ffba94fdf063969ed8f479262d5e86e81 Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 6 Dec 2011 23:09:36 -0800 Subject: [PATCH] Moved vim-ruby-debugger into bundle/git submodule --- .gitmodules | 3 + README.md | 2 +- vim/autoload/ruby_debugger.vim | 1979 ------------------------- vim/bin/ruby_debugger.rb | 140 -- vim/bundle/astashov-vim-ruby-debugger | 1 + 5 files changed, 5 insertions(+), 2120 deletions(-) delete mode 100644 vim/autoload/ruby_debugger.vim delete mode 100644 vim/bin/ruby_debugger.rb create mode 160000 vim/bundle/astashov-vim-ruby-debugger diff --git a/.gitmodules b/.gitmodules index 5a01e11..cd2c7f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "vim/bundle/raimondi-delimitMate"] path = vim/bundle/raimondi-delimitMate url = https://github.com/Raimondi/delimitMate +[submodule "vim/bundle/astashov-vim-ruby-debugger"] + path = vim/bundle/astashov-vim-ruby-debugger + url = https://github.com/astashov/vim-ruby-debugger diff --git a/README.md b/README.md index 54c0999..4b376b6 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Included vim plugins * Try Gstatus and hit '-' on the screen to toggle files. Hit 'd' on the same screen for a diff. * Watch: http://vimcasts.org/blog/2011/05/the-fugitive-series/ * GitGrep - much better than the grep provided with fugitive; use :GitGrep or hit K to grep current word - + * ruby-debug-ide - not quite working for me, but maybe it will for you. supposedly a graphical debugger you can step through Setup for Git diff --git a/vim/autoload/ruby_debugger.vim b/vim/autoload/ruby_debugger.vim deleted file mode 100644 index ebf358a..0000000 --- a/vim/autoload/ruby_debugger.vim +++ /dev/null @@ -1,1979 +0,0 @@ -" Init section - set default values, highlight colors - -let s:rdebug_port = 39767 -let s:debugger_port = 39768 -" hostname() returns something strange in Windows (E98BD9A419BB41D), so set hostname explicitly -let s:hostname = 'localhost' "hostname() -" ~/.vim for Linux, vimfiles for Windows -let s:runtime_dir = expand(':h:h') -" File for communicating between intermediate Ruby script ruby_debugger.rb and -" this plugin -let s:tmp_file = s:runtime_dir . '/tmp/ruby_debugger' -let s:server_output_file = s:runtime_dir . '/tmp/ruby_debugger_output' -" Default id for sign of current line -let s:current_line_sign_id = 120 -let s:separator = "++vim-ruby-debugger separator++" -let s:sign_id = 0 - -" Create tmp directory if it doesn't exist -if !isdirectory(s:runtime_dir . '/tmp') - call mkdir(s:runtime_dir . '/tmp') -endif - -" Init breakpoint signs -hi def link Breakpoint Error -sign define breakpoint linehl=Breakpoint text=xx - -" Init current line signs -hi def link CurrentLine DiffAdd -sign define current_line linehl=CurrentLine text=>> - -" Loads this file. Required for autoloading the code for this plugin -fun! ruby_debugger#load_debugger() - if !s:check_prerequisites() - finish - endif -endf - - -" Check all requirements for the current plugin -fun! s:check_prerequisites() - let problems = [] - if v:version < 700 - call add(problems, "RubyDebugger: This plugin requires Vim >= 7.") - endif - if !has("clientserver") - call add(problems, "RubyDebugger: This plugin requires +clientserver option") - endif - if !executable("rdebug-ide") - call add(problems, "RubyDebugger: You don't have installed 'ruby-debug-ide' gem or executable 'rdebug-ide' can't be found in your PATH") - endif - if !(has("win32") || has("win64")) && !executable("lsof") - call add(problems, "RubyDebugger: You don't have 'lsof' installed or executable 'lsof' can't be found in your PATH") - endif - if g:ruby_debugger_builtin_sender && !has("ruby") - call add(problems, "RubyDebugger: You are trying to use built-in Ruby in Vim, but your Vim doesn't compiled with +ruby. Set g:ruby_debugger_builtin_sender = 0 in your .vimrc to resolve that issue.") - end - if empty(problems) - return 1 - else - for p in problems - echoerr p - endfor - return 0 - endif -endf - - -" End of init section - - -" *** Common (global) functions - -" Split string of tags to List. E.g., -" -" will be splitted to -" [ '', '' ] -function! s:get_tags(cmd) - let tags = [] - let cmd = a:cmd - " Remove wrap tags - let inner_tags_match = s:get_inner_tags(cmd) - if !empty(inner_tags_match) - " Then find every tag and remove it from source string - let pattern = '<.\{-}\/>' - let inner_tags = inner_tags_match[1] - let tagmatch = matchlist(inner_tags, pattern) - while empty(tagmatch) == 0 - call add(tags, tagmatch[0]) - " These symbols are interpretated as special, we need to escape them - let tagmatch[0] = escape(tagmatch[0], '[]~*\') - " Remove it from source string - let inner_tags = substitute(inner_tags, tagmatch[0], '', '') - " Find next tag - let tagmatch = matchlist(inner_tags, pattern) - endwhile - endif - return tags -endfunction - - -" Return match of inner tags without wrap tags. E.g.: -" mathes only -function! s:get_inner_tags(cmd) - return matchlist(a:cmd, '^<.\{-}>\(.\{-}\)<\/.\{-}>$') -endfunction - - -" Return Dict of attributes. -" E.g., from it returns -" {'name' : 'a', 'value' : 'b'} -function! s:get_tag_attributes(cmd) - let attributes = {} - let cmd = a:cmd - " Find type of used quotes (" or ') - let quote_match = matchlist(cmd, "\\w\\+=\\(.\\)") - let quote = empty(quote_match) ? "\"" : escape(quote_match[1], "'\"") - let pattern = "\\(\\w\\+\\)=" . quote . "\\(.\\{-}\\)" . quote - " Find every attribute and remove it from source string - let attrmatch = matchlist(cmd, pattern) - while !empty(attrmatch) - " Values of attributes can be escaped by HTML entities, unescape them - let attributes[attrmatch[1]] = s:unescape_html(attrmatch[2]) - " These symbols are interpretated as special, we need to escape them - let attrmatch[0] = escape(attrmatch[0], '[]~*\') - " Remove it from source string - let cmd = substitute(cmd, attrmatch[0], '', '') - " Find next attribute - let attrmatch = matchlist(cmd, pattern) - endwhile - return attributes -endfunction - - -" Unescape HTML entities -function! s:unescape_html(html) - let result = substitute(a:html, "&", "\\&", "g") - let result = substitute(result, """, "\"", "g") - let result = substitute(result, "<", "<", "g") - let result = substitute(result, ">", ">", "g") - return result -endfunction - - -function! s:quotify(exp) - let quoted = a:exp - let quoted = substitute(quoted, "\"", "\\\\\"", 'g') - return quoted -endfunction - - -" Get filename of current buffer -function! s:get_filename() - return expand("%:p") -endfunction - - -" Send message to debugger. This function should never be used explicitly, -" only through g:RubyDebugger.send_command function -function! s:send_message_to_debugger(message) - if g:ruby_debugger_fast_sender - call system(s:runtime_dir . "/bin/socket " . s:hostname . " " . s:debugger_port . " \"" . a:message . "\"") - else - if g:ruby_debugger_builtin_sender -ruby << RUBY - require 'socket' - attempts = 0 - a = nil - host = VIM::evaluate("s:hostname") - port = VIM::evaluate("s:debugger_port") - message = VIM::evaluate("a:message").gsub("\\\"", '"') - begin - a = TCPSocket.open(host, port) - a.puts(message) - a.close - rescue Errno::ECONNREFUSED - attempts += 1 - if attempts < 400 - sleep 0.05 - retry - else - puts("#{host}:#{port} can not be opened") - exit - end - ensure - a.close if a && !a.closed? - end -RUBY - else - let script = "ruby -e \"require 'socket'; " - let script .= "attempts = 0; " - let script .= "a = nil; " - let script .= "begin; " - let script .= "a = TCPSocket.open('" . s:hostname . "', " . s:debugger_port . "); " - let script .= "a.puts(%q[" . substitute(substitute(a:message, '[', '\[', 'g'), ']', '\]', 'g') . "]);" - let script .= "a.close; " - let script .= "rescue Errno::ECONNREFUSED; " - let script .= "attempts += 1; " - let script .= "if attempts < 400; " - let script .= "sleep 0.05; " - let script .= "retry; " - let script .= "else; " - let script .= "puts('" . s:hostname . ":" . s:debugger_port . " can not be opened'); " - let script .= "exit; " - let script .= "end; " - let script .= "ensure; " - let script .= "a.close if a && !a.closed?; " - let script .= "end; \"" - let output = system(script) - if output =~ 'can not be opened' - call g:RubyDebugger.logger.put("Can't send a message to rdebug - port is not opened") - endif - endif - endif -endfunction - - -function! s:unplace_sign_of_current_line() - if has("signs") - exe ":sign unplace " . s:current_line_sign_id - endif -endfunction - - -" Remove all variables of current line, remove current line sign. Usually it -" is needed before next/step/cont commands -function! s:clear_current_state() - call s:unplace_sign_of_current_line() - let g:RubyDebugger.variables = {} - let g:RubyDebugger.frames = [] - " Clear variables and frames window (just show our empty variables Dict) - if s:variables_window.is_open() - call s:variables_window.open() - endif - if s:frames_window.is_open() - call s:frames_window.open() - endif -endfunction - - -" Open given file and jump to given line -" (stolen from NERDTree) -function! s:jump_to_file(file, line) - "if the file is already open in this tab then just stick the cursor in it - let window_number = bufwinnr('^' . a:file . '$') - if window_number != -1 - exe window_number . "wincmd w" - else - " Check if last accessed window is usable to use it - " Usable window - not quickfix, explorer, modified, etc - if !s:is_window_usable(winnr("#")) - exe s:first_normal_window() . "wincmd w" - else - " If it is usable, jump to it - exe 'wincmd p' - endif - exe "edit " . a:file - endif - exe "normal " . a:line . "G" -endfunction - - -" Return 1 if window is usable (not quickfix, explorer, modified, only one -" window, ...) -function! s:is_window_usable(winnumber) - "If there is only one window (winnr("$") - windows count) - if winnr("$") ==# 1 - return 0 - endif - - " Current window number - let oldwinnr = winnr() - - " Switch to given window and check it - exe a:winnumber . "wincmd p" - let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') - let modified = &modified - - exe oldwinnr . "wincmd p" - - "if it is a special window, e.g. quickfix or another explorer plugin - if specialWindow - return 0 - endif - - if &hidden - return 1 - endif - - " If this window is modified, but there is another opened window with - " current file, return 1. Otherwise - 0 - return !modified || s:buf_in_windows(winbufnr(a:winnumber)) >= 2 -endfunction - - -" Determine the number of windows open to this buffer number. -function! s:buf_in_windows(buffer_number) - let count = 0 - let window_number = 1 - while 1 - let buffer_number = winbufnr(window_number) - if buffer_number < 0 - break - endif - if buffer_number ==# a:buffer_number - let count = count + 1 - endif - let window_number = window_number + 1 - endwhile - - return count -endfunction - - -" Find first 'normal' window (not quickfix, explorer, etc) -function! s:first_normal_window() - let i = 1 - while i <= winnr("$") - let bnum = winbufnr(i) - if bnum != -1 && getbufvar(bnum, '&buftype') ==# '' && !getwinvar(i, '&previewwindow') - return i - endif - let i += 1 - endwhile - return -1 -endfunction - -" *** Queue class (start) - -let s:Queue = {} - -" ** Public methods - -" Constructor of new queue. -function! s:Queue.new() dict - let var = copy(self) - let var.queue = [] - let var.after = "" - return var -endfunction - - -" Execute next command in the queue and remove it from queue -function! s:Queue.execute() dict - if !empty(self.queue) - let message = join(self.queue, s:separator) - call self.empty() - call g:RubyDebugger.send_command(message) - endif -endfunction - - -" Execute 'after' hook only if queue is empty -function! s:Queue.after_hook() dict - if self.after != "" && empty(self.queue) - call self.after() - endif -endfunction - - -function! s:Queue.add(element) dict - call add(self.queue, a:element) -endfunction - - -function! s:Queue.empty() dict - let self.queue = [] -endfunction - - -" *** Queue class (end) - - - - -" *** Public interface (start) - -let RubyDebugger = { 'commands': {}, 'variables': {}, 'settings': {}, 'breakpoints': [], 'frames': [], 'exceptions': [] } -let g:RubyDebugger.queue = s:Queue.new() - - -" Run debugger server. It takes one optional argument with path to debugged -" ruby script ('script/server webrick' by default) -function! RubyDebugger.start(...) dict - let g:RubyDebugger.server = s:Server.new(s:hostname, s:rdebug_port, s:debugger_port, s:runtime_dir, s:tmp_file, s:server_output_file) - let script_string = a:0 && !empty(a:1) ? a:1 : 'script/server webrick' - if script_string[0] != '/' - let script_string = "'" . getcwd() . '/' . substitute(script_string, "'", "", "g") . "'" - endif - - echo "Loading debugger..." - call g:RubyDebugger.server.start(script_string) - - let g:RubyDebugger.exceptions = [] - for breakpoint in g:RubyDebugger.breakpoints - call g:RubyDebugger.queue.add(breakpoint.command()) - endfor - call g:RubyDebugger.queue.add('start') - echo "Debugger started" - call g:RubyDebugger.queue.execute() -endfunction - - -" Stop running server. -function! RubyDebugger.stop() dict - if has_key(g:RubyDebugger, 'server') - call g:RubyDebugger.server.stop() - endif -endfunction - - -" This function receives commands from the debugger. When ruby_debugger.rb -" gets output from rdebug-ide, it writes it to the special file and 'kick' -" the plugin by remotely calling RubyDebugger.receive_command(), e.g.: -" vim --servername VIM --remote-send 'call RubyDebugger.receive_command()' -" That's why +clientserver is required -" This function analyzes the special file and gives handling to right command -function! RubyDebugger.receive_command() dict - let file_contents = join(readfile(s:tmp_file), "") - call g:RubyDebugger.logger.put("Received command: " . file_contents) - let commands = split(file_contents, s:separator) - for cmd in commands - if !empty(cmd) - if match(cmd, '') != -1 - call g:RubyDebugger.commands.set_variables(cmd) - elseif match(cmd, '') != -1 - call g:RubyDebugger.commands.error(cmd) - elseif match(cmd, '') != -1 - call g:RubyDebugger.commands.message(cmd) - elseif match(cmd, '') != -1 - call g:RubyDebugger.commands.trace(cmd) - endif - endif - endfor - call g:RubyDebugger.queue.after_hook() - call g:RubyDebugger.queue.execute() -endfunction - - -function! RubyDebugger.send_command_wrapper(command) - call g:RubyDebugger.send_command(a:command) -endfunction - -" We set function this way, because we want have possibility to mock it by -" other function in tests -let RubyDebugger.send_command = function("send_message_to_debugger") - - -" Open variables window -function! RubyDebugger.open_variables() dict - call s:variables_window.toggle() - call g:RubyDebugger.logger.put("Opened variables window") - call g:RubyDebugger.queue.execute() -endfunction - - -" Open breakpoints window -function! RubyDebugger.open_breakpoints() dict - call s:breakpoints_window.toggle() - call g:RubyDebugger.logger.put("Opened breakpoints window") - call g:RubyDebugger.queue.execute() -endfunction - - -" Open frames window -function! RubyDebugger.open_frames() dict - call s:frames_window.toggle() - call g:RubyDebugger.logger.put("Opened frames window") - call g:RubyDebugger.queue.execute() -endfunction - - -" Set/remove breakpoint at current position. If argument -" is given, it will set conditional breakpoint (argument is condition) -function! RubyDebugger.toggle_breakpoint(...) dict - let line = line(".") - let file = s:get_filename() - let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') - " If breakpoint with current file/line doesn't exist, create it. Otherwise - - " remove it - if empty(existed_breakpoints) - let breakpoint = s:Breakpoint.new(file, line) - call add(g:RubyDebugger.breakpoints, breakpoint) - call breakpoint.send_to_debugger() - else - let breakpoint = existed_breakpoints[0] - call filter(g:RubyDebugger.breakpoints, 'v:val.id != ' . breakpoint.id) - call breakpoint.delete() - endif - " Update info in Breakpoints window - if s:breakpoints_window.is_open() - call s:breakpoints_window.open() - exe "wincmd p" - endif - call g:RubyDebugger.queue.execute() -endfunction - - -" Remove all breakpoints -function! RubyDebugger.remove_breakpoints() dict - for breakpoint in g:RubyDebugger.breakpoints - call breakpoint.delete() - endfor - let g:RubyDebugger.breakpoints = [] - call g:RubyDebugger.queue.execute() -endfunction - - -" Eval the passed in expression -function! RubyDebugger.eval(exp) dict - let quoted = s:quotify(a:exp) - call g:RubyDebugger.queue.add("eval " . quoted) - call g:RubyDebugger.queue.execute() -endfunction - - -" Sets conditional breakpoint where cursor is placed -function! RubyDebugger.conditional_breakpoint(exp) dict - let line = line(".") - let file = s:get_filename() - let existed_breakpoints = filter(copy(g:RubyDebugger.breakpoints), 'v:val.line == ' . line . ' && v:val.file == "' . escape(file, '\') . '"') - " If breakpoint with current file/line doesn't exist, create it. Otherwise - - " remove it - if empty(existed_breakpoints) - echo "You can set condition only to already set breakpoints. Move cursor to set breakpoint and add condition" - else - let breakpoint = existed_breakpoints[0] - let quoted = s:quotify(a:exp) - call breakpoint.add_condition(quoted) - " Update info in Breakpoints window - if s:breakpoints_window.is_open() - call s:breakpoints_window.open() - exe "wincmd p" - endif - call g:RubyDebugger.queue.execute() - endif -endfunction - - -" Catch all exceptions with given name -function! RubyDebugger.catch_exception(exp) dict - if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() - let quoted = s:quotify(a:exp) - let exception = s:Exception.new(quoted) - call add(g:RubyDebugger.exceptions, exception) - if s:breakpoints_window.is_open() - call s:breakpoints_window.open() - exe "wincmd p" - endif - call g:RubyDebugger.queue.execute() - else - echo "Sorry, but you can set Exceptional Breakpoints only with running debugger" - endif -endfunction - - -" Next -function! RubyDebugger.next() dict - call g:RubyDebugger.queue.add("next") - call s:clear_current_state() - call g:RubyDebugger.logger.put("Step over") - call g:RubyDebugger.queue.execute() -endfunction - - -" Step -function! RubyDebugger.step() dict - call g:RubyDebugger.queue.add("step") - call s:clear_current_state() - call g:RubyDebugger.logger.put("Step into") - call g:RubyDebugger.queue.execute() -endfunction - - -" Finish -function! RubyDebugger.finish() dict - call g:RubyDebugger.queue.add("finish") - call s:clear_current_state() - call g:RubyDebugger.logger.put("Step out") - call g:RubyDebugger.queue.execute() -endfunction - - -" Continue -function! RubyDebugger.continue() dict - call g:RubyDebugger.queue.add("cont") - call s:clear_current_state() - call g:RubyDebugger.logger.put("Continue") - call g:RubyDebugger.queue.execute() -endfunction - - -" Exit -function! RubyDebugger.exit() dict - call g:RubyDebugger.queue.add("exit") - call s:clear_current_state() - call g:RubyDebugger.queue.execute() -endfunction - - -" Show output log of Ruby script -function! RubyDebugger.show_log() dict - exe "view " . s:server_output_file - setlocal autoread - " Per gorkunov's request - setlocal wrap - setlocal nonumber - if exists(":AnsiEsc") - exec ":AnsiEsc" - endif -endfunction - - -" Debug current opened test -function! RubyDebugger.run_test() dict - let file = s:get_filename() - if file =~ '_spec\.rb$' - call g:RubyDebugger.start(g:ruby_debugger_spec_path . ' ' . file) - elseif file =~ '\.feature$' - call g:RubyDebugger.start(g:ruby_debugger_cucumber_path . ' ' . file) - elseif file =~ '_test\.rb$' - call g:RubyDebugger.start(file) - endif -endfunction - - -" *** Public interface (end) - - - - -" *** RubyDebugger Commands (what debugger returns) - - -" -" -" Jump to file/line where execution was suspended, set current line sign and get local variables -function! RubyDebugger.commands.jump_to_breakpoint(cmd) dict - let attrs = s:get_tag_attributes(a:cmd) - call s:jump_to_file(attrs.file, attrs.line) - call g:RubyDebugger.logger.put("Jumped to breakpoint " . attrs.file . ":" . attrs.line) - - if has("signs") - exe ":sign place " . s:current_line_sign_id . " line=" . attrs.line . " name=current_line file=" . attrs.file - endif -endfunction - - -" -" Show message error and jump to given file/line -function! RubyDebugger.commands.handle_exception(cmd) dict - let message_match = matchlist(a:cmd, 'message="\(.\{-}\)"') - call g:RubyDebugger.commands.jump_to_breakpoint(a:cmd) - echo "Exception message: " . s:unescape_html(message_match[1]) -endfunction - - -" -" Confirm setting of exception catcher -function! RubyDebugger.commands.set_exception(cmd) dict - let attrs = s:get_tag_attributes(a:cmd) - call g:RubyDebugger.logger.put("Exception successfully set: " . attrs.exception) -endfunction - - -" -" Add debugger info to breakpoints (pid of debugger, debugger breakpoint's id) -" Assign rest breakpoints to debugger recursively, if there are breakpoints -" from old server runnings or not assigned breakpoints (e.g., if you at first -" set some breakpoints, and then run the debugger by :Rdebugger) -function! RubyDebugger.commands.set_breakpoint(cmd) - let attrs = s:get_tag_attributes(a:cmd) - let file_match = matchlist(attrs.location, '\(.*\):\(.*\)') - let pid = g:RubyDebugger.server.rdebug_pid - - " Find added breakpoint in array and assign debugger's info to it - for breakpoint in g:RubyDebugger.breakpoints - if expand(breakpoint.file) == expand(file_match[1]) && expand(breakpoint.line) == expand(file_match[2]) - let breakpoint.debugger_id = attrs.no - let breakpoint.rdebug_pid = pid - if has_key(breakpoint, 'condition') - call breakpoint.add_condition(breakpoint.condition) - endif - endif - endfor - - call g:RubyDebugger.logger.put("Breakpoint is set: " . file_match[1] . ":" . file_match[2]) - call g:RubyDebugger.queue.execute() -endfunction - - -" -" -" -" Assign list of got variables to parent variable and (optionally) show them -function! RubyDebugger.commands.set_variables(cmd) - let tags = s:get_tags(a:cmd) - let list_of_variables = [] - - " Create hash from list of tags - for tag in tags - let attrs = s:get_tag_attributes(tag) - let variable = s:Var.new(attrs) - call add(list_of_variables, variable) - endfor - - " If there is no variables, create unnamed root variable. Local variables - " will be chilren of this variable - if g:RubyDebugger.variables == {} - let g:RubyDebugger.variables = s:VarParent.new({'hasChildren': 'true'}) - let g:RubyDebugger.variables.is_open = 1 - let g:RubyDebugger.variables.children = [] - endif - - " If g:RubyDebugger.current_variable exists, then it contains parent - " variable of got subvariables. Assign them to it. - if has_key(g:RubyDebugger, 'current_variable') - let variable = g:RubyDebugger.current_variable - if variable != {} - call variable.add_childs(list_of_variables) - call g:RubyDebugger.logger.put("Opening child variable: " . variable.attributes.objectId) - " Variables Window is always open if we got subvariables - call s:variables_window.open() - else - call g:RubyDebugger.logger.put("Can't found variable") - endif - unlet g:RubyDebugger.current_variable - else - " Otherwise, assign them to unnamed root variable - if g:RubyDebugger.variables.children == [] - call g:RubyDebugger.variables.add_childs(list_of_variables) - call g:RubyDebugger.logger.put("Initializing local variables") - if s:variables_window.is_open() - " show variables only if Variables Window is open - call s:variables_window.open() - endif - endif - endif - -endfunction - - -" -" Just show result of evaluation -function! RubyDebugger.commands.eval(cmd) - " rdebug-ide-gem doesn't escape attributes of tag properly, so we should not - " use usual attribute extractor here... - let match = matchlist(a:cmd, "") - echo "Evaluated expression:\n" . s:unescape_html(match[1]) ."\nResulted value is:\n" . match[2] . "\n" -endfunction - - -" -" Just show exception message -function! RubyDebugger.commands.processing_exception(cmd) - let attrs = s:get_tag_attributes(a:cmd) - let message = "RubyDebugger Exception, type: " . attrs.type . ", message: " . attrs.message - echo message - call g:RubyDebugger.logger.put(message) -endfunction - - -" -" -" -" -" Assign all frames, fill Frames window by them -function! RubyDebugger.commands.trace(cmd) - let tags = s:get_tags(a:cmd) - let list_of_frames = [] - - " Create hash from list of tags - for tag in tags - let attrs = s:get_tag_attributes(tag) - let frame = s:Frame.new(attrs) - call add(list_of_frames, frame) - endfor - - let g:RubyDebugger.frames = list_of_frames - - if s:frames_window.is_open() - " show backtrace only if Backtrace Window is open - call s:frames_window.open() - endif -endfunction - - -" Error -" Just show error -function! RubyDebugger.commands.error(cmd) - let error_match = s:get_inner_tags(a:cmd) - if !empty(error_match) - let error = error_match[1] - echo "RubyDebugger Error: " . error - call g:RubyDebugger.logger.put("Got error: " . error) - endif -endfunction - - -" Message -" Just show message -function! RubyDebugger.commands.message(cmd) - let message_match = s:get_inner_tags(a:cmd) - if !empty(message_match) - let message = message_match[1] - echo "RubyDebugger Message: " . message - call g:RubyDebugger.logger.put("Got message: " . message) - endif -endfunction - - -" *** End of debugger Commands - - - -" *** Window class (start). Abstract Class for creating window. -" Must be inherited. Mostly, stolen from the NERDTree. - -let s:Window = {} -let s:Window['next_buffer_number'] = 1 -let s:Window['position'] = 'botright' -let s:Window['size'] = 10 - -" ** Public methods - -" Constructs new window -function! s:Window.new(name, title) dict - let new_variable = copy(self) - let new_variable.name = a:name - let new_variable.title = a:title - return new_variable -endfunction - - -" Clear all data from window -function! s:Window.clear() dict - silent 1,$delete _ -endfunction - - -" Close window -function! s:Window.close() dict - if !self.is_open() - throw "RubyDebug: Window " . self.name . " is not open" - endif - - if winnr("$") != 1 - call self.focus() - close - exe "wincmd p" - else - " If this is only one window, just quit - :q - endif - call self._log("Closed window with name: " . self.name) -endfunction - - -" Get window number -function! s:Window.get_number() dict - if self._exist_for_tab() - return bufwinnr(self._buf_name()) - else - return -1 - endif -endfunction - - -" Display data to the window -function! s:Window.display() - call self._log("Start displaying data in window with name: " . self.name) - call self.focus() - setlocal modifiable - - let current_line = line(".") - let current_column = col(".") - let top_line = line("w0") - - call self.clear() - - call self._insert_data() - call self._restore_view(top_line, current_line, current_column) - - setlocal nomodifiable - call self._log("Complete displaying data in window with name: " . self.name) -endfunction - - -" Put cursor to the window -function! s:Window.focus() dict - exe self.get_number() . " wincmd w" - call self._log("Set focus to window with name: " . self.name) -endfunction - - -" Return 1 if window is opened -function! s:Window.is_open() dict - return self.get_number() != -1 -endfunction - - -" Open window and display data (stolen from NERDTree) -function! s:Window.open() dict - if !self.is_open() - " create the window - silent exec self.position . ' ' . self.size . ' new' - - if !self._exist_for_tab() - " If the window is not opened/exists, create new - call self._set_buf_name(self._next_buffer_name()) - silent! exec "edit " . self._buf_name() - " This function does not exist in Window class and should be declared in - " descendants - call self.bind_mappings() - else - " Or just jump to opened buffer - silent! exec "buffer " . self._buf_name() - endif - - " set buffer options - setlocal winfixheight - setlocal noswapfile - setlocal buftype=nofile - setlocal nowrap - setlocal foldcolumn=0 - setlocal nobuflisted - setlocal nospell - setlocal nolist - iabc - setlocal cursorline - setfiletype ruby_debugger_window - call self._log("Opened window with name: " . self.name) - endif - - if has("syntax") && exists("g:syntax_on") && !has("syntax_items") - call self.setup_syntax_highlighting() - endif - - call self.display() -endfunction - - -" Open/close window -function! s:Window.toggle() dict - call self._log("Toggling window with name: " . self.name) - if self._exist_for_tab() && self.is_open() - call self.close() - else - call self.open() - end -endfunction - - -" ** Private methods - - -" Return buffer name, that is stored in tab variable -function! s:Window._buf_name() dict - return t:window_{self.name}_buf_name -endfunction - - -" Return 1 if the window exists in current tab -function! s:Window._exist_for_tab() dict - return exists("t:window_" . self.name . "_buf_name") -endfunction - - -" Insert data to the window -function! s:Window._insert_data() dict - let old_p = @p - " Put data to the register and then show it by 'put' command - let @p = self.render() - silent exe "normal \"pP" - let @p = old_p - call self._log("Inserted data to window with name: " . self.name) -endfunction - - -function! s:Window._log(string) dict - if has_key(self, 'logger') - call self.logger.put(a:string) - endif -endfunction - - -" Calculate correct name for the window -function! s:Window._next_buffer_name() dict - let name = self.name . s:Window.next_buffer_number - let s:Window.next_buffer_number += 1 - return name -endfunction - - -" Restore the view -function! s:Window._restore_view(top_line, current_line, current_column) dict - let old_scrolloff=&scrolloff - let &scrolloff=0 - call cursor(a:top_line, 1) - normal! zt - call cursor(a:current_line, a:current_column) - let &scrolloff = old_scrolloff - call self._log("Restored view of window with name: " . self.name) -endfunction - - -function! s:Window._set_buf_name(name) dict - let t:window_{self.name}_buf_name = a:name -endfunction - - -" *** Window class (end) - - - -" *** WindowVariables class (start) - -" Inherits variables window from abstract window class -let s:WindowVariables = copy(s:Window) - -" ** Public methods - -function! s:WindowVariables.bind_mappings() - nnoremap <2-leftmouse> :call window_variables_activate_node() - nnoremap o :call window_variables_activate_node()" -endfunction - - -" Returns string that contains all variables (for Window.display()) -function! s:WindowVariables.render() dict - let variables = self.title . "\n" - let variables .= (g:RubyDebugger.variables == {} ? '' : g:RubyDebugger.variables.render()) - return variables -endfunction - - -" TODO: Is there some way to call s:WindowVariables.activate_node from mapping -" command? -" Expand/collapse variable under cursor -function! s:window_variables_activate_node() - let variable = s:Var.get_selected() - if variable != {} && variable.type == "VarParent" - if variable.is_open - call variable.close() - else - call variable.open() - endif - endif - call g:RubyDebugger.queue.execute() -endfunction - - -" Add syntax highlighting -function! s:WindowVariables.setup_syntax_highlighting() - execute "syn match rdebugTitle #" . self.title . "#" - - syn match rdebugPart #[| `]\+# - syn match rdebugPartFile #[| `]\+-# contains=rdebugPart nextgroup=rdebugChild contained - syn match rdebugChild #.\{-}\t# nextgroup=rdebugType contained - - syn match rdebugClosable #[| `]\+\~# contains=rdebugPart nextgroup=rdebugParent contained - syn match rdebugOpenable #[| `]\++# contains=rdebugPart nextgroup=rdebugParent contained - syn match rdebugParent #.\{-}\t# nextgroup=rdebugType contained - - syn match rdebugType #.\{-}\t# nextgroup=rdebugValue contained - syn match rdebugValue #.*\t#he=e-1 nextgroup=rdebugId contained - syn match rdebugId #.*# contained - - syn match rdebugParentLine '[| `]\+[+\~].*' contains=rdebugClosable,rdebugOpenable transparent - syn match rdebugChildLine '[| `]\+-.*' contains=rdebugPartFile transparent - - hi def link rdebugTitle Identifier - hi def link rdebugClosable Type - hi def link rdebugOpenable Title - hi def link rdebugPart Special - hi def link rdebugPartFile Type - hi def link rdebugChild Normal - hi def link rdebugParent Directory - hi def link rdebugType Type - hi def link rdebugValue Special - hi def link rdebugId Ignore -endfunction - - -" *** WindowVariables class (end) - - - -" *** WindowBreakpoints class (start) - -" Inherits WindowBreakpoints from Window -let s:WindowBreakpoints = copy(s:Window) - -" ** Public methods - -function! s:WindowBreakpoints.bind_mappings() - nnoremap <2-leftmouse> :call window_breakpoints_activate_node() - nnoremap o :call window_breakpoints_activate_node() - nnoremap d :call window_breakpoints_delete_node() -endfunction - - -" Returns string that contains all breakpoints (for Window.display()) -function! s:WindowBreakpoints.render() dict - let breakpoints = "" - let breakpoints .= self.title . "\n" - for breakpoint in g:RubyDebugger.breakpoints - let breakpoints .= breakpoint.render() - endfor - let exceptions = map(copy(g:RubyDebugger.exceptions), 'v:val.render()') - let breakpoints .= "\nException breakpoints: " . join(exceptions, ", ") - return breakpoints -endfunction - - -" TODO: Is there some way to call s:WindowBreakpoints.activate_node from mapping -" command? -" Open breakpoint under cursor -function! s:window_breakpoints_activate_node() - let breakpoint = s:Breakpoint.get_selected() - if breakpoint != {} - call breakpoint.open() - endif -endfunction - - -" Delete breakpoint under cursor -function! s:window_breakpoints_delete_node() - let breakpoint = s:Breakpoint.get_selected() - if breakpoint != {} - call breakpoint.delete() - call filter(g:RubyDebugger.breakpoints, "v:val.id != " . breakpoint.id) - call s:breakpoints_window.open() - endif -endfunction - - -" Add syntax highlighting -function! s:WindowBreakpoints.setup_syntax_highlighting() dict - execute "syn match rdebugTitle #" . self.title . "#" - - syn match rdebugId "^\d\+\s" contained nextgroup=rdebugDebuggerId - syn match rdebugDebuggerId "\d*\s" contained nextgroup=rdebugFile - syn match rdebugFile ".*:" contained nextgroup=rdebugLine - syn match rdebugLine "\d\+" contained - - syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent - - hi def link rdebugId Directory - hi def link rdebugDebuggerId Type - hi def link rdebugFile Normal - hi def link rdebugLine Special -endfunction - - -" *** WindowBreakpoints class (end) - - - -" *** WindowFrames class (start) - -" Inherits WindowFrames from Window -let s:WindowFrames = copy(s:Window) - -" ** Public methods - -function! s:WindowFrames.bind_mappings() - nnoremap <2-leftmouse> :call window_frames_activate_node() - nnoremap o :call window_frames_activate_node() -endfunction - - -" Returns string that contains all frames (for Window.display()) -function! s:WindowFrames.render() dict - let frames = "" - let frames .= self.title . "\n" - for frame in g:RubyDebugger.frames - let frames .= frame.render() - endfor - return frames -endfunction - - -" Open frame under cursor -function! s:window_frames_activate_node() - let frame = s:Frame.get_selected() - if frame != {} - call frame.open() - endif -endfunction - - -" Add syntax highlighting -function! s:WindowFrames.setup_syntax_highlighting() dict - execute "syn match rdebugTitle #" . self.title . "#" - - syn match rdebugId "^\d\+\s" contained nextgroup=rdebugFile - syn match rdebugFile ".*:" contained nextgroup=rdebugLine - syn match rdebugLine "\d\+" contained - - syn match rdebugWrapper "^\d\+.*" contains=rdebugId transparent - - hi def link rdebugId Directory - hi def link rdebugFile Normal - hi def link rdebugLine Special -endfunction - - -" *** WindowFrames class (end) - - - - -" *** Var proxy class (start) - -let s:Var = { 'id' : 0 } - -" ** Public methods - -" This is a proxy method for creating new variable -function! s:Var.new(attrs) - if has_key(a:attrs, 'hasChildren') && a:attrs['hasChildren'] == 'true' - return s:VarParent.new(a:attrs) - else - return s:VarChild.new(a:attrs) - end -endfunction - - -" Get variable under cursor -function! s:Var.get_selected() - let line = getline(".") - " Get its id - it is last in the string - let match = matchlist(line, '.*\t\(\d\+\)$') - let id = get(match, 1) - if id - let variable = g:RubyDebugger.variables.find_variable({'id' : id}) - return variable - else - return {} - endif -endfunction - - -" *** Var proxy class (end) - - - -" *** VarChild class (start) - -let s:VarChild = {} - -" ** Public methods - -" Constructs new variable without childs -function! s:VarChild.new(attrs) - let new_variable = copy(self) - let new_variable.attributes = a:attrs - let new_variable.parent = {} - let new_variable.level = 0 - let new_variable.type = "VarChild" - let s:Var.id += 1 - let new_variable.id = s:Var.id - return new_variable -endfunction - - -" Renders data of the variable -function! s:VarChild.render() - return self._render(0, 0, [], len(self.parent.children) ==# 1) -endfunction - - -" VarChild can't be opened because it can't have children. But VarParent can -function! s:VarChild.open() - return 0 -endfunction - - -" VarChild can't be closed because it can't have children. But VarParent can -function! s:VarChild.close() - return 0 -endfunction - - -" VarChild can't be parent. But VarParent can. If Var have hasChildren == -" true, then it is parent -function! s:VarChild.is_parent() - return has_key(self.attributes, 'hasChildren') && get(self.attributes, 'hasChildren') ==# 'true' -endfunction - - -" Output format for Variables Window -function! s:VarChild.to_s() - return get(self.attributes, "name", "undefined") . "\t" . get(self.attributes, "type", "undefined") . "\t" . get(self.attributes, "value", "undefined") . "\t" . get(self, "id", "0") -endfunction - - -" Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} -function! s:VarChild.find_variable(attrs) - if self._match_attributes(a:attrs) - return self - else - return {} - endif -endfunction - - -" Find and return array of variables that match given Dict of attrs -function! s:VarChild.find_variables(attrs) - let variables = [] - if self._match_attributes(a:attrs) - call add(variables, self) - endif - return variables -endfunction - - -" ** Private methods - - -" Recursive function, that renders Variable and all its childs (if they are -" presented). Stolen from NERDTree -function! s:VarChild._render(depth, draw_text, vertical_map, is_last_child) - let output = "" - if a:draw_text ==# 1 - let tree_parts = '' - - " get all the leading spaces and vertical tree parts for this line - if a:depth > 1 - for j in a:vertical_map[0:-2] - if j ==# 1 - let tree_parts = tree_parts . '| ' - else - let tree_parts = tree_parts . ' ' - endif - endfor - endif - - " get the last vertical tree part for this line which will be different - " if this node is the last child of its parent - if a:is_last_child - let tree_parts = tree_parts . '`' - else - let tree_parts = tree_parts . '|' - endif - - " smack the appropriate dir/file symbol on the line before the file/dir - " name itself - if self.is_parent() - if self.is_open - let tree_parts = tree_parts . '~' - else - let tree_parts = tree_parts . '+' - endif - else - let tree_parts = tree_parts . '-' - endif - let line = tree_parts . self.to_s() - let output = output . line . "\n" - - endif - - if self.is_parent() && self.is_open - if len(self.children) > 0 - - " draw all the nodes children except the last - let last_index = len(self.children) - 1 - if last_index > 0 - for i in self.children[0:last_index - 1] - let output = output . i._render(a:depth + 1, 1, add(copy(a:vertical_map), 1), 0) - endfor - endif - - " draw the last child, indicating that it IS the last - let output = output . self.children[last_index]._render(a:depth + 1, 1, add(copy(a:vertical_map), 0), 1) - - endif - endif - - return output - -endfunction - - -" Return 1 if *all* given attributes (pairs key/value) match to current -" variable -function! s:VarChild._match_attributes(attrs) - let conditions = 1 - for attr in keys(a:attrs) - if has_key(self.attributes, attr) - " If current key is contained in attributes of variable (they were - " attributes in tag, then trying to match there. - let conditions = conditions && self.attributes[attr] == a:attrs[attr] - elseif has_key(self, attr) - " Otherwise, if current key is contained in auxiliary attributes of the - " variable, trying to match there - let conditions = conditions && self[attr] == a:attrs[attr] - else - " Otherwise, this variable is not match - let conditions = 0 - break - endif - endfor - return conditions -endfunction - - -" *** VarChild class (end) - - - - -" *** VarParent class (start) - -" Inherits VarParent from VarChild -let s:VarParent = copy(s:VarChild) - -" ** Public methods - - -" Initializes new variable with childs -function! s:VarParent.new(attrs) - if !has_key(a:attrs, 'hasChildren') || a:attrs['hasChildren'] != 'true' - throw "RubyDebug: VarParent must be initialized with hasChildren = true" - endif - let new_variable = copy(self) - let new_variable.attributes = a:attrs - let new_variable.parent = {} - let new_variable.is_open = 0 - let new_variable.level = 0 - let new_variable.children = [] - let new_variable.type = "VarParent" - let s:Var.id += 1 - let new_variable.id = s:Var.id - return new_variable -endfunction - - -" Open variable, init its children and display them -function! s:VarParent.open() - let self.is_open = 1 - call self._init_children() - return 0 -endfunction - - -" Close variable and display it -function! s:VarParent.close() - let self.is_open = 0 - call s:variables_window.display() - if has_key(g:RubyDebugger, "current_variable") - unlet g:RubyDebugger.current_variable - endif - return 0 -endfunction - - -" Renders data of the variable -function! s:VarParent.render() - return self._render(0, 0, [], len(self.children) ==# 1) -endfunction - - - -" Add childs to the variable. You always should use this method instead of -" explicit assigning to children property (like 'add(self.children, variables)') -function! s:VarParent.add_childs(childs) - " If children are given by array, extend self.children by this array - if type(a:childs) == type([]) - for child in a:childs - let child.parent = self - let child.level = self.level + 1 - endfor - call extend(self.children, a:childs) - else - " Otherwise, add child to self.children - let a:childs.parent = self - let child.level = self.level + 1 - call add(self.children, a:childs) - end -endfunction - - -" Find and return variable by given Dict of attrs, e.g.: {'name' : 'var1'} -" If current variable doesn't match these attributes, try to find in children -function! s:VarParent.find_variable(attrs) - if self._match_attributes(a:attrs) - return self - else - for child in self.children - let result = child.find_variable(a:attrs) - if result != {} - return result - endif - endfor - endif - return {} -endfunction - - -" Find and return array of variables that match given Dict of attrs. -" Try to match current variable and its children -function! s:VarParent.find_variables(attrs) - let variables = [] - if self._match_attributes(a:attrs) - call add(variables, self) - endif - for child in self.children - call extend(variables, child.find_variables(a:attrs)) - endfor - return variables -endfunction - - -" ** Private methods - - -" Update children of the variable -function! s:VarParent._init_children() - " Remove all the current child nodes - let self.children = [] - - " Get children - if has_key(self.attributes, 'objectId') - let g:RubyDebugger.current_variable = self - call g:RubyDebugger.queue.add('var instance ' . self.attributes.objectId) - endif - -endfunction - - -" *** VarParent class (end) - - - -" *** Logger class (start) - - -let s:Logger = {} - -function! s:Logger.new(file) - let new_variable = copy(self) - let new_variable.file = a:file - call writefile([], new_variable.file) - return new_variable -endfunction - - -" Log datetime and then message -function! s:Logger.put(string) - let file = readfile(self.file) - let string = strftime("%Y/%m/%d %H:%M:%S") . ' ' . a:string - call add(file, string) - call writefile(file, self.file) -endfunction - - -" *** Logger class (end) - - -" *** Breakpoint class (start) - -let s:Breakpoint = { 'id': 0 } - -" ** Public methods - -" Constructor of new brekpoint. Create new breakpoint and set sign. -function! s:Breakpoint.new(file, line) - let var = copy(self) - let var.file = a:file - let var.line = a:line - let s:Breakpoint.id += 1 - let var.id = s:Breakpoint.id - - call var._set_sign() - call var._log("Set breakpoint to: " . var.file . ":" . var.line) - return var -endfunction - - -" Destroyer of the breakpoint. It just sends commands to debugger and destroys -" sign, but you should manually remove it from breakpoints array -function! s:Breakpoint.delete() dict - call self._unset_sign() - call self._send_delete_to_debugger() -endfunction - - -" Add condition to breakpoint. If server is not running, just store it, it -" will be evaluated after starting the server -function! s:Breakpoint.add_condition(condition) dict - let self.condition = a:condition - if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() && has_key(self, 'debugger_id') - call g:RubyDebugger.queue.add(self.condition_command()) - endif -endfunction - - - -" Send adding breakpoint message to debugger, if it is run -function! s:Breakpoint.send_to_debugger() dict - if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() - call g:RubyDebugger.queue.add(self.command()) - endif -endfunction - - -" Command for setting breakpoint (e.g.: 'break /path/to/file:23') -function! s:Breakpoint.command() dict - return 'break ' . self.file . ':' . self.line -endfunction - - -" Command for adding condition to breakpoin (e.g.: 'condition 1 x>5') -function! s:Breakpoint.condition_command() dict - return 'condition ' . self.debugger_id . ' ' . self.condition -endfunction - - -" Find and return breakpoint under cursor -function! s:Breakpoint.get_selected() dict - let line = getline(".") - let match = matchlist(line, '^\(\d\+\)') - let id = get(match, 1) - let breakpoints = filter(copy(g:RubyDebugger.breakpoints), "v:val.id == " . id) - if !empty(breakpoints) - return breakpoints[0] - else - return {} - endif -endfunction - - -" Output format for Breakpoints Window -function! s:Breakpoint.render() dict - let output = self.id . " " . (exists("self.debugger_id") ? self.debugger_id : '') . " " . self.file . ":" . self.line - if exists("self.condition") - let output .= " " . self.condition - endif - return output . "\n" -endfunction - - -" Open breakpoint in existed/new window -function! s:Breakpoint.open() dict - call s:jump_to_file(self.file, self.line) -endfunction - - -" ** Private methods - - -function! s:Breakpoint._set_sign() dict - if has("signs") - exe ":sign place " . self.id . " line=" . self.line . " name=breakpoint file=" . self.file - endif -endfunction - - -function! s:Breakpoint._unset_sign() dict - if has("signs") - exe ":sign unplace " . self.id - endif -endfunction - - -function! s:Breakpoint._log(string) dict - call g:RubyDebugger.logger.put(a:string) -endfunction - - -" Send deleting breakpoint message to debugger, if it is run -" (e.g.: 'delete 5') -function! s:Breakpoint._send_delete_to_debugger() dict - if has_key(g:RubyDebugger, 'server') && g:RubyDebugger.server.is_running() - let message = 'delete ' . self.debugger_id - call g:RubyDebugger.queue.add(message) - endif -endfunction - - -" *** Breakpoint class (end) - -" *** Exception class (start) -" These are ruby exceptions we catch with 'catch Exception' command -" (:RdbCatch) - -let s:Exception = { } - -" ** Public methods - -" Constructor of new exception. -function! s:Exception.new(name) - let var = copy(self) - let var.name = a:name - call var._log("Trying to set exception: " . var.name) - call g:RubyDebugger.queue.add(var.command()) - return var -endfunction - - -" Command for setting exception (e.g.: 'catch NameError') -function! s:Exception.command() dict - return 'catch ' . self.name -endfunction - - -" Output format for Breakpoints Window -function! s:Exception.render() dict - return self.name -endfunction - - -" ** Private methods - - -function! s:Exception._log(string) dict - call g:RubyDebugger.logger.put(a:string) -endfunction - - -" *** Exception class (end) - - - - -" *** Frame class (start) - -let s:Frame = { } - -" ** Public methods - -" Constructor of new frame. -" Create new frame and set sign to it. -function! s:Frame.new(attrs) - let var = copy(self) - let var.no = a:attrs.no - let var.file = a:attrs.file - let var.line = a:attrs.line - if has_key(a:attrs, 'current') - let var.current = (a:attrs.current == 'true') - else - let var.current = 0 - endif - "let s:sign_id += 1 - "let var.sign_id = s:sign_id - "call var._set_sign() - return var -endfunction - - -" Find and return frame under cursor -function! s:Frame.get_selected() dict - let line = getline(".") - let match = matchlist(line, '^\(\d\+\)') - let no = get(match, 1) - let frames = filter(copy(g:RubyDebugger.frames), "v:val.no == " . no) - if !empty(frames) - return frames[0] - else - return {} - endif -endfunction - - -" Output format for Frame Window -function! s:Frame.render() dict - return self.no . (self.current ? ' Current' : ''). " " . self.file . ":" . self.line . "\n" -endfunction - - -" Open frame in existed/new window -function! s:Frame.open() dict - call s:jump_to_file(self.file, self.line) -endfunction - - -" ** Private methods - -function! s:Frame._log(string) dict - call g:RubyDebugger.logger.put(a:string) -endfunction - - -function! s:Frame._set_sign() dict - if has("signs") - exe ":sign place " . self.sign_id . " line=" . self.line . " name=frame file=" . self.file - endif -endfunction - - -function! s:Frame._unset_sign() dict - if has("signs") - exe ":sign unplace " . self.sign_id - endif -endfunction - - -" *** Frame class (end) - - - -" *** Server class (start) - -let s:Server = {} - -" ** Public methods - -" Constructor of new server. Just inits it, not runs -function! s:Server.new(hostname, rdebug_port, debugger_port, runtime_dir, tmp_file, output_file) dict - let var = copy(self) - let var.hostname = a:hostname - let var.rdebug_port = a:rdebug_port - let var.debugger_port = a:debugger_port - let var.runtime_dir = a:runtime_dir - let var.tmp_file = a:tmp_file - let var.output_file = a:output_file - return var -endfunction - - -" Start the server. It will kill any listeners on given ports before. -function! s:Server.start(script) dict - call self._stop_server(self.rdebug_port) - call self._stop_server(self.debugger_port) - " Remove leading and trailing quotes - let script_name = substitute(a:script, "\\(^['\"]\\|['\"]$\\)", '', 'g') - let rdebug = 'rdebug-ide -p ' . self.rdebug_port . ' -- ' . script_name - let os = has("win32") || has("win64") ? 'win' : 'posix' - " Example - ruby ~/.vim/bin/ruby_debugger.rb 39767 39768 vim VIM /home/anton/.vim/tmp/ruby_debugger posix - let debugger_parameters = ' ' . self.hostname . ' ' . self.rdebug_port . ' ' . self.debugger_port . ' ' . g:ruby_debugger_progname . ' ' . v:servername . ' "' . self.tmp_file . '" ' . os - - " Start in background - if has("win32") || has("win64") - silent exe '! start ' . rdebug - let debugger = 'ruby "' . expand(self.runtime_dir . "/bin/ruby_debugger.rb") . '"' . debugger_parameters - silent exe '! start ' . debugger - else - call system(rdebug . ' > ' . self.output_file . ' 2>&1 &') - let debugger = 'ruby ' . expand(self.runtime_dir . "/bin/ruby_debugger.rb") . debugger_parameters - call system(debugger. ' &') - endif - - " Set PIDs of processes - let self.rdebug_pid = self._get_pid(self.rdebug_port, 1) - let self.debugger_pid = self._get_pid(self.debugger_port, 1) - - call g:RubyDebugger.logger.put("Start debugger") -endfunction - - -" Kill servers and empty PIDs -function! s:Server.stop() dict - call self._kill_process(self.rdebug_pid) - call self._kill_process(self.debugger_pid) - let self.rdebug_pid = "" - let self.debugger_pid = "" -endfunction - - -" Return 1 if processes with set PID exist. -function! s:Server.is_running() dict - return (self._get_pid(self.rdebug_port, 0) =~ '^\d\+$') && (self._get_pid(self.debugger_port, 0) =~ '^\d\+$') -endfunction - - -" ** Private methods - - -" Get PID of process, that listens given port on given host. If must_get_pid -" parameter is true, it will try to get PID for 20 seconds. -function! s:Server._get_pid(port, must_get_pid) - let attempt = 0 - let pid = self._get_pid_attempt(a:port) - while a:must_get_pid && pid == "" && attempt < 2000 - sleep 10m - let attempt += 1 - let pid = self._get_pid_attempt(a:port) - endwhile - return pid -endfunction - - -" Just try to get PID of process and return empty string if it was -" unsuccessful -function! s:Server._get_pid_attempt(port) - if has("win32") || has("win64") - let netstat = system("netstat -anop tcp") - let pid_match = matchlist(netstat, ':' . a:port . '\s.\{-}LISTENING\s\+\(\d\+\)') - let pid = len(pid_match) > 0 ? pid_match[1] : "" - elseif executable('lsof') - let pid = system("lsof -i tcp:" . a:port . " | grep LISTEN | awk '{print $2}'") - let pid = substitute(pid, '\n', '', '') - else - let pid = "" - endif - return pid -endfunction - - -" Kill listener of given host/port -function! s:Server._stop_server(port) dict - let pid = self._get_pid(a:port, 0) - if pid =~ '^\d\+$' - call self._kill_process(pid) - endif -endfunction - - -" Kill process with given PID -function! s:Server._kill_process(pid) dict - echo "Killing server with pid " . a:pid - call system("ruby -e 'Process.kill(9," . a:pid . ")'") - sleep 100m - call self._log("Killed server with pid: " . a:pid) -endfunction - - -function! s:Server._log(string) dict - call g:RubyDebugger.logger.put(a:string) -endfunction - - -" *** Server class (end) - - - -" *** Creating instances (start) - -if !exists("g:ruby_debugger_fast_sender") - let g:ruby_debugger_fast_sender = 0 -endif -" This variable allows to use built-in Ruby (see ':help ruby' and s:send_message_to_debugger function) -if !exists("g:ruby_debugger_builtin_sender") - if has("ruby") - let g:ruby_debugger_builtin_sender = 1 - else - let g:ruby_debugger_builtin_sender = 0 - endif -endif -if !exists("g:ruby_debugger_spec_path") - let g:ruby_debugger_spec_path = '/usr/bin/spec' -endif -if !exists("g:ruby_debugger_cucumber_path") - let g:ruby_debugger_cucumber_path = '/usr/bin/cucumber' -endif -if !exists("g:ruby_debugger_progname") - let g:ruby_debugger_progname = v:progname -endif - -" Creating windows -let s:variables_window = s:WindowVariables.new("variables", "Variables_Window") -let s:breakpoints_window = s:WindowBreakpoints.new("breakpoints", "Breakpoints_Window") -let s:frames_window = s:WindowFrames.new("frames", "Backtrace_Window") - -" Init logger. The plugin logs all its actions. If you have some troubles, -" this file can help -let s:logger_file = s:runtime_dir . '/tmp/ruby_debugger_log' -let RubyDebugger.logger = s:Logger.new(s:logger_file) -let s:variables_window.logger = RubyDebugger.logger -let s:breakpoints_window.logger = RubyDebugger.logger -let s:frames_window.logger = RubyDebugger.logger - -" *** Creating instances (end) - diff --git a/vim/bin/ruby_debugger.rb b/vim/bin/ruby_debugger.rb deleted file mode 100644 index bf48585..0000000 --- a/vim/bin/ruby_debugger.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'socket' - -class VimRubyDebugger - - def initialize(params) - @params = params - create_directory(@params[:messages_file]) - @rdebug = wait_for_opened_socket(@params[:host], @params[:rdebug_port]) - @vim_ruby_debugger = TCPServer.new(@params[:host], @params[:vim_ruby_debugger_port]) - @queue = [] - @result = [] - @separator = "++vim-ruby-debugger separator++" - run - end - - private - - def wait_for_opened_socket(host, port, &block) - attempts = 0 - begin - socket = TCPSocket.open(host, port) - yield if block_given? - rescue Errno::ECONNREFUSED => msg - attempts += 1 - # If socket wasn't be opened for 20 seconds, exit - if attempts < 400 - sleep 0.05 - retry - else - raise Errno::ECONNREFUSED, "#{host}:#{port} wasn't be opened" - end - end - socket - end - - - def create_directory(file) - dir = File.dirname(file) - Dir.mkdir(dir) unless File.exist?(dir) && File.directory?(dir) - end - - - def run - t1 = Thread.new do - while(session = @vim_ruby_debugger.accept) - input = session.gets - @queue = input.split(@separator) - handle_queue - end - end - t2 = Thread.new do - loop do - response = select([@rdebug], nil, nil) - output = read_socket(response, @rdebug) - @result << output - # If we stop at breakpoint, add taking of local variables into queue - stop_commands = [ '" : "") - system("#{@params[:vim_executable]} --servername #{@params[:vim_servername]} -u NONE -U NONE --remote-send \"#{starter}#{command}\""); - end - end - - - def have_unclosed_tag?(output) - start_match = output.match(/^<([a-zA-Z0-9\-_]+)>/) - if start_match - end_match = output.match(/<\/#{start_match[1]}>$/) - return end_match ? false : true - else - return false - end - end - -end - - -VimRubyDebugger.new( - :host => ARGV[0], - :rdebug_port => ARGV[1], - :vim_ruby_debugger_port => ARGV[2], - :vim_executable => ARGV[3], - :vim_servername => ARGV[4], - :messages_file => ARGV[5], - :os => ARGV[6] -) diff --git a/vim/bundle/astashov-vim-ruby-debugger b/vim/bundle/astashov-vim-ruby-debugger new file mode 160000 index 0000000..1df8626 --- /dev/null +++ b/vim/bundle/astashov-vim-ruby-debugger @@ -0,0 +1 @@ +Subproject commit 1df86267406288a99cebffae127958880aa19c23