Merge branch 'node-inspect' of https://github.com/daniellmorris/vim-vebugger into node-inspect

This commit is contained in:
Daniel Morris 2018-08-14 17:15:53 -05:00
commit 9d3b0b2600
12 changed files with 1261 additions and 496 deletions

View File

@ -10,12 +10,13 @@ supports:
* Debugger flow commands - step-in, set-over, set-out and continue * Debugger flow commands - step-in, set-over, set-out and continue
* Breakpoints management * Breakpoints management
* Evaluating expressions in the current executed scope * Evaluating expressions in the current executed scope
* Messing with the program's state(changing values, calling functions) * Messing with the program's state (changing values, calling functions)
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,199 +1,199 @@
"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 a:pipeObject.buffer.=l:text let l:totalBytesRead += len(l:text)
let l:text=a:pipeObject.pipe.read(1000,0) let a:pipeObject.buffer .= l:text
endwhile let l:text = a:pipeObject.pipe.read(1024, 0)
endwhile
"parse return l:totalBytesRead
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={}
"Terminate the debugger "Terminate the debugger
function! s:f_debugger.kill() dict 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
let &updatetime=self.prevUpdateTime if !has('timers')
call self.shell.kill(15) let &updatetime=self.prevUpdateTime
if exists('s:debugger') endif
for l:closeHandler in s:debugger.closeHandlers call self.shell.kill(15)
call l:closeHandler.handle(self) if exists('s:debugger')
endfor for l:closeHandler in s:debugger.closeHandlers
endif call l:closeHandler.handle(self)
endfor
endif
endfunction endfunction
"Write a line to the debugger's interactive shell "Write a line to the debugger's interactive shell
function! s:f_debugger.writeLine(line) dict function! s:f_debugger.writeLine(line) dict
call self.shell.stdin.write(a:line."\n") call self.shell.stdin.write(a:line."\n")
endfunction endfunction
"Check for new lines from the debugger's interactive shell and handle them "Check for new lines from the debugger's interactive shell and handle them
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<len(l:nl) if 0 < s:fillBufferFromPipe(l:pipe)
let l:newLines[l:k]=l:nl let l:nl = l:pipe.bufferer()
endif if 0 < len(l:nl)
endfor let l:newLines[l:k] = l:nl
for l:k in keys(l:newLines) endif
for l:line in l:newLines[l:k] endif
call self.handleLine(l:k,l:line) endfor
endfor for l:k in keys(l:newLines)
endfor for l:line in l:newLines[l:k]
call self.handleLine(l:k, l:line)
endfor
endfor
let l:checkpid=self.shell.checkpid() let l:checkpid=self.shell.checkpid()
if 'exit'==l:checkpid[0] if 'exit'==l:checkpid[0]
\|| '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
function! s:f_debugger.handleLine(pipeName,line) dict function! s:f_debugger.handleLine(pipeName,line) dict
call self.addLineToTerminal(a:pipeName,a:line) call self.addLineToTerminal(a:pipeName,a:line)
let l:readResult=deepcopy(self.readResultTemplate,1) let l:readResult=deepcopy(self.readResultTemplate,1)
for l:readHandler in self.readHandlers for l:readHandler in self.readHandlers
call l:readHandler.handle(a:pipeName,a:line,l:readResult,self) call l:readHandler.handle(a:pipeName,a:line,l:readResult,self)
endfor endfor
for l:thinkHandler in self.thinkHandlers for l:thinkHandler in self.thinkHandlers
call l:thinkHandler.handle(l:readResult,self) call l:thinkHandler.handle(l:readResult,self)
endfor endfor
call self.performWriteActions() call self.performWriteActions()
endfunction endfunction
"Perform all write actions "Perform all write actions
function! s:f_debugger.performWriteActions() dict function! s:f_debugger.performWriteActions() dict
for l:namespace in keys(self.writeActions) for l:namespace in keys(self.writeActions)
let l:handlers=get(self.writeHandlers,l:namespace) let l:handlers=get(self.writeHandlers,l:namespace)
if !empty(l:handlers) if !empty(l:handlers)
for l:writeAction in items(self.writeActions[l:namespace]) for l:writeAction in items(self.writeActions[l:namespace])
if !empty(l:writeAction[1]) if !empty(l:writeAction[1])
if has_key(l:handlers,l:writeAction[0]) if has_key(l:handlers,l:writeAction[0])
call l:handlers[l:writeAction[0]].handle(l:writeAction[1],self) call l:handlers[l:writeAction[0]].handle(l:writeAction[1],self)
endif endif
endif endif
endfor endfor
endif endif
endfor endfor
call self.generateWriteActionsFromTemplate() call self.generateWriteActionsFromTemplate()
endfunction endfunction
"Show the terminal buffer that gets it's content from the debugger's "Show the terminal buffer that gets it's content from the debugger's
"interactive shell "interactive shell
function! s:f_debugger.showTerminalBuffer() dict function! s:f_debugger.showTerminalBuffer() dict
if has_key(self,'terminalBuffer') if has_key(self,'terminalBuffer')
if -1<bufwinnr(self.terminalBuffer) if -1<bufwinnr(self.terminalBuffer)
return return
endif endif
endif endif
new new
setlocal buftype=nofile setlocal buftype=nofile
setlocal bufhidden=wipe setlocal bufhidden=wipe
let self.terminalBuffer=bufnr('') setlocal filetype=VebuggerTerminal
silent file Vebugger:Ternimal let self.terminalBuffer=bufnr('')
wincmd p silent file Vebugger:Terminal
wincmd p
endfunction endfunction
"Close the terminal buffer "Close the terminal buffer
function! s:f_debugger.closeTerminalBuffer() dict function! s:f_debugger.closeTerminalBuffer() dict
if has_key(self,'terminalBuffer') if has_key(self,'terminalBuffer')
if -1<bufwinnr(self.terminalBuffer) if -1<bufwinnr(self.terminalBuffer)
let l:bufwin=bufwinnr(self.terminalBuffer) let l:bufwin=bufwinnr(self.terminalBuffer)
exe l:bufwin.'wincmd w' exe l:bufwin.'wincmd w'
wincmd c wincmd c
wincmd p wincmd p
endif endif
endif endif
endfunction endfunction
"Check if the terminal buffer associated with this debugger is currently open "Check if the terminal buffer associated with this debugger is currently open
function! s:f_debugger.isTerminalBufferOpen() dict function! s:f_debugger.isTerminalBufferOpen() dict
if has_key(self,'terminalBuffer') if has_key(self,'terminalBuffer')
if -1<bufwinnr(self.terminalBuffer) if -1<bufwinnr(self.terminalBuffer)
return 1 return 1
endif endif
endif endif
return 0 return 0
endfunction endfunction
"Turn on and off the terminal buffer associated with this debugger "Turn on and off the terminal buffer associated with this debugger
function! s:f_debugger.toggleTerminalBuffer() dict function! s:f_debugger.toggleTerminalBuffer() dict
if self.isTerminalBufferOpen() if self.isTerminalBufferOpen()
call self.closeTerminalBuffer() call self.closeTerminalBuffer()
else else
call self.showTerminalBuffer() call self.showTerminalBuffer()
endif endif
endfunction endfunction
"Write a line to the terminal buffer. This function does not process the line "Write a line to the terminal buffer. This function does not process the line
function! s:f_debugger.addLineToTerminal(pipeName,line) dict 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')
call append (line('$'),(self.pipes[a:pipeName].annotation).(a:line)) call append (line('$'),(self.pipes[a:pipeName].annotation).(a:line))
else else
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
"Add an handler to a handler list "Add an handler to a handler list
function! s:addHandler(list,handler) function! s:addHandler(list,handler)
if type(a:handler) == type({}) if type(a:handler) == type({})
call add(a:list,a:handler) call add(a:list,a:handler)
elseif type(a:handler) == type(function('tr')) elseif type(a:handler) == type(function('tr'))
call add(a:list,{'handle':a:handler}) call add(a:list,{'handle':a:handler})
endif endif
endfunction endfunction
"Set a named handler in a handler dictionary "Set a named handler in a handler dictionary
function! s:setHandler(dict,namespace,name,handler) function! s:setHandler(dict,namespace,name,handler)
if !has_key(a:dict,a:namespace) if !has_key(a:dict,a:namespace)
let a:dict[a:namespace]={} let a:dict[a:namespace]={}
endif endif
if type(a:handler) == type({}) if type(a:handler) == type({})
let a:dict[a:namespace][a:name]=a:handler let a:dict[a:namespace][a:name]=a:handler
elseif type(a:handler) == type(function('tr')) elseif type(a:handler) == type(function('tr'))
let a:dict[a:namespace][a:name]={'handle':a:handler} let a:dict[a:namespace][a:name]={'handle':a:handler}
endif endif
endfunction endfunction
"Add a read handler. Read handlers process output from the debugger's "Add a read handler. Read handlers process output from the debugger's
"interactive shell and modify read result objects with structured information "interactive shell and modify read result objects with structured information
"parsed from those lines "parsed from those lines
function! s:f_debugger.addReadHandler(handler) dict function! s:f_debugger.addReadHandler(handler) dict
call s:addHandler(self.readHandlers,a:handler) call s:addHandler(self.readHandlers,a:handler)
endfunction endfunction
"Add a think handler. Think handlers are debugger agnostic - they look at "Add a think handler. Think handlers are debugger agnostic - they look at
"read result objects and decide what to do with them. "read result objects and decide what to do with them.
function! s:f_debugger.addThinkHandler(handler) dict function! s:f_debugger.addThinkHandler(handler) dict
call s:addHandler(self.thinkHandlers,a:handler) call s:addHandler(self.thinkHandlers,a:handler)
endfunction endfunction
"Set a write handler. Write handlers get write action objects and convert them "Set a write handler. Write handlers get write action objects and convert them
@ -201,147 +201,179 @@ endfunction
"of the namespace and name it is registered for, to prevent the same write "of the namespace and name it is registered for, to prevent the same write
"action handled by multiple write handlers. "action handled by multiple write handlers.
function! s:f_debugger.setWriteHandler(namespace,name,handler) dict function! s:f_debugger.setWriteHandler(namespace,name,handler) dict
call s:setHandler(self.writeHandlers,a:namespace,a:name,a:handler) call s:setHandler(self.writeHandlers,a:namespace,a:name,a:handler)
endfunction endfunction
"Add a close handler. Close handlers are called when the debugger is closed to "Add a close handler. Close handlers are called when the debugger is closed to
"tidy things up. "tidy things up.
function! s:f_debugger.addCloseHandler(handler) dict function! s:f_debugger.addCloseHandler(handler) dict
call s:addHandler(self.closeHandlers,a:handler) call s:addHandler(self.closeHandlers,a:handler)
endfunction endfunction
"Create an empty write action that follows the write actions template. That "Create an empty write action that follows the write actions template. That
"action will later be filled by think handlers or from outside. "action will later be filled by think handlers or from outside.
function! s:f_debugger.generateWriteActionsFromTemplate() dict function! s:f_debugger.generateWriteActionsFromTemplate() dict
let self.writeActions=deepcopy(self.writeActionsTemplate) let self.writeActions=deepcopy(self.writeActionsTemplate)
endfunction endfunction
"Set a write action of a specific namespace and name, for write actions that "Set a write action of a specific namespace and name, for write actions that
"do not support a list "do not support a list
function! s:f_debugger.setWriteAction(namespace,name,value) dict 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
"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 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
"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 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)
let l:debugger=deepcopy(s:f_debugger) let l:debugger=deepcopy(s:f_debugger)
let l:debugger.shell=vimproc#ptyopen(a:command,3) let l:debugger.shell=vimproc#ptyopen(a:command,3)
let l:debugger.outBuffer='' let l:debugger.outBuffer=''
let l:debugger.errBuffer='' let l:debugger.errBuffer=''
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={}
let l:debugger.writeActionsTemplate={} let l:debugger.writeActionsTemplate={}
let l:debugger.readHandlers=[] let l:debugger.readHandlers=[]
let l:debugger.thinkHandlers=[] let l:debugger.thinkHandlers=[]
let l:debugger.writeHandlers={} let l:debugger.writeHandlers={}
let l:debugger.closeHandlers=[] let l:debugger.closeHandlers=[]
let l:debugger.prevUpdateTime=&updatetime if !has('timers')
let l:debugger.prevUpdateTime=&updatetime
set updatetime=500
endif
set updatetime=500 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)
call vebugger#killDebugger() call vebugger#killDebugger()
let s:debugger=vebugger#createDebugger(a:command) let s:debugger=vebugger#createDebugger(a:command)
augroup vebugger_shell if has('timers')
autocmd! let s:timerId = timer_start(500, function('s:readingTimerCallback'), {'repeat': -1})
autocmd CursorHold * call s:debugger.invokeReading() else
augroup END augroup vebugger_shell
autocmd!
autocmd CursorHold * call s:debugger.invokeReading()
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()
augroup vebugger_shell if has('timers')
autocmd! if exists('s:timerId')
augroup END call timer_stop(s:timerId)
if exists('s:debugger') unlet s:timerId
call s:debugger.closeTerminalBuffer() endif
call s:debugger.kill() else
unlet s:debugger augroup vebugger_shell
endif autocmd!
augroup END
endif
if exists('s:debugger')
call vebugger#std#closeShellBuffer(s:debugger)
call s:debugger.closeTerminalBuffer()
call s:debugger.kill()
unlet s:debugger
endif
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 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')
call s:debugger.writeLine(a:line) call s:debugger.writeLine(a:line)
endif endif
endfunction endfunction
"Invoke reading for the currently active debugger "Invoke reading for the currently active debugger
function! vebugger#invokeReading() function! vebugger#invokeReading()
if exists('s:debugger') if exists('s:debugger')
call s:debugger.invokeReading() call s:debugger.invokeReading()
endif endif
endfunction
"Toggle the terminal buffer for the currently active debugger
function! vebugger#toggleTerminalBuffer()
if exists('s:debugger')
call s:debugger.toggleTerminalBuffer()
endif
endfunction 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')
return s:debugger return s:debugger
else else
return {} return {}
endif endif
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 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
@ -132,9 +184,9 @@ function! vebugger#gdb#_executeStatements(writeAction,debugger)
endfunction 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={}
call l:debugger.writeLine('stop on '.a:entryClass.'.main') if !has_key(a:args,'attach')
call l:debugger.writeLine('run '.a:entryClass.' '.vebugger#util#commandLineArgsForProgram(a:args)) call l:debugger.writeLine('stop on '.a:entryClass.'.main')
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,15 +67,46 @@ 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)
for l:dirname in split(a:nameFromStackTrace,'\.') if (len(l:tags) > 0)
let l:nextPath=l:path.'/'.fnameescape(l:dirname) for l:tag in l:tags
if empty(glob(l:nextPath)) if (filereadable(l:tag.filename) && match(readfile(l:tag.filename), a:str) >= 0)
return l:path return l:tag
endif endif
let l:path=l:nextPath endfor
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,'\.')
let l:nextPath=l:path.'/'.fnameescape(l:dirname)
if empty(glob(l:nextPath))
return l:path
endif
let l:path=l:nextPath
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,75 +1,96 @@
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={
\'config':{ \'config':{
\ 'externalFileStop_flowCommand':''}, \ 'externalFileStop_flowCommand':''},
\'location':{}, \'location':{},
\'callstack':[], \'callstack':[],
\'evaluateExpressions':[]} \'evaluateExpressions':[]}
endfunction endfunction
"Initialize the std part of the debugger's read result template "Initialize the std part of the debugger's read result template
function! vebugger#std#setStandardReadResultTemplate(debugger) function! vebugger#std#setStandardReadResultTemplate(debugger)
let a:debugger.readResultTemplate.std={ let a:debugger.readResultTemplate.std={
\'programOutput':{}, \'programOutput':{},
\'location':{}, \'location':{},
\'callstack':{}, \'callstack':{},
\'evaluatedExpression':{}, \'evaluatedExpression':{},
\'programFinish':{}, \'programFinish':{},
\'exception':{}} \'exception':{}}
endfunction endfunction
"Initialize the std part of the debugger's write actions template "Initialize the std part of the debugger's write actions template
function! vebugger#std#setStandardWriteactionsTemplate(debugger) function! vebugger#std#setStandardWriteactionsTemplate(debugger)
let a:debugger.writeActionsTemplate.std={ let a:debugger.writeActionsTemplate.std={
\'flow':'', \'flow':'',
\'breakpoints':[], \'breakpoints':[],
\'evaluateExpressions':[], \'evaluateExpressions':[],
\'executeStatements':[], \'executeStatements':[],
\'removeAfterDisplayed':[], \'removeAfterDisplayed':[],
\'closeDebugger':''} \'closeDebugger':''}
endfunction endfunction
"Adds the std_ functions to the debugger object "Adds the std_ functions to the debugger object
function! vebugger#std#addStandardFunctions(debugger) function! vebugger#std#addStandardFunctions(debugger)
for l:k in keys(s:standardFunctions) for l:k in keys(s:standardFunctions)
let a:debugger['std_'.l:k]=s:standardFunctions[l:k] let a:debugger['std_'.l:k]=s:standardFunctions[l:k]
endfor endfor
endfunction endfunction
"Add the standard think handlers to the debugger "Add the standard think handlers to the debugger
function! vebugger#std#addStandardThinkHandlers(debugger) function! vebugger#std#addStandardThinkHandlers(debugger)
for l:ThinkHandler in values(s:standardThinkHandlers) for l:ThinkHandler in values(s:standardThinkHandlers)
call a:debugger.addThinkHandler(l:ThinkHandler) call a:debugger.addThinkHandler(l:ThinkHandler)
endfor endfor
endfunction endfunction
"Add the standard close handlers to the debugger "Add the standard close handlers to the debugger
function! vebugger#std#addStandardCloseHandlers(debugger) function! vebugger#std#addStandardCloseHandlers(debugger)
for l:CloseHandler in values(s:standardCloseHandlers) for l:CloseHandler in values(s:standardCloseHandlers)
call a:debugger.addCloseHandler(l:CloseHandler) call a:debugger.addCloseHandler(l:CloseHandler)
endfor endfor
endfunction 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#setStandardState(a:debugger) call vebugger#std#setStandardBufferers(a:debugger)
call vebugger#std#setStandardReadResultTemplate(a:debugger) call vebugger#std#setStandardState(a:debugger)
call vebugger#std#setStandardWriteactionsTemplate(a:debugger) call vebugger#std#setStandardReadResultTemplate(a:debugger)
call vebugger#std#addStandardFunctions(a:debugger) call vebugger#std#setStandardWriteactionsTemplate(a:debugger)
call vebugger#std#addStandardThinkHandlers(a:debugger) call vebugger#std#addStandardFunctions(a:debugger)
call vebugger#std#addStandardCloseHandlers(a:debugger) call vebugger#std#addStandardThinkHandlers(a:debugger)
call vebugger#std#addStandardCloseHandlers(a:debugger)
endfunction endfunction
"Start a debugger with the std settings "Start a debugger with the std settings
function! vebugger#std#startDebugger(command) function! vebugger#std#startDebugger(command)
let l:debugger=vebugger#startDebugger(a:command) let l:debugger=vebugger#startDebugger(a:command)
call vebugger#std#standardInit(l:debugger) call vebugger#std#standardInit(l:debugger)
return l:debugger return l:debugger
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 endfunction
@ -78,69 +99,82 @@ endfunction
"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
"process. "process.
function! vebugger#std#openShellBuffer(debugger) function! vebugger#std#openShellBuffer(debugger)
if has_key(a:debugger,'shellBuffer') if has_key(a:debugger,'shellBuffer')
if -1<bufwinnr(a:debugger.shellBuffer) if -1<bufwinnr(a:debugger.shellBuffer)
return return
endif endif
endif endif
let l:oldBuffer=bufnr('Vebugger:Shell') let l:oldBuffer=bufnr('Vebugger:Shell')
if -1<l:oldBuffer if -1<l:oldBuffer
let a:debugger.shellBuffer=l:oldBuffer let a:debugger.shellBuffer=l:oldBuffer
call a:debugger.std_addLineToShellBuffer('') call a:debugger.std_addLineToShellBuffer('')
call a:debugger.std_addLineToShellBuffer('==================') call a:debugger.std_addLineToShellBuffer('==================')
call a:debugger.std_addLineToShellBuffer('') call a:debugger.std_addLineToShellBuffer('')
return return
endif endif
8 new 8 new
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 bufhidden=wipe setlocal filetype=VebuggerShell
let a:debugger.shellBuffer=bufnr('') setlocal bufhidden=wipe
silent file Vebugger:Shell let a:debugger.shellBuffer=bufnr('')
wincmd p silent file Vebugger:Shell
wincmd p
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 endfunction
let s:standardFunctions={} let s:standardFunctions={}
"Write a line to the shell buffer "Write a line to the shell buffer
function! s:standardFunctions.addLineToShellBuffer(line) dict 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
"Set the write actions to add all breakpoints registered in Vebugger "Set the write actions to add all breakpoints registered in Vebugger
function! s:standardFunctions.addAllBreakpointActions(breakpoints) dict function! s:standardFunctions.addAllBreakpointActions(breakpoints) dict
for l:breakpoint in a:breakpoints for l:breakpoint in a:breakpoints
call self.addWriteAction('std','breakpoints',{ call self.addWriteAction('std','breakpoints',{
\'action':'add', \'action':'add',
\'file':(l:breakpoint.file), \'file':(l:breakpoint.file),
\'line':(l:breakpoint.line)}) \'line':(l:breakpoint.line)})
endfor endfor
endfunction endfunction
"Make the debugger evaluate an expression "Make the debugger evaluate an expression
function! s:standardFunctions.eval(expression) dict function! s:standardFunctions.eval(expression) dict
if -1==index(self.state.std.evaluateExpressions,a:expression) if -1==index(self.state.std.evaluateExpressions,a:expression)
call add(self.state.std.evaluateExpressions,a:expression) call add(self.state.std.evaluateExpressions,a:expression)
endif endif
call self.addWriteAction('std','evaluateExpressions',{ call self.addWriteAction('std','evaluateExpressions',{
\'expression':(a:expression)}) \'expression':(a:expression)})
call self.performWriteActions() call self.performWriteActions()
endfunction endfunction
"Execute a statement in the debugged program "Execute a statement in the debugged program
function! s:standardFunctions.execute(statement) dict function! s:standardFunctions.execute(statement) dict
call self.addWriteAction('std','executeStatements',{ call self.addWriteAction('std','executeStatements',{
\'statement':(a:statement)}) \'statement':(a:statement)})
call self.performWriteActions() call self.performWriteActions()
endfunction endfunction
@ -148,175 +182,216 @@ let s:standardThinkHandlers={}
"Update the shell buffer with program output "Update the shell buffer with program output
function! s:standardThinkHandlers.addProgramOutputToShell(readResult,debugger) dict function! s:standardThinkHandlers.addProgramOutputToShell(readResult,debugger) dict
let l:programOutput=a:readResult.std.programOutput let l:programOutput=a:readResult.std.programOutput
if !empty(l:programOutput) if !empty(l:programOutput)
call a:debugger.std_addLineToShellBuffer(l:programOutput.line) call a:debugger.std_addLineToShellBuffer(l:programOutput.line)
endif endif
endfunction endfunction
"Make Vim jump to the currently executed line "Make Vim jump to the currently executed line
function! s:standardThinkHandlers.moveToCurrentLine(readResult,debugger) dict function! s:standardThinkHandlers.moveToCurrentLine(readResult,debugger) dict
if !empty(a:readResult.std.location) if !empty(a:readResult.std.location)
if !empty(a:debugger.state.std.config.externalFileStop_flowCommand) " Do we need to worry about stopping at external files? if !empty(a:debugger.state.std.config.externalFileStop_flowCommand) " Do we need to worry about stopping at external files?
if 0!=stridx(tolower(fnamemodify(a:readResult.std.location.file,':p')),tolower(getcwd())) if 0!=stridx(tolower(fnamemodify(a:readResult.std.location.file,':p')),tolower(getcwd()))
call a:debugger.setWriteAction('std','flow',a:debugger.state.std.config.externalFileStop_flowCommand) call a:debugger.setWriteAction('std','flow',a:debugger.state.std.config.externalFileStop_flowCommand)
return return
endif endif
endif endif
if a:debugger.state.std.location!=a:readResult.std.location if a:debugger.state.std.location!=a:readResult.std.location
if has_key(a:debugger.state.std.location,'file') if has_key(a:debugger.state.std.location,'file')
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'))
endif endif
endif endif
endfunction endfunction
"Update the call stack "Update the call stack
function! s:standardThinkHandlers.updateCallStack(readResult,debugger) dict function! s:standardThinkHandlers.updateCallStack(readResult,debugger) dict
let l:callstack=a:readResult.std.callstack let l:callstack=a:readResult.std.callstack
if !empty(l:callstack) if !empty(l:callstack)
if get(l:callstack,'clearOld') if get(l:callstack,'clearOld')
let a:debugger.state.std.callstack=[] let a:debugger.state.std.callstack=[]
endif endif
let l:frame={'file':(l:callstack.file),'line':(l:callstack.line)} let l:frame={'file':(l:callstack.file),'line':(l:callstack.line)}
if 'after'==get(l:callstack,'add') if 'after'==get(l:callstack,'add')
call add(a:debugger.state.std.callstack,l:frame) call add(a:debugger.state.std.callstack,l:frame)
elseif 'before'==get(l:callstack,'add') elseif 'before'==get(l:callstack,'add')
call insert(a:debugger.state.std.callstack,l:frame) call insert(a:debugger.state.std.callstack,l:frame)
endif endif
endif endif
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 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" let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression)
else if 0<=l:index
let l:index=index(a:debugger.state.std.evaluateExpressions,l:evaluatedExpression.expression) call remove(a:debugger.state.std.evaluateExpressions,l:index)
if 0<=l:index endif
call remove(a:debugger.state.std.evaluateExpressions,l:index) endif
echo l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n" call s:printWithFeedKeys(l:evaluatedExpression)
let g:echo=l:evaluatedExpression.expression.': '.l:evaluatedExpression.value."\n" call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult)
endif endif
endif
call a:debugger.addWriteAction('std','removeAfterDisplayed',a:readResult)
endif
endfunction endfunction
"Close the debugger when the program is finished but the debugger wasn't "Close the debugger when the program is finished but the debugger wasn't
"closed automatically "closed automatically
function! s:standardThinkHandlers.closeDebuggerWhenProgramFinishes(readResult,debugger) dict function! s:standardThinkHandlers.closeDebuggerWhenProgramFinishes(readResult,debugger) dict
if !empty(a:readResult.std.programFinish) if !empty(a:readResult.std.programFinish)
call a:debugger.setWriteAction('std','closeDebugger','close') call a:debugger.setWriteAction('std','closeDebugger','close')
endif endif
endfunction endfunction
"Print an exception message "Print an exception message
function! s:standardThinkHandlers.printException(readResult,debugger) dict function! s:standardThinkHandlers.printException(readResult,debugger) dict
if !empty(a:readResult.std.exception) if !empty(a:readResult.std.exception)
echohl WarningMsg echohl WarningMsg
echo a:readResult.std.exception.message."\n" echo a:readResult.std.exception.message."\n"
echohl None echohl None
endif endif
endfunction endfunction
let s:standardCloseHandlers={} let s:standardCloseHandlers={}
"Remove the currently executed line when a debugger is closed "Remove the currently executed line when a debugger is closed
function! s:standardCloseHandlers.removeCurrentMarker(debugger) dict function! s:standardCloseHandlers.removeCurrentMarker(debugger) dict
let a:debugger.state.std.location={} let a:debugger.state.std.location={}
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)
exe 'sign unplace 1 file='.fnameescape(fnamemodify(l:filename,':p')) if -1 < l:bufnr
exe 'sign unplace 2 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'))
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')
exe 'sign place 2 name=vebugger_breakpoint line='.l:breakpoint.line.' file='.fnameescape(fnamemodify(l:breakpoint.file,':p')) exe 'sign place 2 name=vebugger_breakpoint line='.l:breakpoint.line.' file='.fnameescape(fnamemodify(l:breakpoint.file,':p'))
endif endif
endfor endfor
if !empty(a:state) if !empty(a:state)
if !empty(a:state.std.location) if !empty(a:state.std.location)
if fnamemodify(a:state.std.location.file,':p')==fnamemodify(a:filename,':p') if fnamemodify(a:state.std.location.file,':p')==fnamemodify(a:filename,':p')
exe 'sign place 1 name=vebugger_current line='.a:state.std.location.line.' file='.fnameescape(fnamemodify(l:filename,':p')) exe 'sign place 1 name=vebugger_current line='.a:state.std.location.line.' file='.fnameescape(fnamemodify(l:filename,':p'))
endif endif
endif endif
endif endif
endif endif
endfunction endfunction
"Toggle a breakpoint on and off "Toggle a breakpoint on and off
function! vebugger#std#toggleBreakpoint(file,line) function! vebugger#std#toggleBreakpoint(file,line)
let l:debugger=vebugger#getActiveDebugger() let l:debugger=vebugger#getActiveDebugger()
let l:debuggerState=empty(l:debugger) let l:debuggerState=empty(l:debugger)
\? {} \? {}
\: l:debugger.state \: l:debugger.state
for l:i in range(len(g:vebugger_breakpoints)) for l:i in range(len(g:vebugger_breakpoints))
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)
\'action':'remove', call l:debugger.addWriteActionAndPerform('std','breakpoints',{
\'file':(a:file), \'action':'remove',
\'line':(a:line)}) \'file':(a:file),
call vebugger#std#updateMarksForFile(l:debuggerState,a:file) \'line':(a:line)})
return endif
endif call vebugger#std#updateMarksForFile(l:debuggerState,a:file)
endfor return
call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)}) endif
call vebugger#addWriteActionAndPerform('std','breakpoints',{ endfor
\'action':'add', call add(g:vebugger_breakpoints,{'file':(a:file),'line':(a:line)})
\'file':(a:file), if !empty(l:debugger)
\'line':(a:line)}) call l:debugger.addWriteActionAndPerform('std','breakpoints',{
call vebugger#std#updateMarksForFile(l:debuggerState,a:file) \'action':'add',
\'file':(a:file),
\'line':(a:line)})
endif
call vebugger#std#updateMarksForFile(l:debuggerState,a:file)
endfunction endfunction
"Clear all breakpoints "Clear all breakpoints
function! vebugger#std#clearBreakpoints() function! vebugger#std#clearBreakpoints()
let l:debugger=vebugger#getActiveDebugger() let l:debugger=vebugger#getActiveDebugger()
let l:debuggerState=empty(l:debugger) ? {} : l:debugger.state let l:debuggerState=empty(l:debugger) ? {} : l:debugger.state
let l:files=[] let l:files=[]
for l:breakpoint in g:vebugger_breakpoints for l:breakpoint in g:vebugger_breakpoints
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)
endfor call l:debugger.addWriteAction('std','breakpoints',extend({'action':'remove'},l:breakpoint))
call vebugger#performWriteActions() endif
let g:vebugger_breakpoints=[] endfor
for l:file in l:files if !empty(l:debugger)
call vebugger#std#updateMarksForFile(l:debuggerState,l:file) call l:debugger.performWriteActions()
endfor endif
endfunction let g:vebugger_breakpoints=[]
for l:file in l:files
"Ask the active debugger to evaluate an expression call vebugger#std#updateMarksForFile(l:debuggerState,l:file)
function! vebugger#std#eval(expression) endfor
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 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
@ -77,25 +78,56 @@ Example: >
If a debugger is not in the PATH you can set the direct path to it by setting 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_jdb* defaults to "jdb" *g:vebugger_path_python_lldb* defaults to "python2"
*g:vebugger_path_mdbg* defaults to "Mdbg.exe" *g:vebugger_path_jdb* defaults to "jdb"
*g:vebugger_path_python* defaults to "python" *g:vebugger_path_mdbg* defaults to "Mdbg.exe"
*g:vebugger_path_ruby* defaults to "ruby" *g:vebugger_path_python* defaults to "python"
*g:vebugger_path_node* defaults to "node inspect" *g:vebugger_path_ruby* defaults to "ruby"
*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(".")',