-- 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|F11', 'Step Over|do_next|F10', '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|Shift+F11', 'Up|do_up|Alt+U', 'Down|do_down|Alt+D', } end function do_delete_menu() scite_delete_Command{ 'Step|do_step|F11', 'Step Over|do_next|F10', '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|Shift+F11', '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|Alt+F9' } scite_require 'extlib.lua' local my_path=lua_LoadPath() local ATM_OFFSET = 0x1000 local ATM_STARTDEBUG = ATM_OFFSET+0 local ATM_STOPDEBUG = ATM_OFFSET+1 local ATM_RELOAD = ATM_OFFSET+2 local ATM_DIRECTOR = ATM_OFFSET+3 local ATM_DRUNTOGGLE = ATM_OFFSET+4 local ATM_GETCMDID = ATM_OFFSET+5 local ATM_GETSTATUS = ATM_OFFSET+6 local ATM_EXIT = ATM_OFFSET+99 local ATM_HELLO = ATM_OFFSET+100 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' --status = 'dead' local com_status = 'dead' local lua_status = 'dead' local last_command local last_breakpoint local traced local dbg local catdbg function dbg_last_command() return dbg.last_command end function dbg_status() return status end function dbg_obj() return dbg end function command_status(status) if status then com_status = status end return com_status end ----- debugger/Lua prompt -------------- -- pretty_print() will try to expand tables, rather like Python will, but with a limit -- obviously table.concat is much more efficient, but requires that the table values -- be strings. local pretty_print_limit = tonumber(scite_GetProp('pretty.print.limit', 20)) local function join(tbl, delim, limit) local n =#(tbl) local res = '' local k = 0 -- this is a hack to work out if a table is 'list-like' or 'map-like' local index1 = n > 0 and tbl[1] local index2 = n > 1 and tbl[2] if index1 and index2 then for i, v in ipairs(tbl)do res = res..delim..tostring(v) k = k + 1 if k > limit then res = res.." ... " end end else for i, v in pairs(tbl)do res = res..delim..tostring(i)..'='..tostring(v) k = k + 1 if k > limit then res = res.." ... " end end end return string.sub(res, 2) end function pretty_print(...) for i, val in ipairs(arg)do if type(val) == 'table' then if val.__tostring then print(val) else print('{'..join(val, ',', pretty_print_limit)..'}') end elseif type(val) == 'string' then print("'"..val.."'") else print(val) end end end function strip_prompt(line) local prompt_bit = sub(line, 1, prompt_len) if prompt_bit == prompt or prompt_bit == lua_prompt then line = sub(line, prompt_len+1) end return line end function edit(f) scite.Open(f) end function cd(path) --~ os.chdir(path) end function eval_lua(line) if sub(line, 1, 1) == '=' then line = 'pretty_print('..sub(line, 2)..')' end local f, err = load(line, 'local') if not f then print(err) else local ok, res = pcall(f) if ok then if res then print('result= '..res)end else print(res) end end end function set_prompt(p) prompt = p..' ' prompt_len = len(prompt) end function print_prompt() trace(prompt) end -- *doc* Either we are running, or not. If running, commands are passed directly -- to the debugger through the spawner; otherwise we look for property -- initializations $var = val ($var= will clear the property) or property -- vars $var; if none of these, then evaluate as a Lua expression, like -- the canonical Lua prompt (= prints out a value; otherwise any -- Lua statement) scite_OnOutputLine(function(line) local line = strip_prompt(line) local state = dbg_status() local dbg = dbg_obj() --print(state) if state ~= 'dead' then dbg.last_command = '' spawner_command(line) set_prompt(dbg.prompt) else local _, prop, val _, _, val = find(line, '^%s*([^=%s]+)%s*') local sta = command_status() if sta == 'dead' then if val == 'lua' then command_status('lua') line = '' elseif val == 'help' then display_help() return true else return false end elseif sta == 'lua' then if val == 'exit' or val == 'quit' then command_status('dead') return true end else return false end _, _, prop, val = find(line, '^%$([^=%s]+)%s*=%s*(.*)') if _ then props[prop] = val else _, _, prop = find(line, '^%$(.+)') if _ then print(prop..' = "'..props[prop]..'"') elseif line:sub(1, 1) == '>' then local r = spawner.popen(line:sub(2)) trace(r:read('*a')) r:close() else eval_lua(line) end end set_prompt(lua_prompt) end trace(prompt) return true 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) local menu_init = false function set_status(s) if s ~= status then if debug_status then print('setting status to '..s)end --print('old status:'..status..','..'new status:'..s) status = s props['status.msg'] = s scite.UpdateStatusBar(true) if status == 'dead' then menu_init = false do_delete_menu() if type(debug_end) == 'function' then debug_end() end else --status == 'running' if not menu_init then do_set_menu() menu_init = true if type(debug_start) == 'function' then debug_start() end end end end local toolbar = scite_GetPropNumber('DirectorHwnd', 0) if toolbar > 0 then local stat_num = 0 if status == 'dead' then stat_num = 0 elseif status == 'running' then stat_num = 1 elseif status == 'active' then stat_num = 2 end SendMessage(toolbar, ATM_GETSTATUS, stat_num, 1) --print ('stat_num:'..stat_num ) end 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('delete '..num) dbg_command('clear', file..':'..line) else print('no breakpoint at '..file..':'..line) end end -- run until the indicated file:line is reached function Dbg:go_to(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 function do_step() dbg:step() end function do_run() if status == 'dead' then if do_launch()then set_status('running') else print 'Unknown command: unable to spawn process!' end else RemoveLastMarker(true) dbg:continue() set_status('running') end end function do_kill() if status == 'running' then -- this actually kills the debugger process spawner_obj:kill() closing_process() else -- this will ask the debugger nicely to exit dbg:quit() end end function do_next() dbg:step_over() end function breakpoint_from_position(lno, file) local buffer = scite_CurrentFile() if not file then file = buffer end --print('breakpoint_from_position:'..file..' '..lno) for b in Breakpoints()do --print('breakpoint:'..b.file..' '..b.line) if b.file == file and b.line == lno then --print('input:'..file..' '..lno) return b end end return nil end function com_breakpoint(lno) 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 scite_OnInit(function(order1, order2) if order1 == 0 and order2 == 0 then editor.MarginSensitiveN[1] = true end return false end) scite_OnMarginClick(function(modifiers, position, margin) --print (margin) if margin == 1 then local lno = editor:LineFromPosition(position)+1 com_breakpoint(lno) return true else return false end end) function do_breakpoint() local lno = current_line()+ 1 com_breakpoint(lno) end function do_temp_breakpoint() local lno = current_line()+ 1 local file = props['FileNameExt'] dbg:go_to(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 --scite_debug('ppp:'..pos) 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 local function catdbg_write(s) catdbg:write(s, '\n') catdbg:flush() end function do_inspect() local w = current_expr() if len(w)> 0 then if catdbg then dbg:set_display_handler(catdbg_write)end dbg:inspect(w) end end function do_locals() dbg:locals() end function do_watch() dbg:watch(current_expr()) end function do_backtrace() dbg:backtrace(scite_GetProp('debug.backtrace.depth', '20')) end function do_up() dbg:up() end function do_down() dbg:down() end function do_finish() 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 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") if dbg.special_debugger_setup then dbg:special_debugger_setup(out) end 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 debuggers[v] = dclas 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() output:BeginUndoAction() local no_host_symbols local no_remote_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 if target:find('^:remote')then no_remote_symbols = false else no_remote_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 dbg.local_symbols = no_remote_symbols -- this isn't ideal! root = props['FileDir'] dbg:init(root) do_skip_includes = scite_GetProp('debug.skip.includes', true) if do_skip_includes then local inc_path if GTK then inc_path = '^/usr/' else inc_path = '<<>>' 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) print('####:'..dbg_cmd) continued_line = nil -- first create the cmd file for the debugger create_existing_breakpoints() if scite_GetProp('debug.output')then catdbg = io.open('/tmp/scite-debug-out', 'w') else catdbg = nil end --- and go!! 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) --print('spawner_command:'..line) spawner_obj:write(line..'\n') end --local ferr = io.stderr function dbg_command(s, argument) 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() output:EndUndoAction() print 'quitting debugger' dbg.bIsAttach = false -- spawner_obj:close() set_status('dead') if catdbg ~= nil then print(catdbg); catdbg:close()end RemoveLastMarker(true) 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 function ProcessOutput(line) --print('ProcessOutput::'..line) -- on the GTK version, commands are currently echoed.... if last_command_line and find(line, last_command_line)then return end --~ print('*',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 print(line) 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 print(line) 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() 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 if dbg.last_command and dbg.last_command == '' then local _, _, command, file, lineno = find(line, '^(%w+):([^%s]+)%s+(%d+)') if _ then local file_tmp = file file = gsub(file_tmp, dbg.remote_dir, dbg.local_dir) lineno = tonumber(lineno) if command == 'SETB' then SetBreakMarker(lineno, file, true) elseif command == 'DELB' then local brk = breakpoint_from_position(lineno, file) if brk then brk:delete(true) end end else --print('other command:'..line) end 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.local_dir and dbg.local_dir ~= "" and dbg.remote_dir and dbg.remote_dir ~= "" then local file_tmp = file file = gsub(file_tmp, dbg.remote_dir, dbg.local_dir) scite_debug('remote to local:'..file_tmp..'->'..file..' ('..lineno..')') end 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 scite_debug('file,lineno,status:'..file..'('..lineno..')['..status..']') OpenAtPos(file, lineno, status) raise_scite() auto_backtrace() dbg.last_comand = '' end else local cmd = dbg.last_command if (debug_verbose or dbg.last_command == '')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) scite_OnInit(function(order1, order2) if order1 == 0 and order2 == 0 then local f = io.open(my_path.."/debugger.cnf", "r") if not f then return false end for line in f:lines()do local field, Value = line:match('^(.+):(%d+)$') SetBreakMarker(tonumber(Value),field) end f:close() end return false end) scite_OnFinalise(function(level1, level2) if level1 == 1 then local f = io.open( my_path .."/debugger.cnf", "w") if not f then return false end for b in Breakpoints()do --print('breakpoint:'..b.file..' '..b.line) if b then f:write(b.file .. ':' .. b.line ..'\n') end end f:close() end return false end) --debug breakpoint list /////////////////////////////////////////////// scite_Command { 'debug List|updateDbgContent|Ctrl+F5', } local cmd_updateAll_tag = '' local cmd_clearAll_tag = '' local dbg_pos_start = -1 local dbg_cmd_pos_start = -1 local dbg_cmd_pos_end = -1 local dbg_pos_end = -1 local function clearDgbParam() dbg_pos_start = -1 dbg_cmd_pos_start = -1 dbg_cmd_pos_end = -1 dbg_pos_end = -1 end local function remove_dbg_list() outputRemove(output, dbg_pos_start, dbg_pos_end) clearDgbParam() end local function DbglistAdjust(diff) dbg_pos_start = dbg_pos_start + diff dbg_cmd_pos_start = dbg_cmd_pos_start + diff dbg_cmd_pos_end = dbg_cmd_pos_end + diff dbg_pos_end = dbg_pos_end + diff end scite_outputChange(function(pane, bPos, length) --print('start:'..output.SelectionStart..',end:'..output.SelectionEnd) if pane == output then --print("---"..keyval) RoutineAdjustChange(bPos, length, mark_pos_start, mark_pos_end, DbglistAdjust, clearDgbParam) end return false end) function updateDbgContent( ) if dbg_pos_start >0 and dbg_pos_end>0 then remove_dbg_list() end output.CurrentPos = output.Length dbg_pos_start = output.CurrentPos print("----debug breakpoints------------") dbg_cmd_pos_start = output.CurrentPos print(cmd_updateAll_tag .. cmd_clearAll_tag) dbg_cmd_pos_end = output.CurrentPos print("---------------------------") local num = 1 for b in Breakpoints()do --print('breakpoint:'..b.file..' '..b.line) if b then print(b.file .. ':' .. b.line .. ': ' .. num ) end num = num + 1 end print("---------------------------") dbg_pos_end = output.CurrentPos end scite_OnDoubleClick(function() if output.Focus and output.CurrentPos > dbg_cmd_pos_start and output.CurrentPos < dbg_cmd_pos_end then brks = {} if output.CurrentPos > dbg_cmd_pos_start + len(cmd_updateAll_tag) - 1 then for brk in Breakpoints()do table.insert(brks, brk) end local num = 1 print("---------------------------") for i, brk in ipairs(brks)do if brk then print(brk.file .. ':' .. brk.line .. ': ' .. num ) scite.Open(brk.file) brk:delete() num = num + 1 end end print("---------------------------") end updateDbgContent() return true end return false end)