820 lines
20 KiB
Lua
820 lines
20 KiB
Lua
|
-- A simple gdb interface for SciTE
|
||
|
-- Steve Donovan, 2007
|
||
|
-- changes:
|
||
|
-- (1) debug.backtrace.depth will configure depth of stack frame dump (default is 20)
|
||
|
-- (2) initially only adds Run and Breakpoint to the menu
|
||
|
-- (3) first generalized version
|
||
|
local GTK = scite_GetProp('PLAT_GTK')
|
||
|
|
||
|
function do_set_menu()
|
||
|
--~ scite_Command {
|
||
|
--~ 'Step|do_step|Alt+C',
|
||
|
--~ 'Step Over|do_next|Alt+N',
|
||
|
--~ 'Go To|do_temp_breakpoint|Alt+G',
|
||
|
--~ 'Kill|do_kill|Alt+K',
|
||
|
--~ 'Inspect|do_inspect|Alt+I',
|
||
|
--~ 'Locals|do_locals|Alt+Ctrl+L',
|
||
|
--~ 'Watch|do_watch|Alt+W',
|
||
|
--~ 'Backtrace|do_backtrace|Alt+Ctrl+B',
|
||
|
--~ 'Step Out|do_finish|Alt+M',
|
||
|
--~ 'Up|do_up|Alt+U',
|
||
|
--~ 'Down|do_down|Alt+D',
|
||
|
--~ }
|
||
|
end
|
||
|
|
||
|
-- only bring up the absolute minimum commands initially....
|
||
|
scite_Command {
|
||
|
--~ 'Run|do_run|*{savebefore:yes}|Alt+R',
|
||
|
--~ 'Breakpoint|do_breakpoint|F9'
|
||
|
}
|
||
|
|
||
|
scite_require 'extlib.lua'
|
||
|
|
||
|
local lua_prompt = '(lua)'
|
||
|
local prompt
|
||
|
local prompt_len
|
||
|
local sub = string.sub
|
||
|
local find = string.find
|
||
|
local len = string.len
|
||
|
local gsub = string.gsub
|
||
|
local status = 'dead'
|
||
|
local last_command
|
||
|
local last_breakpoint
|
||
|
local traced
|
||
|
local dbg
|
||
|
|
||
|
function dbg_last_command()
|
||
|
return dbg.last_command
|
||
|
end
|
||
|
|
||
|
function dbg_status()
|
||
|
return status
|
||
|
end
|
||
|
|
||
|
function dbg_obj()
|
||
|
return dbg
|
||
|
end
|
||
|
|
||
|
function debug_line_handler(line)
|
||
|
local state = dbg_status()
|
||
|
local dbg = dbg_obj()
|
||
|
if state ~= 'dead' then
|
||
|
dbg.last_command = '<inter>'
|
||
|
spawner_command(line)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local debug_status = scite_GetProp('debug.status',false)
|
||
|
|
||
|
-- *doc* you can add $(status.msg) to your statusbar.text.1 property if
|
||
|
-- you want to see debugger status.
|
||
|
-- (see SciTEGlobal.properties for examples)
|
||
|
function set_status(s)
|
||
|
if s ~= status then
|
||
|
if debug_status then print('setting status to '..s) end
|
||
|
status = s
|
||
|
local str = s
|
||
|
if s == 'dead' then str = '' end
|
||
|
props['status.msg'] = str
|
||
|
scite.UpdateStatusBar(true)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function dbg_status()
|
||
|
return status
|
||
|
end
|
||
|
|
||
|
------- Generic debugger interface, based on GDB ------
|
||
|
Dbg = class()
|
||
|
|
||
|
function Dbg:init(root)
|
||
|
end
|
||
|
|
||
|
function Dbg:default_target()
|
||
|
local ext = self.no_target_ext
|
||
|
if ext then
|
||
|
local res = props['FileName']
|
||
|
if ext ~= '' then res = res..'.'..ext end
|
||
|
return res
|
||
|
else
|
||
|
return props['FileNameExt']
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Dbg:step()
|
||
|
dbg_command('step')
|
||
|
end
|
||
|
|
||
|
function Dbg:step_over()
|
||
|
dbg_command('next')
|
||
|
end
|
||
|
|
||
|
function Dbg:continue()
|
||
|
dbg_command('cont')
|
||
|
end
|
||
|
|
||
|
function Dbg:quit()
|
||
|
spawner_command('quit')
|
||
|
if not self.no_quit_confirm then
|
||
|
spawner_command('y')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Dbg:set_breakpoint(file,lno)
|
||
|
dbg_command('break',file..':'..lno)
|
||
|
end
|
||
|
|
||
|
-- generally there are two ways to kill breakpoints in debuggers;
|
||
|
-- either by number or by explicit file:line.
|
||
|
function Dbg:clear_breakpoint(file,line,num)
|
||
|
if file then
|
||
|
dbg_command('clear',file..':'..line)
|
||
|
else
|
||
|
print ('no breakpoint at '..file..':'..line)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- run until the indicated file:line is reached
|
||
|
function Dbg:goto(file,lno)
|
||
|
dbg_command('tbreak',file..':'..lno)
|
||
|
dbg_command('continue')
|
||
|
end
|
||
|
|
||
|
function Dbg:set_display_handler(fun)
|
||
|
-- 0.8 change: if a handler is already been set, don't try to set a new one!
|
||
|
if self.result_handler then return end
|
||
|
self.result_handler = fun
|
||
|
end
|
||
|
|
||
|
function Dbg:inspect(word)
|
||
|
dbg_command('print',word)
|
||
|
end
|
||
|
|
||
|
local skip_file_pattern
|
||
|
local do_skip_includes
|
||
|
|
||
|
--- *doc* you can choose a directory pattern for files which you don't want to skip through
|
||
|
--- for Unix, this is usually easy, but for mingw you have to supply the path to
|
||
|
--- your gcc directory.
|
||
|
function Dbg:auto_skip_over_file(file)
|
||
|
if not do_skip_includes then return end
|
||
|
return find(file,skip_file_pattern)
|
||
|
end
|
||
|
|
||
|
function Dbg:finish()
|
||
|
dbg_command('finish')
|
||
|
end
|
||
|
|
||
|
function Dbg:locals()
|
||
|
dbg_command('info locals')
|
||
|
end
|
||
|
|
||
|
function Dbg:watch(word)
|
||
|
dbg_command('display',word)
|
||
|
end
|
||
|
|
||
|
function Dbg:up()
|
||
|
dbg_command('up')
|
||
|
end
|
||
|
|
||
|
function Dbg:down()
|
||
|
dbg_command('down')
|
||
|
end
|
||
|
|
||
|
function Dbg:backtrace(depth)
|
||
|
dbg_command('backtrace',depth)
|
||
|
end
|
||
|
|
||
|
function Dbg:frame(f)
|
||
|
dbg_command('frame',f)
|
||
|
end
|
||
|
|
||
|
function Dbg:detect_frame(line)
|
||
|
local _,_,frame = find(line,'#(%d+)')
|
||
|
if _ then
|
||
|
dbg:frame(frame)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Dbg:special_debugger_setup(out)
|
||
|
end
|
||
|
|
||
|
function Dbg:breakpoint_confirmation(line)
|
||
|
-- breakpoint defintion confirmation
|
||
|
-- ISSUE: only picking up for breakpoints added _during_ session!
|
||
|
local _,_,bnum = find(line,"Breakpoint (%d+) at")
|
||
|
if _ then
|
||
|
if last_breakpoint then
|
||
|
print('breakpoint:',last_breakpoint.line,bnum)
|
||
|
last_breakpoint.num = bnum
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function quote(s)
|
||
|
return '"'..s..'"'
|
||
|
end
|
||
|
|
||
|
function Dbg:find_execution_break(line)
|
||
|
local _,_,file,lineno = find(line,self.break_line)
|
||
|
if _ then return file,lineno end
|
||
|
end
|
||
|
|
||
|
function Dbg:check_breakpoint (b)
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- add our currently defined breakpoints
|
||
|
function Dbg:dump_breakpoints(out)
|
||
|
for b in Breakpoints() do
|
||
|
if self:check_breakpoint(b) then
|
||
|
local f = basename(b.file)
|
||
|
print (b.file,f)
|
||
|
out:write('break '..f..':'..b.line..'\n')
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Dbg:run_program(out,parms)
|
||
|
out:write('run '..parms..'\n')
|
||
|
end
|
||
|
|
||
|
function Dbg:detect_program_crash(line)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
----- Debugger commands --------
|
||
|
local spawner_obj
|
||
|
|
||
|
local function launch_debugger()
|
||
|
if do_launch() then
|
||
|
set_status('running')
|
||
|
return true
|
||
|
else
|
||
|
print 'Unable to debug program!'
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function try_start_debugger()
|
||
|
if not dbg then
|
||
|
return launch_debugger()
|
||
|
else
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function do_step()
|
||
|
if not try_start_debugger() then return end
|
||
|
dbg:step()
|
||
|
end
|
||
|
|
||
|
function do_run()
|
||
|
if status == 'dead' then
|
||
|
launch_debugger()
|
||
|
else
|
||
|
RemoveLastMarker(true)
|
||
|
dbg:continue()
|
||
|
set_status('running')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function do_kill()
|
||
|
if not dbg then return end
|
||
|
if status == 'running' then
|
||
|
-- this actually kills the debugger process
|
||
|
spawner_obj:kill()
|
||
|
else
|
||
|
-- this will ask the debugger nicely to exit
|
||
|
dbg:quit()
|
||
|
end
|
||
|
closing_process()
|
||
|
end
|
||
|
|
||
|
function do_next()
|
||
|
if not try_start_debugger() then return end
|
||
|
dbg:step_over()
|
||
|
end
|
||
|
|
||
|
function breakpoint_from_position(lno)
|
||
|
for b in Breakpoints() do
|
||
|
if b.file == scite_CurrentFile() and b.line == lno then
|
||
|
return b
|
||
|
end
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
function do_breakpoint()
|
||
|
local lno = current_line() + 1
|
||
|
local file = props['FileNameExt']
|
||
|
-- do we have already have a breakpoint here?
|
||
|
local brk = breakpoint_from_position(lno)
|
||
|
if brk then
|
||
|
local bnum = brk.num
|
||
|
brk:delete()
|
||
|
if status ~= 'dead' then
|
||
|
dbg:clear_breakpoint(file,lno,bnum)
|
||
|
end
|
||
|
else
|
||
|
last_breakpoint = SetBreakMarker(lno)
|
||
|
if last_breakpoint then
|
||
|
if status ~= 'dead' then
|
||
|
dbg:set_breakpoint(file,lno)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function do_temp_breakpoint()
|
||
|
if not try_start_debugger() then return end
|
||
|
local lno = current_line() + 1
|
||
|
local file = props['FileNameExt']
|
||
|
dbg:goto(file,lno)
|
||
|
end
|
||
|
|
||
|
local function char_at(p)
|
||
|
return string.char(editor.CharAt[p])
|
||
|
end
|
||
|
|
||
|
-- used to pick up current expression from current document position
|
||
|
-- We use the selection, if available, and otherwise pick up the word;
|
||
|
-- if it seems to be a field expression, look for the object before.
|
||
|
local function current_expr(pos)
|
||
|
local s = editor:GetSelText()
|
||
|
if s == '' then -- no selection, so find the word
|
||
|
pos = pos or editor.CurrentPos
|
||
|
local p1 = editor:WordStartPosition(pos,true)
|
||
|
local p2 = editor:WordEndPosition(pos,true)
|
||
|
-- is this a field of some object?
|
||
|
while true do
|
||
|
if char_at(p1-1) == '.' then -- generic member access
|
||
|
p1 = editor:WordStartPosition(p1-2,true)
|
||
|
elseif char_at(p1-1) == '>' and char_at(p1-2) == '-' then --C/C++ pointer
|
||
|
p1 = editor:WordStartPosition(p1-3,true)
|
||
|
else
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
return editor:textrange(p1,p2)
|
||
|
else
|
||
|
return s
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function actually_inspect(w)
|
||
|
if len(w) > 0 then
|
||
|
dbg:inspect(w)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function do_inspect()
|
||
|
if not dbg then return end
|
||
|
local w = current_expr()
|
||
|
scite.Prompt("Inspect which expression:",w,"actually_inspect")
|
||
|
end
|
||
|
|
||
|
function do_locals()
|
||
|
if not dbg then return end
|
||
|
dbg:locals()
|
||
|
end
|
||
|
|
||
|
function actually_watch(w)
|
||
|
dbg:watch(w)
|
||
|
end
|
||
|
|
||
|
function do_watch()
|
||
|
if not dbg then return end
|
||
|
scite.Prompt("Watch which expression:",current_expr(),"actually_watch")
|
||
|
end
|
||
|
|
||
|
function do_backtrace()
|
||
|
if not dbg then return end
|
||
|
dbg:backtrace(scite_GetProp('debug.backtrace.depth','20'))
|
||
|
end
|
||
|
|
||
|
function do_up()
|
||
|
if not dbg then return end
|
||
|
dbg:up()
|
||
|
end
|
||
|
|
||
|
function do_down()
|
||
|
if not dbg then return end
|
||
|
dbg:down()
|
||
|
end
|
||
|
|
||
|
function do_finish()
|
||
|
if not dbg then return end
|
||
|
dbg:finish()
|
||
|
end
|
||
|
|
||
|
local root
|
||
|
|
||
|
function Dbg:parameter_string()
|
||
|
-- any parameters defined with View|Parameters
|
||
|
local parms = ' '
|
||
|
local i = 1
|
||
|
local parm = props[i]
|
||
|
while parm ~= '' do
|
||
|
if find(parm,'%s') then
|
||
|
-- if it's already quoted, then preserve the quotes
|
||
|
if find(parm,'"') == 1 then
|
||
|
parm = gsub(parm,'"','\\"')
|
||
|
end
|
||
|
parm = '"'..parm..'"'
|
||
|
end
|
||
|
parms = parms..' '..parm
|
||
|
i = i + 1
|
||
|
parm = props[i]
|
||
|
end
|
||
|
return parms
|
||
|
end
|
||
|
|
||
|
local menu_init = false
|
||
|
local debug_verbose
|
||
|
local debuggers = {}
|
||
|
local append = table.insert
|
||
|
local remove = table.remove
|
||
|
|
||
|
---- event handling
|
||
|
|
||
|
-- If an event returns true, then this event will persist.
|
||
|
-- The return value of this function is true if any event returns an extra true result
|
||
|
-- Note: we iterate over a copy of the list, because this is the only way I've
|
||
|
-- found to make this method re-enterant. With this scheme it is
|
||
|
-- safe to raise an event within an event handler.
|
||
|
function Dbg:raise_event (event,...)
|
||
|
local events = self.events
|
||
|
if not events then return end
|
||
|
-- not recommended for big tables!
|
||
|
local cpy = {unpack(events)}
|
||
|
local ignore
|
||
|
for i,evt in ipairs(cpy) do
|
||
|
if evt.event == event then
|
||
|
local keep,want_to_ignore = evt.handler(...)
|
||
|
if not keep then
|
||
|
remove(events,i)
|
||
|
end
|
||
|
ignore = ignore or want_to_ignore
|
||
|
end
|
||
|
end
|
||
|
return ignore
|
||
|
end
|
||
|
|
||
|
function Dbg:set_event (name,handler)
|
||
|
if not self.events then self.events = {} end
|
||
|
append(self.events,{event=name,handler=handler})
|
||
|
end
|
||
|
|
||
|
function Dbg:queue_command (cmd)
|
||
|
self:set_event('prompt',function() spawner_command(cmd) end)
|
||
|
end
|
||
|
|
||
|
function create_existing_breakpoints()
|
||
|
local out = io.open(dbg.cmd_file,"w")
|
||
|
dbg:special_debugger_setup(out)
|
||
|
dbg:dump_breakpoints(out)
|
||
|
local parms = dbg:parameter_string()
|
||
|
dbg:run_program(out,parms)
|
||
|
out:close();
|
||
|
end
|
||
|
|
||
|
-- you may register more than one debugger class (@dclass) but such classes must
|
||
|
-- have a static method discriminate() which will be passed the full target name.
|
||
|
function register_debugger(name,ext,dclass)
|
||
|
if type(ext) == 'table' then
|
||
|
for i,v in ipairs(ext) do
|
||
|
register_debugger(name,v,dclass) --**
|
||
|
end
|
||
|
else
|
||
|
if not debuggers[ext] then
|
||
|
debuggers[ext] = {dclass}
|
||
|
else
|
||
|
if not dclass.discriminator then
|
||
|
error("Multiple debuggers registered for this extension, with no discriminator function")
|
||
|
end
|
||
|
append(debuggers[ext],dclass)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function create_debugger(ext,target)
|
||
|
local dclasses = debuggers[ext]
|
||
|
if not dclasses then dclasses = debuggers['*'] end
|
||
|
if #dclasses == 1 then -- there is only one possible debugger for this extension!
|
||
|
return dclasses[1]
|
||
|
else -- there are several registered. We need to call the discriminator!
|
||
|
for i,d in ipairs(dclasses) do
|
||
|
if d.discriminator(target) then
|
||
|
return d
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
error("unable to find appropriate debugger")
|
||
|
end
|
||
|
|
||
|
local initialized
|
||
|
local was_error = false
|
||
|
local continued_line, end_line_action, postproc
|
||
|
|
||
|
function do_launch()
|
||
|
if not menu_init then
|
||
|
do_set_menu()
|
||
|
menu_init = true
|
||
|
end
|
||
|
scite.MenuCommand(IDM_SAVE)
|
||
|
local no_host_symbols
|
||
|
traced = false
|
||
|
debug_verbose = true
|
||
|
-- *doc* detect the debugger we want to use, based on target extension
|
||
|
-- if there is no explicit target, then use the current file.
|
||
|
local target = scite_GetProp('debug.target')
|
||
|
local ext
|
||
|
if target then
|
||
|
-- @doc the target may not actually have debug symbols, in the case
|
||
|
-- where we are debugging some dynamic libraries. Indicate this
|
||
|
-- by prefixing target with [n]
|
||
|
if target:find('^%[n%]') then
|
||
|
target = target:sub(4)
|
||
|
no_host_symbols = true
|
||
|
end
|
||
|
ext = extension_of(target)
|
||
|
else
|
||
|
ext = props['FileExt']
|
||
|
end
|
||
|
dbg = create_debugger(ext,choose(target,target,props['FileName']))
|
||
|
dbg.host_symbols = not no_host_symbols
|
||
|
-- this isn't ideal!
|
||
|
root = props['TMP']
|
||
|
dbg:init(root)
|
||
|
do_skip_includes = scite_GetProp('debug.skip.includes',false)
|
||
|
if do_skip_includes then
|
||
|
local inc_path
|
||
|
if GTK then inc_path = '^/usr/' else inc_path = '<<<DONUT>>>' end
|
||
|
local file_pat_prop = 'debug.skip.file.matching'
|
||
|
if dbg.skip_system_extension then
|
||
|
file_pat_prop = file_pat_prop..dbg.skip_system_extension
|
||
|
end
|
||
|
skip_file_pattern = scite_GetProp(file_pat_prop,inc_path)
|
||
|
end
|
||
|
-- *doc* the default target depends on the debugger (it wd have extension for pydb, etc)
|
||
|
if not target then target = dbg:default_target() end
|
||
|
target = quote_if_needed(target)
|
||
|
-- *doc* this determines the time before calltips appear; you can set this as a SciTE property.
|
||
|
if props['dwell.period'] == '' then props['dwell.period'] = 500 end
|
||
|
-- get the debugger process command string
|
||
|
local dbg_cmd = dbg:command_line(target)
|
||
|
print(dbg_cmd)
|
||
|
continued_line = nil
|
||
|
-- first create the cmd file for the debugger
|
||
|
create_existing_breakpoints()
|
||
|
scite_InteractivePromptHandler (dbg.prompt,debug_line_handler)
|
||
|
--- and go!!
|
||
|
scite.SetDirectory(props['FileDir'])
|
||
|
spawner.verbose(scite_GetPropBool('debug.spawner.verbose',false))
|
||
|
-- spawner.fulllines(1)
|
||
|
spawner_obj = spawner.new(dbg_cmd)
|
||
|
spawner_obj:set_output('ProcessChunk')
|
||
|
spawner_obj:set_result('ProcessResult')
|
||
|
return spawner_obj:run()
|
||
|
end
|
||
|
|
||
|
-- speaking to the spawned process goes through a named pipe on both
|
||
|
-- platforms.
|
||
|
local pipe = nil
|
||
|
local last_command_line
|
||
|
|
||
|
function dbg_command_line(s)
|
||
|
if status == 'active' or status == 'error' then
|
||
|
spawner_command(s)
|
||
|
last_command_line = s
|
||
|
if dbg.trailing_prompt then
|
||
|
last_command_line = dbg.prompt..last_command_line
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function spawner_command(line)
|
||
|
if not dbg then return end
|
||
|
spawner_obj:write(line..'\n')
|
||
|
end
|
||
|
|
||
|
--local ferr = io.stderr
|
||
|
|
||
|
function dbg_command(s,argument)
|
||
|
if not dbg then return end
|
||
|
dbg.last_command = s
|
||
|
dbg.last_arg = argument
|
||
|
if argument then s = s..' '..argument end
|
||
|
dbg_command_line(s)
|
||
|
end
|
||
|
|
||
|
-- *doc* currently, only win32-spawner understands the !up command; I can't
|
||
|
-- find the Unix/GTK equivalent! It is meant to bring the debugger
|
||
|
-- SciTE instance to the front.
|
||
|
function raise_scite()
|
||
|
--spawner.foreground()
|
||
|
end
|
||
|
|
||
|
-- output of inspected variables goes here; this mechanism allows us
|
||
|
-- to redirect command output (to a tooltip in this case)
|
||
|
function display(s)
|
||
|
if dbg.result_handler then
|
||
|
dbg.result_handler(s)
|
||
|
dbg.result_handler = nil
|
||
|
else
|
||
|
print(s)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function closing_process()
|
||
|
print 'quitting debugger'
|
||
|
-- spawner_obj:close()
|
||
|
set_status('dead')
|
||
|
RemoveLastMarker(true)
|
||
|
scite_LeaveInteractivePrompt()
|
||
|
dbg = nil
|
||
|
end
|
||
|
|
||
|
local function finish_pending_actions()
|
||
|
if continued_line then
|
||
|
end_line_action(continued_line,dbg)
|
||
|
continued_line = nil
|
||
|
if postproc.once then dbg.last_command = '' end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function set_error_state()
|
||
|
if was_error then
|
||
|
set_status('error')
|
||
|
was_error = false
|
||
|
else
|
||
|
set_status('active')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function auto_backtrace()
|
||
|
if status == 'error' and not traced then
|
||
|
raise_scite()
|
||
|
do_backtrace()
|
||
|
traced = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local current_file
|
||
|
|
||
|
local function error(s)
|
||
|
io.stderr:write(s..'\n')
|
||
|
end
|
||
|
|
||
|
local was_prompt
|
||
|
|
||
|
function ProcessOutput(line)
|
||
|
-- on the GTK version, commands are currently echoed....
|
||
|
if last_command_line and find(line,last_command_line) then
|
||
|
return
|
||
|
end
|
||
|
-- Debuggers (esp. clidebug) can emit spurious blank lines. This makes them quieter!
|
||
|
if was_prompt and line:find('^%s*$') then
|
||
|
was_prompt = false
|
||
|
return
|
||
|
end
|
||
|
--~ trace('*'..line)
|
||
|
-- sometimes it's useful to know when the debugger process has properly started
|
||
|
if dbg.detect_start and find(line,dbg.detect_start) then
|
||
|
dbg:handle_debug_start()
|
||
|
dbg.detect_start = nil
|
||
|
return
|
||
|
end
|
||
|
-- detecting end of program execution
|
||
|
local prog_ended,process_fininished = dbg:detect_program_end(line)
|
||
|
if prog_ended then
|
||
|
if not processed_finished then spawner_command('quit') end
|
||
|
set_status('dead')
|
||
|
closing_process()
|
||
|
return
|
||
|
end
|
||
|
-- ignore prompt; this is the point at which we know that commands have finished
|
||
|
if find(line,dbg.prompt) then
|
||
|
dbg:raise_event 'prompt'
|
||
|
finish_pending_actions()
|
||
|
if was_error then set_error_state() end
|
||
|
auto_backtrace()
|
||
|
was_prompt = true
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- the result of some commands require postprocessing;
|
||
|
-- it will collect multi-line output together!
|
||
|
postproc = dbg.postprocess_command[dbg.last_command]
|
||
|
if postproc then
|
||
|
local tline = rtrim(line)
|
||
|
if find(tline,postproc.pattern)
|
||
|
or (postproc.alt_pat and find(tline,postproc.alt_pat)) then
|
||
|
if not postproc.single_pattern then
|
||
|
finish_pending_actions()
|
||
|
continued_line = tline
|
||
|
end_line_action = postproc.action
|
||
|
else
|
||
|
postproc.action(tline,dbg)
|
||
|
end
|
||
|
else
|
||
|
if continued_line then continued_line = continued_line..tline end
|
||
|
end
|
||
|
end
|
||
|
-- did we get a confirmation message about a created breakpoint?
|
||
|
dbg:breakpoint_confirmation(line)
|
||
|
-- have we crashed?
|
||
|
if dbg:detect_program_crash(line) then
|
||
|
was_error = true
|
||
|
end
|
||
|
-- looking for break at line pattern
|
||
|
local file,lineno,explicit_error = dbg:find_execution_break(line)
|
||
|
if file and status ~= 'dead' then
|
||
|
if dbg.check_skip_always or current_file ~= file then
|
||
|
current_file = file
|
||
|
if dbg:auto_skip_over_file(file) then
|
||
|
dbg:finish()
|
||
|
spawner_command('step') --??
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
-- a debugger can indicate an explicit error, rather than depending on
|
||
|
-- detect_program_crash()
|
||
|
if explicit_error then
|
||
|
was_error = true
|
||
|
end
|
||
|
set_error_state()
|
||
|
-- if any of the break events wishes, we can ignore this break...
|
||
|
if not dbg:raise_event ('break',file,lineno,status) then
|
||
|
OpenAtPos(file,lineno,status)
|
||
|
raise_scite()
|
||
|
auto_backtrace()
|
||
|
dbg.last_comand = ''
|
||
|
-- may schedule a command to be executed after the error backtrace
|
||
|
if type(explicit_error) == 'string' then
|
||
|
dbg:queue_command(explicit_error)
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
local cmd = dbg.last_command
|
||
|
if (debug_verbose or dbg.last_command == '<inter>') and
|
||
|
not (dbg.silent_command[cmd] or dbg.postprocess_command[cmd]) then
|
||
|
trace(line)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ProcessChunk(s)
|
||
|
local i1 = 1
|
||
|
local i2 = find(s,'\n',i1)
|
||
|
while i2 do
|
||
|
local line = sub(s,i1,i2)
|
||
|
ProcessOutput(line)
|
||
|
i1 = i2 + 1
|
||
|
i2 = find(s,'\n',i1)
|
||
|
end
|
||
|
if i1 <= len(s) then
|
||
|
local line = sub(s,i1)
|
||
|
ProcessOutput(line)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function ProcessResult(res)
|
||
|
if status ~= 'dead' then
|
||
|
closing_process()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- *doc* currently, double-clicking in the output pane will try to recognize
|
||
|
--- a stack frame pattern and move to that frame if possible.
|
||
|
scite_OnDoubleClick(function()
|
||
|
if output.Focus and status == 'active' or status == 'error' then
|
||
|
dbg:detect_frame(output:GetLine(current_output_line()))
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
-- *doc* if your scite has OnDwellStart, then the current symbol under the mouse
|
||
|
-- pointer will be evaluated and shown in a calltip.
|
||
|
local _pos
|
||
|
|
||
|
function calltip(s)
|
||
|
editor:CallTipShow(_pos,s)
|
||
|
end
|
||
|
|
||
|
scite_OnDwellStart(function (pos,s)
|
||
|
if status == 'active' or status == 'error' then
|
||
|
if s ~= '' then
|
||
|
s = current_expr(pos)
|
||
|
_pos = pos
|
||
|
dbg:set_display_handler(calltip)
|
||
|
dbg:inspect(s)
|
||
|
else
|
||
|
editor:CallTipCancel()
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
|