summaryrefslogtreecommitdiff
path: root/love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua
diff options
context:
space:
mode:
Diffstat (limited to 'love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua')
-rw-r--r--love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua498
1 files changed, 498 insertions, 0 deletions
diff --git a/love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua b/love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua
new file mode 100644
index 0000000..523ac8c
--- /dev/null
+++ b/love2dToAPK/tools/tools/zbstudio-win/src/editor/output.lua
@@ -0,0 +1,498 @@
+-- Copyright 2011-15 Paul Kulchenko, ZeroBrane LLC
+-- authors: Lomtik Software (J. Winwood & John Labenski)
+-- Luxinia Dev (Eike Decker & Christoph Kubisch)
+---------------------------------------------------------
+
+local ide = ide
+local frame = ide.frame
+local bottomnotebook = frame.bottomnotebook
+local errorlog = bottomnotebook.errorlog
+
+-------
+-- setup errorlog
+local MESSAGE_MARKER = StylesGetMarker("message")
+local PROMPT_MARKER = StylesGetMarker("prompt")
+local PROMPT_MARKER_VALUE = 2^PROMPT_MARKER
+
+errorlog:Show(true)
+errorlog:SetFont(ide.font.oNormal)
+errorlog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.oNormal)
+errorlog:SetBufferedDraw(not ide.config.hidpi and true or false)
+errorlog:StyleClearAll()
+errorlog:SetMarginWidth(1, 16) -- marker margin
+errorlog:SetMarginType(1, wxstc.wxSTC_MARGIN_SYMBOL)
+errorlog:MarkerDefine(StylesGetMarker("message"))
+errorlog:MarkerDefine(StylesGetMarker("prompt"))
+errorlog:SetReadOnly(true)
+if (ide.config.outputshell.usewrap) then
+ errorlog:SetWrapMode(wxstc.wxSTC_WRAP_WORD)
+ errorlog:SetWrapStartIndent(0)
+ errorlog:SetWrapVisualFlags(wxstc.wxSTC_WRAPVISUALFLAG_END)
+ errorlog:SetWrapVisualFlagsLocation(wxstc.wxSTC_WRAPVISUALFLAGLOC_END_BY_TEXT)
+end
+
+StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.font.oNormal,ide.font.oItalic)
+
+function ClearOutput(force)
+ if not (force or ide:GetMenuBar():IsChecked(ID_CLEAROUTPUT)) then return end
+ errorlog:SetReadOnly(false)
+ errorlog:ClearAll()
+ errorlog:SetReadOnly(true)
+end
+
+local inputBound = 0 -- to track where partial output ends for input editing purposes
+local function getInputLine()
+ return errorlog:MarkerPrevious(errorlog:GetLineCount()+1, PROMPT_MARKER_VALUE)
+end
+local function getInputText(bound)
+ return errorlog:GetTextRangeDyn(
+ errorlog:PositionFromLine(getInputLine())+(bound or 0), errorlog:GetLength())
+end
+local function updateInputMarker()
+ local lastline = errorlog:GetLineCount()-1
+ errorlog:MarkerDeleteAll(PROMPT_MARKER)
+ errorlog:MarkerAdd(lastline, PROMPT_MARKER)
+ inputBound = #getInputText()
+end
+function OutputEnableInput() updateInputMarker() end
+
+function DisplayOutputNoMarker(...)
+ local message = ""
+ local cnt = select('#',...)
+ for i=1,cnt do
+ local v = select(i,...)
+ message = message..tostring(v)..(i<cnt and "\t" or "")
+ end
+
+ local promptLine = getInputLine()
+ local insertedAt = promptLine == -1 and errorlog:GetLength() or errorlog:PositionFromLine(promptLine) + inputBound
+ local current = errorlog:GetReadOnly()
+ errorlog:SetReadOnly(false)
+ errorlog:InsertTextDyn(insertedAt, errorlog.useraw and message or FixUTF8(message, "\022"))
+ errorlog:EmptyUndoBuffer()
+ errorlog:SetReadOnly(current)
+ errorlog:GotoPos(errorlog:GetLength())
+ if promptLine ~= -1 then updateInputMarker() end
+end
+function DisplayOutput(...)
+ errorlog:MarkerAdd(errorlog:GetLineCount()-1, MESSAGE_MARKER)
+ DisplayOutputNoMarker(...)
+end
+function DisplayOutputLn(...)
+ DisplayOutput(...)
+ DisplayOutputNoMarker("\n")
+end
+
+local streamins = {}
+local streamerrs = {}
+local streamouts = {}
+local customprocs = {}
+local textout = '' -- this is a buffer for any text sent to external scripts
+
+function DetachChildProcess()
+ for _, custom in pairs(customprocs) do
+ -- since processes are detached, their END_PROCESS event is not going
+ -- to be called; call endcallback() manually if registered.
+ if custom.endcallback then custom.endcallback() end
+ if custom.proc then custom.proc:Detach() end
+ end
+end
+
+function CommandLineRunning(uid)
+ for pid, custom in pairs(customprocs) do
+ if (custom.uid == uid and custom.proc and custom.proc.Exists(tonumber(pid))) then
+ return pid, custom.proc
+ end
+ end
+
+ return
+end
+
+function CommandLineToShell(uid,state)
+ for pid,custom in pairs(customprocs) do
+ if (pid == uid or custom.uid == uid) and custom.proc and custom.proc.Exists(tonumber(pid)) then
+ if (streamins[pid]) then streamins[pid].toshell = state end
+ if (streamerrs[pid]) then streamerrs[pid].toshell = state end
+ return true
+ end
+ end
+end
+
+-- logic to "unhide" wxwidget window using winapi
+pcall(require, 'winapi')
+local checkstart, checknext, checkperiod
+local pid = nil
+local function unHideWindow(pidAssign)
+ -- skip if not configured to do anything
+ if not ide.config.unhidewindow then return end
+ if pidAssign then
+ pid = pidAssign > 0 and pidAssign or nil
+ end
+ if pid and winapi then
+ local now = TimeGet()
+ if pidAssign and pidAssign > 0 then
+ checkstart, checknext, checkperiod = now, now, 0.02
+ end
+ if now - checkstart > 1 and checkperiod < 0.5 then
+ checkperiod = checkperiod * 2
+ end
+ if now >= checknext then
+ checknext = now + checkperiod
+ else
+ return
+ end
+ local wins = winapi.find_all_windows(function(w)
+ return w:get_process():get_pid() == pid
+ end)
+ local any = ide.interpreter.unhideanywindow
+ local show, hide, ignore = 1, 2, 0
+ for _,win in pairs(wins) do
+ -- win:get_class_name() can return nil if the window is already gone
+ -- between getting the list and this check.
+ local action = ide.config.unhidewindow[win:get_class_name()]
+ or (any and show or ignore)
+ if action == show and not win:is_visible()
+ or action == hide and win:is_visible() then
+ -- use show_async call (ShowWindowAsync) to avoid blocking the IDE
+ -- if the app is busy or is being debugged
+ win:show_async(action == show and winapi.SW_SHOW or winapi.SW_HIDE)
+ pid = nil -- indicate that unhiding is done
+ end
+ end
+ end
+end
+
+local function nameTab(tab, name)
+ local index = bottomnotebook:GetPageIndex(tab)
+ if index ~= -1 then bottomnotebook:SetPageText(index, name) end
+end
+
+function OutputSetCallbacks(pid, proc, callback, endcallback)
+ local streamin = proc and proc:GetInputStream()
+ local streamerr = proc and proc:GetErrorStream()
+ if streamin then
+ streamins[pid] = {stream=streamin, callback=callback,
+ proc=proc, check=proc and proc.IsInputAvailable}
+ end
+ if streamerr then
+ streamerrs[pid] = {stream=streamerr, callback=callback,
+ proc=proc, check=proc and proc.IsErrorAvailable}
+ end
+ customprocs[pid] = {proc=proc, endcallback=endcallback}
+end
+
+function CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
+ if (not cmd) then return end
+
+ -- expand ~ at the beginning of the command
+ if ide.oshome and cmd:find('~') then
+ cmd = cmd:gsub([[^(['"]?)~]], '%1'..ide.oshome:gsub('[\\/]$',''), 1)
+ end
+
+ -- try to extract the name of the executable from the command
+ -- the executable may not have the extension and may be in quotes
+ local exename = string.gsub(cmd, "\\", "/")
+ local _,_,fullname = string.find(exename,'^[\'"]([^\'"]+)[\'"]')
+ exename = fullname and string.match(fullname,'/?([^/]+)$')
+ or string.match(exename,'/?([^/]-)%s') or exename
+
+ uid = uid or exename
+
+ if (CommandLineRunning(uid)) then
+ DisplayOutputLn(TR("Program can't start because conflicting process is running as '%s'.")
+ :format(cmd))
+ return
+ end
+
+ DisplayOutputLn(TR("Program starting as '%s'."):format(cmd))
+
+ local proc = wx.wxProcess(errorlog)
+ if (tooutput) then proc:Redirect() end -- redirect the output if requested
+
+ -- set working directory if specified
+ local oldcwd
+ if (wdir and #wdir > 0) then -- directory can be empty; ignore in this case
+ oldcwd = wx.wxFileName.GetCwd()
+ oldcwd = wx.wxFileName.SetCwd(wdir) and oldcwd
+ end
+
+ -- launch process
+ local params = wx.wxEXEC_ASYNC + wx.wxEXEC_MAKE_GROUP_LEADER + (nohide and wx.wxEXEC_NOHIDE or 0)
+ local pid = wx.wxExecute(cmd, params, proc)
+
+ if oldcwd then wx.wxFileName.SetCwd(oldcwd) end
+
+ -- For asynchronous execution, the return value is the process id and
+ -- zero value indicates that the command could not be executed.
+ -- The return value of -1 in this case indicates that we didn't launch
+ -- a new process, but connected to the running one (e.g. DDE under Windows).
+ if not pid or pid == -1 or pid == 0 then
+ DisplayOutputLn(TR("Program unable to run as '%s'."):format(cmd))
+ return
+ end
+
+ DisplayOutputLn(TR("Program '%s' started in '%s' (pid: %d).")
+ :format(uid, (wdir and wdir or wx.wxFileName.GetCwd()), pid))
+
+ OutputSetCallbacks(pid, proc, stringcallback, endcallback)
+ customprocs[pid].uid=uid
+ customprocs[pid].started = TimeGet()
+
+ local streamout = proc and proc:GetOutputStream()
+ if streamout then streamouts[pid] = {stream=streamout, callback=stringcallback, out=true} end
+
+ unHideWindow(pid)
+ nameTab(errorlog, TR("Output (running)"))
+
+ return pid
+end
+
+local readonce = 4096
+local maxread = readonce * 10 -- maximum number of bytes to read before pausing
+local function getStreams()
+ local function readStream(tab)
+ for _,v in pairs(tab) do
+ -- periodically stop reading to get a chance to process other events
+ local processed = 0
+ while (v.check(v.proc) and processed <= maxread) do
+ local str = v.stream:Read(readonce)
+ -- the buffer has readonce bytes, so cut it to the actual size
+ str = str:sub(1, v.stream:LastRead())
+ processed = processed + #str
+
+ local pfn
+ if (v.callback) then
+ str,pfn = v.callback(str)
+ end
+ if not str then
+ -- skip if nothing to display
+ elseif (v.toshell) then
+ DisplayShell(str)
+ else
+ DisplayOutputNoMarker(str)
+ if str and (getInputLine() > -1 or errorlog:GetReadOnly()) then
+ ActivateOutput()
+ updateInputMarker()
+ end
+ end
+ pfn = pfn and pfn()
+ end
+ end
+ end
+ local function sendStream(tab)
+ local str = textout
+ if not str then return end
+ textout = nil
+ str = str .. "\n"
+ for _,v in pairs(tab) do
+ local pfn
+ if (v.callback) then
+ str,pfn = v.callback(str)
+ end
+ v.stream:Write(str, #str)
+ updateInputMarker()
+ pfn = pfn and pfn()
+ end
+ end
+
+ readStream(streamins)
+ readStream(streamerrs)
+ sendStream(streamouts)
+end
+
+errorlog:Connect(wx.wxEVT_END_PROCESS, function(event)
+ local pid = event:GetPid()
+ if (pid ~= -1) then
+ getStreams()
+ streamins[pid] = nil
+ streamerrs[pid] = nil
+ streamouts[pid] = nil
+
+ if not customprocs[pid] then return end
+ if customprocs[pid].endcallback then customprocs[pid].endcallback() end
+ -- if this wasn't started with CommandLineRun, skip the rest
+ if not customprocs[pid].uid then return end
+
+ -- delete markers and set focus to the editor if there is an input marker
+ if errorlog:MarkerPrevious(errorlog:GetLineCount(), PROMPT_MARKER_VALUE) > -1 then
+ errorlog:MarkerDeleteAll(PROMPT_MARKER)
+ local editor = GetEditor()
+ -- check if editor still exists; it may not if the window is closed
+ if editor then editor:SetFocus() end
+ end
+ unHideWindow(0)
+ DebuggerStop(true)
+ nameTab(errorlog, TR("Output"))
+ DisplayOutputLn(TR("Program completed in %.2f seconds (pid: %d).")
+ :format(TimeGet() - customprocs[pid].started, pid))
+ customprocs[pid] = nil
+ end
+ end)
+
+errorlog:Connect(wx.wxEVT_IDLE, function()
+ if (#streamins or #streamerrs) then getStreams() end
+ if ide.osname == 'Windows' then unHideWindow() end
+ end)
+
+local jumptopatterns = {
+ -- <filename>(line,linepos):
+ "^%s*(.-)%((%d+),(%d+)%)%s*:",
+ -- <filename>(line):
+ "^%s*(.-)%((%d+).*%)%s*:",
+ --[string "<filename>"]:line:
+ '^.-%[string "([^"]+)"%]:(%d+)%s*:',
+ -- <filename>:line:linepos
+ "^%s*(.-):(%d+):(%d+):",
+ -- <filename>:line:
+ "^%s*(.-):(%d+)%s*:",
+}
+
+errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
+ function(event)
+ local line = errorlog:GetCurrentLine()
+ local linetx = errorlog:GetLineDyn(line)
+
+ -- try to detect a filename and line in linetx
+ local fname, jumpline, jumplinepos
+ for _,pattern in ipairs(jumptopatterns) do
+ fname,jumpline,jumplinepos = linetx:match(pattern)
+ if (fname and jumpline) then break end
+ end
+
+ if not (fname and jumpline) then return end
+
+ -- fname may include name of executable, as in "path/to/lua: file.lua";
+ -- strip it and try to find match again if needed.
+ -- try the stripped name first as if it doesn't match, the longer
+ -- name may have parts that may be interpreter as network path and
+ -- may take few seconds to check.
+ local name
+ local fixedname = fname:match(":%s+(.+)")
+ if fixedname then
+ name = GetFullPathIfExists(FileTreeGetDir(), fixedname)
+ or FileTreeFindByPartialName(fixedname)
+ end
+ name = name
+ or GetFullPathIfExists(FileTreeGetDir(), fname)
+ or FileTreeFindByPartialName(fname)
+
+ local editor = LoadFile(name or fname,nil,true)
+ if not editor then
+ local ed = GetEditor()
+ if ed and ide:GetDocument(ed):GetFileName() == (name or fname) then
+ editor = ed
+ end
+ end
+ if editor then
+ jumpline = tonumber(jumpline)
+ jumplinepos = tonumber(jumplinepos)
+
+ editor:GotoPos(editor:PositionFromLine(math.max(0,jumpline-1))
+ + (jumplinepos and (math.max(0,jumplinepos-1)) or 0))
+ editor:EnsureVisibleEnforcePolicy(jumpline)
+ editor:SetFocus()
+ end
+
+ -- doubleclick can set selection, so reset it
+ local pos = event:GetPosition()
+ if pos == -1 then pos = errorlog:GetLineEndPosition(event:GetLine()) end
+ errorlog:SetSelection(pos, pos)
+ end)
+
+local function positionInLine(line)
+ return errorlog:GetCurrentPos() - errorlog:PositionFromLine(line)
+end
+local function caretOnInputLine(disallowLeftmost)
+ local inputLine = getInputLine()
+ local boundary = inputBound + (disallowLeftmost and 0 or -1)
+ return (errorlog:GetCurrentLine() > inputLine
+ or errorlog:GetCurrentLine() == inputLine
+ and positionInLine(inputLine) > boundary)
+end
+
+errorlog:Connect(wx.wxEVT_KEY_DOWN,
+ function (event)
+ -- this loop is only needed to allow to get to the end of function easily
+ -- "return" aborts the processing and ignores the key
+ -- "break" aborts the processing and processes the key normally
+ while true do
+ -- no special processing if it's readonly
+ if errorlog:GetReadOnly() then break end
+
+ local key = event:GetKeyCode()
+ if key == wx.WXK_UP or key == wx.WXK_NUMPAD_UP then
+ if errorlog:GetCurrentLine() > getInputLine() then break
+ else return end
+ elseif key == wx.WXK_DOWN or key == wx.WXK_NUMPAD_DOWN then
+ break -- can go down
+ elseif key == wx.WXK_LEFT or key == wx.WXK_NUMPAD_LEFT then
+ if not caretOnInputLine(true) then return end
+ elseif key == wx.WXK_BACK then
+ if not caretOnInputLine(true) then return end
+ elseif key == wx.WXK_DELETE or key == wx.WXK_NUMPAD_DELETE then
+ if not caretOnInputLine()
+ or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
+ return
+ end
+ elseif key == wx.WXK_PAGEUP or key == wx.WXK_NUMPAD_PAGEUP
+ or key == wx.WXK_PAGEDOWN or key == wx.WXK_NUMPAD_PAGEDOWN
+ or key == wx.WXK_END or key == wx.WXK_NUMPAD_END
+ or key == wx.WXK_HOME or key == wx.WXK_NUMPAD_HOME
+ or key == wx.WXK_RIGHT or key == wx.WXK_NUMPAD_RIGHT
+ or key == wx.WXK_SHIFT or key == wx.WXK_CONTROL
+ or key == wx.WXK_ALT then
+ break
+ elseif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER then
+ if not caretOnInputLine()
+ or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
+ return
+ end
+ errorlog:GotoPos(errorlog:GetLength()) -- move to the end
+ textout = (textout or '') .. getInputText(inputBound)
+ -- remove selection if any, otherwise the text gets replaced
+ errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
+ break -- don't need to do anything else with return
+ else
+ -- move cursor to end if not already there
+ if not caretOnInputLine() then
+ errorlog:GotoPos(errorlog:GetLength())
+ -- check if the selection starts before the input line and reset it
+ elseif errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine(-1) then
+ errorlog:GotoPos(errorlog:GetLength())
+ errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
+ end
+ end
+ break
+ end
+ event:Skip()
+ end)
+
+local function inputEditable(line)
+ local inputLine = getInputLine()
+ local currentLine = line or errorlog:GetCurrentLine()
+ return inputLine > -1 and
+ (currentLine > inputLine or
+ currentLine == inputLine and positionInLine(inputLine) >= inputBound) and
+ not (errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine())
+end
+
+errorlog:Connect(wxstc.wxEVT_STC_UPDATEUI,
+ function () errorlog:SetReadOnly(not inputEditable()) end)
+
+-- only allow copy/move text by dropping to the input line
+errorlog:Connect(wxstc.wxEVT_STC_DO_DROP,
+ function (event)
+ if not inputEditable(errorlog:LineFromPosition(event:GetPosition())) then
+ event:SetDragResult(wx.wxDragNone)
+ end
+ end)
+
+if ide.config.outputshell.nomousezoom then
+ -- disable zoom using mouse wheel as it triggers zooming when scrolling
+ -- on OSX with kinetic scroll and then pressing CMD.
+ errorlog:Connect(wx.wxEVT_MOUSEWHEEL,
+ function (event)
+ if wx.wxGetKeyState(wx.WXK_CONTROL) then return end
+ event:Skip()
+ end)
+end