" Author: Kannan Rajah " " License: {{{ " " Copyright (C) 2014 Eric Van Dewoestine " " This program is free software: you can redistribute it and/or modify " it under the terms of the GNU General Public License as published by " the Free Software Foundation, either version 3 of the License, or " (at your option) any later version. " " This program is distributed in the hope that it will be useful, " but WITHOUT ANY WARRANTY; without even the implied warranty of " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " GNU General Public License for more details. " " You should have received a copy of the GNU General Public License " along with this program. If not, see . " " }}} " Script Variables {{{ " Regex used to search for any object ID " Use a non greedy search to match the closing parenthesis using \{-} let s:id_search_by_regex = '(id=.\{-})' " Pattern used to search for specific object ID let s:id_search_by_value = '(id=)' " TODO This needs to be maintained per thread. We can use debug session id + " thread id as key let s:debug_step_prev_file = '' let s:debug_step_prev_line = '' let s:breakpoint_sign = 'eclim_breakpoint' let s:breakpoint_sign_current = 'eclim_breakpoint_cur' let s:variable_buf_name = 'Debug Variables' let s:thread_buf_name = 'Debug Threads' let s:breakpoint_buf_name = 'Debug Breakpoints' let s:command_start = \ '-command java_debug_start -p "" ' . \ '-h "" -n "" -v ""' let s:command_stop = '-command java_debug_stop' let s:command_session_suspend = '-command java_debug_thread_suspend' let s:command_thread_suspend = '-command java_debug_thread_suspend ' . \ '-t ""' let s:command_session_resume = '-command java_debug_thread_resume' let s:command_thread_resume = '-command java_debug_thread_resume ' . \ '-t ""' let s:command_breakpoint_toggle = \ '-command java_debug_breakpoint_toggle -p ""' let s:command_breakpoint_remove = \ '-command java_debug_breakpoint_remove -p ""' let s:command_breakpoints_list = \ '-command java_debug_breakpoint_list -p ""' let s:command_step = '-command java_debug_step -a ""' let s:command_step_thread = '-command java_debug_step -a "" -t ""' let s:command_status = '-command java_debug_status' let s:command_variable_expand = '-command java_debug_variable_expand -v ""' let s:command_variable_detail = '-command java_debug_variable_detail -v ""' " }}} function! s:DefineStatusWinSettings() " {{{ " Defines settings that are applicable in any of the debug status windows. if !exists(":JavaDebug") command -nargs=0 -buffer JavaDebugStop :call eclim#java#debug#DebugStop() command -nargs=0 -buffer JavaDebugStatus :call eclim#java#debug#Status() command -nargs=+ -buffer JavaDebugStep :call eclim#java#debug#Step() command -nargs=0 -buffer JavaDebugThreadSuspendAll \ :call eclim#java#debug#DebugThreadSuspendAll() command -nargs=0 -buffer JavaDebugThreadResume \ :call eclim#java#debug#DebugThreadResume() command -nargs=0 -buffer JavaDebugThreadResumeAll \ :call eclim#java#debug#DebugThreadResumeAll() endif endfunction " }}} function! s:DefineThreadWinSettings() " {{{ " Defines settings that are applicable only in thread window. nnoremap s :call eclim#java#debug#DebugThreadSuspend() nnoremap S :call eclim#java#debug#DebugThreadSuspendAll() nnoremap r :call eclim#java#debug#DebugThreadResume() nnoremap R :call eclim#java#debug#DebugThreadResumeAll() nnoremap B :call eclim#java#debug#BreakpointsList('!') nnoremap ? :call eclim#help#BufferHelp( \ [ \ 's - suspend the thread under the cursor', \ 'S - suspend all threads', \ 'r - resume the thread under the cursor', \ 'R - resume all threads', \ 'B - view all breakpoints', \ ], \ 'vertical', 40) endfunction " }}} function! s:DefineVariableWinSettings() " {{{ " Defines settings that are applicable only in variable window. nnoremap :call VariableExpand() nnoremap p :call VariableDetail() nnoremap B :call eclim#java#debug#BreakpointsList('!') nnoremap ? :call eclim#help#BufferHelp( \ [ \ ' - expand the variable under the cursor', \ 'p - preview toString value', \ 'B - view all breakpoints', \ ], \ 'vertical', 40) endfunction " }}} function! s:DefineBreakpointWinSettings() " {{{ " Defines settings that are applicable only in breakpoint window. call eclim#display#signs#Define(s:breakpoint_sign, '•', '') nnoremap T \ :call BreakpointAction("java_debug_breakpoint_toggle") nnoremap D \ :call BreakpointAction("java_debug_breakpoint_remove") nnoremap ? :call eclim#help#BufferHelp( \ [ \ 'T - toggle breakpoint (or group of breakpoints) under cursor', \ 'D - delete breakpoint (or group of breakpoints) under cursor', \ ], \ 'vertical', 40) endfunction " }}} function! eclim#java#debug#DebugStart(...) " {{{ if !eclim#project#util#IsCurrentFileInProject() return endif if v:servername == '' call eclim#util#EchoError( \ "Error: To debug, VIM must be running in server mode.\n" . \ "Example: vim --servername ") return endif if a:0 != 2 call eclim#util#EchoError( \ "Please specify the host and port of the java process to connect to.\n" . \ "Example: JavaDebugStart locahost 1044") return endif let host = a:1 let port = a:2 if port !~ '^\d\+' call eclim#util#EchoError("Error: Please specify a valid port number.") return endif let project = eclim#project#util#GetCurrentProjectName() let command = s:command_start let command = substitute(command, '', project, '') let command = substitute(command, '', host, '') let command = substitute(command, '', port, '') let command = substitute(command, '', v:servername, '') let result = eclim#Execute(command) call eclim#util#Echo(result) endfunction " }}} function! eclim#java#debug#DebugStop() " {{{ let command = s:command_stop let result = eclim#Execute(command) " Auto close the debug status window call eclim#util#DeleteBuffer(s:variable_buf_name) call eclim#util#DeleteBuffer(s:thread_buf_name) " Remove the sign from previous location if (s:debug_step_prev_line != '' && s:debug_step_prev_file != '') call eclim#display#signs#UnplaceFromBuffer(s:debug_step_prev_line, \ bufnr(s:debug_step_prev_file)) endif let s:debug_step_prev_line = '' let s:debug_step_prev_file = '' call eclim#util#Echo(result) endfunction " }}} function! eclim#java#debug#DebugThreadSuspend() " {{{ " Check if we are in the right window if (bufname("%") != s:thread_buf_name) call eclim#util#EchoError("Thread suspend command not applicable in this window.") return endif " Suspends thread under cursor. let thread_id = s:GetIdUnderCursor() if thread_id == "" call eclim#util#Echo("No valid thread found under cursor") return endif let command = s:command_thread_suspend let command = substitute(command, '', thread_id, '') call eclim#util#Echo(eclim#Execute(command)) endfunction " }}} function! eclim#java#debug#DebugThreadSuspendAll() " {{{ " Suspends all threads. let command = s:command_session_suspend call eclim#util#Echo(eclim#Execute(command)) endfunction " }}} function! eclim#java#debug#DebugThreadResume() " {{{ " Resume a single thread. " Even if thread_id is empty, invoke resume. If there is atleast one " suspended thread, then the server could resume that. If not, it will " re-run a message. " Check if we are in the right window if (bufname("%") == s:thread_buf_name) let thread_id = s:GetIdUnderCursor() else let thread_id = "" endif let command = s:command_thread_resume let command = substitute(command, '', thread_id, '') let result = eclim#Execute(command) " Remove the sign from previous location. This is needed here even though it " is done in GoToFile function. There may be a time gap until the next " breakpoint is hit or the program terminates. We don't want to highlight " the current line until then. if (s:debug_step_prev_line != '' && s:debug_step_prev_file != '') call eclim#display#signs#UnplaceFromBuffer(s:debug_step_prev_line, \ bufnr(s:debug_step_prev_file)) endif call eclim#util#Echo(result) endfunction " }}} function! eclim#java#debug#DebugThreadResumeAll() " {{{ " Resumes all threads. let command = s:command_session_resume let result = eclim#Execute(command) " Remove the sign from previous location. This is needed here even though it " is done in GoToFile function. There may be a time gap until the next " breakpoint is hit or the program terminates. We don't want to highlight " the current line until then. if (s:debug_step_prev_line != '' && s:debug_step_prev_file != '') call eclim#display#signs#UnplaceFromBuffer(s:debug_step_prev_line, \ bufnr(s:debug_step_prev_file)) endif call eclim#util#Echo(result) endfunction " }}} function! eclim#java#debug#BreakpointToggle(bang) " {{{ " Adds, enables, disables, or deletes a breakpoint for current cursor " position. if !eclim#project#util#IsCurrentFileInProject() return endif let project = eclim#project#util#GetCurrentProjectName() let file = eclim#lang#SilentUpdate() let line = line('.') let command = s:command_breakpoint_toggle . ' -f "" -l ' let command = substitute(command, '', project, '') let command = substitute(command, '', file, '') let command = substitute(command, '', line, '') if a:bang == '!' let command .= ' -d' endif call eclim#util#Echo(eclim#Execute(command)) let name = eclim#util#EscapeBufferName(s:breakpoint_buf_name) if bufwinnr(name) != -1 let curwinnr = winnr() exec bufwinnr(name) . "winc w" call s:BreakpointsListRefresh() exec curwinnr . "winc w" endif endfunction " }}} function! eclim#java#debug#BreakpointsList(bang) " {{{ " List breakpoints in the current file or the entire workspace. if !eclim#project#util#IsCurrentFileInProject() && a:bang == '' return endif let project = eclim#project#util#GetCurrentProjectName() let command = s:command_breakpoints_list let command = substitute(command, '', project, '') if a:bang != '!' let file = eclim#project#util#GetProjectRelativeFilePath() let command .= ' -f "' . file . '"' endif let results = eclim#Execute(command) if (type(results) != g:LIST_TYPE) return endif if empty(results) call eclim#util#Echo('No breakpoints found.') return endif " when run from one of the status windows, force the list to open above them " if possible if &buftype == 'nofile' winc k endif call s:BreakpointsList(command, results) endfunction " }}} function! s:BreakpointsList(command, results) " {{{ let group_by = ['project', 'name'] if a:command =~ '\s-f\s' let group_by = ['name'] endif call eclim#util#FileList(s:breakpoint_buf_name, a:results, { \ 'location': '', \ 'group_by': group_by, \ }) call s:DefineBreakpointWinSettings() let b:eclim_breakpoints = a:command " Place sign for breakpoints that are enabled let line_num = 0 for entry in b:eclim_filelist let line_num += 1 if get(entry, 'enabled') call eclim#display#signs#Place(s:breakpoint_sign, line_num) endif endfor endfunction " }}} function! s:BreakpointsListRefresh() " {{{ " FIXME: restore all closed fold states let results = eclim#Execute(b:eclim_breakpoints) if (type(results) != g:LIST_TYPE) return endif if empty(results) call eclim#util#Echo('No breakpoints found.') close return endif let line = line('.') let col = col('.') " remove all existing signs first for existing in eclim#display#signs#GetExisting(s:breakpoint_sign) call eclim#display#signs#Unplace(existing.id) endfor call s:BreakpointsList(b:eclim_breakpoints, results) if line > line('$') let line = line('$') endif call cursor(line, col) endfunction " }}} function! eclim#java#debug#BreakpointRemove(bang) " {{{ " Removes breakpoints defined in file or the entire project. if !eclim#project#util#IsCurrentFileInProject() && a:bang == '' return endif let project = eclim#project#util#GetCurrentProjectName() if a:bang == '!' let command = s:command_breakpoint_remove let command = substitute(command, '', file, '') else let file = eclim#project#util#GetProjectRelativeFilePath() let command = s:command_breakpoint_remove . ' -f ""' let command = substitute(command, '', project, '') let command = substitute(command, '', file, '') endif call eclim#util#Echo(eclim#Execute(command)) let name = eclim#util#EscapeBufferName(s:breakpoint_buf_name) if bufwinnr(name) != -1 let curwinnr = winnr() exec bufwinnr(name) . "winc w" call s:BreakpointsListRefresh() exec curwinnr . "winc w" endif endfunction " }}} function! s:BreakpointAction(command) " {{{ " Removes breakpoint defined under the cursor. let entry = b:eclim_filelist[line('.') - 1] let command = '' " single breakpoint if has_key(entry, 'filename') let file = eclim#project#util#GetProjectRelativeFilePath(entry.filename) let command = '-command ' . a:command . ' -p "" -f "" -l ' let command = substitute(command, '', entry.project, '') let command = substitute(command, '', file, '') let command = substitute(command, '', entry.line, '') " all breakpoints for a file elseif has_key(b:eclim_filelist[line('.')], 'filename') let info = b:eclim_filelist[line('.')] let file = eclim#project#util#GetProjectRelativeFilePath(info.filename) let command = '-command ' . a:command . ' -p "" -f ""' let command = substitute(command, '', info.project, '') let command = substitute(command, '', file, '') " all breakpoints for a project elseif has_key(b:eclim_filelist[line('.') + 1], 'filename') let info = b:eclim_filelist[line('.') + 1] let command = '-command ' . a:command . ' -p ""' let command = substitute(command, '', info.project, '') endif if command != '' let result = eclim#Execute(command) call s:BreakpointsListRefresh() call eclim#util#Echo(result) endif endfunction " }}} function! eclim#java#debug#Step(action) " {{{ if (bufname("%") == s:thread_buf_name) let thread_id = s:GetIdUnderCursor() else let thread_id = "" endif if thread_id != "" let command = s:command_step_thread let command = substitute(command, '', thread_id, '') else " Step through the currently stepping thread, if one exists let command = s:command_step endif let command = substitute(command, '', a:action, '') let result = eclim#Execute(command) if type(result) == g:STRING_TYPE call eclim#util#Echo(result) endif endfunction " }}} function! eclim#java#debug#Status() " {{{ let command = s:command_status let results = eclim#Execute(command) if type(results) != g:DICT_TYPE return endif if has_key(results, 'state') let state = [results.state] else let state = [] endif if has_key(results, 'threads') let threads = results.threads else let threads = [] endif if has_key(results, 'variables') let vars = results.variables else let vars = [] endif call s:CreateStatusWindow(results.project, state + threads, vars) endfunction " }}} function! s:CreateStatusWindow(project, threads, vars) " {{{ " Creates the debug status windows if they do not already exist. " The newly created windows are initialized with given content. " Store current position and restore in the end so that creation of new " window does not end up moving the cursor let cur_bufnr = bufnr('%') let cur_line = line('.') let cur_col = col('.') let thread_win_opts = {'orientation': 'horizontal'} call eclim#util#TempWindow(s:thread_buf_name, a:threads, thread_win_opts) setlocal foldmethod=expr setlocal foldexpr=eclim#display#fold#GetTreeFold(v:lnum) setlocal foldtext=eclim#display#fold#TreeFoldText() " Display the stacktrace of suspended threads setlocal foldlevel=5 " Avoid the ugly - symbol on folded lines setlocal fillchars="fold:\ " setlocal nonumber let b:eclim_project = a:project call s:DefineThreadWinSettings() call s:DefineStatusWinSettings() let var_win_opts = { \ 'orientation': g:EclimJavaDebugStatusWinOrientation, \ 'width': g:EclimJavaDebugStatusWinWidth, \ 'height': g:EclimJavaDebugStatusWinHeight \ } call eclim#util#TempWindow(s:variable_buf_name, a:vars, var_win_opts) setlocal foldmethod=expr setlocal foldexpr=eclim#display#fold#GetTreeFold(v:lnum) setlocal foldtext=eclim#display#fold#TreeFoldText() " Avoid the ugly - symbol on folded lines setlocal fillchars="fold:\ " setlocal nonumber let b:eclim_project = a:project call s:DefineVariableWinSettings() call s:DefineStatusWinSettings() " Restore position call eclim#util#GoToBufferWindow(cur_bufnr) call cursor(cur_line, cur_col) redraw! endfunction " }}} function! s:VariableExpand() " {{{ " Expands the variable value under cursor and adds the child variables under " it in the tree. " Check if we are in the right window if (bufname("%") != s:variable_buf_name) call eclim#util#EchoError("Variable expand command not applicable in this window.") return endif " Return if the current line does not contain any fold if (matchstr(getline(line('.')), "▸\\|▾") == "") return endif " Make the buffer writable setlocal modifiable setlocal noreadonly let id = s:GetIdUnderCursor() if (id != "") let command = s:command_variable_expand let command = substitute(command, '', id, '') let results = eclim#Execute(command) if (type(results) == g:LIST_TYPE && len(results) > 0) call append(line('.'), results) " Remove the placeholder line used to get folding to work. " But first unfold if its folded. foldopen let cur_line = line('.') let cur_col = col('.') let empty_line = line('.') + len(results) + 1 exec 'silent ' . empty_line . ',' . empty_line . 'd' " Restore cursor position call cursor(cur_line, cur_col) else exec "normal! za" endif else exec "normal! za" endif " Restore settings setlocal nomodified setlocal nomodifiable setlocal readonly endfunction " }}} function! s:VariableDetail() " {{{ " Displays the detail of the variable value under cursor in status line. " Check if we are in the right window if (bufname("%") != s:variable_buf_name) call eclim#util#EchoError("Variable detail command not applicable in this window.") return endif let id = s:GetIdUnderCursor() if (id == "") return endif let command = s:command_variable_detail let command = substitute(command, '', id, '') let result = eclim#Execute(command) call eclim#util#Echo(result) endfunction " }}} function! eclim#java#debug#GoToFile(file, line) " {{{ " Remove the sign from previous location if (s:debug_step_prev_line != '' && s:debug_step_prev_file != '') call eclim#display#signs#UnplaceFromBuffer(s:debug_step_prev_line, \ bufnr(s:debug_step_prev_file)) endif " Jump out of status window so that the buffer is opened in the code window " If you are in variable window, first go to thread window if (bufname("%") == s:thread_buf_name) call eclim#util#GoToBufferWindow(b:filename) endif " If you are in thread window, go back to code window if (bufname("%") == s:variable_buf_name) call eclim#util#GoToBufferWindow(b:filename) call eclim#util#GoToBufferWindow(b:filename) endif let s:debug_step_prev_file = a:file let s:debug_step_prev_line = a:line if &buftype == 'nofile' winc k endif call eclim#util#GoToBufferWindowOrOpen(a:file, 'edit', a:line, '^') " gross, but seems to be the easiest way to remove the sign from all buffers silent! call eclim#display#signs#Undefine(s:breakpoint_sign_current) call eclim#display#signs#Define( \ s:breakpoint_sign_current, \ g:EclimJavaDebugLineSignText, \ g:EclimHighlightSuccess, \ g:EclimJavaDebugLineHighlight) call eclim#display#signs#PlaceInBuffer( \ s:breakpoint_sign_current, bufnr(a:file), a:line) endfunction " }}} function! s:GetIdUnderCursor() " {{{ " Returns the object ID under cursor. Object ID is present in the form: " (id=X) where X is the ID. " " An empty string is returned if there is no valid ID. " Look for the substring (id=X) where X is the object ID let id_substr = matchstr(getline("."), s:id_search_by_regex) if (id_substr == "") return "" else let id = split(id_substr, '=')[1] " remove the trailing ) character return substitute(id, ")","","g") endif endfunction " }}} function! eclim#java#debug#ThreadViewUpdate(thread_id, kind, value) " {{{ " Updates the thread window with new thread information. " Update kind can be one of the following: " a: Add a new thread " m: Modify an existing thread " r: Remove an existing thread " Overwrite the message containing the long list of arguments to this function call eclim#util#Echo("Refreshing ...") " Store current position and restore in the end let cur_bufnr = bufnr('%') let cur_line = line('.') let cur_col = col('.') let found = eclim#util#GoToBufferWindow(s:thread_buf_name) if found == 0 return endif setlocal modifiable setlocal noreadonly let lines = split(a:value, "") if a:kind == 'a' " Add to the end call append(line('$'), lines) else " Go to line having the thread id call cursor(1, 1) let pattern = substitute(s:id_search_by_value, '', a:thread_id, '') " Don't wrap search. let match_line = search(pattern, 'W') if match_line != 0 " Get the next thread entry. Don't wrap search. let next_match_line = search(s:id_search_by_regex, 'W') if next_match_line == 0 let last_line_del = line('$') else let last_line_del = next_match_line - 1 endif " Delete the desired lines let undolevels = &undolevels set undolevels=-1 silent exec match_line . ',' . last_line_del . 'delete _' let &undolevels = undolevels " In case of modify, add the new values if a:kind == 'm' call append(match_line - 1, lines) endif endif endif setlocal nomodifiable setlocal readonly " Restore position call eclim#util#GoToBufferWindow(cur_bufnr) call cursor(cur_line, cur_col) call eclim#util#Echo(" ") endfunction " }}} function! eclim#java#debug#SessionTerminated() " {{{ " Cleans up the debug status window. " Store current position and restore in the end let cur_bufnr = bufnr('%') let cur_line = line('.') let cur_col = col('.') let found = eclim#util#GoToBufferWindow(s:thread_buf_name) if found == 0 return endif setlocal modifiable setlocal noreadonly " Go to line having the thread id call cursor(1, 1) s/Connect\(ed\|ing\)/Disconnected/e " Delete any threads that have not been cleared if line('$') > 1 silent 2,$delete _ endif setlocal nomodifiable setlocal readonly " Restore position call eclim#util#GoToBufferWindow(cur_bufnr) call cursor(cur_line, cur_col) call eclim#util#TempWindowClear(s:variable_buf_name) endfunction " }}} function! eclim#java#debug#VariableViewUpdate(value) " {{{ " Updates the variable window with new set of values. call eclim#util#Echo("Refreshing ...") call eclim#util#TempWindowClear(s:variable_buf_name, split(a:value, "")) call eclim#util#Echo(" ") endfunction " }}} " vim:ft=vim:fdm=marker