SciTE/scite-debug/scite_lua/debug/clidebugger.lua
-- Clidebug is  used for local Lua debugging sessions
scite_require 'debugger.lua'
local find = string.find
local sub = string.sub
local match  = string.match
local GTK = scite_GetProp('PLAT_GTK')

-- the convention with cli debug is that all Windows filenames are lowercase
-- and all spaces are replaced by %
local function canonical(file)
	if not GTK then file = file:lower():gsub(' ','%%') end
	return file
end

local function fpath(file)
	return canonical(fullpath(file))
end

-- 'eval' has been added to cli debugger; works exactly like remDebug.
local p_pattern = '^= '
local p_error_pattern = 'Run error: %[[^%]]%]:%d+:(.+)'
--local p_error_pattern = 'Run error:'

local function print_process(s,dbg)
	local err = s:match(p_error_pattern)
	if err then
		s = '(error)'..err
	else
		s = s:sub(3)
	end
    display(dbg.last_arg..' = '..s)
end

-- special actions for commands which require postprocessing
local postprocess_command = {
    eval = {pattern=p_pattern, action=print_process, single_pattern=true,
			alt_pat = p_error_pattern},
}

local function backslash(path)
	return path:gsub('\\','/')
end

Clidebug = class(Dbg)

function Clidebug.discriminator(target)
	print('target',target)
	return find(target,'^:') == nil -- otherwise, we want remDebug!
end

function Clidebug:init(root)
	self.prompt = 'DBG' --'[DEBUG]>'
    self.no_target_ext = false
	self.cmd_file = join(root,'clidebug.cmd')
    -- this is added to the package.path of the Lua program
    self.clidebug_path = scite_GetProp('clidebug.path',join(extman_Path(),'lua_clidebugger'))
	self.clidebug_debugger = join(self.clidebug_path,'debugger.lua')
	if not GTK then 
		self.clidebug_debugger = self.clidebug_debugger:lower() -- the lower here is a Windows peculiarity...
	end
	print('clidebug',self.clidebug_debugger)
    self.no_quit_confirm = true
	self.skip_system_extension = ".lua"	
--~ 	local drive = '';
	-- clidebug doesn't give fullpath
--~ 	if not GTK then  = '[a-zA-Z]:' end
	-- slightly different from remDebug
	-- *NB* doesn't handle [thread] thingy! Easiest to put this elsewhere??
    self.break_line  = '^Paused at file ([^%s]+) line (%d+)'
	self.silent_command = {}
	self.postprocess_command = postprocess_command
end

local GTK = scite_GetProp('PLAT_GTK')

local function slashify(s)
	return s:gsub('\\','\\\\')
end

function Clidebug:command_line(target)
	-- find out where the Lua executable is; you can use the debug.lua property if it isn't the same
	-- as your lua executable specified in lua.properties.
	self.lua = scite_GetProp("debug.lua")
	local ext = extension_of(target)
	if not self.lua then		
		self.lua = scite_GetProp('command.go.*.lua'):match('^(%S+)')
	end
    self.target = target
	self.target_dir = props['FileDir']
	local ppath = slashify(join(self.clidebug_path,'?.lua;'))
	local res = self.lua..' -e "package.path=\''..ppath..'\'..package.path" -lclidebug '..self.target..' '..self:parameter_string()
	if ext == 'wlua' then
		res = 'cmd /c '..res
	end
	return res
end

function Clidebug:dump_breakpoints(out)
	for b in Breakpoints() do
		-- note: different order to remDebug. *RD!
		out:write('setb '..b.line..' '..canonical(b.file)..'\n')
	end
end

function Clidebug:run_program(out,parms)
	self.target_dir = canonical(props['FileDir'])
	out:write('rootpath '..self.target_dir..'\n')
    if scite_GetPropBool('debug.run.automatically',false) then out:write('run\n') end
end

function Clidebug:step()
	dbg_command('step')
end

function Clidebug:step_over()
	dbg_command('over')
end

function Clidebug:finish()
    dbg_command('out')
end

function Clidebug:continue()
	dbg_command('run')
end

function Clidebug:quit()
	dbg_command('os.exit(0)')
end

function Clidebug:inspect(word)
	dbg_command('eval',word)
end

-- these function will only pass us the filename part, not the path!
function Clidebug:set_breakpoint(file,lno)
	dbg_command('setb',lno..' '..fpath(file))
end

function Clidebug:goto(file,lno)
	dbg_command('tb',lno..' '..fpath(file))
	self:continue()
end

function Clidebug:clear_breakpoint(file,line,num)
	if file then
        dbg_command('delb',line..' '..fpath(file))
	else
		print ('no breakpoint at '..file..':'..line)
	end
end

function Clidebug:backtrace(count)
	dbg_command('trace')
end

function Clidebug:finish()
	dbg_command('out')
end

-- this is not quite precisely right....
function Clidebug:locals()
    dbg_command('vars')
end

function Clidebug:frame(idx)
	dbg_command('set',idx)
end

function Clidebug:detect_program_end(line)
	return find(line,'^Program finished')
end

function Clidebug:goto_file_line(file,line)	
	ProcessOutput("Paused at file "..self.target_dir..'/'..file.." line "..line..'\n')
end

-- there is some clidebugger hackery going on here. It will put us into its own version of debug.stacktrace,
-- and we need to put the program into frame #3, which is where the wobby originally happened. The usual Lua
-- error message is put out by the 'Message: ' line, which we use to capture the file:line needed to jump to.
-- The jumping is achieved by pushing the correct break pattern back into the input above (there must be
-- a more elegant way of doing this!)
local fmsg,lmsg

function Clidebug:find_execution_break(line)
    local _,_,file,lineno = find(line,self.break_line)
    if _ then
		if not GTK then
			file = file:gsub('%%',' ')
		end
		if file == self.clidebug_debugger and fmsg then -- our program threw a wobbly!
			return fmsg,lmsg,'set 3'
		else
			return file,lineno
		end
	else
		local f,l = match(line,'Message: (%S+):(%d+)')
		if f then
			print 'message!'
			fmsg = f:gsub('%%',' ')
			lmsg = l
		end
	end
end

function Clidebug:detect_frame(line)
	local _,_,frame,file,line = find(line,'%[(%d+)%]%s+%w+ in (%S+):(%d+)')
	if _ then
		self:frame(frame)
		self:goto_file_line(file,line)
	end
end

register_debugger('clidebug',{'lua','wlua'},Clidebug)
