--creating an Interactive Lua prompt ------------------------------- Lua prompt ----------------------- scite_require 'switch_buffers.lua' scite_Command 'Start Interactive Lua|start_lua_prompt' scite_Command 'Load Lua into Session|load_current_lua_file' local lua_p local skipping local lua_interactive local SL_prefix='\01SL\02' function OnExit(s) s = tonumber(s) if s ~= 0 then scite.MenuCommand(IDM_NEXTMSG) end end local function write_to_lua(line,skip_output) skipping = skip_output lua_p:write(line..'\n') end function eval_lua(line) local f,err = loadstring(line,'local') if not f then scite_Trace(err) else pcall(f) end end function write_output(s) if not skipping then local i1,i2 = s:find(SL_prefix) if i1 == 1 then eval_lua(s:sub(i2+1)) else scite_Trace(s) end else skipping = false end end function edit (f) scite.Open(f) end -- the cd() command is special, because it is evaluated both in the Lua session _and_ in SciTE. -- (cmd type 3s are evaluated in both contexts) function cd (path) if path then os.chdir(path) end end local cmds = { cd = 3, dir = 1, edit = 2 } local function check_command (line) local cmd,rest = line:match('([%w_]+)(.*)') local ctype = cmds[cmd] if ctype then local arg = rest:match('(%S+)') if arg then return cmd..'[['..arg..']]', ctype else return cmd..'()',ctype end else return line,1 end end -- whenever SciTE changes its current directory, make sure that the prompt and SciTE are in sync! scite_OnDirChange(function(dir) if lua_p then write_to_lua(('cd(%q)'):format(dir),true) cd(dir) end end) function evaluate_line(line) line,ct = check_command (line) if line == 'quit' then if lua_interactive then write_to_lua 'os.exit()' else write_to_lua 'quit' end print 'goodbye!' scite.MenuCommand(IDM_WRAPOUTPUT) lua_p = nil return true end if ct == 2 or ct == 3 then eval_lua(line) if ct == 2 then scite_Trace '> ' end end if ct == 1 or ct == 3 then write_to_lua(line) end end function start_lua_prompt() if lua_p ~= nil then return end local cmd = props['lua.prompt'] lua_interactive = cmd:find('-i',1,true) scite.MenuCommand(IDM_WRAPOUTPUT) lua_p = spawner.new(cmd) lua_p:set_output('write_output') lua_p:run() scite_InteractivePromptHandler('>+ ',evaluate_line) end function load_current_lua_file() if lua_p then scite.MenuCommand(IDM_SAVE) write_to_lua('print(pcall(dofile,[['..props['FilePath']..']]))',true) scite.MenuCommand(IDM_SWITCHPANE) end end --- a general interactive prompt engine ----- local keymap local lines local lines_idx = 1 local line_callback local prompt local lastpos = 1 if not GTK then -- see winuser.h keymap = { [40] = SCK_DOWN, [38] = SCK_UP, [37] = SCK_LEFT, [39] = SCK_RIGHT, [36] = SCK_HOME, [35] = SCK_END, [33] = SCK_PRIOR, [34] = SCK_NEXT, [46] = SCK_DELETE, [45] = SCK_INSERT, [0x1B] = SCK_ESCAPE, [8] = SCK_BACK, [9] = SCK_TAB, [13] = SCK_RETURN, [0x6B] = SCK_ADD, [0x6D] = SCK_SUBTRACT, [0x6F] = SCK_DIVIDE, [0x5B] = SCK_WIN, [0x5C] = SCK_RWIN, [18] = SCK_MENU, } else -- see gdk/gdkkeysyms.h keymap = { [0xFF54] = SCK_DOWN, [0xFF52] = SCK_UP, [0xFF51] = SCK_LEFT, [0xFF53] = SCK_RIGHT, [0xFF50] = SCK_HOME, [0xFF57] = SCK_END, [0xFF55] = SCK_PRIOR, [0xFF56] = SCK_NEXT, [0xFFFF] = SCK_DELETE, [0xFF63] = SCK_INSERT, [0xFF1B] = SCK_ESCAPE, [0xFF08] = SCK_BACK, [0xFF09] = SCK_TAB, [0xFF0D] = SCK_RETURN, [0xFFAB] = SCK_ADD, [0xFFAD] = SCK_SUBTRACT, [0xFFAF] = SCK_DIVIDE, [0xFFEB] = SCK_WIN, [0xFFEC] = SCK_RWIN, [0xFF67] = SCK_MENU, } end function scite_ConvertToSCK (key) local res = keymap[key] if res then return res else return key end end function enable_arrow_keys (yesno) if yesno then output:AssignCmdKey(SCK_TAB,0,SCI_TAB) output:AssignCmdKey(SCK_UP,0,SCI_LINEUP) output:AssignCmdKey(SCK_DOWN,0,SCI_LINEDOWN) else output:AssignCmdKey(SCK_TAB,0,SCI_NULL) output:AssignCmdKey(SCK_UP,0,SCI_NULL) output:AssignCmdKey(SCK_DOWN,0,SCI_NULL) end end function scite_ReplaceLine(pane,l,txt) pane.TargetStart = lastpos --pane:PositionFromLine(l) pane.TargetEnd = pane.LineEndPosition[l] pane:ReplaceTarget(txt) pane:GotoPos(pane.TargetEnd) end local function current_line () return output:LineFromPosition(output.CurrentPos) end function replace_current_line (txt) local l = current_line() --~ scite_ReplaceLine(output,l,prompt..txt) scite_ReplaceLine(output,l,txt) end local function strip_prompt(line) --~ local i1,i2 = line:find(prompt) --~ if i1 == 1 then --~ line = line:sub(i2+1) --~ end --~ return line local l = current_line()-1 local p = output:PositionFromLine(l) local offset = lastpos - p + 1 return line:sub(offset) end function handle_keys (key) if editor.Focus then return end key = scite_ConvertToSCK(key) local delta if key == SCK_UP then if lines_idx == 1 then return end delta = -1 elseif key == SCK_DOWN then if lines_idx == #lines then return end delta = 1 elseif key == SCK_TAB then local line = strip_prompt(output:GetCurLine()) local matches = {} for i,l in ipairs(lines) do if l:find(line,1,true) == 1 then table.insert(matches,1,l) end end if #matches == 0 then return end if #matches == 1 then replace_current_line(matches[1]) return end enable_arrow_keys (true) scite_UserListShow(matches,1,function(l) replace_current_line(l) enable_arrow_keys (false) end) return end if delta then lines_idx = lines_idx + delta local txt = lines[lines_idx] if txt then replace_current_line(txt) end end end function scite_Trace (s) trace(s) lastpos = output.CurrentPos end local function line_handler(line) line = strip_prompt(line) -- prevent immediate duplicates if lines[#lines] ~= line then table.insert(lines,line) end lines_idx = #lines + 1 if line_callback(line) then scite_LeaveInteractivePrompt() end end function scite_InteractivePromptHandler(the_prompt,fun) prompt = the_prompt line_callback = fun lines_idx = 1 lines = {} enable_arrow_keys(false) scite_OnKey(handle_keys) scite_OnOutputLine(line_handler) end function scite_LeaveInteractivePrompt () scite_OnOutputLine(line_handler,true) enable_arrow_keys(true) scite_OnKey(handle_keys,'remove') end