From 999622254ea1b9966274df8806d8191469603249 Mon Sep 17 00:00:00 2001 From: Gorm Hauerberg Date: Tue, 7 Mar 2017 10:13:58 +0100 Subject: [PATCH] 1. full management of breakpoints using the sign system in vim, enabling the plugin to define and manage breakpoints when not running. 2. Added JDBDebugProcess which will directly start debug process with @Test annontated unit tests --- README.md | 9 ++ plugin/vim-jdb.vim | 221 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 200 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c201f59..33d4eaf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # vim-jdb +This is a fork of Dica-Developer/vim-jdb so checkout the main branch as well. + Its a JAVA debugger frontend plugin for VIM. It allows to debug a JAVA program via the JDB debugger. It allows remote debugging via attach parameter. It marks by vim-jdb setted breakpoints and shows the current file and line the debugger stays in. @@ -18,6 +20,10 @@ It requires VIM >= 8.0 and VIM compiled with `channel`, `signs` and `job` suppor 8. use the command `:JDBStepOver` to execute to the next line 9. with `:JDBCommand` you can send any JDB command to the JDB JAVA process, e.g. you want to see all locals do `:JDBCommands locals` 10. with `:JDBContinue` you can resume the execution until the next breakpoint is hits +11. with `:JDBDebugProcess` will (currently) pick the function name you are currently in if it is annontated with "@Test". It will then construct the the path to the class from the class name and package name in the file. + finally it will start jdb like this e.g.: jdb -Dtest.single=functionmane org.junit.runner.JUnitCore yourpackage.classname + for this to work properly you need to set the env var CLASSPATH with your projects dependencies. For me I use gradle for dev so have a task named "classpath" and can do this from with in gradle before starting. + :let $CLASSPATH=system("gradle -q classpath") . ":build/classes/main:build/classes/test" ## Commands |Command|Description| @@ -32,8 +38,11 @@ It requires VIM >= 8.0 and VIM compiled with `channel`, `signs` and `job` suppor |JDBStepUp|steps a level up in the stack| |JDBStepI|steps to the next instruction| |JDBCommand|send any JDB command to the application under debug| +|JDBDebugProcess|While in a JUnit test this will start a debug process using junit -Dtest.single| ## Global variables To specify the JDB command to use you can overwrite the following variable `g:vimjdb_jdb_command`. The default is `jdb`. +For starting debug process for Unit tests you can override the following variables. +g:vimjdb_unit_test_class by default it is set tp 'org.junit.runnint.JUnitCore' diff --git a/plugin/vim-jdb.vim b/plugin/vim-jdb.vim index a890291..66eb83f 100644 --- a/plugin/vim-jdb.vim +++ b/plugin/vim-jdb.vim @@ -26,10 +26,12 @@ if !has('signs') finish endif +command! -nargs=? JDBDebugProcess call s:createDebugProcess() command! -nargs=? JDBAttach call s:attach() +command! JDBDebugUnit call s:startUnitTest() command! JDBDetach call s:detach() -command! JDBBreakpointOnLine call s:breakpointOnLine(expand('%:~:.'), line('.')) -command! JDBClearBreakpointOnLine call s:clearBreakpointOnLine(expand('%:~:.'), line('.')) +command! JDBBreakpointOnLine call s:setBreakpoint() +command! JDBClearBreakpointOnLine call s:removeBreakpoint() command! JDBContinue call s:continue() command! JDBStepOver call s:stepOver() command! JDBStepIn call s:stepIn() @@ -49,6 +51,9 @@ sign define currentline text=-> texthl=Search let s:job = '' let s:channel = '' +let s:running = 0 +let s:org_win_id = 0 +let s:currentfile = '' function! s:hash(name, linenumber) let l:result = 1 @@ -56,7 +61,7 @@ function! s:hash(name, linenumber) let l:result = (l:result * 2) + char2nr(c) endfor let l:result = (l:result * 2) + a:linenumber - return l:result + return strpart(l:result, 4) endfunction function! s:getClassNameFromFile(filename) @@ -72,36 +77,54 @@ endfunction function! JdbOutHandler(channel, msg) "TODO make debug logging out of it - "echom a:msg + echom a:msg let l:breakpoint = '' - if -1 < stridx(a:msg, 'Breakpoint hit:') + if -1 < stridx(a:msg, 'Breakpoint hit: "thread') echom "breakpoint hit" let l:breakpoint = split(a:msg, ',') let l:filename = l:breakpoint[1] - let l:filename = substitute(l:filename, '\.\a*()$', '', '') + let l:filename = substitute(l:filename, '\.\w*()$', '', '') let l:filename = substitute(l:filename, ' ', '', 'g') - let l:filename = join(split(l:filename, '\.'), '/') + let l:filenamefrag= split(l:filename, '\.') + let l:filename = findfile(filenamefrag[-1]) + echom 'found file ' . l:filename let l:linenumber = substitute(l:breakpoint[2], ',\|\.\| \|bci=\d*\|line=', '', 'g') - " TODO only open when current buffer is not the file to open - exe 'e +%foldopen! **/'. l:filename .'.java' - exe l:linenumber - exe 'sign unplace 2' - exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") + " only open when current buffer is not the file to open + if l:filename != s:currentfile + if filereadable(l:filename) + exe 'e +%foldopen! **/'. l:filename + let s:currentfile = l:filename + exe l:linenumber + exe 'sign unplace 2' + exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") + else + echom 'tried to open file ' . l:filename + endif + endif endif - if -1 < stridx(a:msg, 'Step completed:') + if -1 < stridx(a:msg, 'Step completed: "thread') " TODO handle ClassName$3.get() echom "Step completed" let l:breakpoint = split(a:msg, ',') let l:filename = l:breakpoint[1] - let l:filename = substitute(l:filename, '\.\a*()$', '', '') + let l:filename = substitute(l:filename, '\.\w*()$', '', '') let l:filename = substitute(l:filename, ' ', '', 'g') - let l:filename = join(split(l:filename, '\.'), '/') + let l:filenamefrag= split(l:filename, '\.') + let l:filename = findfile(filenamefrag[-1]) + echom 'found file ' . l:filename let l:linenumber = substitute(l:breakpoint[2], ',\|\.\| \|bci=\d*\|line=', '', 'g') - " TODO only open when current buffer is not the file to open - exe 'e +%foldopen! **/'. l:filename .'.java' - exe l:linenumber - exe 'sign unplace 2' - exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") + " only open when current buffer is not the file to open + " if l:filename != l:currentfile + if filereadable(l:filename) + exe 'e +%foldopen! **/'. l:filename + let s:currentfile = l:filename + exe l:linenumber + exe 'sign unplace 2' + exe 'sign place 2 line='. l:linenumber .' name=currentline file='. expand("%:p") + else + echom 'tried to open file ' . l:filename + endif + " endif endif if -1 < stridx(a:msg, 'Set breakpoint ') || -1 < stridx(a:msg, 'Set deferred breakpoint ') echom "Set breakpoint" @@ -121,12 +144,64 @@ function! JdbOutHandler(channel, msg) echom "Continue" exe 'sign unplace 2' endif + if -1 < stridx(a:msg, 'The application exited') + exe 'sign unplace 2' + let s:channel = '' + let s:job = '' + let s:running = 0 + let win = bufwinnr('_JDB_SHELL_') + if win != -1 + exe win . 'wincmd w' + exe 'close' + endif + endif endfunction function! JdbErrHandler(channel, msg) + let s:running = 0 echoe 'Error on JDB communication: '. a:msg endfunction +let s:signCounter = 10 +let s:signtype = 'breakpoint' + +function! s:applyBreakPoints(channel, name) + let l:points = split(execute('sign place'), '\n') + let l:fileName = '' + for line in l:points + " Start of new file, listing all signs for this + if -1 < stridx(line, 'Signs for') + let l:lineparts = split(line, ' ') + let l:fileName = l:lineparts[-1] + let l:fileName = substitute(l:fileName ,':$', '', 'g') + endif + " if sign found with given name + if -1 < stridx(line, 'name='. a:name) + let l:lineparts = split(line, ' ') + let l:lineNumber = substitute(l:lineparts[0], 'line=','','g') + let l:className = s:getClassNameFromFile(l:fileName) + echom 'breakpoint line: ' . l:fileName .':'. l:lineNumber + call ch_sendraw(a:channel, "stop at " . l:className . ":" . l:lineNumber . "\n") + endif + endfor +endfunction + +function! s:setBreakpoint() + let l:fileName = expand('%:p') + let l:lineNumber = line('.') + let l:currentBuffer = bufnr('%') + exe 'sign place ' . s:signCounter . ' line=' . l:lineNumber . ' name=' . s:signtype . ' buffer=' . l:currentBuffer + let s:signCounter = s:signCounter + 1 + if s:running == 1 + call s:breakpointOnLine(l:fileName, l:lineNumber) + endif +endfunction + +function! s:removeBreakpoint() + sign unplace + call s:clearBreakpointOnLine(expand('%:p'), line('.')) +endfunction + function! s:openWindow(name, mode, size) let win = bufwinnr(a:name) if win == -1 @@ -160,13 +235,27 @@ endfunction function! s:attach(...) if s:job == '' || job_status(s:job) != 'run' - let l:hostAndPort = get(a:, 1, 'localhost:5005') + " save current window state + let s:org_win_id = win_getid() + let l:winview = winsaveview() + + let l:arg1 = get(a:, 1, 'localhost:5005') let l:jdbCommand = get(g:, 'vimjdb_jdb_command', 'jdb') call s:openWindow('_JDB_SHELL_', '', 15) - let s:job = job_start(l:jdbCommand .' -attach '. l:hostAndPort, {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_", "err_cb": "JdbErrHandler"}) + echom "Attaching to java process" + let s:job = job_start(l:jdbCommand .' -attach '. l:arg1, {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_", "err_cb": "JdbErrHandler"}) let s:channel = job_getchannel(s:job) + call ch_sendraw(s:channel, "run\n") call ch_sendraw(s:channel, "monitor where\n") + + " apply breakpoint into jdb process from defined signs + call s:applyBreakPoints(s:channel, 'breakpoint') + let s:running = 1 + + " Set cursor in where you started the process, in the correct buffer + call win_gotoid(s:org_win_id) + call winrestview(l:winview) else echom 'There is already a JDB session running. Detach first before you start a new one.' endif @@ -177,6 +266,7 @@ function! s:detach() call ch_sendraw(s:channel, "exit\n") let s:channel = '' let s:job = '' + let s:running = 0 let win = bufwinnr('_JDB_SHELL_') if win != -1 exe win . 'wincmd w' @@ -186,36 +276,57 @@ function! s:detach() endfunction function! s:breakpointOnLine(fileName, lineNumber) - "TODO check if we are on a java file and fail if not - let fileName = s:getClassNameFromFile(a:fileName) - "TODO store command temporary if not already connected - call ch_sendraw(s:channel, "stop at " . fileName . ":" . a:lineNumber . "\n") + if s:channel != '' + let l:fileName = s:getClassNameFromFile(a:fileName) + call ch_sendraw(s:channel, "stop at " . l:fileName . ":" . a:lineNumber . "\n") + endif endfunction function! s:clearBreakpointOnLine(fileName, lineNumber) - "TODO check if we are on a java file and fail if not - let fileName = s:getClassNameFromFile(a:fileName) - "TODO store command temporary if not already connected - call ch_sendraw(s:channel, "clear " . fileName . ":" . a:lineNumber . "\n") + if s:channel != '' + let l:fileName = s:getClassNameFromFile(a:fileName) + call ch_sendraw(s:channel, "clear " . l:fileName . ":" . a:lineNumber . "\n") + endif endfunction +" for all driver options check if running=0 if so please start the process by sending run first function! s:continue() + if s:running == 0 + call ch_sendraw(s:channel, "run\n") + let s:running = 1 + endif call ch_sendraw(s:channel, "resume\n") endfunction function! s:stepOver() + if s:running == 0 + call ch_sendraw(s:channel, "run\n") + let s:running = 1 + endif call ch_sendraw(s:channel, "next\n") endfunction function! s:stepUp() + if s:running == 0 + call ch_sendraw(s:channel, "run\n") + let s:running = 1 + endif call ch_sendraw(s:channel, "step up\n") endfunction function! s:stepIn() + if s:running == 0 + call ch_sendraw(s:channel, "run\n") + let s:running = 1 + endif call ch_sendraw(s:channel, "step in\n") endfunction function! s:stepI() + if s:running == 0 + call ch_sendraw(s:channel, "run\n") + let s:running = 1 + endif call ch_sendraw(s:channel, "stepi\n") endfunction @@ -233,3 +344,53 @@ function! s:toggleWatchWindow() endif endfunction +function! s:createDebugProcess() + let l:running = 0 + + " save current window state + let s:org_win_id = win_getid() + let l:winview = winsaveview() + " TODO Figure out a way to properly detect if the current function is a test + " or not, if so start the debugging wih this otherwise fall back to setting + " main + " get current function name + echom 'Finding java process and starting it' + " Backwards search for @Test annotation and grab the function it defines + call search('@Test','bce') + execute "normal! wwwyt(" + let l:fnname = @" + echom 'Function name: '. l:fnname + + " get package name + execute "normal! ggwyt;" + let l:pcname = @" + + " get class name + call search("public class", 'e') + execute "normal! wyiW" + let l:clname = @" + + " Build unit test process string + let l:appArgs = '' + if l:fnname != '' + let l:appArgs .= ' -Dtest.single=' . l:fnname + endif + let l:appArgs .= ' ' . get(g:, 'vimjdb_unit_test_class', 'org.junit.runner.JUnitCore') + let l:appArgs .= ' ' . l:pcname . '.' . l:clname + + " start jdb process in a new buffer + let l:jdbCommand = get(g:, 'vimjdb_jdb_command', 'jdb') + let l:process = l:jdbCommand .' '. l:appArgs + echom 'starting job: ' . l:process + call s:openWindow('_JDB_SHELL_', '', 15) + let s:job = job_start(l:process , {"out_modifiable": 0, "out_io": "buffer", "out_name": "_JDB_SHELL_", "out_cb": "JdbOutHandler", "err_modifiable": 0, "err_io": "buffer", "err_name": "_JDB_SHELL_","err_cb": "JdbErrHandler"}) + let s:channel = job_getchannel(s:job) + + " Apply breakpoints stored in sign's into the debug process + call s:applyBreakPoints(s:channel, 'breakpoint') + + " Set cursor in where you started the process, in the correct buffer + call win_gotoid(s:org_win_id) + call winrestview(l:winview) + +endfunction