Skip to content
This repository was archived by the owner on Aug 10, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# vim-jdb

This is a fork of Dica-Developer/vim-jdb so checkout the main branch as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be removed before I can merge.

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.

Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate "the"

Copy link
Member

@mschaaf mschaaf Mar 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command name is misleading in my opinion. I would prefer JDBDebugTest or JDBDebugMethode

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail in a multi project gradle environment. I am wondering why if gradle is always there not starting the single test via gradle and then debug via this plugin. It could also start the gradle process before i debug mode.


## Commands
|Command|Description|
Expand All @@ -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'

221 changes: 191 additions & 30 deletions plugin/vim-jdb.vim
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ if !has('signs')
finish
endif

command! -nargs=? JDBDebugProcess call s:createDebugProcess(<f-args>)
command! -nargs=? JDBAttach call s:attach(<f-args>)
command! JDBDebugUnit call s:startUnitTest()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is startUnitTest defined?

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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give me more context why you changed that.

command! JDBContinue call s:continue()
command! JDBStepOver call s:stepOver()
command! JDBStepIn call s:stepIn()
Expand All @@ -49,14 +51,17 @@ sign define currentline text=-> texthl=Search

let s:job = ''
let s:channel = ''
let s:running = 0
let s:org_win_id = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use camel case and not underscores

let s:currentfile = ''

function! s:hash(name, linenumber)
let l:result = 1
for c in split(a:name, '\zs')
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for that change?

endfunction

function! s:getClassNameFromFile(filename)
Expand All @@ -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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for that change?

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the todo is fixed please remove the comment

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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You no longer call this for the file open case. How is the sign placed in this case?

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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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'
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, both todo's are not fixed. I would like to keep them until they are fixed or decided not to be implemented.

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
Copy link
Member

@mschaaf mschaaf Mar 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code should be moved to its own method to avoid the duplication.

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

Expand All @@ -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