Merge branch 'develop' into node-inspect

This commit is contained in:
Daniel L Morris 2018-08-12 21:26:24 -05:00 committed by GitHub
commit 1c87c72d52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1261 additions and 496 deletions

View File

@ -15,7 +15,8 @@ supports:
Vebugger is built as a generic framework for building frontends for Vebugger is built as a generic framework for building frontends for
interactive shell debugger, and comes with implementations for: interactive shell debugger, and comes with implementations for:
* GDB - doesn't need introdcution... * GDB - doesn't need introduction...
* LLDB - debugger based on LLVM for C-family languages
* JDB - a Java debugger * JDB - a Java debugger
* Mdbg - a .NET debugger(Windows only) * Mdbg - a .NET debugger(Windows only)
* PDB - a Python module for debugging Python scripts * PDB - a Python module for debugging Python scripts

View File

@ -1,21 +1,13 @@
"Read and return all new lines from a Vebugger pipe object. "Read bytes from pipe to buffer
function! s:readNewLinesFromPipe(pipeObject) function! s:fillBufferFromPipe(pipeObject)
"read let l:text = a:pipeObject.pipe.read(1024, 0)
let l:text=a:pipeObject.pipe.read(1000,0) let l:totalBytesRead = 0
while 0 < len(l:text) while 0 < len(l:text)
let l:totalBytesRead += len(l:text)
let a:pipeObject.buffer .= l:text let a:pipeObject.buffer .= l:text
let l:text=a:pipeObject.pipe.read(1000,0) let l:text = a:pipeObject.pipe.read(1024, 0)
endwhile endwhile
return l:totalBytesRead
"parse
let l:lastNewline=strridx(a:pipeObject.buffer,"\n")
if 0<=l:lastNewline
let l:outLines=split(strpart(a:pipeObject.buffer,0,l:lastNewline),'\r\n\|\n\|\r')
let a:pipeObject.buffer=strpart(a:pipeObject.buffer,l:lastNewline+1)
return l:outLines
endif
return []
endfunction endfunction
let s:f_debugger={} let s:f_debugger={}
@ -25,7 +17,9 @@ function! s:f_debugger.kill() dict
if self.shell.is_valid if self.shell.is_valid
call self.addLineToTerminal('','== DEBUGGER TERMINATED ==') call self.addLineToTerminal('','== DEBUGGER TERMINATED ==')
endif endif
if !has('timers')
let &updatetime=self.prevUpdateTime let &updatetime=self.prevUpdateTime
endif
call self.shell.kill(15) call self.shell.kill(15)
if exists('s:debugger') if exists('s:debugger')
for l:closeHandler in s:debugger.closeHandlers for l:closeHandler in s:debugger.closeHandlers
@ -43,10 +37,13 @@ endfunction
function! s:f_debugger.invokeReading() dict function! s:f_debugger.invokeReading() dict
let l:newLines = {} let l:newLines = {}
for l:k in keys(self.pipes) for l:k in keys(self.pipes)
let l:nl=s:readNewLinesFromPipe(self.pipes[l:k]) let l:pipe = self.pipes[l:k]
if 0 < s:fillBufferFromPipe(l:pipe)
let l:nl = l:pipe.bufferer()
if 0 < len(l:nl) if 0 < len(l:nl)
let l:newLines[l:k] = l:nl let l:newLines[l:k] = l:nl
endif endif
endif
endfor endfor
for l:k in keys(l:newLines) for l:k in keys(l:newLines)
for l:line in l:newLines[l:k] for l:line in l:newLines[l:k]
@ -59,7 +56,9 @@ function! s:f_debugger.invokeReading() dict
\|| 'error'==l:checkpid[0] \|| 'error'==l:checkpid[0]
call self.kill() call self.kill()
endif endif
call feedkeys("f\e", 'n') " Make sure the CursorHold event is refired even if the user does nothing if !has('timers')
call feedkeys("f\e", '\n') " Make sure the CursorHold event is refired even if the user does nothing
endif
endfunction endfunction
"Handle a single line from the debugger's interactive shell "Handle a single line from the debugger's interactive shell
@ -107,8 +106,9 @@ function! s:f_debugger.showTerminalBuffer() dict
new new
setlocal buftype=nofile setlocal buftype=nofile
setlocal bufhidden=wipe setlocal bufhidden=wipe
setlocal filetype=VebuggerTerminal
let self.terminalBuffer=bufnr('') let self.terminalBuffer=bufnr('')
silent file Vebugger:Ternimal silent file Vebugger:Terminal
wincmd p wincmd p
endfunction endfunction
@ -148,7 +148,7 @@ function! s:f_debugger.addLineToTerminal(pipeName,line) dict
if has_key(self,'terminalBuffer') if has_key(self,'terminalBuffer')
let l:bufwin=bufwinnr(self.terminalBuffer) let l:bufwin=bufwinnr(self.terminalBuffer)
if -1<l:bufwin if -1<l:bufwin
exe l:bufwin.'wincmd w' exe 'noautocmd '.l:bufwin.'wincmd w'
if has_key(self,'pipes') if has_key(self,'pipes')
\&&has_key(self.pipes,a:pipeName) \&&has_key(self.pipes,a:pipeName)
\&&has_key(self.pipes[a:pipeName],'annotation') \&&has_key(self.pipes[a:pipeName],'annotation')
@ -157,7 +157,7 @@ function! s:f_debugger.addLineToTerminal(pipeName,line) dict
call append (line('$'),a:line) call append (line('$'),a:line)
endif endif
normal G normal G
wincmd p noautocmd wincmd p
endif endif
endif endif
endfunction endfunction
@ -222,11 +222,23 @@ function! s:f_debugger.setWriteAction(namespace,name,value) dict
let self.writeActions[a:namespace][a:name]=a:value let self.writeActions[a:namespace][a:name]=a:value
endfunction endfunction
"Set a write action and perform it
function! s:f_debugger.setWriteActionAndPerform(namespace, name, value) dict
call self.setWriteAction(a:namespace, a:name, a:value)
call self.performWriteActions()
endfunction
"Add a write action of a specific namespace and name, for write actions that supports a list "Add a write action of a specific namespace and name, for write actions that supports a list
function! s:f_debugger.addWriteAction(namespace,name,value) dict function! s:f_debugger.addWriteAction(namespace,name,value) dict
call add(self.writeActions[a:namespace][a:name],a:value) call add(self.writeActions[a:namespace][a:name],a:value)
endfunction endfunction
"Add a write action and perform it
function! s:f_debugger.addWriteActionAndPerform(namespace, name, value) dict
call self.addWriteAction(a:namespace, a:name, a:value)
call self.performWriteActions()
endfunction
"Create a bare debugger object from a raw shell line "Create a bare debugger object from a raw shell line
function! vebugger#createDebugger(command) function! vebugger#createDebugger(command)
@ -240,6 +252,11 @@ function! vebugger#createDebugger(command)
let l:debugger.pipes = { let l:debugger.pipes = {
\ 'out': {'pipe':(l:debugger.shell.stdout), 'buffer': ''}, \ 'out': {'pipe':(l:debugger.shell.stdout), 'buffer': ''},
\ 'err': {'pipe':(l:debugger.shell.stderr), 'buffer': '', 'annotation': "err:\t\t"}} \ 'err': {'pipe':(l:debugger.shell.stderr), 'buffer': '', 'annotation': "err:\t\t"}}
for l:pipe in values(l:debugger.pipes)
"let l:pipe.buffer = ''
"let l:pipe.readIntoBuffer = function('vebugger#readIntoBuffer')
"let l:pipe.bufferer = function('vebugger#readNewLinesFromPipe')
endfor
let l:debugger.readResultTemplate={} let l:debugger.readResultTemplate={}
let l:debugger.state={} let l:debugger.state={}
@ -250,12 +267,19 @@ function! vebugger#createDebugger(command)
let l:debugger.writeHandlers={} let l:debugger.writeHandlers={}
let l:debugger.closeHandlers=[] let l:debugger.closeHandlers=[]
if !has('timers')
let l:debugger.prevUpdateTime=&updatetime let l:debugger.prevUpdateTime=&updatetime
set updatetime=500 set updatetime=500
endif
return l:debugger return l:debugger
endfunction endfunction
if has('timers')
function! s:readingTimerCallback(timerId)
call s:debugger.invokeReading()
endfunction
endif
"Create a debugger and set it as the currently active debugger "Create a debugger and set it as the currently active debugger
function! vebugger#startDebugger(command) function! vebugger#startDebugger(command)
@ -263,26 +287,74 @@ function! vebugger#startDebugger(command)
let s:debugger=vebugger#createDebugger(a:command) let s:debugger=vebugger#createDebugger(a:command)
if has('timers')
let s:timerId = timer_start(500, function('s:readingTimerCallback'), {'repeat': -1})
else
augroup vebugger_shell augroup vebugger_shell
autocmd! autocmd!
autocmd CursorHold * call s:debugger.invokeReading() autocmd CursorHold * call s:debugger.invokeReading()
augroup END augroup END
endif
return s:debugger return s:debugger
endfunction endfunction
"Terminate the currently active debugger "Terminate the currently active debugger
function! vebugger#killDebugger() function! vebugger#killDebugger()
if has('timers')
if exists('s:timerId')
call timer_stop(s:timerId)
unlet s:timerId
endif
else
augroup vebugger_shell augroup vebugger_shell
autocmd! autocmd!
augroup END augroup END
endif
if exists('s:debugger') if exists('s:debugger')
call vebugger#std#closeShellBuffer(s:debugger)
call s:debugger.closeTerminalBuffer() call s:debugger.closeTerminalBuffer()
call s:debugger.kill() call s:debugger.kill()
unlet s:debugger unlet s:debugger
endif endif
endfunction endfunction
"Perform an action on the actvie debugger, and register that action
function! vebugger#userAction(action, ...)
if exists('s:debugger')
if has_key(s:debugger, a:action)
let s:debugger.lastUserAction = {
\'action': a:action,
\'args': a:000}
try
doautocmd User Vebugger_PreUserAction
finally
call call(s:debugger[a:action], a:000, s:debugger)
doautocmd User Vebugger_PostUserAction
endtry
else
throw 'Current debugger does not support action '.a:action
endif
endif
endfunction
augroup vebugger_hooks
autocmd!
"Make a blank action hook to prevent 'No matching autocommands" warning
autocmd User Vebugger_* echo
augroup END
"Repeat the last action performed on the active debugger
function! vebugger#repeatLastUserAction()
if exists('s:debugger')
if has_key(s:debugger, 'lastUserAction')
let l:lastUserAction = s:debugger.lastUserAction
call call(s:debugger[l:lastUserAction.action], l:lastUserAction.args, s:debugger)
endif
endif
endfunction
"Write a line to the currently active debugger "Write a line to the currently active debugger
function! vebugger#writeLine(line) function! vebugger#writeLine(line)
if exists('s:debugger') if exists('s:debugger')
@ -297,13 +369,6 @@ function! vebugger#invokeReading()
endif endif
endfunction endfunction
"Toggle the terminal buffer for the currently active debugger
function! vebugger#toggleTerminalBuffer()
if exists('s:debugger')
call s:debugger.toggleTerminalBuffer()
endif
endfunction
"Fetch the currently active debugger object "Fetch the currently active debugger object
function! vebugger#getActiveDebugger() function! vebugger#getActiveDebugger()
if exists('s:debugger') if exists('s:debugger')
@ -312,36 +377,3 @@ function! vebugger#getActiveDebugger()
return {} return {}
endif endif
endfunction endfunction
"Set a write action for the currently active debugger
function! vebugger#setWriteAction(namespace,name,value)
if exists('s:debugger')
call s:debugger.setWriteAction(a:namespace,a:name,a:value)
endif
endfunction
"Add a write action to the currently active debugger
function! vebugger#addWriteAction(namespace,name,value)
if exists('s:debugger')
call s:debugger.addWriteAction(a:namespace,a:name,a:value)
endif
endfunction
"Force performing all the write of the currently active debugger
function! vebugger#performWriteActions()
if exists('s:debugger')
call s:debugger.performWriteActions()
endif
endfunction
"Set a write action for the currently active debugger and perform it
function! vebugger#setWriteActionAndPerform(namespace,name,value)
call vebugger#setWriteAction(a:namespace,a:name,a:value)
call vebugger#performWriteActions()
endfunction
"Add a write action to the currently active debugger and perform it
function! vebugger#addWriteActionAndPerform(namespace,name,value)
call vebugger#addWriteAction(a:namespace,a:name,a:value)
call vebugger#performWriteActions()
endfunction

View File

@ -1,22 +1,20 @@
function! vebugger#gdb#searchAndAttach(binaryFile)
let l:processId=vebugger#util#selectProcessOfFile(a:binaryFile)
if 0<l:processId
call vebugger#gdb#start(a:binaryFile,{'pid':l:processId})
endif
endfunction
function! vebugger#gdb#start(binaryFile,args) function! vebugger#gdb#start(binaryFile,args)
let l:debugger=vebugger#std#startDebugger(shellescape(vebugger#util#getToolFullPath('gdb',get(a:args,'version'),'gdb')) let l:debugger=vebugger#std#startDebugger(shellescape(vebugger#util#getToolFullPath('gdb',get(a:args,'version'),'gdb'))
\.' -i mi --silent '.fnameescape(a:binaryFile)) \.' -i mi --silent '.fnameescape(a:binaryFile))
let l:debugger.pipes.out.bufferer = function('vebugger#gdb#readMILinesFromPipe')
let l:debugger.state.gdb={} let l:debugger.state.gdb={}
let l:debugger.pipes.err.annotation = "err&prg\t\t" let l:debugger.pipes.err.annotation = "err&prg\t\t"
call l:debugger.writeLine("set width 0") call l:debugger.writeLine("set width 0")
call l:debugger.writeLine("set print pretty off")
call l:debugger.writeLine("set print array off")
call l:debugger.writeLine("define hook-stop\nwhere\nend") call l:debugger.writeLine("define hook-stop\nwhere\nend")
if get(a:args,'pid') "Attach to process if get(a:args,'pid') "Attach to process
call l:debugger.writeLine('attach '.string(a:args.pid)) call l:debugger.writeLine('attach '.string(a:args.pid))
elseif has_key(a:args,'con') "Attach to gdbserver
call l:debugger.writeLine('target remote '.a:args.con)
else else
call l:debugger.writeLine('set args '.vebugger#util#commandLineArgsForProgram(a:args).' 1>&2') call l:debugger.writeLine('set args '.vebugger#util#commandLineArgsForProgram(a:args).' 1>&2')
if !has('win32') if !has('win32')
@ -62,6 +60,61 @@ function! s:findFolderFromStackTrace(src,nameFromStackTrace)
return l:path return l:path
endfunction endfunction
function! vebugger#gdb#readMILinesFromPipe() dict
if !has_key(self, 'tildeBuffer')
let self.tildeBuffer = ''
endif
let l:result = []
while 1
let l:newLinePos = stridx(self.buffer, "\n")
if l:newLinePos < 0
break
endif
let l:firstChar = self.buffer[0]
if l:firstChar == '~'
while self.buffer[0] == '~'
let l:newLinePos = stridx(self.buffer, "\n")
let l:line = self.buffer[0 : l:newLinePos]
let l:line = l:line[2 : strridx(l:line, '"') - 1]
let self.buffer = strpart(self.buffer, l:newLinePos + 1)
"let self.tildeBuffer .= l:line
let l:line = substitute(l:line, '\\n', "\n", "g")
let l:line = substitute(l:line, '\\t', "\t", "g")
let l:line = substitute(l:line, '\\"', '"', "g")
let self.tildeBuffer .= l:line
endwhile
let l:lastTildeNewLine = strridx(self.tildeBuffer, "\n")
if 0 <= l:lastTildeNewLine
let l:newTildeLines = split(self.tildeBuffer[: l:lastTildeNewLine - 1], '\n')
call map(l:newTildeLines, '"~".v:val')
let self.tildeBuffer = strpart(self.tildeBuffer, l:lastTildeNewLine + 1)
call extend(l:result, l:newTildeLines)
endif
continue
else
if !empty(self.tildeBuffer)
call extend(l:result, split(self.tildeBuffer, '\n'))
let self.tildeBuffer = ''
endif
endif
let l:line = self.buffer[0 : l:newLinePos]
if l:firstChar == '&'
let l:line = l:line[2 : strridx(l:line, '"') - 1]
let l:line = substitute(l:line, '\\n', "\n", "g")
let l:line = substitute(l:line, '\\t', "\t", "g")
let l:line = substitute(l:line, '\\"', '"', "g")
let l:line = '&'.l:line
endif
let l:line = substitute(l:line, '\v[\n\r]+$', '', "")
call add(l:result, l:line)
let self.buffer = strpart(self.buffer, l:newLinePos + 1)
endwhile
return l:result
endfunction
function! vebugger#gdb#_readProgramOutput(pipeName,line,readResult,debugger) function! vebugger#gdb#_readProgramOutput(pipeName,line,readResult,debugger)
if 'err'==a:pipeName if 'err'==a:pipeName
\&&a:line!~'\v^[=~*&^]' \&&a:line!~'\v^[=~*&^]'
@ -72,11 +125,10 @@ endfunction
function! vebugger#gdb#_readWhere(pipeName,line,readResult,debugger) function! vebugger#gdb#_readWhere(pipeName,line,readResult,debugger)
if 'out'==a:pipeName if 'out'==a:pipeName
"let l:matches=matchlist(a:line,'\v^\~"#(\d+)\s+(.+)\s+\(.*\)\s+at\s+([^:]+):(\d+)')
let l:matches=matchlist(a:line,'\v^\*stopped.*fullname\=\"([^"]+)\",line\=\"(\d+)"') let l:matches=matchlist(a:line,'\v^\*stopped.*fullname\=\"([^"]+)\",line\=\"(\d+)"')
if 2<len(l:matches) if 2<len(l:matches)
let l:file=l:matches[1] let l:file=l:matches[1]
let l:file=fnamemodify(l:file,':~:.') let l:file=fnamemodify(l:file,':p')
let a:readResult.std.location={ let a:readResult.std.location={
\'file':(l:file), \'file':(l:file),
\'line':str2nr(l:matches[2])} \'line':str2nr(l:matches[2])}
@ -85,7 +137,7 @@ function! vebugger#gdb#_readWhere(pipeName,line,readResult,debugger)
endfunction endfunction
function! vebugger#gdb#_readFinish(pipeName,line,readResult,debugger) function! vebugger#gdb#_readFinish(pipeName,line,readResult,debugger)
if a:line=~'\c\V\^~"[Inferior \.\*exited normally]' if a:line=~'\c\V*stopped\.\*exited\[ -]normally'
let a:readResult.std.programFinish={'finish':1} let a:readResult.std.programFinish={'finish':1}
endif endif
endfunction endfunction
@ -134,7 +186,7 @@ endfunction
function! vebugger#gdb#_readEvaluatedExpressions(pipeName,line,readResult,debugger) dict function! vebugger#gdb#_readEvaluatedExpressions(pipeName,line,readResult,debugger) dict
if 'out' == a:pipeName if 'out' == a:pipeName
if has_key(self, 'nextExpressionToBePrinted') if has_key(self, 'nextExpressionToBePrinted')
let l:matches=matchlist(a:line,'\v^\~"\$(\d+) \= (.*)"$') let l:matches=matchlist(a:line,'\v^\~\$(\d+) \= (.*)$')
if 2<len(l:matches) if 2<len(l:matches)
let l:expression=l:matches[1] let l:expression=l:matches[1]
let l:value=l:matches[2] let l:value=l:matches[2]
@ -144,7 +196,7 @@ function! vebugger#gdb#_readEvaluatedExpressions(pipeName,line,readResult,debugg
endif endif
call remove(self,'nextExpressionToBePrinted') call remove(self,'nextExpressionToBePrinted')
else else
let l:matches=matchlist(a:line,'\v^\&"print (.{-})(\\r)?(\\n)?"$') let l:matches=matchlist(a:line,'\v^\&print (.+)$')
if 1<len(l:matches) if 1<len(l:matches)
let self.nextExpressionToBePrinted=s:unescapeString(l:matches[1]) let self.nextExpressionToBePrinted=s:unescapeString(l:matches[1])
endif endif

View File

@ -1,6 +1,7 @@
function! vebugger#jdb#start(entryClass,args) function! vebugger#jdb#start(entryClass,args)
let l:debugger=vebugger#std#startDebugger(shellescape(vebugger#util#getToolFullPath('jdb',get(a:args,'version'),'jdb')) let l:debugger=vebugger#std#startDebugger(shellescape(vebugger#util#getToolFullPath('jdb',get(a:args,'version'),'jdb'))
\.(has_key(a:args,'classpath') ? ' -classpath '.fnameescape(a:args.classpath) : '')) \.(has_key(a:args,'classpath') && !has_key(a:args,'attach') ? ' -classpath '.fnameescape(a:args.classpath) : '')
\.(has_key(a:args,'attach') ? ' -attach '.shellescape(a:args.attach) : ''))
let l:debugger.state.jdb={} let l:debugger.state.jdb={}
if has_key(a:args,'srcpath') if has_key(a:args,'srcpath')
let l:debugger.state.jdb.srcpath=a:args.srcpath let l:debugger.state.jdb.srcpath=a:args.srcpath
@ -9,10 +10,14 @@ function! vebugger#jdb#start(entryClass,args)
endif endif
let l:debugger.state.jdb.filesToClassesMap={} let l:debugger.state.jdb.filesToClassesMap={}
if !has_key(a:args,'attach')
call l:debugger.writeLine('stop on '.a:entryClass.'.main') call l:debugger.writeLine('stop on '.a:entryClass.'.main')
call l:debugger.writeLine('run '.a:entryClass.' '.vebugger#util#commandLineArgsForProgram(a:args)) call l:debugger.writeLine('run '.a:entryClass.' '.vebugger#util#commandLineArgsForProgram(a:args))
else
call l:debugger.writeLine('run')
endif
call l:debugger.writeLine('monitor where') call l:debugger.writeLine('monitor where')
if !has('win32') if !has('win32') && !has_key(a:args,'attach')
call vebugger#std#openShellBuffer(l:debugger) call vebugger#std#openShellBuffer(l:debugger)
endif endif
@ -33,6 +38,12 @@ function! vebugger#jdb#start(entryClass,args)
return l:debugger return l:debugger
endfunction endfunction
function! vebugger#jdb#attach(address, ...)
let l:args = a:0 ? a:{1} : {}
let l:args.attach = a:address
call vebugger#jdb#start('', l:args)
endfunction
function! vebugger#jdb#_readProgramOutput(pipeName,line,readResult,debugger) dict function! vebugger#jdb#_readProgramOutput(pipeName,line,readResult,debugger) dict
if 'out'==a:pipeName if 'out'==a:pipeName
if a:line=~'\v^\> \>' if a:line=~'\v^\> \>'
@ -56,14 +67,45 @@ function! vebugger#jdb#_readProgramOutput(pipeName,line,readResult,debugger) dic
endif endif
endfunction endfunction
function! s:findFolderFromStackTrace(src,nameFromStackTrace) function! s:getTagContainingString(tag, str)
let l:path=a:src let l:tags = taglist(a:tag)
if (len(l:tags) > 0)
for l:tag in l:tags
if (filereadable(l:tag.filename) && match(readfile(l:tag.filename), a:str) >= 0)
return l:tag
endif
endfor
endif
return {}
endfunction
function! s:findFolderFromStackTrace(src,nameFromStackTrace,frameNumber)
" Remove method name.
let l:canonicalClassName = strpart(a:nameFromStackTrace, 0, strridx(a:nameFromStackTrace, "."))
" Remove package name.
let l:simpleClassName = strridx(l:canonicalClassName, ".") >= 0 ? strpart(l:canonicalClassName, strridx(l:canonicalClassName, ".") + 1) : l:canonicalClassName
" Remove class name.
let l:package = strridx(l:canonicalClassName, ".") >= 0 ? strpart(l:canonicalClassName, 0, strridx(l:canonicalClassName, ".")) : ""
" We don't really use callstack, so we use tags only for the current location.
" Otherwise it makes everything too slow.
if exists('g:vebugger_use_tags') && g:vebugger_use_tags && a:frameNumber == 1
" Now first try to find a tag for the class from the required package.
let l:classTag = s:getTagContainingString(l:simpleClassName, l:package)
if (has_key(l:classTag, "filename"))
return fnamemodify(l:classTag.filename, ":h")
endif
endif
" If no such tag was found, try to find it using the src path.
for l:path in vebugger#util#listify(a:src)
for l:dirname in split(a:nameFromStackTrace,'\.') for l:dirname in split(a:nameFromStackTrace,'\.')
let l:nextPath=l:path.'/'.fnameescape(l:dirname) let l:nextPath=l:path.'/'.fnameescape(l:dirname)
if empty(glob(l:nextPath)) if empty(glob(l:nextPath))
return l:path return l:path
endif endif
let l:path=l:nextPath let l:path=l:nextPath
endfor
endfor endfor
return l:path return l:path
endfunction endfunction
@ -72,9 +114,9 @@ function! vebugger#jdb#_readWhere(pipeName,line,readResult,debugger)
if 'out'==a:pipeName if 'out'==a:pipeName
let l:matches=matchlist(a:line,'\v\s*\[(\d+)]\s*(\S+)\s*\(([^:]*):(\d*)\)') let l:matches=matchlist(a:line,'\v\s*\[(\d+)]\s*(\S+)\s*\(([^:]*):(\d*)\)')
if 4<len(l:matches) if 4<len(l:matches)
let l:file=s:findFolderFromStackTrace(a:debugger.state.jdb.srcpath,l:matches[2]).'/'.l:matches[3]
let l:file=fnamemodify(l:file,':~:.')
let l:frameNumber=str2nr(l:matches[1]) let l:frameNumber=str2nr(l:matches[1])
let l:file=s:findFolderFromStackTrace(a:debugger.state.jdb.srcpath,l:matches[2],l:frameNumber).'/'.l:matches[3]
let l:file=fnamemodify(l:file,':p')
if 1==l:frameNumber " first stackframe is the current location if 1==l:frameNumber " first stackframe is the current location
let a:readResult.std.location={ let a:readResult.std.location={
\'file':(l:file), \'file':(l:file),
@ -114,7 +156,8 @@ endfunction
function! s:getClassNameFromFile(filename) function! s:getClassNameFromFile(filename)
let l:className=fnamemodify(a:filename,':t:r') " Get only the name of the file, without path or extension let l:className=fnamemodify(a:filename,':t:r') " Get only the name of the file, without path or extension
for l:line in readfile(a:filename) for l:line in readfile(a:filename)
let l:matches=matchlist(l:line,'\vpackage\s+(%(\w|\.)+)\s*;') " trailing ; is optional to make it work for groovy as well
let l:matches=matchlist(l:line,'\vpackage\s+(%(\w|\.)+)\s*;?')
if 1<len(l:matches) if 1<len(l:matches)
return l:matches[1].'.'.l:className return l:matches[1].'.'.l:className
endif endif

139
autoload/vebugger/lldb.vim Normal file
View File

@ -0,0 +1,139 @@
let s:script_dir_path=expand('<sfile>:p:h')
function! vebugger#lldb#start(binaryFile,args)
let l:debuggerExe=vebugger#util#getToolFullPath('python','lldb','python2')
let l:debugger=vebugger#std#startDebugger(shellescape(l:debuggerExe)
\.' '.s:script_dir_path.'/lldb_wrapper.py '.fnameescape(a:binaryFile))
let l:debugger.state.lldb={}
if get(a:args,'pid') "Attach to process
call l:debugger.writeLine('process attach --pid '.string(a:args.pid))
elseif has_key(a:args,'con') "Attach to lldbserver
call l:debugger.writeLine('platform connect connect://'.a:args.con)
else
call l:debugger.writeLine('settings set target.run-args '.vebugger#util#commandLineArgsForProgram(a:args))
if !has('win32')
call vebugger#std#openShellBuffer(l:debugger)
endif
" TODO: remove 'and false'; add a temporary breakpoint to lldb
if has_key(a:args,'entry') && 0
" call l:debugger.writeLine('tbreak '.a:args.entry)
" call l:debugger.writeLine('run')
else
call l:debugger.writeLine('breakpoint set --name main')
call l:debugger.writeLine('process launch')
endif
endif
call l:debugger.addReadHandler(function('vebugger#lldb#_readProgramOutput'))
call l:debugger.addReadHandler(function('vebugger#lldb#_readWhere'))
call l:debugger.addReadHandler(function('vebugger#lldb#_readFinish'))
call l:debugger.addReadHandler(function('vebugger#lldb#_readEvaluatedExpressions'))
call l:debugger.setWriteHandler('std','flow',function('vebugger#lldb#_writeFlow'))
call l:debugger.setWriteHandler('std','breakpoints',function('vebugger#lldb#_writeBreakpoints'))
call l:debugger.setWriteHandler('std','closeDebugger',function('vebugger#lldb#_closeDebugger'))
call l:debugger.setWriteHandler('std','evaluateExpressions',function('vebugger#lldb#_requestEvaluateExpression'))
call l:debugger.setWriteHandler('std','executeStatements',function('vebugger#lldb#_executeStatements'))
call l:debugger.generateWriteActionsFromTemplate()
call l:debugger.std_addAllBreakpointActions(g:vebugger_breakpoints)
return l:debugger
endfunction
function! vebugger#lldb#_readProgramOutput(pipeName,line,readResult,debugger)
if 'out'==a:pipeName
\&&(a:line=~'\v^program_stdout:'
\||a:line=~'\v^program_stderr:')
let a:readResult.std.programOutput={'line':strpart(a:line, 16)}
endif
endfunction
function! vebugger#lldb#_readWhere(pipeName,line,readResult,debugger)
if 'out'==a:pipeName
\&&a:line=~'\v^where:'
let l:matches=matchlist(a:line,'\v^where:\s([^:]+):(\d+)')
if 2<len(l:matches)
let l:file=l:matches[1]
let l:file=fnamemodify(l:file,':p')
let a:readResult.std.location={
\'file':(l:file),
\'line':str2nr(l:matches[2])}
endif
endif
endfunction
function! vebugger#lldb#_readFinish(pipeName,line,readResult,debugger)
if 'out'==a:pipeName
\&&a:line=~'\v^program_state:\sExited'
let a:readResult.std.programFinish={'finish':1}
endif
endfunction
function! vebugger#lldb#_writeFlow(writeAction,debugger)
if 'stepin'==a:writeAction
call a:debugger.writeLine('step')
elseif 'stepover'==a:writeAction
call a:debugger.writeLine('next')
elseif 'stepout'==a:writeAction
call a:debugger.writeLine('finish')
elseif 'continue'==a:writeAction
call a:debugger.writeLine('continue')
endif
endfunction
function! vebugger#lldb#_closeDebugger(writeAction,debugger)
call a:debugger.writeLine('quit')
endfunction
function! vebugger#lldb#_writeBreakpoints(writeAction,debugger)
for l:breakpoint in a:writeAction
if 'add'==(l:breakpoint.action)
call a:debugger.writeLine('br '.fnameescape(l:breakpoint.file).':'.l:breakpoint.line)
elseif 'remove'==l:breakpoint.action
call a:debugger.writeLine('clear '.fnameescape(l:breakpoint.file).':'.l:breakpoint.line)
endif
endfor
endfunction
function! vebugger#lldb#_requestEvaluateExpression(writeAction,debugger)
for l:evalAction in a:writeAction
call a:debugger.writeLine('print '.l:evalAction.expression)
endfor
endfunction
function! vebugger#lldb#_executeStatements(writeAction,debugger)
for l:evalAction in a:writeAction
if has_key(l:evalAction,'statement')
"Use eval to run the statement - but first we need to remove the ;
call a:debugger.writeLine('print '.substitute(l:evalAction.statement,'\v;\s*$','',''))
endif
endfor
endfunction
function! vebugger#lldb#_readEvaluatedExpressions(pipeName,line,readResult,debugger) dict
if 'out' == a:pipeName
if has_key(self, 'nextExpressionToBePrinted')
\&&a:line=~'\v^debugger_output:'
let l:matches=matchlist(a:line,'\v^[^\$]*\$(\d+) \= (.*)$')
if 2<len(l:matches)
let l:expression=l:matches[1]
let l:value=l:matches[2]
let a:readResult.std.evaluatedExpression={
\'expression':self.nextExpressionToBePrinted,
\'value':(l:value)}
endif
call remove(self,'nextExpressionToBePrinted')
else
let l:matches=matchlist(a:line,'\v^print (.+)$')
if 1<len(l:matches)
let self.nextExpressionToBePrinted=l:matches[1]
endif
endif
endif
endfunction

255
autoload/vebugger/lldb_wrapper.py Executable file
View File

@ -0,0 +1,255 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import collections
import os
import platform
import re
import subprocess
import sys
FilePosition = collections.namedtuple('FilePosition', ['filepath', 'linenumber'])
try:
# Just try for LLDB in case PYTHONPATH is already correctly setup
import lldb
except ImportError:
lldb_python_dirs = []
# lldb is not in the PYTHONPATH, try some defaults for the current platform
platform_system = platform.system()
if platform_system == 'Darwin':
# On Darwin, try the currently selected Xcode directory
try:
xcode_dir = subprocess.check_output(['xcode-select', '--print-path'])
except subprocess.CalledProcessError:
xcode_dir = None
if xcode_dir:
lldb_python_dirs.append(
os.path.realpath(
os.path.join(xcode_dir, '../SharedFrameworks/LLDB.framework/Versions/A/Resources/Python')
)
)
lldb_python_dirs.append(
'/Library/Developer/CommandLineTools/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python'
)
for lldb_python_dir in lldb_python_dirs:
if os.path.exists(lldb_python_dir):
if lldb_python_dir not in sys.path:
sys.path.append(lldb_python_dir)
try:
import lldb
except ImportError:
pass
else:
break
else:
sys.stderr.write('Error: could not locate the "lldb" module, please set PYTHONPATH correctly\n')
sys.exit(1)
class NoCustomCommandError(Exception):
pass
class Debugger(object):
class BreakpointManager(object):
def __init__(self):
self._next_breakpoint_id = 1
self._location_to_id = {}
def add_breakpoint(self, filename, linenumber):
location = (filename, linenumber)
breakpoint_id = self._next_breakpoint_id
self._location_to_id[location] = breakpoint_id
self._next_breakpoint_id += 1
return breakpoint_id
def remove_breakpoint(self, filename, linenumber):
location = (filename, linenumber)
breakpoint_id = self._location_to_id[location]
del self._location_to_id[location]
return breakpoint_id
def __init__(self, executable):
self._executable = executable
self._debugger = lldb.SBDebugger.Create()
self._debugger.SetAsync(False)
self._command_interpreter = self._debugger.GetCommandInterpreter()
error = lldb.SBError()
self._target = self._debugger.CreateTarget(
self._executable, None, None, True, error
)
self._process = None
self._last_debugger_return_obj = None
self._state_dict = None
self._breakpoint_manager = self.BreakpointManager()
self._custom_commands = ['br', 'clear']
self._set_options()
def _set_options(self):
# -> first read lldbinit
try:
with open(os.path.expanduser('~/.lldbinit')) as f:
for line in f:
self.run_command(line)
except IOError:
pass
self.run_command('settings set frame-format frame #${frame.index}: ${frame.pc}{ ${module.file.basename}`${function.name}{${function.pc-offset}}}{ at:${line.file.fullpath}:${line.number}}\n')
self.run_command('settings set auto-confirm 1')
def run_command(self, commandline):
if self._is_custom_command(commandline):
self._run_custom_command(commandline)
else:
if isinstance(commandline, unicode):
commandline = commandline.encode('utf-8')
return_obj = lldb.SBCommandReturnObject()
self._command_interpreter.HandleCommand(commandline, return_obj)
if self._process is None or self._process.GetState() == lldb.eStateInvalid:
self._process = self._command_interpreter.GetProcess()
self._last_debugger_return_obj = return_obj
def _is_custom_command(self, commandline):
command = commandline.split()[0]
return command in self._custom_commands
def _run_custom_command(self, commandline):
def br(arguments):
filename, linenumber = arguments[0].split(':')
linenumber = int(linenumber)
self._breakpoint_manager.add_breakpoint(filename, linenumber)
self.run_command('b {}'.format(*arguments))
def clear(arguments):
filename, linenumber = arguments[0].split(':')
linenumber = int(linenumber)
breakpoint_id = self._breakpoint_manager.remove_breakpoint(filename,
linenumber)
self.run_command('breakpoint delete {:d}'.format(breakpoint_id))
if not self._is_custom_command(commandline):
raise NoCustomCommandError
parts = commandline.split()
print('parts:', parts)
command = parts[0]
arguments = parts[1:]
locals()[command](arguments)
@property
def debugger_output(self):
if self._last_debugger_return_obj is not None:
return_obj = self._last_debugger_return_obj
self._last_debugger_return_obj = None
return return_obj.GetOutput()
else:
return ''
@property
def where(self):
def extract_where(backtrace):
where = None
pattern = re.compile('at:([^:]+):(\d+)')
backtrace_lines = backtrace.split('\n')
for line in backtrace_lines:
match_obj = pattern.search(line)
if match_obj:
filepath = match_obj.group(1)
linenumber = int(match_obj.group(2))
if os.access(filepath, os.R_OK):
where = FilePosition(filepath, linenumber)
break
return where
where = None
self.run_command('bt')
debugger_output = self.debugger_output
if debugger_output is not None:
where = extract_where(debugger_output)
return where
@property
def program_stdout(self):
stdout = []
has_text = True
while has_text:
text = self._process.GetSTDOUT(1024)
if text:
stdout.append(text)
else:
has_text = False
return ''.join(stdout)
@property
def program_stderr(self):
stderr = []
has_text = True
while has_text:
text = self._process.GetSTDERR(1024)
if text:
stderr.append(text)
else:
has_text = False
return ''.join(stderr)
@property
def program_state(self):
return self._state_id_to_name(self._process.GetState())
def _state_id_to_name(self, state_id):
if self._state_dict is None:
self._state_dict = {}
for key, value in lldb.__dict__.iteritems():
if key.startswith('eState'):
self._state_dict[value] = key[6:]
return self._state_dict[state_id]
def prefix_output(output, prefix):
if output is None:
output = ''
lines = output.split('\n')
lines = [prefix + line for line in lines]
prefixed_output = '\n'.join(lines)
return prefixed_output
def main():
if len(sys.argv) < 2:
sys.stderr.write('An executable is needed as an argument.\n')
sys.exit(1)
executable = sys.argv[1]
debugger = Debugger(executable)
try:
while True:
line = raw_input()
# TODO: find a way to check directly if the debugger was terminated
if line in ['exit', 'quit']:
raise EOFError
debugger.run_command(line)
program_stdout = debugger.program_stdout
if program_stdout:
print(prefix_output(program_stdout, 'program_stdout: '))
program_stderr = debugger.program_stderr
if program_stderr:
print(prefix_output(program_stderr, 'program_stderr: '))
print(prefix_output(debugger.debugger_output, 'debugger_output: '))
where = debugger.where
if where:
print(prefix_output('{:s}:{:d}'.format(where.filepath, where.linenumber), 'where: '))
print(prefix_output(debugger.program_state, 'program_state: '))
except EOFError:
print('Exiting')
if __name__ == '__main__':
main()

View File

@ -95,7 +95,7 @@ function! vebugger#mdbg#_readWhere(pipeName,line,readResult,debugger)
if 3<len(l:matches) if 3<len(l:matches)
let l:frameNumber=str2nr(l:matches[1]) let l:frameNumber=str2nr(l:matches[1])
let l:file=s:findFilePath(a:debugger.state.mdbg.srcpath,l:matches[3],l:matches[2]) let l:file=s:findFilePath(a:debugger.state.mdbg.srcpath,l:matches[3],l:matches[2])
let l:file=fnamemodify(l:file,':~:.') let l:file=fnamemodify(l:file,':p')
if 0==l:frameNumber " first stackframe is the current location if 0==l:frameNumber " first stackframe is the current location
let a:readResult.std.location={ let a:readResult.std.location={
\'file':(l:file), \'file':(l:file),

View File

@ -56,7 +56,7 @@ endfunction
function! vebugger#pdb#_readWhere(pipeName,line,readResult,debugger) function! vebugger#pdb#_readWhere(pipeName,line,readResult,debugger)
if 'out'==a:pipeName if 'out'==a:pipeName
let l:matches=matchlist(a:line,'\v^\> (.+)\((\d+)\).*\(\)$') let l:matches=matchlist(a:line,'\v^\> (.+)\((\d+)\).*\(\)%(-\>.*)?$')
if 2<len(l:matches) if 2<len(l:matches)
let l:file=l:matches[1] let l:file=l:matches[1]

View File

@ -1,5 +1,12 @@
let g:vebugger_breakpoints=[] let g:vebugger_breakpoints=[]
"Initialize the default pipe bufferers
function! vebugger#std#setStandardBufferers(debugger)
for l:pipe in values(a:debugger.pipes)
let l:pipe.bufferer = function('vebugger#std#readNewLinesFromPipe')
endfor
endfunction
"Initialize the std part of the debugger's state "Initialize the std part of the debugger's state
function! vebugger#std#setStandardState(debugger) function! vebugger#std#setStandardState(debugger)
let a:debugger.state.std={ let a:debugger.state.std={
@ -55,6 +62,7 @@ endfunction
"Performs the standard initialization of the debugger object "Performs the standard initialization of the debugger object
function! vebugger#std#standardInit(debugger) function! vebugger#std#standardInit(debugger)
call vebugger#std#setStandardBufferers(a:debugger)
call vebugger#std#setStandardState(a:debugger) call vebugger#std#setStandardState(a:debugger)
call vebugger#std#setStandardReadResultTemplate(a:debugger) call vebugger#std#setStandardReadResultTemplate(a:debugger)
call vebugger#std#setStandardWriteactionsTemplate(a:debugger) call vebugger#std#setStandardWriteactionsTemplate(a:debugger)
@ -73,6 +81,19 @@ function! vebugger#std#startDebugger(command)
endfunction endfunction
"Read and return all new lines from a Vebugger pipe object.
function! vebugger#std#readNewLinesFromPipe() dict
let l:lastNewline = strridx(self.buffer, "\n")
if 0 <= l:lastNewline
let l:outLines = split(strpart(self.buffer, 0, l:lastNewline), '\r\n\|\n\|\r')
let self.buffer = strpart(self.buffer, l:lastNewline + 1)
return l:outLines
endif
return []
endfunction
"Opens the shell buffer for a debugger. The shell buffer displays the output "Opens the shell buffer for a debugger. The shell buffer displays the output
"of the debugged program, and when it's closed the debugger gets terminated. "of the debugged program, and when it's closed the debugger gets terminated.
"Shell buffers should not be used when attaching a debugger to a running "Shell buffers should not be used when attaching a debugger to a running
@ -95,12 +116,25 @@ function! vebugger#std#openShellBuffer(debugger)
let b:debugger=a:debugger let b:debugger=a:debugger
autocmd BufDelete <buffer> if exists('b:debugger') | call b:debugger.kill() | endif autocmd BufDelete <buffer> if exists('b:debugger') | call b:debugger.kill() | endif
setlocal buftype=nofile setlocal buftype=nofile
setlocal filetype=VebuggerShell
setlocal bufhidden=wipe setlocal bufhidden=wipe
let a:debugger.shellBuffer=bufnr('') let a:debugger.shellBuffer=bufnr('')
silent file Vebugger:Shell silent file Vebugger:Shell
wincmd p wincmd p
endfunction endfunction
"Closes the shell buffer
function! vebugger#std#closeShellBuffer(debugger)
if has_key(a:debugger,'shellBuffer')
if -1<bufwinnr(a:debugger.shellBuffer)
let l:bufwin=bufwinnr(a:debugger.shellBuffer)
exe l:bufwin.'wincmd w'
wincmd c
wincmd p
endif
endif
endfunction
let s:standardFunctions={} let s:standardFunctions={}
"Write a line to the shell buffer "Write a line to the shell buffer
@ -108,10 +142,10 @@ function! s:standardFunctions.addLineToShellBuffer(line) dict
if has_key(self,'shellBuffer') if has_key(self,'shellBuffer')
let l:bufwin=bufwinnr(self.shellBuffer) let l:bufwin=bufwinnr(self.shellBuffer)
if -1<l:bufwin if -1<l:bufwin
exe l:bufwin.'wincmd w' exe 'noautocmd '.l:bufwin.'wincmd w'
call append (line('$'),a:line) call append (line('$'),a:line)
normal G normal G
wincmd p noautocmd wincmd p
endif endif
endif endif
endfunction endfunction
@ -168,8 +202,8 @@ function! s:standardThinkHandlers.moveToCurrentLine(readResult,debugger) dict
exe 'sign unplace 1 file='.fnameescape(fnamemodify(a:debugger.state.std.location.file,':p')) exe 'sign unplace 1 file='.fnameescape(fnamemodify(a:debugger.state.std.location.file,':p'))
endif endif
let a:debugger.state.std.location=deepcopy(a:readResult.std.location) let a:debugger.state.std.location=deepcopy(a:readResult.std.location)
if !bufexists(a:readResult.std.location.file) if -1 == bufwinnr(a:readResult.std.location.file)
exe 'new '.(a:readResult.std.location.file) exe get(g:, 'vebugger_view_source_cmd', 'new').' '.(a:readResult.std.location.file)
endif endif
call vebugger#std#updateMarksForFile(a:debugger.state,a:readResult.std.location.file) call vebugger#std#updateMarksForFile(a:debugger.state,a:readResult.std.location.file)
exe 'sign jump 1 file='.fnameescape(fnamemodify(a:readResult.std.location.file,':p')) exe 'sign jump 1 file='.fnameescape(fnamemodify(a:readResult.std.location.file,':p'))
@ -193,20 +227,52 @@ function! s:standardThinkHandlers.updateCallStack(readResult,debugger) dict
endif endif
endfunction endfunction
"Helper print function; when using vim timers feedkeys ensures that messages
"can be confirmend by the user.
function! s:printWithFeedKeys(evaluatedExpression)
if empty(get(a:evaluatedExpression,'expression'))
let l:echo = a:evaluatedExpression.value
else
let l:echo = a:evaluatedExpression.expression.': '.a:evaluatedExpression.value
endif
let l:echoLines = split(l:echo, '\r\n\|\n\|\r')
if len(l:echoLines) == 1
call add(l:echoLines, '')
endif
if has('timers')
" Convert to an expression we can use with feedkeys.
" string(l:echo) won't do because it uses single-quotes which
" do not escape newlines
let l:echoJoinExpr = 'join('.string(l:echoLines).', "\n")'
let l:echoKeys = ':echo '.l:echoJoinExpr."\<Cr>"
if mode() == 'n'
" Call echo normaly
call feedkeys(l:echoKeys)
elseif mode() == 'i'
" Execute command in insert mode
call feedkeys("\<C-o>".l:echoKeys)
endif
" NOTE: Other modes are not supported
else
" Without timer support, feedkeys won't work and we have
" to echo directly
echo join(l:echoLines, "\n")
endif
endfunction
"Print an expression that it's evaluation was previously requested "Print an expression that it's evaluation was previously requested
function! s:standardThinkHandlers.printEvaluatedExpression(readResult,debugger) dict function! s:standardThinkHandlers.printEvaluatedExpression(readResult,debugger) dict
let l:evaluatedExpression=a:readResult.std.evaluatedExpression let l:evaluatedExpression=a:readResult.std.evaluatedExpression
if !empty(l:evaluatedExpression) if !empty(l:evaluatedExpression)
if empty(get(l:evaluatedExpression,'expression')) if !empty(get(l:evaluatedExpression,'expression'))
echo l:evaluatedExpression.value."\n"
else
let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression) let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression)
if 0<=l:index if 0<=l:index
call remove(a:debugger.state.std.evaluateExpressions,l:index) call remove(a:debugger.state.std.evaluateExpressions,l:index)
echo l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n"
let g:echo=l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n"
endif endif
endif endif
call s:printWithFeedKeys(l:evaluatedExpression)
call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult) call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult)
endif endif
endfunction endfunction
@ -235,15 +301,32 @@ function! s:standardCloseHandlers.removeCurrentMarker(debugger) dict
sign unplace 1 sign unplace 1
endfunction endfunction
sign define vebugger_current text=-> let s:breakpoint_text = get(g:, 'vebugger_breakpoint_text', '->')
sign define vebugger_breakpoint text=** linehl=ColorColumn let s:currentline_text = get(g:, 'vebugger_currentline_text', '**')
if hlexists("DebuggedLine")
sign define vebugger_current linehl=DebuggedLine
else
execute 'sign define vebugger_current text=' . s:currentline_text
endif
if hlexists('BreakPoint')
execute 'sign define vebugger_breakpoint text=' . s:breakpoint_text . ' linehl=BreakPoint texthl=BreakPoint'
else
execute 'sign define vebugger_breakpoint text=' . s:breakpoint_text . ' linehl=ColorColumn texthl=ColorColumn'
endif
"Update all the marks(currently executed line and breakpoints) for a file "Update all the marks(currently executed line and breakpoints) for a file
function! vebugger#std#updateMarksForFile(state,filename) function! vebugger#std#updateMarksForFile(state,filename)
let l:filename=fnamemodify(a:filename,":p") let l:filename=fnamemodify(a:filename,":p")
if bufexists(l:filename) let l:bufnr = bufnr(l:filename)
if -1 < l:bufnr
exe 'sign unplace 1 file='.fnameescape(fnamemodify(l:filename,':p')) exe 'sign unplace 1 file='.fnameescape(fnamemodify(l:filename,':p'))
for l:sign in vebugger#util#listSignsInBuffer(l:bufnr)
if l:sign.name == 'vebugger_breakpoint'
exe 'sign unplace 2 file='.fnameescape(fnamemodify(l:filename,':p')) exe 'sign unplace 2 file='.fnameescape(fnamemodify(l:filename,':p'))
endif
endfor
for l:breakpoint in g:vebugger_breakpoints for l:breakpoint in g:vebugger_breakpoints
if fnamemodify(l:breakpoint.file,':p')==fnamemodify(a:filename,':p') if fnamemodify(l:breakpoint.file,':p')==fnamemodify(a:filename,':p')
@ -271,19 +354,23 @@ function! vebugger#std#toggleBreakpoint(file,line)
let l:breakpoint=g:vebugger_breakpoints[l:i] let l:breakpoint=g:vebugger_breakpoints[l:i]
if l:breakpoint.file==a:file && l:breakpoint.line==a:line if l:breakpoint.file==a:file && l:breakpoint.line==a:line
call remove(g:vebugger_breakpoints,l:i) call remove(g:vebugger_breakpoints,l:i)
call vebugger#addWriteActionAndPerform('std','breakpoints',{ if !empty(l:debugger)
call l:debugger.addWriteActionAndPerform('std','breakpoints',{
\'action':'remove', \'action':'remove',
\'file':(a:file), \'file':(a:file),
\'line':(a:line)}) \'line':(a:line)})
endif
call vebugger#std#updateMarksForFile(l:debuggerState,a:file) call vebugger#std#updateMarksForFile(l:debuggerState,a:file)
return return
endif endif
endfor endfor
call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)}) call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)})
call vebugger#addWriteActionAndPerform('std','breakpoints',{ if !empty(l:debugger)
call l:debugger.addWriteActionAndPerform('std','breakpoints',{
\'action':'add', \'action':'add',
\'file':(a:file), \'file':(a:file),
\'line':(a:line)}) \'line':(a:line)})
endif
call vebugger#std#updateMarksForFile(l:debuggerState,a:file) call vebugger#std#updateMarksForFile(l:debuggerState,a:file)
endfunction endfunction
@ -296,27 +383,15 @@ function! vebugger#std#clearBreakpoints()
if index(l:files,l:breakpoint.file)<0 if index(l:files,l:breakpoint.file)<0
call add(l:files,l:breakpoint.file) call add(l:files,l:breakpoint.file)
endif endif
call vebugger#addWriteAction('std','breakpoints',extend({'action':'remove'},l:breakpoint)) if !empty(l:debugger)
call l:debugger.addWriteAction('std','breakpoints',extend({'action':'remove'},l:breakpoint))
endif
endfor endfor
call vebugger#performWriteActions() if !empty(l:debugger)
call l:debugger.performWriteActions()
endif
let g:vebugger_breakpoints=[] let g:vebugger_breakpoints=[]
for l:file in l:files for l:file in l:files
call vebugger#std#updateMarksForFile(l:debuggerState,l:file) call vebugger#std#updateMarksForFile(l:debuggerState,l:file)
endfor endfor
endfunction endfunction
"Ask the active debugger to evaluate an expression
function! vebugger#std#eval(expression)
let l:debugger=vebugger#getActiveDebugger()
if !empty(l:debugger) && !empty(l:debugger.std_eval)
call l:debugger.std_eval(a:expression)
endif
endfunction
"Ask the active debugger to execute a statement
function! vebugger#std#execute(statement)
let l:debugger=vebugger#getActiveDebugger()
if !empty(l:debugger) && !empty(l:debugger.std_eval)
call l:debugger.std_execute(a:statement)
endif
endfunction

View File

@ -57,7 +57,8 @@ function! vebugger#util#selectProcessOfFile(ofFile)
return str2nr(l:resultLinesParsed[l:chosenId][1]) return str2nr(l:resultLinesParsed[l:chosenId][1])
else else
let l:chosenLine=l:resultLines[l:chosenId] let l:chosenLine=l:resultLines[l:chosenId]
return str2nr(matchlist(l:chosenLine,'\v^\s*\d+\)\s+(\d+)')[1]) let g:chosenLine = l:chosenLine
return str2nr(matchstr(l:chosenLine,'\v^\s*\zs(\d+)\ze\s*'))
endif endif
endfunction endfunction
@ -114,3 +115,34 @@ function! vebugger#util#isPathAbsolute(path)
return a:path[0]=~'\v^[/~$]' "Absolute paths in Linux start with ~(home),/(root dir) or $(environment variable) return a:path[0]=~'\v^[/~$]' "Absolute paths in Linux start with ~(home),/(root dir) or $(environment variable)
endif endif
endfunction endfunction
function! vebugger#util#listify(stringOrList)
if type(a:stringOrList) == type([])
return copy(a:stringOrList) " so it could safely be modified by map&filter
else
return [a:stringOrList]
endif
endfunction
function! s:listSigns(filter) abort
let l:result = []
for l:line in split(execute('sign place '.a:filter), '\n')
let l:match = matchlist(l:line, '\C\v^\s+line\=(\d+)\s+id\=(\d+)\s+name\=(.+)$')
if !empty(l:match)
call add(l:result, {
\ 'line': str2nr(l:match[1]),
\ 'id': str2nr(l:match[2]),
\ 'name': l:match[3],
\ })
endif
endfor
return l:result
endfunction
function! vebugger#util#listSignsInBuffer(bufnr) abort
return s:listSigns('buffer='.a:bufnr)
endfunction
function! vebugger#util#listSignsInFile(filename) abort
return s:listSigns('file='.a:filename)
endfunction

View File

@ -4,7 +4,7 @@
Author: Idan Arye <https://github.com/idanarye/> Author: Idan Arye <https://github.com/idanarye/>
License: Same terms as Vim itself (see |license|) License: Same terms as Vim itself (see |license|)
Version: 1.2.3 Version: 1.2.3+
INTRODUCTION *vebugger* INTRODUCTION *vebugger*
@ -20,6 +20,7 @@ supports:
Vebugger is built as a generic framework for building frontends for Vebugger is built as a generic framework for building frontends for
interactive shell debugger, and comes with implementations for: interactive shell debugger, and comes with implementations for:
* GDB - doesn't need introdcution... * GDB - doesn't need introdcution...
* LLDB - debugger based on LLVM for C-family languages
* JDB - a Java debugger * JDB - a Java debugger
* Mdbg - a .NET debugger(Windows only) * Mdbg - a .NET debugger(Windows only)
* PDB - a Python module for debugging Python scripts * PDB - a Python module for debugging Python scripts
@ -78,24 +79,55 @@ If a debugger is not in the PATH you can set the direct path to it by setting
g:vebugger_path_XXX, where XXX is the executable used for the debugger: g:vebugger_path_XXX, where XXX is the executable used for the debugger:
*g:vebugger_path_gdb* defaults to "gdb" *g:vebugger_path_gdb* defaults to "gdb"
*g:vebugger_path_python_lldb* defaults to "python2"
*g:vebugger_path_jdb* defaults to "jdb" *g:vebugger_path_jdb* defaults to "jdb"
*g:vebugger_path_mdbg* defaults to "Mdbg.exe" *g:vebugger_path_mdbg* defaults to "Mdbg.exe"
*g:vebugger_path_python* defaults to "python" *g:vebugger_path_python* defaults to "python"
*g:vebugger_path_ruby* defaults to "ruby" *g:vebugger_path_ruby* defaults to "ruby"
*g:vebugger_path_node* defaults to "node inspect" *g:vebugger_path_node* defaults to "node inspect"
Notice that for PDB and RDebug you use "python" and "ruby", since the debugger Notice that for LLDB, PDB and RDebug you use "python_lldb", "python" and
is actually a module bundled in the interpreter. "ruby", since the debugger is actually a module bundled in the interpreter
(LLDB is called by a python wrapper because the LLDB executable has no
machine interface like GDB to interact with).
You can set multiple versions for each debugger, by appending the version name You can set multiple versions for each debugger (except LLDB), by appending
to the debugger name with "_". These versions will be used when the "version" the version name to the debugger name with "_". These versions will be used
argument is supplied when running the debugger: when the "version" argument is supplied when running the debugger:
*g:vebugger_path_python_2* defaults to "python2" *g:vebugger_path_python_2* defaults to "python2"
*g:vebugger_path_python_3* defaults to "python3" *g:vebugger_path_python_3* defaults to "python3"
*g:vebugger_path_mdbg_32* No default - use it for the 32bit version of Mdbg *g:vebugger_path_mdbg_32* No default - use it for the 32bit version of Mdbg
*g:vebugger_path_mdbg_64* No default - use it for the 64bit version of Mdbg *g:vebugger_path_mdbg_64* No default - use it for the 64bit version of Mdbg
For LLDB a special python variable is needed ("g:vebugger_path_python_lldb")
since the lldb python api is only python 2 compatible. So, it is possible to
use a python 2 interpreter for LLDB and another python version for PDB at
the same time.
When stepping into functions in unopened files, they are opened with :new by
default. This can be configured by setting *g:vebugger_view_source_cmd*
Example: >
let g:vebugger_view_source_cmd='edit'
<
If you want to change the sign text of current line and breakpoint, use
*g:vebugger_breakpoint_text* and *g:vebugger_currentline_text*
Example: >
let g:vebugger_breakpoint_text='->'
let g:vebugger_currentline_text='**'
<
Some debuggers (currently jdb only) may use vim tags to find required source
files. This is disabled by default, to enable this set *g:vebugger_use_tags*
option:
Example: >
let g:vebugger_use_tags=1
<
LAUNCHING DEBUGGERS *vebugger-launching* LAUNCHING DEBUGGERS *vebugger-launching*
A debugger's implementation is responsible for starting it. The standard is to A debugger's implementation is responsible for starting it. The standard is to
@ -119,6 +151,7 @@ GDB can be launched with *vebugger#gdb#start*
The supported extra arguments are: The supported extra arguments are:
* "args": Command line arguments for the debugged program * "args": Command line arguments for the debugged program
* "pid": Process id to attach to * "pid": Process id to attach to
* "con": URL of GDB-server to connect to
* "entry": The entry point for starting the debugging(default "main") * "entry": The entry point for starting the debugging(default "main")
* "version": The version of the debugger to run * "version": The version of the debugger to run
You can't specify both ("args" and/or "entry") and "pid". You can't specify both ("args" and/or "entry") and "pid".
@ -132,9 +165,40 @@ attach to, and attaches to them:
> >
VBGattachGDB a.out VBGattachGDB a.out
< <
VBGattachGDB accepts as a second argument the process ID of the process to
attach to or the URL for a GDB-server to connect to.
The *VBGstartGDBForD* command is the same as VBGstartGDB but for Dlang The *VBGstartGDBForD* command is the same as VBGstartGDB but for Dlang
programs. programs.
LAUNCHING LLDB *vebugger-lldb*
LLDB can be launched with *vebugger#lldb#start*
>
call vebugger#lldb#start('a.out',{'args':['hello','world']})
<
The supported extra arguments are:
* "args": Command line arguments for the debugged program
* "pid": Process id to attach to
You can't specify both "args" and "pid".
LLDB can also be launched with the *VBGstartLLDB* command:
>
VBGstartLLDB a.out hello world
<
The *VBGattachLLDB* command searches for processes launched from the EXE to
attach to, and attaches to them:
>
VBGattachLLDB a.out
<
VBGattachLLDB accepts as a second argument the process ID of the process to
attach to.
LAUNCHING JDB *vebugger-jdb* LAUNCHING JDB *vebugger-jdb*
JDB is launched with *vebugger#jdb#start* JDB is launched with *vebugger#jdb#start*
@ -148,14 +212,27 @@ Unlike in the other debuggers, the first argument here is not the name of a
file - it's the name of the class to run. The supported extra arguments are: file - it's the name of the class to run. The supported extra arguments are:
* "args": Command line arguments for the debugged program * "args": Command line arguments for the debugged program
* "classpath": Where to look for class files * "classpath": Where to look for class files
* "srcpath": Where to look for source files * "srcpath": Where to look for source files. You can have multiple srcpaths, passed as a list
* "version": The version of the debugger to run * "version": The version of the debugger to run
Alternatively, to attach to a running process you can use *vebugger#jdb#attach*
>
call vebugger#jdb#attach('8000',{
\'srcpath':'src'})
<
The first argument here is the the address to attach to, e.g. 127.0.0.1:8000
or just 8000. It does not support the "args" or "classpath" arguments.
If you don't supply "classpath" and "srcpath", Vebugger will assume you are If you don't supply "classpath" and "srcpath", Vebugger will assume you are
using the current directory for source files and class files. using the current directory for source files and class files.
JDB does not have a command for starting it, since you usually want to supply JDB does not have a command for starting it, since you usually want to supply
"classpath" and "srcpath". "classpath" and "srcpath".
If you need Vebugger to search for the source files in multiple locations, you can
specify `srcpath` as a list. Vebugger will search on all these folders
and the first match will be returned as the Source file to be shown.
LAUNCHING RDEBUG *vebugger-rdebug* LAUNCHING RDEBUG *vebugger-rdebug*
@ -258,7 +335,7 @@ MANAGE BREAKPOINTS *vebugger-breakpoints*
*:VBGtoggleBreakpoint* Toggle a breakpoint. The file and line should be supplied as arguments. *:VBGtoggleBreakpoint* Toggle a breakpoint. The file and line should be supplied as arguments.
*:VBGtoggleBreakpointThisLine* Toggle a breakpoint for the current line. *:VBGtoggleBreakpointThisLine* Toggle a breakpoint for the current line.
*:VBGclearBreakpints* Clear all breakpoints. *:VBGclearBreakpoints* Clear all breakpoints.
EVALUATE EXPRESSIONS *vebugger-evalutate* EVALUATE EXPRESSIONS *vebugger-evalutate*
@ -299,7 +376,7 @@ O |:VBGstepOut|
c |:VBGcontinue| c |:VBGcontinue|
b |:VBGtoggleBreakpointThisLine| b |:VBGtoggleBreakpointThisLine|
B |:VBGclearBreakpints| B |:VBGclearBreakpoints|
e |:VBGevalWordUnderCursor| in normal mode e |:VBGevalWordUnderCursor| in normal mode
|:VBGevalSelectedText| in select mode |:VBGevalSelectedText| in select mode
@ -490,3 +567,26 @@ defines the "std" namespace, and plugins built on top of Vebugger can define a
namespaced named after the plugin to contain their own read results. A namespaced named after the plugin to contain their own read results. A
debugger frontend implementation can also define it's own namespace for debugger frontend implementation can also define it's own namespace for
storing debugger-specific data and configuration. storing debugger-specific data and configuration.
COMPILER OUTPUT BUFFERERS *vebugger-architecture-bufferers*
Read handlers receive lines created by the debugger output bufferer. There is
only one for each debugger, and if you just want to get the debugger output
line-by-line "as is" you can just use the default one and ignore this section.
The debugger bufferer is a dict function(|:func-dict|) that operates on pipe
objects. It will be called when the pipe object's "self.buffer", which
represents everything read from a debugger's output stream, has new data.
The bufferer function should:
* return a list of new lines that will be send to the read handlers, or an
empty list if there are no new lines.
* "advance" the buffer - delete from it's head everything that was handled.
This can easily be done with |strpart()|.
Bufferers should be set individually for each pipe, usually in the debugger
creation function:
Example: >
let l:debugger.pipes.out.bufferer = function('g:myCustomBufferer')
<

View File

@ -1,28 +1,64 @@
command! -nargs=1 VBGrawWrite call vebugger#writeLine(<q-args>) command! -nargs=0 VBGrepeat call vebugger#repeatLastUserAction()
command! -nargs=1 VBGrawWrite call vebugger#userAction('writeLine', <q-args>)
command! -nargs=0 VBGkill call vebugger#killDebugger() command! -nargs=0 VBGkill call vebugger#killDebugger()
command! -nargs=0 VBGstepIn call vebugger#setWriteActionAndPerform('std','flow','stepin') command! -nargs=0 VBGstepIn call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepin')
command! -nargs=0 VBGstepOver call vebugger#setWriteActionAndPerform('std','flow','stepover') command! -nargs=0 VBGstepOver call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepover')
command! -nargs=0 VBGstepOut call vebugger#setWriteActionAndPerform('std','flow','stepout') command! -nargs=0 VBGstepOut call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'stepout')
command! -nargs=0 VBGcontinue call vebugger#setWriteActionAndPerform('std','flow','continue') command! -nargs=0 VBGcontinue call vebugger#userAction('setWriteActionAndPerform', 'std', 'flow', 'continue')
command! -nargs=0 VBGtoggleTerminalBuffer call vebugger#toggleTerminalBuffer() command! -nargs=0 VBGtoggleTerminalBuffer call vebugger#userAction('toggleTerminalBuffer')
command! -nargs=+ -complete=file VBGtoggleBreakpoint call vebugger#std#toggleBreakpoint(<f-args>) command! -nargs=+ -complete=file VBGtoggleBreakpoint call vebugger#std#toggleBreakpoint(<f-args>)
command! -nargs=0 VBGtoggleBreakpointThisLine call vebugger#std#toggleBreakpoint(expand('%:~:.'),line('.')) command! -nargs=0 VBGtoggleBreakpointThisLine call vebugger#std#toggleBreakpoint(expand('%:p:.'),line('.'))
command! -nargs=0 VBGclearBreakpints call vebugger#std#clearBreakpoints() command! -nargs=0 VBGclearBreakpoints call vebugger#std#clearBreakpoints()
command! -nargs=1 VBGeval call vebugger#std#eval(<q-args>) command! -nargs=1 VBGeval call vebugger#userAction('std_eval', <q-args>)
command! -nargs=0 VBGevalWordUnderCursor call vebugger#std#eval(expand('<cword>')) command! -nargs=0 VBGevalWordUnderCursor call vebugger#userAction('std_eval', expand('<cword>'))
command! -nargs=1 VBGexecute call vebugger#std#execute(<q-args>) command! -nargs=1 VBGexecute call vebugger#userAction('std_execute', <q-args>)
command! -range -nargs=0 VBGevalSelectedText call vebugger#std#eval(vebugger#util#get_visual_selection()) command! -range -nargs=0 VBGevalSelectedText call vebugger#userAction('std_eval', vebugger#util#get_visual_selection())
command! -range -nargs=0 VBGexecuteSelectedText call vebugger#std#execute(vebugger#util#get_visual_selection()) command! -range -nargs=0 VBGexecuteSelectedText call vebugger#userAction('std_execute', vebugger#util#get_visual_selection())
command! -range -nargs=0 VBGrawWriteSelectedText call vebugger#writeLine(vebugger#util#get_visual_selection()) command! -range -nargs=0 VBGrawWriteSelectedText call vebugger#userAction('writeLine', vebugger#util#get_visual_selection())
command! -nargs=+ -complete=file VBGstartGDB call vebugger#gdb#start([<f-args>][0],{'args':[<f-args>][1:]}) command! -nargs=+ -complete=file VBGstartGDB call vebugger#gdb#start([<f-args>][0],{'args':[<f-args>][1:]})
command! -nargs=1 -complete=file VBGattachGDB call vebugger#gdb#searchAndAttach(<q-args>) function! s:attachGDB(...)
if 1 == a:0
let l:processId=vebugger#util#selectProcessOfFile(a:1)
if 0 < l:processId
call vebugger#gdb#start(a:1, {'pid': l:processId})
endif
elseif 2 == a:0
if a:2 =~ '\v^\d+$'
call vebugger#gdb#start(a:1,{'pid': str2nr(a:2)})
else
call vebugger#gdb#start(a:1, {'con': a:2})
endif
else
throw "Can't call VBGattachGDB with ".a:0." arguments"
endif
endfunction
command! -nargs=+ -complete=file VBGattachGDB call s:attachGDB(<f-args>)
command! -nargs=+ -complete=file VBGstartLLDB call vebugger#lldb#start([<f-args>][0],{'args':[<f-args>][1:]})
function! s:attachLLDB(...)
if 1 == a:0
let l:processId=vebugger#util#selectProcessOfFile(a:1)
if 0 < l:processId
call vebugger#lldb#start(a:1, {'pid': l:processId})
endif
elseif 2 == a:0
if a:2 =~ '\v^\d+$'
call vebugger#lldb#start(a:1,{'pid': str2nr(a:2)})
else
call vebugger#lldb#start(a:1, {'con': a:2})
endif
else
throw "Can't call VBGattachLLDB with ".a:0." arguments"
endif
endfunction
command! -nargs=+ -complete=file VBGattachLLDB call s:attachLLDB(<f-args>)
command! -nargs=+ -complete=file VBGstartRDebug call vebugger#rdebug#start([<f-args>][0],{'args':[<f-args>][1:]}) command! -nargs=+ -complete=file VBGstartRDebug call vebugger#rdebug#start([<f-args>][0],{'args':[<f-args>][1:]})
command! -nargs=+ -complete=file VBGstartNInspect call vebugger#ninspect#start([<f-args>][0],{'args':[<f-args>][1:]}) command! -nargs=+ -complete=file VBGstartNInspect call vebugger#ninspect#start([<f-args>][0],{'args':[<f-args>][1:]})
command! -nargs=+ -complete=file VBGstartPDB call vebugger#pdb#start([<f-args>][0],{'args':[<f-args>][1:]}) command! -nargs=+ -complete=file VBGstartPDB call vebugger#pdb#start([<f-args>][0],{'args':[<f-args>][1:]})
@ -39,7 +75,7 @@ if exists('g:vebugger_leader')
\'c':'VBGcontinue', \'c':'VBGcontinue',
\'t':'VBGtoggleTerminalBuffer', \'t':'VBGtoggleTerminalBuffer',
\'b':'VBGtoggleBreakpointThisLine', \'b':'VBGtoggleBreakpointThisLine',
\'B':'VBGclearBreakpints', \'B':'VBGclearBreakpoints',
\'e':'VBGevalWordUnderCursor', \'e':'VBGevalWordUnderCursor',
\'E':'exe "VBGeval ".input("VBG-Eval> ")', \'E':'exe "VBGeval ".input("VBG-Eval> ")',
\'x':'exe "VBGexecute ".getline(".")', \'x':'exe "VBGexecute ".getline(".")',