2385 lines
89 KiB
Lua
2385 lines
89 KiB
Lua
-------------------------------------------------------------------------=---
|
|
-- Name: Editor.wx.lua
|
|
-- Purpose: wxLua IDE
|
|
-- Author: J Winwood
|
|
-- Created: March 2002
|
|
-- Copyright: (c) 2002-5 Lomtick Software. All rights reserved.
|
|
-- Licence: wxWidgets licence
|
|
-------------------------------------------------------------------------=---
|
|
|
|
-- Load the wxLua module, does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit
|
|
package.cpath = package.cpath..";./?.dll;./?.so;../lib/?.so;../lib/vc_dll/?.dll;../lib/bcc_dll/?.dll;../lib/mingw_dll/?.dll;"
|
|
require("wx")
|
|
|
|
-- Equivalent to C's "cond ? a : b", all terms will be evaluated
|
|
function iff(cond, a, b) if cond then return a else return b end end
|
|
|
|
-- Does the num have all the bits in value
|
|
function HasBit(value, num)
|
|
for n = 32, 0, -1 do
|
|
local b = 2^n
|
|
local num_b = num - b
|
|
local value_b = value - b
|
|
if num_b >= 0 then
|
|
num = num_b
|
|
else
|
|
return true -- already tested bits in num
|
|
end
|
|
if value_b >= 0 then
|
|
value = value_b
|
|
end
|
|
if (num_b >= 0) and (value_b < 0) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Generate a unique new wxWindowID
|
|
local ID_IDCOUNTER = wx.wxID_HIGHEST + 1
|
|
function NewID()
|
|
ID_IDCOUNTER = ID_IDCOUNTER + 1
|
|
return ID_IDCOUNTER
|
|
end
|
|
|
|
-- File menu
|
|
local ID_NEW = wx.wxID_NEW
|
|
local ID_OPEN = wx.wxID_OPEN
|
|
local ID_CLOSE = NewID()
|
|
local ID_SAVE = wx.wxID_SAVE
|
|
local ID_SAVEAS = wx.wxID_SAVEAS
|
|
local ID_SAVEALL = NewID()
|
|
local ID_EXIT = wx.wxID_EXIT
|
|
-- Edit menu
|
|
local ID_CUT = wx.wxID_CUT
|
|
local ID_COPY = wx.wxID_COPY
|
|
local ID_PASTE = wx.wxID_PASTE
|
|
local ID_SELECTALL = wx.wxID_SELECTALL
|
|
local ID_UNDO = wx.wxID_UNDO
|
|
local ID_REDO = wx.wxID_REDO
|
|
local ID_AUTOCOMPLETE = NewID()
|
|
local ID_AUTOCOMPLETE_ENABLE = NewID()
|
|
local ID_COMMENT = NewID()
|
|
local ID_FOLD = NewID()
|
|
-- Find menu
|
|
local ID_FIND = wx.wxID_FIND
|
|
local ID_FINDNEXT = NewID()
|
|
local ID_FINDPREV = NewID()
|
|
local ID_REPLACE = NewID()
|
|
local ID_GOTOLINE = NewID()
|
|
local ID_SORT = NewID()
|
|
-- Debug menu
|
|
local ID_TOGGLEBREAKPOINT = NewID()
|
|
local ID_COMPILE = NewID()
|
|
local ID_RUN = NewID()
|
|
local ID_ATTACH_DEBUG = NewID()
|
|
local ID_START_DEBUG = NewID()
|
|
local ID_USECONSOLE = NewID()
|
|
|
|
local ID_STOP_DEBUG = NewID()
|
|
local ID_STEP = NewID()
|
|
local ID_STEP_OVER = NewID()
|
|
local ID_STEP_OUT = NewID()
|
|
local ID_CONTINUE = NewID()
|
|
local ID_BREAK = NewID()
|
|
local ID_VIEWCALLSTACK = NewID()
|
|
local ID_VIEWWATCHWINDOW = NewID()
|
|
local ID_SHOWHIDEWINDOW = NewID()
|
|
local ID_CLEAROUTPUT = NewID()
|
|
local ID_DEBUGGER_PORT = NewID()
|
|
-- Help menu
|
|
local ID_ABOUT = wx.wxID_ABOUT
|
|
-- Watch window menu items
|
|
local ID_WATCH_LISTCTRL = NewID()
|
|
local ID_ADDWATCH = NewID()
|
|
local ID_EDITWATCH = NewID()
|
|
local ID_REMOVEWATCH = NewID()
|
|
local ID_EVALUATEWATCH = NewID()
|
|
|
|
-- Markers for editor marker margin
|
|
local BREAKPOINT_MARKER = 1
|
|
local BREAKPOINT_MARKER_VALUE = 2 -- = 2^BREAKPOINT_MARKER
|
|
local CURRENT_LINE_MARKER = 2
|
|
local CURRENT_LINE_MARKER_VALUE = 4 -- = 2^CURRENT_LINE_MARKER
|
|
|
|
-- ASCII values for common chars
|
|
local char_CR = string.byte("\r")
|
|
local char_LF = string.byte("\n")
|
|
local char_Tab = string.byte("\t")
|
|
local char_Sp = string.byte(" ")
|
|
|
|
-- Global variables
|
|
programName = nil -- the name of the wxLua program to be used when starting debugger
|
|
editorApp = wx.wxGetApp()
|
|
|
|
debuggerServer = nil -- wxLuaDebuggerServer object when debugging, else nil
|
|
debuggerServer_ = nil -- temp wxLuaDebuggerServer object for deletion
|
|
debuggee_running = false -- true when the debuggee is running
|
|
debugger_destroy = 0 -- > 0 if the debugger is to be destroyed in wxEVT_IDLE
|
|
debuggee_pid = 0 -- pid of the debuggee process
|
|
debuggerPortNumber = 1551 -- the port # to use for debugging
|
|
|
|
-- wxWindow variables
|
|
frame = nil -- wxFrame the main top level window
|
|
splitter = nil -- wxSplitterWindow for the notebook and errorLog
|
|
notebook = nil -- wxNotebook of editors
|
|
errorLog = nil -- wxStyledTextCtrl log window for messages
|
|
watchWindow = nil -- the watchWindow, nil when not created
|
|
watchListCtrl = nil -- the child listctrl in the watchWindow
|
|
|
|
in_evt_focus = false -- true when in editor focus event to avoid recursion
|
|
openDocuments = {} -- open notebook editor documents[winId] = {
|
|
-- editor = wxStyledTextCtrl,
|
|
-- index = wxNotebook page index,
|
|
-- filePath = full filepath, nil if not saved,
|
|
-- fileName = just the filename,
|
|
-- modTime = wxDateTime of disk file or nil,
|
|
-- isModified = bool is the document modified? }
|
|
ignoredFilesList = {}
|
|
editorID = 100 -- window id to create editor pages with, incremented for new editors
|
|
exitingProgram = false -- are we currently exiting, ID_EXIT
|
|
autoCompleteEnable = true -- value of ID_AUTOCOMPLETE_ENABLE menu item
|
|
wxkeywords = nil -- a string of the keywords for scintilla of wxLua's wx.XXX items
|
|
font = nil -- fonts to use for the editor
|
|
fontItalic = nil
|
|
|
|
findReplace = {
|
|
dialog = nil, -- the wxDialog for find/replace
|
|
replace = false, -- is it a find or replace dialog
|
|
fWholeWord = false, -- match whole words
|
|
fMatchCase = false, -- case sensitive
|
|
fDown = true, -- search downwards in doc
|
|
fRegularExpr = false, -- use regex
|
|
fWrap = false, -- search wraps around
|
|
findTextArray = {}, -- array of last entered find text
|
|
findText = "", -- string to find
|
|
replaceTextArray = {}, -- array of last entered replace text
|
|
replaceText = "", -- string to replace find string with
|
|
foundString = false, -- was the string found for the last search
|
|
|
|
-- HasText() is there a string to search for
|
|
-- GetSelectedString() get currently selected string if it's on one line
|
|
-- FindString(reverse) find the findText string
|
|
-- Show(replace) create the dialog
|
|
}
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
-- Pick some reasonable fixed width fonts to use for the editor
|
|
if wx.__WXMSW__ then
|
|
font = wx.wxFont(10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, "Andale Mono")
|
|
fontItalic = wx.wxFont(10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, "Andale Mono")
|
|
else
|
|
font = wx.wxFont(10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, "")
|
|
fontItalic = wx.wxFont(10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, "")
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Initialize the wxConfig for loading/saving the preferences
|
|
|
|
config = wx.wxFileConfig("wxLuaIDE", "WXLUA")
|
|
if config then
|
|
config:SetRecordDefaults()
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Create the wxFrame
|
|
-- ----------------------------------------------------------------------------
|
|
frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, "wxLua")
|
|
|
|
statusBar = frame:CreateStatusBar( 4 )
|
|
local status_txt_width = statusBar:GetTextExtent("OVRW")
|
|
frame:SetStatusWidths({-1, status_txt_width, status_txt_width, status_txt_width*5})
|
|
frame:SetStatusText("Welcome to wxLua")
|
|
|
|
toolBar = frame:CreateToolBar(wx.wxNO_BORDER + wx.wxTB_FLAT + wx.wxTB_DOCKABLE)
|
|
-- note: Ususally the bmp size isn't necessary, but the HELP icon is not the right size in MSW
|
|
local toolBmpSize = toolBar:GetToolBitmapSize()
|
|
toolBar:AddTool(ID_NEW, "New", wx.wxArtProvider.GetBitmap(wx.wxART_NORMAL_FILE, wx.wxART_MENU, toolBmpSize), "Create an empty document")
|
|
toolBar:AddTool(ID_OPEN, "Open", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_MENU, toolBmpSize), "Open an existing document")
|
|
toolBar:AddTool(ID_SAVE, "Save", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE, wx.wxART_MENU, toolBmpSize), "Save the current document")
|
|
toolBar:AddTool(ID_SAVEALL, "Save All", wx.wxArtProvider.GetBitmap(wx.wxART_NEW_DIR, wx.wxART_MENU, toolBmpSize), "Save all documents")
|
|
toolBar:AddSeparator()
|
|
toolBar:AddTool(ID_CUT, "Cut", wx.wxArtProvider.GetBitmap(wx.wxART_CUT, wx.wxART_MENU, toolBmpSize), "Cut the selection")
|
|
toolBar:AddTool(ID_COPY, "Copy", wx.wxArtProvider.GetBitmap(wx.wxART_COPY, wx.wxART_MENU, toolBmpSize), "Copy the selection")
|
|
toolBar:AddTool(ID_PASTE, "Paste", wx.wxArtProvider.GetBitmap(wx.wxART_PASTE, wx.wxART_MENU, toolBmpSize), "Paste text from the clipboard")
|
|
toolBar:AddSeparator()
|
|
toolBar:AddTool(ID_UNDO, "Undo", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_MENU, toolBmpSize), "Undo last edit")
|
|
toolBar:AddTool(ID_REDO, "Redo", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_MENU, toolBmpSize), "Redo last undo")
|
|
toolBar:AddSeparator()
|
|
toolBar:AddTool(ID_FIND, "Find", wx.wxArtProvider.GetBitmap(wx.wxART_FIND, wx.wxART_MENU, toolBmpSize), "Find text")
|
|
toolBar:AddTool(ID_REPLACE, "Replace", wx.wxArtProvider.GetBitmap(wx.wxART_FIND_AND_REPLACE, wx.wxART_MENU, toolBmpSize), "Find and replace text")
|
|
toolBar:Realize()
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Add the child windows to the frame
|
|
|
|
splitter = wx.wxSplitterWindow(frame, wx.wxID_ANY,
|
|
wx.wxDefaultPosition, wx.wxDefaultSize,
|
|
wx.wxSP_3DSASH)
|
|
|
|
notebook = wx.wxNotebook(splitter, wx.wxID_ANY,
|
|
wx.wxDefaultPosition, wx.wxDefaultSize,
|
|
wx.wxCLIP_CHILDREN)
|
|
|
|
notebook:Connect(wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED,
|
|
function (event)
|
|
if not exitingProgram then
|
|
SetEditorSelection(event:GetSelection())
|
|
end
|
|
event:Skip() -- skip to let page change
|
|
end)
|
|
|
|
errorLog = wxstc.wxStyledTextCtrl(splitter, wx.wxID_ANY)
|
|
errorLog:Show(false)
|
|
errorLog:SetFont(font)
|
|
errorLog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, font)
|
|
errorLog:StyleClearAll()
|
|
errorLog:SetMarginWidth(1, 16) -- marker margin
|
|
errorLog:SetMarginType(1, wxstc.wxSTC_MARGIN_SYMBOL);
|
|
errorLog:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_ARROWS, wx.wxBLACK, wx.wxWHITE)
|
|
errorLog:SetReadOnly(true)
|
|
|
|
splitter:Initialize(notebook) -- split later to show errorLog
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- wxConfig load/save preferences functions
|
|
|
|
function ConfigRestoreFramePosition(window, windowName)
|
|
local path = config:GetPath()
|
|
config:SetPath("/"..windowName)
|
|
|
|
local _, s = config:Read("s", -1)
|
|
local _, x = config:Read("x", 0)
|
|
local _, y = config:Read("y", 0)
|
|
local _, w = config:Read("w", 0)
|
|
local _, h = config:Read("h", 0)
|
|
|
|
if (s ~= -1) and (s ~= 2) then
|
|
local clientX, clientY, clientWidth, clientHeight
|
|
clientX, clientY, clientWidth, clientHeight = wx.wxClientDisplayRect()
|
|
|
|
if x < clientX then x = clientX end
|
|
if y < clientY then y = clientY end
|
|
|
|
if w > clientWidth then w = clientWidth end
|
|
if h > clientHeight then h = clientHeight end
|
|
|
|
window:SetSize(x, y, w, h)
|
|
elseif s == 1 then
|
|
window:Maximize(true)
|
|
end
|
|
|
|
config:SetPath(path)
|
|
end
|
|
|
|
function ConfigSaveFramePosition(window, windowName)
|
|
local path = config:GetPath()
|
|
config:SetPath("/"..windowName)
|
|
|
|
local s = 0
|
|
local w, h = window:GetSizeWH()
|
|
local x, y = window:GetPositionXY()
|
|
|
|
if window:IsMaximized() then
|
|
s = 1
|
|
elseif window:IsIconized() then
|
|
s = 2
|
|
end
|
|
|
|
config:Write("s", s)
|
|
|
|
if s == 0 then
|
|
config:Write("x", x)
|
|
config:Write("y", y)
|
|
config:Write("w", w)
|
|
config:Write("h", h)
|
|
end
|
|
|
|
config:SetPath(path)
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Get/Set notebook editor page, use nil for current page, returns nil if none
|
|
function GetEditor(selection)
|
|
local editor = nil
|
|
if selection == nil then
|
|
selection = notebook:GetSelection()
|
|
end
|
|
if (selection >= 0) and (selection < notebook:GetPageCount()) then
|
|
editor = notebook:GetPage(selection):DynamicCast("wxStyledTextCtrl")
|
|
end
|
|
return editor
|
|
end
|
|
|
|
-- init new notebook page selection, use nil for current page
|
|
function SetEditorSelection(selection)
|
|
local editor = GetEditor(selection)
|
|
if editor then
|
|
editor:SetFocus()
|
|
editor:SetSTCFocus(true)
|
|
IsFileAlteredOnDisk(editor)
|
|
end
|
|
UpdateStatusText(editor) -- update even if nil
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Update the statusbar text of the frame using the given editor.
|
|
-- Only update if the text has changed.
|
|
statusTextTable = { "OVR?", "R/O?", "Cursor Pos" }
|
|
|
|
function UpdateStatusText(editor)
|
|
local texts = { "", "", "" }
|
|
if frame and editor then
|
|
local pos = editor:GetCurrentPos()
|
|
local line = editor:LineFromPosition(pos)
|
|
local col = 1 + pos - editor:PositionFromLine(line)
|
|
|
|
texts = { iff(editor:GetOvertype(), "OVR", "INS"),
|
|
iff(editor:GetReadOnly(), "R/O", "R/W"),
|
|
"Ln "..tostring(line + 1).." Col "..tostring(col) }
|
|
end
|
|
|
|
if frame then
|
|
for n = 1, 3 do
|
|
if (texts[n] ~= statusTextTable[n]) then
|
|
frame:SetStatusText(texts[n], n)
|
|
statusTextTable[n] = texts[n]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Get file modification time, returns a wxDateTime (check IsValid) or nil if
|
|
-- the file doesn't exist
|
|
function GetFileModTime(filePath)
|
|
if filePath and (string.len(filePath) > 0) then
|
|
local fn = wx.wxFileName(filePath)
|
|
if fn:FileExists() then
|
|
return fn:GetModificationTime()
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- Check if file is altered, show dialog to reload it
|
|
function IsFileAlteredOnDisk(editor)
|
|
if not editor then return end
|
|
|
|
local id = editor:GetId()
|
|
if openDocuments[id] then
|
|
local filePath = openDocuments[id].filePath
|
|
local fileName = openDocuments[id].fileName
|
|
local oldModTime = openDocuments[id].modTime
|
|
|
|
if filePath and (string.len(filePath) > 0) and oldModTime and oldModTime:IsValid() then
|
|
local modTime = GetFileModTime(filePath)
|
|
if modTime == nil then
|
|
openDocuments[id].modTime = nil
|
|
wx.wxMessageBox(fileName.." is no longer on the disk.",
|
|
"wxLua Message",
|
|
wx.wxOK + wx.wxCENTRE, frame)
|
|
elseif modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
|
|
local ret = wx.wxMessageBox(fileName.." has been modified on disk.\nDo you want to reload it?",
|
|
"wxLua Message",
|
|
wx.wxYES_NO + wx.wxCENTRE, frame)
|
|
if ret ~= wx.wxYES or LoadFile(filePath, editor, true) then
|
|
openDocuments[id].modTime = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Set if the document is modified and update the notebook page text
|
|
function SetDocumentModified(id, modified)
|
|
local pageText = openDocuments[id].fileName or "untitled.lua"
|
|
|
|
if modified then
|
|
pageText = "* "..pageText
|
|
end
|
|
|
|
openDocuments[id].isModified = modified
|
|
notebook:SetPageText(openDocuments[id].index, pageText)
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Create an editor and add it to the notebook
|
|
function CreateEditor(name)
|
|
local editor = wxstc.wxStyledTextCtrl(notebook, editorID,
|
|
wx.wxDefaultPosition, wx.wxDefaultSize,
|
|
wx.wxSUNKEN_BORDER)
|
|
|
|
editorID = editorID + 1 -- increment so they're always unique
|
|
|
|
editor:SetBufferedDraw(true)
|
|
editor:StyleClearAll()
|
|
|
|
editor:SetFont(font)
|
|
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, font)
|
|
for i = 0, 32 do
|
|
editor:StyleSetFont(i, font)
|
|
end
|
|
|
|
editor:StyleSetForeground(0, wx.wxColour(128, 128, 128)) -- White space
|
|
editor:StyleSetForeground(1, wx.wxColour(0, 127, 0)) -- Block Comment
|
|
editor:StyleSetFont(1, fontItalic)
|
|
--editor:StyleSetUnderline(1, false)
|
|
editor:StyleSetForeground(2, wx.wxColour(0, 127, 0)) -- Line Comment
|
|
editor:StyleSetFont(2, fontItalic) -- Doc. Comment
|
|
--editor:StyleSetUnderline(2, false)
|
|
editor:StyleSetForeground(3, wx.wxColour(127, 127, 127)) -- Number
|
|
editor:StyleSetForeground(4, wx.wxColour(0, 127, 127)) -- Keyword
|
|
editor:StyleSetForeground(5, wx.wxColour(0, 0, 127)) -- Double quoted string
|
|
editor:StyleSetBold(5, true)
|
|
--editor:StyleSetUnderline(5, false)
|
|
editor:StyleSetForeground(6, wx.wxColour(127, 0, 127)) -- Single quoted string
|
|
editor:StyleSetForeground(7, wx.wxColour(127, 0, 127)) -- not used
|
|
editor:StyleSetForeground(8, wx.wxColour(0, 127, 127)) -- Literal strings
|
|
editor:StyleSetForeground(9, wx.wxColour(127, 127, 0)) -- Preprocessor
|
|
editor:StyleSetForeground(10, wx.wxColour(0, 0, 0)) -- Operators
|
|
--editor:StyleSetBold(10, true)
|
|
editor:StyleSetForeground(11, wx.wxColour(0, 0, 0)) -- Identifiers
|
|
editor:StyleSetForeground(12, wx.wxColour(0, 0, 0)) -- Unterminated strings
|
|
editor:StyleSetBackground(12, wx.wxColour(224, 192, 224))
|
|
editor:StyleSetBold(12, true)
|
|
editor:StyleSetEOLFilled(12, true)
|
|
|
|
editor:StyleSetForeground(13, wx.wxColour(0, 0, 95)) -- Keyword 2 highlighting styles
|
|
editor:StyleSetForeground(14, wx.wxColour(0, 95, 0)) -- Keyword 3
|
|
editor:StyleSetForeground(15, wx.wxColour(127, 0, 0)) -- Keyword 4
|
|
editor:StyleSetForeground(16, wx.wxColour(127, 0, 95)) -- Keyword 5
|
|
editor:StyleSetForeground(17, wx.wxColour(35, 95, 175)) -- Keyword 6
|
|
editor:StyleSetForeground(18, wx.wxColour(0, 127, 127)) -- Keyword 7
|
|
editor:StyleSetBackground(18, wx.wxColour(240, 255, 255)) -- Keyword 8
|
|
|
|
editor:StyleSetForeground(19, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(19, wx.wxColour(224, 255, 255))
|
|
editor:StyleSetForeground(20, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(20, wx.wxColour(192, 255, 255))
|
|
editor:StyleSetForeground(21, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(21, wx.wxColour(176, 255, 255))
|
|
editor:StyleSetForeground(22, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(22, wx.wxColour(160, 255, 255))
|
|
editor:StyleSetForeground(23, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(23, wx.wxColour(144, 255, 255))
|
|
editor:StyleSetForeground(24, wx.wxColour(0, 127, 127))
|
|
editor:StyleSetBackground(24, wx.wxColour(128, 155, 255))
|
|
|
|
editor:StyleSetForeground(32, wx.wxColour(224, 192, 224)) -- Line number
|
|
editor:StyleSetBackground(33, wx.wxColour(192, 192, 192)) -- Brace highlight
|
|
editor:StyleSetForeground(34, wx.wxColour(0, 0, 255))
|
|
editor:StyleSetBold(34, true) -- Brace incomplete highlight
|
|
editor:StyleSetForeground(35, wx.wxColour(255, 0, 0))
|
|
editor:StyleSetBold(35, true) -- Indentation guides
|
|
editor:StyleSetForeground(37, wx.wxColour(192, 192, 192))
|
|
editor:StyleSetBackground(37, wx.wxColour(255, 255, 255))
|
|
|
|
editor:SetUseTabs(false)
|
|
editor:SetTabWidth(4)
|
|
editor:SetIndent(4)
|
|
editor:SetIndentationGuides(true)
|
|
|
|
editor:SetVisiblePolicy(wxstc.wxSTC_VISIBLE_SLOP, 3)
|
|
--editor:SetXCaretPolicy(wxstc.wxSTC_CARET_SLOP, 10)
|
|
--editor:SetYCaretPolicy(wxstc.wxSTC_CARET_SLOP, 3)
|
|
|
|
editor:SetMarginWidth(0, editor:TextWidth(32, "99999_")) -- line # margin
|
|
|
|
editor:SetMarginWidth(1, 16) -- marker margin
|
|
editor:SetMarginType(1, wxstc.wxSTC_MARGIN_SYMBOL)
|
|
editor:SetMarginSensitive(1, true)
|
|
|
|
editor:MarkerDefine(BREAKPOINT_MARKER, wxstc.wxSTC_MARK_ROUNDRECT, wx.wxWHITE, wx.wxRED)
|
|
editor:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_ARROW, wx.wxBLACK, wx.wxGREEN)
|
|
|
|
editor:SetMarginWidth(2, 16) -- fold margin
|
|
editor:SetMarginType(2, wxstc.wxSTC_MARGIN_SYMBOL)
|
|
editor:SetMarginMask(2, wxstc.wxSTC_MASK_FOLDERS)
|
|
editor:SetMarginSensitive(2, true)
|
|
|
|
editor:SetFoldFlags(wxstc.wxSTC_FOLDFLAG_LINEBEFORE_CONTRACTED +
|
|
wxstc.wxSTC_FOLDFLAG_LINEAFTER_CONTRACTED)
|
|
|
|
editor:SetProperty("fold", "1")
|
|
editor:SetProperty("fold.compact", "1")
|
|
editor:SetProperty("fold.comment", "1")
|
|
|
|
local grey = wx.wxColour(128, 128, 128)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEROPEN, wxstc.wxSTC_MARK_BOXMINUS, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDER, wxstc.wxSTC_MARK_BOXPLUS, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERSUB, wxstc.wxSTC_MARK_VLINE, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERTAIL, wxstc.wxSTC_MARK_LCORNER, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEREND, wxstc.wxSTC_MARK_BOXPLUSCONNECTED, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEROPENMID, wxstc.wxSTC_MARK_BOXMINUSCONNECTED, wx.wxWHITE, grey)
|
|
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERMIDTAIL, wxstc.wxSTC_MARK_TCORNER, wx.wxWHITE, grey)
|
|
grey:delete()
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_MARGINCLICK,
|
|
function (event)
|
|
local line = editor:LineFromPosition(event:GetPosition())
|
|
local margin = event:GetMargin()
|
|
if margin == 1 then
|
|
ToggleDebugMarker(editor, line)
|
|
elseif margin == 2 then
|
|
if wx.wxGetKeyState(wx.WXK_SHIFT) and wx.wxGetKeyState(wx.WXK_CONTROL) then
|
|
FoldSome()
|
|
else
|
|
local level = editor:GetFoldLevel(line)
|
|
if HasBit(level, wxstc.wxSTC_FOLDLEVELHEADERFLAG) then
|
|
editor:ToggleFold(line)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_CHARADDED,
|
|
function (event)
|
|
-- auto-indent
|
|
local ch = event:GetKey()
|
|
if (ch == char_CR) or (ch == char_LF) then
|
|
local pos = editor:GetCurrentPos()
|
|
local line = editor:LineFromPosition(pos)
|
|
|
|
if (line > 0) and (editor:LineLength(line) == 0) then
|
|
local indent = editor:GetLineIndentation(line - 1)
|
|
if indent > 0 then
|
|
editor:SetLineIndentation(line, indent)
|
|
editor:GotoPos(pos + indent)
|
|
end
|
|
end
|
|
elseif autoCompleteEnable then -- code completion prompt
|
|
local pos = editor:GetCurrentPos()
|
|
local start_pos = editor:WordStartPosition(pos, true)
|
|
-- must have "wx.X" otherwise too many items
|
|
if (pos - start_pos > 0) and (start_pos > 2) then
|
|
local range = editor:GetTextRange(start_pos-3, start_pos)
|
|
if range == "wx." then
|
|
local commandEvent = wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
ID_AUTOCOMPLETE)
|
|
wx.wxPostEvent(frame, commandEvent)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_USERLISTSELECTION,
|
|
function (event)
|
|
local pos = editor:GetCurrentPos()
|
|
local start_pos = editor:WordStartPosition(pos, true)
|
|
editor:SetSelection(start_pos, pos)
|
|
editor:ReplaceSelection(event:GetText())
|
|
end)
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTREACHED,
|
|
function (event)
|
|
SetDocumentModified(editor:GetId(), false)
|
|
end)
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTLEFT,
|
|
function (event)
|
|
SetDocumentModified(editor:GetId(), true)
|
|
end)
|
|
|
|
editor:Connect(wxstc.wxEVT_STC_UPDATEUI,
|
|
function (event)
|
|
UpdateStatusText(editor)
|
|
end)
|
|
|
|
editor:Connect(wx.wxEVT_SET_FOCUS,
|
|
function (event)
|
|
event:Skip()
|
|
if in_evt_focus or exitingProgram then return end
|
|
in_evt_focus = true
|
|
IsFileAlteredOnDisk(editor)
|
|
in_evt_focus = false
|
|
end)
|
|
|
|
if notebook:AddPage(editor, name, true) then
|
|
local id = editor:GetId()
|
|
local document = {}
|
|
document.editor = editor
|
|
document.index = notebook:GetSelection()
|
|
document.fileName = nil
|
|
document.filePath = nil
|
|
document.modTime = nil
|
|
document.isModified = false
|
|
openDocuments[id] = document
|
|
end
|
|
|
|
return editor
|
|
end
|
|
|
|
function IsLuaFile(filePath)
|
|
return filePath and (string.len(filePath) > 4) and
|
|
(string.lower(string.sub(filePath, -4)) == ".lua")
|
|
end
|
|
|
|
function SetupKeywords(editor, useLuaParser)
|
|
if useLuaParser then
|
|
editor:SetLexer(wxstc.wxSTC_LEX_LUA)
|
|
|
|
-- Note: these keywords are shamelessly ripped from scite 1.68
|
|
editor:SetKeyWords(0,
|
|
[[and break do else elseif end false for function if
|
|
in local nil not or repeat return then true until while]])
|
|
editor:SetKeyWords(1,
|
|
[[_VERSION assert collectgarbage dofile error gcinfo loadfile loadstring
|
|
print rawget rawset require tonumber tostring type unpack]])
|
|
editor:SetKeyWords(2,
|
|
[[_G getfenv getmetatable ipairs loadlib next pairs pcall
|
|
rawequal setfenv setmetatable xpcall
|
|
string table math coroutine io os debug
|
|
load module select]])
|
|
editor:SetKeyWords(3,
|
|
[[string.byte string.char string.dump string.find string.len
|
|
string.lower string.rep string.sub string.upper string.format string.gfind string.gsub
|
|
table.concat table.foreach table.foreachi table.getn table.sort table.insert table.remove table.setn
|
|
math.abs math.acos math.asin math.atan math.atan2 math.ceil math.cos math.deg math.exp
|
|
math.floor math.frexp math.ldexp math.log math.log10 math.max math.min math.mod
|
|
math.pi math.pow math.rad math.random math.randomseed math.sin math.sqrt math.tan
|
|
string.gmatch string.match string.reverse table.maxn
|
|
math.cosh math.fmod math.modf math.sinh math.tanh math.huge]])
|
|
editor:SetKeyWords(4,
|
|
[[coroutine.create coroutine.resume coroutine.status
|
|
coroutine.wrap coroutine.yield
|
|
io.close io.flush io.input io.lines io.open io.output io.read io.tmpfile io.type io.write
|
|
io.stdin io.stdout io.stderr
|
|
os.clock os.date os.difftime os.execute os.exit os.getenv os.remove os.rename
|
|
os.setlocale os.time os.tmpname
|
|
coroutine.running package.cpath package.loaded package.loadlib package.path
|
|
package.preload package.seeall io.popen
|
|
debug.debug debug.getfenv debug.gethook debug.getinfo debug.getlocal
|
|
debug.getmetatable debug.getregistry debug.getupvalue debug.setfenv
|
|
debug.sethook debug.setlocal debug.setmetatable debug.setupvalue debug.traceback]])
|
|
|
|
-- Get the items in the global "wx" table for autocompletion
|
|
if not wxkeywords then
|
|
local keyword_table = {}
|
|
for index, value in pairs(wx) do
|
|
table.insert(keyword_table, "wx."..index.." ")
|
|
end
|
|
|
|
table.sort(keyword_table)
|
|
wxkeywords = table.concat(keyword_table)
|
|
end
|
|
|
|
editor:SetKeyWords(5, wxkeywords)
|
|
else
|
|
editor:SetLexer(wxstc.wxSTC_LEX_NULL)
|
|
editor:SetKeyWords(0, "")
|
|
end
|
|
|
|
editor:Colourise(0, -1)
|
|
end
|
|
|
|
function CreateAutoCompList(key_) -- much faster than iterating the wx. table
|
|
local key = "wx."..key_;
|
|
local a, b = string.find(wxkeywords, key, 1, 1)
|
|
local key_list = ""
|
|
|
|
while a do
|
|
local c, d = string.find(wxkeywords, " ", b, 1)
|
|
key_list = key_list..string.sub(wxkeywords, a+3, c or -1)
|
|
a, b = string.find(wxkeywords, key, d, 1)
|
|
end
|
|
|
|
return key_list
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the watch window
|
|
|
|
function ProcessWatches()
|
|
if watchListCtrl and debuggerServer then
|
|
for idx = 0, watchListCtrl:GetItemCount() - 1 do
|
|
local expression = watchListCtrl:GetItemText(idx)
|
|
debuggerServer:EvaluateExpr(idx, expression)
|
|
end
|
|
end
|
|
end
|
|
|
|
function CloseWatchWindow()
|
|
if watchWindow then
|
|
watchListCtrl = nil
|
|
watchWindow:Destroy()
|
|
watchWindow = nil
|
|
end
|
|
end
|
|
|
|
function CreateWatchWindow()
|
|
local width = 180
|
|
watchWindow = wx.wxFrame(frame, wx.wxID_ANY, "wxLua Watch Window",
|
|
wx.wxDefaultPosition, wx.wxSize(width, 160))
|
|
|
|
local watchMenu = wx.wxMenu{
|
|
{ ID_ADDWATCH, "&Add Watch" },
|
|
{ ID_EDITWATCH, "&Edit Watch\tF2" },
|
|
{ ID_REMOVEWATCH, "&Remove Watch" },
|
|
{ ID_EVALUATEWATCH, "Evaluate &Watches" }}
|
|
|
|
local watchMenuBar = wx.wxMenuBar()
|
|
watchMenuBar:Append(watchMenu, "&Watches")
|
|
watchWindow:SetMenuBar(watchMenuBar)
|
|
|
|
watchListCtrl = wx.wxListCtrl(watchWindow, ID_WATCH_LISTCTRL,
|
|
wx.wxDefaultPosition, wx.wxDefaultSize,
|
|
wx.wxLC_REPORT + wx.wxLC_EDIT_LABELS)
|
|
|
|
local info = wx.wxListItem()
|
|
info:SetMask(wx.wxLIST_MASK_TEXT + wx.wxLIST_MASK_WIDTH)
|
|
info:SetText("Expression")
|
|
info:SetWidth(width / 2)
|
|
watchListCtrl:InsertColumn(0, info)
|
|
|
|
info:SetText("Value")
|
|
info:SetWidth(width / 2)
|
|
watchListCtrl:InsertColumn(1, info)
|
|
|
|
watchWindow:CentreOnParent()
|
|
ConfigRestoreFramePosition(watchWindow, "WatchWindow")
|
|
watchWindow:Show(true)
|
|
|
|
local function FindSelectedWatchItem()
|
|
local count = watchListCtrl:GetSelectedItemCount()
|
|
if count > 0 then
|
|
for idx = 0, watchListCtrl:GetItemCount() - 1 do
|
|
if watchListCtrl:GetItemState(idx, wx.wxLIST_STATE_FOCUSED) ~= 0 then
|
|
return idx
|
|
end
|
|
end
|
|
end
|
|
return -1
|
|
end
|
|
|
|
watchWindow:Connect( wx.wxEVT_CLOSE_WINDOW,
|
|
function (event)
|
|
ConfigSaveFramePosition(watchWindow, "WatchWindow")
|
|
watchWindow = nil
|
|
watchListCtrl = nil
|
|
event:Skip()
|
|
end)
|
|
|
|
watchWindow:Connect(ID_ADDWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local row = watchListCtrl:InsertItem(watchListCtrl:GetItemCount(), "Expr")
|
|
watchListCtrl:SetItem(row, 0, "Expr")
|
|
watchListCtrl:SetItem(row, 1, "Value")
|
|
watchListCtrl:EditLabel(row)
|
|
end)
|
|
|
|
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local row = FindSelectedWatchItem()
|
|
if row >= 0 then
|
|
watchListCtrl:EditLabel(row)
|
|
end
|
|
end)
|
|
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
|
|
end)
|
|
|
|
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local row = FindSelectedWatchItem()
|
|
if row >= 0 then
|
|
watchListCtrl:DeleteItem(row)
|
|
end
|
|
end)
|
|
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
|
|
end)
|
|
|
|
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ProcessWatches()
|
|
end)
|
|
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable(watchListCtrl:GetItemCount() > 0)
|
|
end)
|
|
|
|
watchListCtrl:Connect(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT,
|
|
function (event)
|
|
watchListCtrl:SetItem(event:GetIndex(), 0, event:GetText())
|
|
ProcessWatches()
|
|
event:Skip()
|
|
end)
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the File menu and attach the callback functions
|
|
|
|
-- force all the wxEVT_UPDATE_UI handlers to be called
|
|
function UpdateUIMenuItems()
|
|
if frame and frame:GetMenuBar() then
|
|
for n = 0, frame:GetMenuBar():GetMenuCount()-1 do
|
|
frame:GetMenuBar():GetMenu(n):UpdateUI()
|
|
end
|
|
end
|
|
end
|
|
|
|
menuBar = wx.wxMenuBar()
|
|
fileMenu = wx.wxMenu({
|
|
{ ID_NEW, "&New\tCtrl-N", "Create an empty document" },
|
|
{ ID_OPEN, "&Open...\tCtrl-O", "Open an existing document" },
|
|
{ ID_CLOSE, "&Close page\tCtrl+W", "Close the current editor window" },
|
|
{ },
|
|
{ ID_SAVE, "&Save\tCtrl-S", "Save the current document" },
|
|
{ ID_SAVEAS, "Save &As...\tAlt-S", "Save the current document to a file with a new name" },
|
|
{ ID_SAVEALL, "Save A&ll...\tCtrl-Shift-S", "Save all open documents" },
|
|
{ },
|
|
{ ID_EXIT, "E&xit\tAlt-X", "Exit Program" }})
|
|
menuBar:Append(fileMenu, "&File")
|
|
|
|
function NewFile(event)
|
|
local editor = CreateEditor("untitled.lua")
|
|
SetupKeywords(editor, true)
|
|
end
|
|
|
|
frame:Connect(ID_NEW, wx.wxEVT_COMMAND_MENU_SELECTED, NewFile)
|
|
|
|
-- Find an editor page that hasn't been used at all, eg. an untouched NewFile()
|
|
function FindDocumentToReuse()
|
|
local editor = nil
|
|
for id, document in pairs(openDocuments) do
|
|
if (document.editor:GetLength() == 0) and
|
|
(not document.isModified) and (not document.filePath) and
|
|
not (document.editor:GetReadOnly() == true) then
|
|
editor = document.editor
|
|
break
|
|
end
|
|
end
|
|
return editor
|
|
end
|
|
|
|
function LoadFile(filePath, editor, file_must_exist)
|
|
local file_text = ""
|
|
local handle = io.open(filePath, "rb")
|
|
if handle then
|
|
file_text = handle:read("*a")
|
|
handle:close()
|
|
elseif file_must_exist then
|
|
return nil
|
|
end
|
|
|
|
if not editor then
|
|
editor = FindDocumentToReuse()
|
|
end
|
|
if not editor then
|
|
editor = CreateEditor(wx.wxFileName(filePath):GetFullName() or "untitled.lua")
|
|
end
|
|
|
|
editor:Clear()
|
|
editor:ClearAll()
|
|
SetupKeywords(editor, IsLuaFile(filePath))
|
|
editor:MarkerDeleteAll(BREAKPOINT_MARKER)
|
|
editor:MarkerDeleteAll(CURRENT_LINE_MARKER)
|
|
editor:AppendText(file_text)
|
|
editor:EmptyUndoBuffer()
|
|
local id = editor:GetId()
|
|
openDocuments[id].filePath = filePath
|
|
openDocuments[id].fileName = wx.wxFileName(filePath):GetFullName()
|
|
openDocuments[id].modTime = GetFileModTime(filePath)
|
|
SetDocumentModified(id, false)
|
|
editor:Colourise(0, -1)
|
|
|
|
return editor
|
|
end
|
|
|
|
function OpenFile(event)
|
|
local fileDialog = wx.wxFileDialog(frame, "Open file",
|
|
"",
|
|
"",
|
|
"Lua files (*.lua)|*.lua|Text files (*.txt)|*.txt|All files (*)|*",
|
|
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
|
|
if fileDialog:ShowModal() == wx.wxID_OK then
|
|
if not LoadFile(fileDialog:GetPath(), nil, true) then
|
|
wx.wxMessageBox("Unable to load file '"..fileDialog:GetPath().."'.",
|
|
"wxLua Error",
|
|
wx.wxOK + wx.wxCENTRE, frame)
|
|
end
|
|
end
|
|
fileDialog:Destroy()
|
|
end
|
|
frame:Connect(ID_OPEN, wx.wxEVT_COMMAND_MENU_SELECTED, OpenFile)
|
|
|
|
-- save the file to filePath or if filePath is nil then call SaveFileAs
|
|
function SaveFile(editor, filePath)
|
|
if not filePath then
|
|
return SaveFileAs(editor)
|
|
else
|
|
local backPath = filePath..".bak"
|
|
os.remove(backPath)
|
|
os.rename(filePath, backPath)
|
|
|
|
local handle = io.open(filePath, "wb")
|
|
if handle then
|
|
local st = editor:GetText()
|
|
handle:write(st)
|
|
handle:close()
|
|
editor:EmptyUndoBuffer()
|
|
local id = editor:GetId()
|
|
openDocuments[id].filePath = filePath
|
|
openDocuments[id].fileName = wx.wxFileName(filePath):GetFullName()
|
|
openDocuments[id].modTime = GetFileModTime(filePath)
|
|
SetDocumentModified(id, false)
|
|
return true
|
|
else
|
|
wx.wxMessageBox("Unable to save file '"..filePath.."'.",
|
|
"wxLua Error Saving",
|
|
wx.wxOK + wx.wxCENTRE, frame)
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
frame:Connect(ID_SAVE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local id = editor:GetId()
|
|
local filePath = openDocuments[id].filePath
|
|
SaveFile(editor, filePath)
|
|
end)
|
|
|
|
frame:Connect(ID_SAVE, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
if editor then
|
|
local id = editor:GetId()
|
|
if openDocuments[id] then
|
|
event:Enable(openDocuments[id].isModified)
|
|
end
|
|
end
|
|
end)
|
|
|
|
function SaveFileAs(editor)
|
|
local id = editor:GetId()
|
|
local saved = false
|
|
local fn = wx.wxFileName(openDocuments[id].filePath or "")
|
|
fn:Normalize() -- want absolute path for dialog
|
|
|
|
local fileDialog = wx.wxFileDialog(frame, "Save file as",
|
|
fn:GetPath(),
|
|
fn:GetFullName(),
|
|
"Lua files (*.lua)|*.lua|Text files (*.txt)|*.txt|All files (*)|*",
|
|
wx.wxSAVE)
|
|
|
|
if fileDialog:ShowModal() == wx.wxID_OK then
|
|
local filePath = fileDialog:GetPath()
|
|
|
|
if SaveFile(editor, filePath) then
|
|
SetupKeywords(editor, IsLuaFile(filePath))
|
|
saved = true
|
|
end
|
|
end
|
|
|
|
fileDialog:Destroy()
|
|
return saved
|
|
end
|
|
|
|
frame:Connect(ID_SAVEAS, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
SaveFileAs(editor)
|
|
end)
|
|
frame:Connect(ID_SAVEAS, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable(editor ~= nil)
|
|
end)
|
|
|
|
function SaveAll()
|
|
for id, document in pairs(openDocuments) do
|
|
local editor = document.editor
|
|
local filePath = document.filePath
|
|
|
|
if document.isModified then
|
|
SaveFile(editor, filePath) -- will call SaveFileAs if necessary
|
|
end
|
|
end
|
|
end
|
|
|
|
frame:Connect(ID_SAVEALL, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
SaveAll()
|
|
end)
|
|
|
|
frame:Connect(ID_SAVEALL, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local atLeastOneModifiedDocument = false
|
|
for id, document in pairs(openDocuments) do
|
|
if document.isModified then
|
|
atLeastOneModifiedDocument = true
|
|
break
|
|
end
|
|
end
|
|
event:Enable(atLeastOneModifiedDocument)
|
|
end)
|
|
|
|
function RemovePage(index)
|
|
local prevIndex = nil
|
|
local nextIndex = nil
|
|
local newOpenDocuments = {}
|
|
|
|
for id, document in pairs(openDocuments) do
|
|
if document.index < index then
|
|
newOpenDocuments[id] = document
|
|
prevIndex = document.index
|
|
elseif document.index == index then
|
|
document.editor:Destroy()
|
|
elseif document.index > index then
|
|
document.index = document.index - 1
|
|
if nextIndex == nil then
|
|
nextIndex = document.index
|
|
end
|
|
newOpenDocuments[id] = document
|
|
end
|
|
end
|
|
|
|
notebook:RemovePage(index)
|
|
openDocuments = newOpenDocuments
|
|
|
|
if nextIndex then
|
|
notebook:SetSelection(nextIndex)
|
|
elseif prevIndex then
|
|
notebook:SetSelection(prevIndex)
|
|
end
|
|
|
|
SetEditorSelection(nil) -- will use notebook GetSelection to update
|
|
end
|
|
|
|
-- Show a dialog to save a file before closing editor.
|
|
-- returns wxID_YES, wxID_NO, or wxID_CANCEL if allow_cancel
|
|
function SaveModifiedDialog(editor, allow_cancel)
|
|
local result = wx.wxID_NO
|
|
local id = editor:GetId()
|
|
local document = openDocuments[id]
|
|
local filePath = document.filePath
|
|
local fileName = document.fileName
|
|
if document.isModified then
|
|
local message
|
|
if fileName then
|
|
message = "Save changes to '"..fileName.."' before exiting?"
|
|
else
|
|
message = "Save changes to 'untitled' before exiting?"
|
|
end
|
|
local dlg_styles = wx.wxYES_NO + wx.wxCENTRE + wx.wxICON_QUESTION
|
|
if allow_cancel then dlg_styles = dlg_styles + wx.wxCANCEL end
|
|
local dialog = wx.wxMessageDialog(frame, message,
|
|
"Save Changes?",
|
|
dlg_styles)
|
|
result = dialog:ShowModal()
|
|
dialog:Destroy()
|
|
if result == wx.wxID_YES then
|
|
SaveFile(editor, filePath)
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
frame:Connect(ID_CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local id = editor:GetId()
|
|
if SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL then
|
|
RemovePage(openDocuments[id].index)
|
|
end
|
|
end)
|
|
|
|
frame:Connect(ID_CLOSE, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((GetEditor() ~= nil) and (debuggerServer == nil))
|
|
end)
|
|
|
|
function SaveOnExit(allow_cancel)
|
|
for id, document in pairs(openDocuments) do
|
|
if (SaveModifiedDialog(document.editor, allow_cancel) == wx.wxID_CANCEL) then
|
|
return false
|
|
end
|
|
|
|
document.isModified = false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
frame:Connect( ID_EXIT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
if not SaveOnExit(true) then return end
|
|
frame:Close() -- will handle wxEVT_CLOSE_WINDOW
|
|
CloseWatchWindow()
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the Edit menu and attach the callback functions
|
|
|
|
editMenu = wx.wxMenu{
|
|
{ ID_CUT, "Cu&t\tCtrl-X", "Cut selected text to clipboard" },
|
|
{ ID_COPY, "&Copy\tCtrl-C", "Copy selected text to the clipboard" },
|
|
{ ID_PASTE, "&Paste\tCtrl-V", "Insert clipboard text at cursor" },
|
|
{ ID_SELECTALL, "Select A&ll\tCtrl-A", "Select all text in the editor" },
|
|
{ },
|
|
{ ID_UNDO, "&Undo\tCtrl-Z", "Undo the last action" },
|
|
{ ID_REDO, "&Redo\tCtrl-Y", "Redo the last action undone" },
|
|
{ },
|
|
{ ID_AUTOCOMPLETE, "Complete &Identifier\tCtrl+K", "Complete the current identifier" },
|
|
{ ID_AUTOCOMPLETE_ENABLE, "Auto complete Identifiers", "Auto complete while typing", wx.wxITEM_CHECK },
|
|
{ },
|
|
{ ID_COMMENT, "C&omment/Uncomment\tCtrl-Q", "Comment or uncomment current or selected lines"},
|
|
{ },
|
|
{ ID_FOLD, "&Fold/Unfold all\tF12", "Fold or unfold all code folds"} }
|
|
menuBar:Append(editMenu, "&Edit")
|
|
|
|
editMenu:Check(ID_AUTOCOMPLETE_ENABLE, autoCompleteEnable)
|
|
|
|
function OnUpdateUIEditMenu(event) -- enable if there is a valid focused editor
|
|
local editor = GetEditor()
|
|
event:Enable(editor ~= nil)
|
|
end
|
|
|
|
function OnEditMenu(event)
|
|
local menu_id = event:GetId()
|
|
local editor = GetEditor()
|
|
if editor == nil then return end
|
|
|
|
if menu_id == ID_CUT then editor:Cut()
|
|
elseif menu_id == ID_COPY then editor:Copy()
|
|
elseif menu_id == ID_PASTE then editor:Paste()
|
|
elseif menu_id == ID_SELECTALL then editor:SelectAll()
|
|
elseif menu_id == ID_UNDO then editor:Undo()
|
|
elseif menu_id == ID_REDO then editor:Redo()
|
|
end
|
|
end
|
|
|
|
frame:Connect(ID_CUT, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_CUT, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_COPY, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_COPY, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_PASTE, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_PASTE, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
-- buggy GTK clipboard runs eventloop and can generate asserts
|
|
event:Enable(editor and (wx.__WXGTK__ or editor:CanPaste()))
|
|
end)
|
|
|
|
frame:Connect(ID_SELECTALL, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_SELECTALL, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_UNDO, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_UNDO, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable(editor and editor:CanUndo())
|
|
end)
|
|
|
|
frame:Connect(ID_REDO, wx.wxEVT_COMMAND_MENU_SELECTED, OnEditMenu)
|
|
frame:Connect(ID_REDO, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable(editor and editor:CanRedo())
|
|
end)
|
|
|
|
frame:Connect(ID_AUTOCOMPLETE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
if (editor == nil) then return end
|
|
local pos = editor:GetCurrentPos()
|
|
local start_pos = editor:WordStartPosition(pos, true)
|
|
-- must have "wx.XX" otherwise too many items
|
|
if (pos - start_pos > 2) and (start_pos > 2) then
|
|
local range = editor:GetTextRange(start_pos-3, start_pos)
|
|
if range == "wx." then
|
|
local key = editor:GetTextRange(start_pos, pos)
|
|
local userList = CreateAutoCompList(key)
|
|
if userList and string.len(userList) > 0 then
|
|
editor:UserListShow(1, userList)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
frame:Connect(ID_AUTOCOMPLETE, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_AUTOCOMPLETE_ENABLE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
autoCompleteEnable = event:IsChecked()
|
|
end)
|
|
|
|
frame:Connect(ID_COMMENT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local buf = {}
|
|
if editor:GetSelectionStart() == editor:GetSelectionEnd() then
|
|
local lineNumber = editor:GetCurrentLine()
|
|
editor:SetSelection(editor:PositionFromLine(lineNumber), editor:GetLineEndPosition(lineNumber))
|
|
end
|
|
for line in string.gmatch(editor:GetSelectedText()..'\n', "(.-)\r?\n") do
|
|
if string.sub(line,1,2) == '--' then
|
|
line = string.sub(line,3)
|
|
else
|
|
line = '--'..line
|
|
end
|
|
table.insert(buf, line)
|
|
end
|
|
editor:ReplaceSelection(table.concat(buf,"\n"))
|
|
end)
|
|
frame:Connect(ID_COMMENT, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
function FoldSome()
|
|
local editor = GetEditor()
|
|
editor:Colourise(0, -1) -- update doc's folding info
|
|
local visible, baseFound, expanded, folded
|
|
for ln = 2, editor.LineCount - 1 do
|
|
local foldRaw = editor:GetFoldLevel(ln)
|
|
local foldLvl = math.mod(foldRaw, 4096)
|
|
local foldHdr = math.mod(math.floor(foldRaw / 8192), 2) == 1
|
|
if not baseFound and (foldLvl == wxstc.wxSTC_FOLDLEVELBASE) then
|
|
baseFound = true
|
|
visible = editor:GetLineVisible(ln)
|
|
end
|
|
if foldHdr then
|
|
if editor:GetFoldExpanded(ln) then
|
|
expanded = true
|
|
else
|
|
folded = true
|
|
end
|
|
end
|
|
if expanded and folded and baseFound then break end
|
|
end
|
|
local show = not visible or (not baseFound and expanded) or (expanded and folded)
|
|
local hide = visible and folded
|
|
|
|
if show then
|
|
editor:ShowLines(1, editor.LineCount-1)
|
|
end
|
|
|
|
for ln = 1, editor.LineCount - 1 do
|
|
local foldRaw = editor:GetFoldLevel(ln)
|
|
local foldLvl = math.mod(foldRaw, 4096)
|
|
local foldHdr = math.mod(math.floor(foldRaw / 8192), 2) == 1
|
|
if show then
|
|
if foldHdr then
|
|
if not editor:GetFoldExpanded(ln) then editor:ToggleFold(ln) end
|
|
end
|
|
elseif hide and (foldLvl == wxstc.wxSTC_FOLDLEVELBASE) then
|
|
if not foldHdr then
|
|
editor:HideLines(ln, ln)
|
|
end
|
|
elseif foldHdr then
|
|
if editor:GetFoldExpanded(ln) then
|
|
editor:ToggleFold(ln)
|
|
end
|
|
end
|
|
end
|
|
editor:EnsureCaretVisible()
|
|
end
|
|
|
|
frame:Connect(ID_FOLD, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
FoldSome()
|
|
end)
|
|
frame:Connect(ID_FOLD, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the Search menu and attach the callback functions
|
|
|
|
findMenu = wx.wxMenu{
|
|
{ ID_FIND, "&Find\tCtrl-F", "Find the specified text" },
|
|
{ ID_FINDNEXT, "Find &Next\tF3", "Find the next occurrence of the specified text" },
|
|
{ ID_FINDPREV, "Find &Previous\tShift-F3", "Repeat the search backwards in the file" },
|
|
{ ID_REPLACE, "&Replace\tCtrl-H", "Replaces the specified text with different text" },
|
|
{ },
|
|
{ ID_GOTOLINE, "&Goto line\tCtrl-G", "Go to a selected line" },
|
|
{ },
|
|
{ ID_SORT, "&Sort", "Sort selected lines"}}
|
|
menuBar:Append(findMenu, "&Search")
|
|
|
|
function EnsureRangeVisible(posStart, posEnd)
|
|
local editor = GetEditor()
|
|
if posStart > posEnd then
|
|
posStart, posEnd = posEnd, posStart
|
|
end
|
|
|
|
local lineStart = editor:LineFromPosition(posStart)
|
|
local lineEnd = editor:LineFromPosition(posEnd)
|
|
for line = lineStart, lineEnd do
|
|
editor:EnsureVisibleEnforcePolicy(line)
|
|
end
|
|
end
|
|
|
|
-------------------- Find replace dialog
|
|
|
|
function SetSearchFlags(editor)
|
|
local flags = 0
|
|
if findReplace.fWholeWord then flags = wxstc.wxSTC_FIND_WHOLEWORD end
|
|
if findReplace.fMatchCase then flags = flags + wxstc.wxSTC_FIND_MATCHCASE end
|
|
if findReplace.fRegularExpr then flags = flags + wxstc.wxSTC_FIND_REGEXP end
|
|
editor:SetSearchFlags(flags)
|
|
end
|
|
|
|
function SetTarget(editor, fDown, fInclude)
|
|
local selStart = editor:GetSelectionStart()
|
|
local selEnd = editor:GetSelectionEnd()
|
|
local len = editor:GetLength()
|
|
local s, e
|
|
if fDown then
|
|
e= len
|
|
s = iff(fInclude, selStart, selEnd +1)
|
|
else
|
|
s = 0
|
|
e = iff(fInclude, selEnd, selStart-1)
|
|
end
|
|
if not fDown and not fInclude then s, e = e, s end
|
|
editor:SetTargetStart(s)
|
|
editor:SetTargetEnd(e)
|
|
return e
|
|
end
|
|
|
|
function findReplace:HasText()
|
|
return (findReplace.findText ~= nil) and (string.len(findReplace.findText) > 0)
|
|
end
|
|
|
|
function findReplace:GetSelectedString()
|
|
local editor = GetEditor()
|
|
if editor then
|
|
local startSel = editor:GetSelectionStart()
|
|
local endSel = editor:GetSelectionEnd()
|
|
if (startSel ~= endSel) and (editor:LineFromPosition(startSel) == editor:LineFromPosition(endSel)) then
|
|
findReplace.findText = editor:GetSelectedText()
|
|
findReplace.foundString = true
|
|
end
|
|
end
|
|
end
|
|
|
|
function findReplace:FindString(reverse)
|
|
if findReplace:HasText() then
|
|
local editor = GetEditor()
|
|
local fDown = iff(reverse, not findReplace.fDown, findReplace.fDown)
|
|
local lenFind = string.len(findReplace.findText)
|
|
SetSearchFlags(editor)
|
|
SetTarget(editor, fDown)
|
|
local posFind = editor:SearchInTarget(findReplace.findText)
|
|
if (posFind == -1) and findReplace.fWrap then
|
|
editor:SetTargetStart(iff(fDown, 0, editor:GetLength()))
|
|
editor:SetTargetEnd(iff(fDown, editor:GetLength(), 0))
|
|
posFind = editor:SearchInTarget(findReplace.findText)
|
|
end
|
|
if posFind == -1 then
|
|
findReplace.foundString = false
|
|
frame:SetStatusText("Find text not found.")
|
|
else
|
|
findReplace.foundString = true
|
|
local start = editor:GetTargetStart()
|
|
local finish = editor:GetTargetEnd()
|
|
EnsureRangeVisible(start, finish)
|
|
editor:SetSelection(start, finish)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReplaceString(fReplaceAll)
|
|
if findReplace:HasText() then
|
|
local replaceLen = string.len(findReplace.replaceText)
|
|
local editor = GetEditor()
|
|
local findLen = string.len(findReplace.findText)
|
|
local endTarget = SetTarget(editor, findReplace.fDown, fReplaceAll)
|
|
if fReplaceAll then
|
|
SetSearchFlags(editor)
|
|
local posFind = editor:SearchInTarget(findReplace.findText)
|
|
if (posFind ~= -1) then
|
|
editor:BeginUndoAction()
|
|
while posFind ~= -1 do
|
|
editor:ReplaceTarget(findReplace.replaceText)
|
|
editor:SetTargetStart(posFind + replaceLen)
|
|
endTarget = endTarget + replaceLen - findLen
|
|
editor:SetTargetEnd(endTarget)
|
|
posFind = editor:SearchInTarget(findReplace.findText)
|
|
end
|
|
editor:EndUndoAction()
|
|
end
|
|
else
|
|
if findReplace.foundString then
|
|
local start = editor:GetSelectionStart()
|
|
editor:ReplaceSelection(findReplace.replaceText)
|
|
editor:SetSelection(start, start + replaceLen)
|
|
findReplace.foundString = false
|
|
end
|
|
findReplace:FindString()
|
|
end
|
|
end
|
|
end
|
|
|
|
function CreateFindReplaceDialog(replace)
|
|
local ID_FIND_NEXT = 1
|
|
local ID_REPLACE = 2
|
|
local ID_REPLACE_ALL = 3
|
|
findReplace.replace = replace
|
|
|
|
local findDialog = wx.wxDialog(frame, wx.wxID_ANY, "Find", wx.wxDefaultPosition, wx.wxDefaultSize)
|
|
|
|
-- Create right hand buttons and sizer
|
|
local findButton = wx.wxButton(findDialog, ID_FIND_NEXT, "&Find Next")
|
|
findButton:SetDefault()
|
|
local replaceButton = wx.wxButton(findDialog, ID_REPLACE, "&Replace")
|
|
local replaceAllButton = nil
|
|
if (replace) then
|
|
replaceAllButton = wx.wxButton(findDialog, ID_REPLACE_ALL, "Replace &All")
|
|
end
|
|
local cancelButton = wx.wxButton(findDialog, wx.wxID_CANCEL, "Cancel")
|
|
|
|
local buttonsSizer = wx.wxBoxSizer(wx.wxVERTICAL)
|
|
buttonsSizer:Add(findButton, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
buttonsSizer:Add(replaceButton, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
if replace then
|
|
buttonsSizer:Add(replaceAllButton, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
end
|
|
buttonsSizer:Add(cancelButton, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
|
|
-- Create find/replace text entry sizer
|
|
local findStatText = wx.wxStaticText( findDialog, wx.wxID_ANY, "Find: ")
|
|
local findTextCombo = wx.wxComboBox(findDialog, wx.wxID_ANY, findReplace.findText, wx.wxDefaultPosition, wx.wxDefaultSize, findReplace.findTextArray, wx.wxCB_DROPDOWN)
|
|
findTextCombo:SetFocus()
|
|
|
|
local replaceStatText, replaceTextCombo
|
|
if (replace) then
|
|
replaceStatText = wx.wxStaticText( findDialog, wx.wxID_ANY, "Replace: ")
|
|
replaceTextCombo = wx.wxComboBox(findDialog, wx.wxID_ANY, findReplace.replaceText, wx.wxDefaultPosition, wx.wxDefaultSize, findReplace.replaceTextArray)
|
|
end
|
|
|
|
local findReplaceSizer = wx.wxFlexGridSizer(2, 2, 0, 0)
|
|
findReplaceSizer:AddGrowableCol(1)
|
|
findReplaceSizer:Add(findStatText, 0, wx.wxALL + wx.wxALIGN_LEFT, 0)
|
|
findReplaceSizer:Add(findTextCombo, 1, wx.wxALL + wx.wxGROW + wx.wxCENTER, 0)
|
|
|
|
if (replace) then
|
|
findReplaceSizer:Add(replaceStatText, 0, wx.wxTOP + wx.wxALIGN_CENTER, 5)
|
|
findReplaceSizer:Add(replaceTextCombo, 1, wx.wxTOP + wx.wxGROW + wx.wxCENTER, 5)
|
|
end
|
|
|
|
-- Create find/replace option checkboxes
|
|
local wholeWordCheckBox = wx.wxCheckBox(findDialog, wx.wxID_ANY, "Match &whole word")
|
|
local matchCaseCheckBox = wx.wxCheckBox(findDialog, wx.wxID_ANY, "Match &case")
|
|
local wrapAroundCheckBox = wx.wxCheckBox(findDialog, wx.wxID_ANY, "Wrap ar&ound")
|
|
local regexCheckBox = wx.wxCheckBox(findDialog, wx.wxID_ANY, "Regular &expression")
|
|
wholeWordCheckBox:SetValue(findReplace.fWholeWord)
|
|
matchCaseCheckBox:SetValue(findReplace.fMatchCase)
|
|
wrapAroundCheckBox:SetValue(findReplace.fWrap)
|
|
regexCheckBox:SetValue(findReplace.fRegularExpr)
|
|
|
|
local optionSizer = wx.wxBoxSizer(wx.wxVERTICAL, findDialog)
|
|
optionSizer:Add(wholeWordCheckBox, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
optionSizer:Add(matchCaseCheckBox, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
optionSizer:Add(wrapAroundCheckBox, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
optionSizer:Add(regexCheckBox, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 3)
|
|
local optionsSizer = wx.wxStaticBoxSizer(wx.wxVERTICAL, findDialog, "Options" );
|
|
optionsSizer:Add(optionSizer, 0, 0, 5)
|
|
|
|
-- Create scope radiobox
|
|
local scopeRadioBox = wx.wxRadioBox(findDialog, wx.wxID_ANY, "Scope", wx.wxDefaultPosition, wx.wxDefaultSize, {"&Up", "&Down"}, 1, wx.wxRA_SPECIFY_COLS)
|
|
scopeRadioBox:SetSelection(iff(findReplace.fDown, 1, 0))
|
|
local scopeSizer = wx.wxBoxSizer(wx.wxVERTICAL, findDialog );
|
|
scopeSizer:Add(scopeRadioBox, 0, 0, 0)
|
|
|
|
-- Add all the sizers to the dialog
|
|
local optionScopeSizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
|
|
optionScopeSizer:Add(optionsSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 5)
|
|
optionScopeSizer:Add(scopeSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 5)
|
|
|
|
local leftSizer = wx.wxBoxSizer(wx.wxVERTICAL)
|
|
leftSizer:Add(findReplaceSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 0)
|
|
leftSizer:Add(optionScopeSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 0)
|
|
|
|
local mainSizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
|
|
mainSizer:Add(leftSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 10)
|
|
mainSizer:Add(buttonsSizer, 0, wx.wxALL + wx.wxGROW + wx.wxCENTER, 10)
|
|
mainSizer:SetSizeHints( findDialog )
|
|
findDialog:SetSizer(mainSizer)
|
|
|
|
local function PrependToArray(t, s)
|
|
if string.len(s) == 0 then return end
|
|
for i, v in ipairs(t) do
|
|
if v == s then
|
|
table.remove(t, i) -- remove old copy
|
|
break
|
|
end
|
|
end
|
|
table.insert(t, 1, s)
|
|
if #t > 15 then table.remove(t, #t) end -- keep reasonable length
|
|
end
|
|
|
|
local function TransferDataFromWindow()
|
|
findReplace.fWholeWord = wholeWordCheckBox:GetValue()
|
|
findReplace.fMatchCase = matchCaseCheckBox:GetValue()
|
|
findReplace.fWrap = wrapAroundCheckBox:GetValue()
|
|
findReplace.fDown = scopeRadioBox:GetSelection() == 1
|
|
findReplace.fRegularExpr = regexCheckBox:GetValue()
|
|
findReplace.findText = findTextCombo:GetValue()
|
|
PrependToArray(findReplace.findTextArray, findReplace.findText)
|
|
if findReplace.replace then
|
|
findReplace.replaceText = replaceTextCombo:GetValue()
|
|
PrependToArray(findReplace.replaceTextArray, findReplace.replaceText)
|
|
end
|
|
return true
|
|
end
|
|
|
|
findDialog:Connect(ID_FIND_NEXT, wx.wxEVT_COMMAND_BUTTON_CLICKED,
|
|
function(event)
|
|
TransferDataFromWindow()
|
|
findReplace:FindString()
|
|
end)
|
|
|
|
findDialog:Connect(ID_REPLACE, wx.wxEVT_COMMAND_BUTTON_CLICKED,
|
|
function(event)
|
|
TransferDataFromWindow()
|
|
event:Skip()
|
|
if findReplace.replace then
|
|
ReplaceString()
|
|
else
|
|
findReplace.dialog:Destroy()
|
|
findReplace.dialog = CreateFindReplaceDialog(true)
|
|
findReplace.dialog:Show(true)
|
|
end
|
|
end)
|
|
|
|
if replace then
|
|
findDialog:Connect(ID_REPLACE_ALL, wx.wxEVT_COMMAND_BUTTON_CLICKED,
|
|
function(event)
|
|
TransferDataFromWindow()
|
|
event:Skip()
|
|
ReplaceString(true)
|
|
end)
|
|
end
|
|
|
|
findDialog:Connect(wx.wxID_ANY, wx.wxEVT_CLOSE_WINDOW,
|
|
function (event)
|
|
TransferDataFromWindow()
|
|
event:Skip()
|
|
findDialog:Show(false)
|
|
findDialog:Destroy()
|
|
end)
|
|
|
|
return findDialog
|
|
end
|
|
|
|
function findReplace:Show(replace)
|
|
self.dialog = nil
|
|
self.dialog = CreateFindReplaceDialog(replace)
|
|
self.dialog:Show(true)
|
|
end
|
|
|
|
frame:Connect(ID_FIND, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
findReplace:GetSelectedString()
|
|
findReplace:Show(false)
|
|
end)
|
|
frame:Connect(ID_FIND, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_REPLACE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
findReplace:GetSelectedString()
|
|
findReplace:Show(true)
|
|
end)
|
|
frame:Connect(ID_REPLACE, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_FINDNEXT, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) findReplace:FindString() end)
|
|
frame:Connect(ID_FINDNEXT, wx.wxEVT_UPDATE_UI, function (event) findReplace:HasText() end)
|
|
|
|
frame:Connect(ID_FINDPREV, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) findReplace:FindString(true) end)
|
|
frame:Connect(ID_FINDPREV, wx.wxEVT_UPDATE_UI, function (event) findReplace:HasText() end)
|
|
|
|
-------------------- Find replace end
|
|
|
|
frame:Connect(ID_GOTOLINE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local linecur = editor:LineFromPosition(editor:GetCurrentPos())
|
|
local linemax = editor:LineFromPosition(editor:GetLength()) + 1
|
|
local linenum = wx.wxGetNumberFromUser( "Enter line number",
|
|
"1 .. "..tostring(linemax),
|
|
"Goto Line",
|
|
linecur, 1, linemax,
|
|
frame)
|
|
if linenum > 0 then
|
|
editor:GotoLine(linenum-1)
|
|
end
|
|
end)
|
|
frame:Connect(ID_GOTOLINE, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
frame:Connect(ID_SORT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local buf = {}
|
|
for line in string.gmatch(editor:GetSelectedText()..'\n', "(.-)\r?\n") do
|
|
table.insert(buf, line)
|
|
end
|
|
if #buf > 0 then
|
|
table.sort(buf)
|
|
editor:ReplaceSelection(table.concat(buf,"\n"))
|
|
end
|
|
end)
|
|
frame:Connect(ID_SORT, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the Debug menu and attach the callback functions
|
|
|
|
debugMenu = wx.wxMenu{
|
|
{ ID_TOGGLEBREAKPOINT, "Toggle &Breakpoint\tF9", "Toggle Breakpoint" },
|
|
{ },
|
|
{ ID_COMPILE, "&Compile\tF7", "Test compile the wxLua program" },
|
|
{ ID_RUN, "&Run\tF6", "Execute the current file" },
|
|
{ ID_ATTACH_DEBUG, "&Attach\tShift-F6", "Allow a client to start a debugging session" },
|
|
{ ID_START_DEBUG, "&Start Debugging\tShift-F5", "Start a debugging session" },
|
|
{ ID_USECONSOLE, "Console", "Use console when running", wx.wxITEM_CHECK },
|
|
{ },
|
|
{ ID_STOP_DEBUG, "S&top Debugging\tShift-F12", "Stop and end the debugging session" },
|
|
{ ID_STEP, "St&ep\tF11", "Step into the next line" },
|
|
{ ID_STEP_OVER, "Step &Over\tShift-F11", "Step over the next line" },
|
|
{ ID_STEP_OUT, "Step O&ut\tF8", "Step out of the current function" },
|
|
{ ID_CONTINUE, "Co&ntinue\tF5", "Run the program at full speed" },
|
|
{ ID_BREAK, "&Break\tF12", "Stop execution of the program at the next executed line of code" },
|
|
{ },
|
|
{ ID_VIEWCALLSTACK, "V&iew Call Stack", "View the LUA call stack" },
|
|
{ ID_VIEWWATCHWINDOW, "View &Watches", "View the Watch window" },
|
|
{ },
|
|
{ ID_SHOWHIDEWINDOW, "View &Output Window\tF8", "View or Hide the output window" },
|
|
{ ID_CLEAROUTPUT, "C&lear Output Window", "Clear the output window before compiling or debugging", wx.wxITEM_CHECK },
|
|
--{ }, { ID_DEBUGGER_PORT, "Set debugger socket port...", "Chose what port to use for debugger sockets." }
|
|
}
|
|
menuBar:Append(debugMenu, "&Debug")
|
|
|
|
menuBar:Check(ID_USECONSOLE, true)
|
|
|
|
function SetAllEditorsReadOnly(enable)
|
|
for id, document in pairs(openDocuments) do
|
|
local editor = document.editor
|
|
editor:SetReadOnly(enable)
|
|
end
|
|
end
|
|
|
|
function MakeDebugFileName(editor, filePath)
|
|
if not filePath then
|
|
filePath = "file"..tostring(editor)
|
|
end
|
|
return filePath
|
|
end
|
|
|
|
function ToggleDebugMarker(editor, line)
|
|
local markers = editor:MarkerGet(line)
|
|
if markers >= CURRENT_LINE_MARKER_VALUE then
|
|
markers = markers - CURRENT_LINE_MARKER_VALUE
|
|
end
|
|
local id = editor:GetId()
|
|
local filePath = MakeDebugFileName(editor, openDocuments[id].filePath)
|
|
if markers >= BREAKPOINT_MARKER_VALUE then
|
|
editor:MarkerDelete(line, BREAKPOINT_MARKER)
|
|
if debuggerServer then
|
|
debuggerServer:RemoveBreakPoint(filePath, line)
|
|
end
|
|
else
|
|
editor:MarkerAdd(line, BREAKPOINT_MARKER)
|
|
if debuggerServer then
|
|
debuggerServer:AddBreakPoint(filePath, line)
|
|
end
|
|
end
|
|
end
|
|
|
|
function ClearAllCurrentLineMarkers()
|
|
for id, document in pairs(openDocuments) do
|
|
local editor = document.editor
|
|
editor:MarkerDeleteAll(CURRENT_LINE_MARKER)
|
|
end
|
|
end
|
|
|
|
function DisplayOutput(message, dont_add_marker)
|
|
if splitter:IsSplit() == false then
|
|
local w, h = frame:GetClientSizeWH()
|
|
splitter:SplitHorizontally(notebook, errorLog, (2 * h) / 3)
|
|
end
|
|
if not dont_add_marker then
|
|
errorLog:MarkerAdd(errorLog:GetLineCount()-1, CURRENT_LINE_MARKER)
|
|
end
|
|
errorLog:SetReadOnly(false)
|
|
errorLog:AppendText(message)
|
|
errorLog:SetReadOnly(true)
|
|
errorLog:GotoPos(errorLog:GetLength())
|
|
end
|
|
|
|
frame:Connect(ID_TOGGLEBREAKPOINT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
local line = editor:LineFromPosition(editor:GetCurrentPos())
|
|
ToggleDebugMarker(editor, line)
|
|
end)
|
|
frame:Connect(ID_TOGGLEBREAKPOINT, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
function CompileProgram(editor)
|
|
local editorText = editor:GetText()
|
|
local id = editor:GetId()
|
|
local filePath = MakeDebugFileName(editor, openDocuments[id].filePath)
|
|
local ret, errMsg, line_num = wxlua.CompileLuaScript(editorText, filePath)
|
|
if menuBar:IsChecked(ID_CLEAROUTPUT) then
|
|
ClearOutput()
|
|
end
|
|
|
|
if line_num > -1 then
|
|
DisplayOutput("Compilation error on line number :"..tostring(line_num).."\n"..errMsg.."\n\n")
|
|
editor:GotoLine(line_num-1)
|
|
else
|
|
DisplayOutput("Compilation successful!\n\n")
|
|
end
|
|
|
|
return line_num == -1 -- return true if it compiled ok
|
|
end
|
|
|
|
frame:Connect(ID_COMPILE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
CompileProgram(editor)
|
|
end)
|
|
frame:Connect(ID_COMPILE, wx.wxEVT_UPDATE_UI, OnUpdateUIEditMenu)
|
|
|
|
function SaveIfModified(editor)
|
|
local id = editor:GetId()
|
|
if openDocuments[id].isModified then
|
|
local saved = false
|
|
if not openDocuments[id].filePath then
|
|
local ret = wx.wxMessageBox("You must save the program before running it.\nPress cancel to abort running.",
|
|
"Save file?", wx.wxOK + wx.wxCANCEL + wx.wxCENTRE, frame)
|
|
if ret == wx.wxOK then
|
|
saved = SaveFileAs(editor)
|
|
end
|
|
else
|
|
saved = SaveFile(editor, openDocuments[id].filePath)
|
|
end
|
|
|
|
if saved then
|
|
openDocuments[id].isModified = false
|
|
else
|
|
return false -- not saved
|
|
end
|
|
end
|
|
|
|
return true -- saved
|
|
end
|
|
|
|
frame:Connect(ID_RUN, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
-- FIXME - I don't understand why you would would want to run *all* the notebook pages?
|
|
--[[
|
|
local fileList = {}
|
|
SaveAll()
|
|
for id, document in pairs(openDocuments) do
|
|
local filePath = document.filePath
|
|
if filePath == nil then
|
|
return
|
|
end
|
|
table.insert(fileList, ' "'..filePath..'"')
|
|
end
|
|
local cmd = '"'..programName..'" '..table.concat(fileList)
|
|
]]
|
|
local editor = GetEditor();
|
|
-- test compile it before we run it, if successful then ask to save
|
|
if not CompileProgram(editor) then
|
|
return
|
|
end
|
|
if not SaveIfModified(editor) then
|
|
return
|
|
end
|
|
|
|
local id = editor:GetId();
|
|
local console = iff(menuBar:IsChecked(ID_USECONSOLE), " -c ", "")
|
|
local cmd = '"'..programName..'" '..console..openDocuments[id].filePath
|
|
|
|
DisplayOutput("Running program: "..cmd.."\n")
|
|
local pid = wx.wxExecute(cmd, wx.wxEXEC_ASYNC)
|
|
|
|
if pid == -1 then
|
|
DisplayOutput("Unknown ERROR Running program!\n", true)
|
|
else
|
|
DisplayOutput("Process id is: "..tostring(pid).."\n", true)
|
|
end
|
|
end)
|
|
frame:Connect(ID_RUN, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer == nil) and (editor ~= nil))
|
|
end)
|
|
|
|
frame:Connect(ID_ATTACH_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local ok = false
|
|
debuggerServer = wxlua.wxLuaDebuggerServer(debuggerPortNumber)
|
|
if debuggerServer then
|
|
ok = debuggerServer:StartServer()
|
|
end
|
|
if ok then
|
|
DisplayOutput("Waiting for client connect. Start client with wxLua -d"..wx.wxGetHostName()..":"..debuggerPortNumber.."\n")
|
|
else
|
|
DisplayOutput("Unable to create debugger server.\n")
|
|
end
|
|
NextDebuggerPort()
|
|
end)
|
|
frame:Connect(ID_ATTACH_DEBUG, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer == nil) and (editor ~= nil))
|
|
end)
|
|
|
|
function NextDebuggerPort()
|
|
-- limit the number if ports we use, for people who need to open
|
|
-- their firewall
|
|
debuggerPortNumber = debuggerPortNumber + 1
|
|
if (debuggerPortNumber > 1559) then
|
|
debuggerPortNumber = 1551
|
|
end
|
|
end
|
|
|
|
function CreateDebuggerServer()
|
|
if (debuggerServer) then
|
|
-- we just delete it here, but this shouldn't happen
|
|
debugger_destroy = 0
|
|
local ds = debuggerServer
|
|
debuggerServer = nil
|
|
ds:Reset()
|
|
ds:StopServer()
|
|
ds:delete()
|
|
end
|
|
|
|
debuggee_running = false
|
|
debuggerServer = wxlua.wxLuaDebuggerServer(debuggerPortNumber)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_DEBUGGEE_CONNECTED,
|
|
function (event)
|
|
local ok = false
|
|
-- FIXME why would you want to run all the notebook pages?
|
|
--for id, document in pairs(openDocuments) do
|
|
local editor = GetEditor() -- MUST use document.editor userdata!
|
|
local document = openDocuments[editor:GetId()]
|
|
local editor = document.editor
|
|
local editorText = editor:GetText()
|
|
local filePath = MakeDebugFileName(editor, document.filePath)
|
|
ok = debuggerServer:Run(filePath, editorText)
|
|
|
|
local nextLine = editor:MarkerNext(0, BREAKPOINT_MARKER_VALUE)
|
|
while ok and (nextLine ~= -1) do
|
|
ok = debuggerServer:AddBreakPoint(filePath, nextLine)
|
|
nextLine = editor:MarkerNext(nextLine + 1, BREAKPOINT_MARKER_VALUE)
|
|
end
|
|
--end
|
|
|
|
if ok then
|
|
ok = debuggerServer:Step()
|
|
end
|
|
debuggee_running = ok
|
|
|
|
UpdateUIMenuItems()
|
|
|
|
if ok then
|
|
DisplayOutput("Client connected ok.\n")
|
|
else
|
|
DisplayOutput("Error connecting to client.\n")
|
|
end
|
|
end)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_DEBUGGEE_DISCONNECTED,
|
|
function (event)
|
|
DisplayOutput("Debug server disconnected.\n")
|
|
DisplayOutput(event:GetMessage().."\n\n")
|
|
DestroyDebuggerServer()
|
|
end)
|
|
|
|
local function DebuggerIgnoreFile(fileName)
|
|
local ignoreFlag = false
|
|
for idx, ignoreFile in pairs(ignoredFilesList) do
|
|
if string.upper(ignoreFile) == string.upper(fileName) then
|
|
ignoreFlag = true
|
|
end
|
|
end
|
|
return ignoreFlag
|
|
end
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_BREAK,
|
|
function (event)
|
|
if exitingProgram then return end
|
|
local line = event:GetLineNumber()
|
|
local eventFileName = event:GetFileName()
|
|
|
|
if string.sub(eventFileName, 1, 1) == '@' then -- FIXME what is this?
|
|
eventFileName = string.sub(eventFileName, 2, -1)
|
|
if wx.wxIsAbsolutePath(eventFileName) == false then
|
|
eventFileName = wx.wxGetCwd().."/"..eventFileName
|
|
end
|
|
end
|
|
if wx.__WXMSW__ then
|
|
eventFileName = wx.wxUnix2DosFilename(eventFileName)
|
|
end
|
|
local fileFound = false
|
|
DisplayOutput("At Breakpoint line: "..tostring(line).." file: "..eventFileName.."\n")
|
|
for id, document in pairs(openDocuments) do
|
|
local editor = document.editor
|
|
local filePath = MakeDebugFileName(editor, document.filePath)
|
|
-- for running in cygwin, use same type of separators
|
|
filePath = string.gsub(filePath, "\\", "/")
|
|
local eventFileName_ = string.gsub(eventFileName, "\\", "/")
|
|
if string.upper(filePath) == string.upper(eventFileName_) then
|
|
local selection = document.index
|
|
notebook:SetSelection(selection)
|
|
SetEditorSelection(selection)
|
|
editor:MarkerAdd(line, CURRENT_LINE_MARKER)
|
|
editor:EnsureVisibleEnforcePolicy(line)
|
|
fileFound = true
|
|
break
|
|
end
|
|
end
|
|
-- if don't ignore file and its not in the notebook, ask to load
|
|
if not DebuggerIgnoreFile(eventFileName) then
|
|
if not fileFound then
|
|
local fileDialog = wx.wxFileDialog(frame,
|
|
"Select file for debugging",
|
|
"",
|
|
eventFileName,
|
|
"Lua files (*.lua)|*.lua|Text files (*.txt)|*.txt|All files (*)|*",
|
|
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
|
|
if fileDialog:ShowModal() == wx.wxID_OK then
|
|
local editor = LoadFile(fileDialog:GetPath(), nil, true)
|
|
if editor then
|
|
editor:MarkerAdd(line, CURRENT_LINE_MARKER)
|
|
editor:EnsureVisibleEnforcePolicy(line)
|
|
editor:SetReadOnly(true)
|
|
fileFound = true
|
|
end
|
|
end
|
|
fileDialog:Destroy()
|
|
end
|
|
if not fileFound then -- they canceled opening the file
|
|
table.insert(ignoredFilesList, eventFileName)
|
|
end
|
|
end
|
|
|
|
if fileFound then
|
|
debuggee_running = false
|
|
ProcessWatches()
|
|
elseif debuggerServer then
|
|
debuggerServer:Continue()
|
|
debuggee_running = true
|
|
end
|
|
end)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_PRINT,
|
|
function (event)
|
|
DisplayOutput(event:GetMessage().."\n")
|
|
end)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_ERROR,
|
|
function (event)
|
|
DisplayOutput("wxLua ERROR: "..event:GetMessage().."\n\n")
|
|
end)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_EXIT,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
DestroyDebuggerServer()
|
|
end
|
|
SetAllEditorsReadOnly(false)
|
|
ignoredFilesList = {}
|
|
end)
|
|
|
|
debuggerServer:Connect(wxlua.wxEVT_WXLUA_DEBUGGER_EVALUATE_EXPR,
|
|
function (event)
|
|
if watchListCtrl then
|
|
watchListCtrl:SetItem(event:GetReference(),
|
|
1,
|
|
event:GetMessage())
|
|
end
|
|
end)
|
|
|
|
local ok = debuggerServer:StartServer()
|
|
if not ok then
|
|
DestroyDebuggerServer()
|
|
DisplayOutput("Error starting the debug server.\n")
|
|
return nil
|
|
end
|
|
|
|
return debuggerServer
|
|
end
|
|
|
|
function DestroyDebuggerServer()
|
|
-- nil debuggerServer so it won't be used and set flag to destroy it in idle
|
|
if (debuggerServer) then
|
|
debuggerServer_ = debuggerServer
|
|
debuggerServer = nil
|
|
debugger_destroy = 1 -- set > 0 to initiate deletion in idle
|
|
end
|
|
end
|
|
|
|
frame:Connect(wx.wxEVT_IDLE,
|
|
function(event)
|
|
|
|
if (debugger_destroy > 0) then
|
|
debugger_destroy = debugger_destroy + 1
|
|
end
|
|
|
|
if (debugger_destroy == 5) then
|
|
-- stop the server and let it end gracefully
|
|
debuggee_running = false
|
|
debuggerServer_:StopServer()
|
|
end
|
|
if (debugger_destroy == 10) then
|
|
-- delete the server and let it die gracefully
|
|
debuggee_running = false
|
|
debuggerServer_:delete()
|
|
end
|
|
if (debugger_destroy > 15) then
|
|
-- finally, kill the debugee process if it still exists
|
|
debugger_destroy = 0;
|
|
local ds = debuggerServer_
|
|
debuggerServer_ = nil
|
|
|
|
if (debuggee_pid > 0) then
|
|
if wx.wxProcess.Exists(debuggee_pid) then
|
|
local ret = wx.wxProcess.Kill(debuggee_pid, wx.wxSIGKILL, wx.wxKILL_CHILDREN)
|
|
if (ret ~= wx.wxKILL_OK) then
|
|
DisplayOutput("Unable to kill debuggee process "..debuggee_pid..", code "..tostring(ret)..".\n")
|
|
else
|
|
DisplayOutput("Killed debuggee process "..debuggee_pid..".\n")
|
|
end
|
|
end
|
|
debuggee_pid = 0
|
|
end
|
|
end
|
|
event:Skip()
|
|
end)
|
|
|
|
frame:Connect(ID_START_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
-- test compile it before we run it
|
|
if not CompileProgram(editor) then
|
|
return
|
|
end
|
|
|
|
debuggee_pid = 0
|
|
debuggerServer = CreateDebuggerServer()
|
|
if debuggerServer then
|
|
debuggee_pid = debuggerServer:StartClient()
|
|
end
|
|
|
|
if debuggerServer and (debuggee_pid > 0) then
|
|
SetAllEditorsReadOnly(true)
|
|
DisplayOutput("Waiting for client connection, process "..tostring(debuggee_pid)..".\n")
|
|
else
|
|
DisplayOutput("Unable to start debuggee process.\n")
|
|
if debuggerServer then
|
|
DestroyDebuggerServer()
|
|
end
|
|
end
|
|
|
|
NextDebuggerPort()
|
|
end)
|
|
frame:Connect(ID_START_DEBUG, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer == nil) and (editor ~= nil))
|
|
end)
|
|
|
|
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
debuggerServer:Reset();
|
|
--DestroyDebuggerServer()
|
|
end
|
|
SetAllEditorsReadOnly(false)
|
|
ignoredFilesList = {}
|
|
debuggee_running = false
|
|
DisplayOutput("\nDebuggee client stopped.\n\n")
|
|
end)
|
|
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer ~= nil) and (editor ~= nil))
|
|
end)
|
|
|
|
frame:Connect(ID_STEP, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
debuggerServer:Step()
|
|
debuggee_running = true
|
|
end
|
|
end)
|
|
frame:Connect(ID_STEP, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running) and (editor ~= nil))
|
|
end)
|
|
|
|
frame:Connect(ID_STEP_OVER, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
debuggerServer:StepOver()
|
|
debuggee_running = true
|
|
end
|
|
end)
|
|
frame:Connect(ID_STEP_OVER, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
local editor = GetEditor()
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running) and (editor ~= nil))
|
|
end)
|
|
|
|
frame:Connect(ID_STEP_OUT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
debuggerServer:StepOut()
|
|
debuggee_running = true
|
|
end
|
|
end)
|
|
frame:Connect(ID_STEP_OUT, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running))
|
|
end)
|
|
|
|
frame:Connect(ID_CONTINUE, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
ClearAllCurrentLineMarkers()
|
|
|
|
if debuggerServer then
|
|
debuggerServer:Continue()
|
|
debuggee_running = true
|
|
end
|
|
end)
|
|
frame:Connect(ID_CONTINUE, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running))
|
|
end)
|
|
|
|
frame:Connect(ID_BREAK, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
if debuggerServer then
|
|
debuggerServer:Break()
|
|
end
|
|
end)
|
|
frame:Connect(ID_BREAK, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((debuggerServer ~= nil) and debuggee_running)
|
|
end)
|
|
|
|
frame:Connect(ID_VIEWCALLSTACK, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
if debuggerServer then
|
|
debuggerServer:DisplayStackDialog(frame)
|
|
end
|
|
end)
|
|
frame:Connect(ID_VIEWCALLSTACK, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running))
|
|
end)
|
|
|
|
frame:Connect(ID_VIEWWATCHWINDOW, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
if not watchWindow then
|
|
CreateWatchWindow()
|
|
end
|
|
end)
|
|
frame:Connect(ID_VIEWWATCHWINDOW, wx.wxEVT_UPDATE_UI,
|
|
function (event)
|
|
event:Enable((debuggerServer ~= nil) and (not debuggee_running))
|
|
end)
|
|
|
|
frame:Connect(ID_SHOWHIDEWINDOW, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function (event)
|
|
if splitter:IsSplit() then
|
|
splitter:Unsplit()
|
|
else
|
|
local w, h = frame:GetClientSizeWH()
|
|
splitter:SplitHorizontally(notebook, errorLog, (2 * h) / 3)
|
|
end
|
|
end)
|
|
|
|
function ClearOutput(event)
|
|
errorLog:SetReadOnly(false)
|
|
errorLog:ClearAll()
|
|
errorLog:SetReadOnly(true)
|
|
end
|
|
|
|
frame:Connect(ID_DEBUGGER_PORT, wx.wxEVT_COMMAND_MENU_SELECTED,
|
|
function(event)
|
|
end)
|
|
frame:Connect(ID_DEBUGGER_PORT, wx.wxEVT_UPDATE_UI,
|
|
function(event)
|
|
event:Enable(debuggerServer == nil)
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Create the Help menu and attach the callback functions
|
|
|
|
helpMenu = wx.wxMenu{
|
|
{ ID_ABOUT, "&About\tF1", "About wxLua IDE" }}
|
|
menuBar:Append(helpMenu, "&Help")
|
|
|
|
function DisplayAbout(event)
|
|
local page = [[
|
|
<html>
|
|
<body bgcolor = "#FFFFFF">
|
|
<table cellspacing = 4 cellpadding = 4 width = "100%">
|
|
<tr>
|
|
<td bgcolor = "#202020">
|
|
<center>
|
|
<font size = +2 color = "#FFFFFF"><br><b>]]..
|
|
wxlua.wxLUA_VERSION_STRING..[[</b></font><br>
|
|
<font size = +1 color = "#FFFFFF">built with</font><br>
|
|
<font size = +2 color = "#FFFFFF"><b>]]..
|
|
wx.wxVERSION_STRING..[[</b></font>
|
|
</center>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td bgcolor = "#DCDCDC">
|
|
<b>Copyright (C) 2002-2005 Lomtick Software</b>
|
|
<p>
|
|
<font size=-1>
|
|
<table cellpadding = 0 cellspacing = 0 width = "100%">
|
|
<tr>
|
|
<td width = "65%">
|
|
J. Winwood (luascript@thersgb.net)<br>
|
|
John Labenski<p>
|
|
</td>
|
|
<td valign = top>
|
|
<img src = "memory:wxLua">
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<font size = 1>
|
|
Licenced under wxWindows Library Licence, Version 3.
|
|
</font>
|
|
</font>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
]]
|
|
|
|
local dlg = wx.wxDialog(frame, wx.wxID_ANY, "About wxLua IDE")
|
|
|
|
local html = wx.wxLuaHtmlWindow(dlg, wx.wxID_ANY,
|
|
wx.wxDefaultPosition, wx.wxSize(360, 150),
|
|
wx.wxHW_SCROLLBAR_NEVER)
|
|
local line = wx.wxStaticLine(dlg, wx.wxID_ANY)
|
|
local button = wx.wxButton(dlg, wx.wxID_OK, "OK")
|
|
|
|
button:SetDefault()
|
|
|
|
html:SetBorders(0)
|
|
html:SetPage(page)
|
|
html:SetSize(html:GetInternalRepresentation():GetWidth(),
|
|
html:GetInternalRepresentation():GetHeight())
|
|
|
|
local topsizer = wx.wxBoxSizer(wx.wxVERTICAL)
|
|
topsizer:Add(html, 1, wx.wxALL, 10)
|
|
topsizer:Add(line, 0, wx.wxEXPAND + wx.wxLEFT + wx.wxRIGHT, 10)
|
|
topsizer:Add(button, 0, wx.wxALL + wx.wxALIGN_RIGHT, 10)
|
|
|
|
dlg:SetAutoLayout(true)
|
|
dlg:SetSizer(topsizer)
|
|
topsizer:Fit(dlg)
|
|
|
|
dlg:ShowModal()
|
|
dlg:Destroy()
|
|
end
|
|
|
|
frame:Connect(ID_ABOUT, wx.wxEVT_COMMAND_MENU_SELECTED, DisplayAbout)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Attach the handler for closing the frame
|
|
|
|
function CloseWindow(event)
|
|
exitingProgram = true -- don't handle focus events
|
|
|
|
if not SaveOnExit(event:CanVeto()) then
|
|
event:Veto()
|
|
exitingProgram = false
|
|
return
|
|
end
|
|
|
|
if debuggerServer then
|
|
local ds = debuggerServer
|
|
debuggerServer = nil
|
|
--ds:Reset()
|
|
ds:KillDebuggee()
|
|
ds:delete()
|
|
end
|
|
debuggee_running = false
|
|
|
|
ConfigSaveFramePosition(frame, "MainFrame")
|
|
config:delete() -- always delete the config
|
|
event:Skip()
|
|
CloseWatchWindow()
|
|
end
|
|
frame:Connect(wx.wxEVT_CLOSE_WINDOW, CloseWindow)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Finish creating the frame and show it
|
|
|
|
frame:SetMenuBar(menuBar)
|
|
ConfigRestoreFramePosition(frame, "MainFrame")
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Load the args that this script is run with
|
|
|
|
--for k, v in pairs(arg) do print(k, v) end
|
|
|
|
if arg then
|
|
-- arguments pushed into wxLua are
|
|
-- [C++ app and it's args][lua prog at 0][args for lua start at 1]
|
|
local n = 1
|
|
while arg[n-1] do
|
|
n = n - 1
|
|
if arg[n] and not arg[n-1] then programName = arg[n] end
|
|
end
|
|
|
|
for index = 1, #arg do
|
|
fileName = arg[index]
|
|
if fileName ~= "--" then
|
|
LoadFile(fileName, nil, true)
|
|
end
|
|
end
|
|
|
|
if notebook:GetPageCount() > 0 then
|
|
notebook:SetSelection(0)
|
|
else
|
|
local editor = CreateEditor("untitled.lua")
|
|
SetupKeywords(editor, true)
|
|
end
|
|
else
|
|
local editor = CreateEditor("untitled.lua")
|
|
SetupKeywords(editor, true)
|
|
end
|
|
|
|
--frame:SetIcon(wxLuaEditorIcon) --FIXME add this back
|
|
frame:Show(true)
|
|
|
|
-- Call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
|
|
-- otherwise the wxLua program will exit immediately.
|
|
-- Does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit since the
|
|
-- MainLoop is already running or will be started by the C++ program.
|
|
wx.wxGetApp():MainLoop()
|