Files
OpenRA/src/editor/editor.lua

961 lines
34 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- authors: Lomtik Software (J. Winwood & John Labenski)
-- Luxinia Dev (Eike Decker & Christoph Kubisch)
---------------------------------------------------------
local wxkeywords = nil -- a string of the keywords for scintilla of wxLua's wx.XXX items
local editorID = 100 -- window id to create editor pages with, incremented for new editors
local openDocuments = ide.openDocuments
local statusBar = ide.frame.statusBar
local notebook = ide.frame.notebook
local funclist = ide.frame.toolBar.funclist
local edcfg = ide.config.editor
local projcombobox = ide.frame.projpanel.projcombobox
-- ----------------------------------------------------------------------------
-- Update the statusbar text of the frame using the given editor.
-- Only update if the text has changed.
local statusTextTable = { "OVR?", "R/O?", "Cursor Pos" }
-- set funclist font to be the same as the combobox in the project dropdown
funclist:SetFont(ide.font.fNormal)
local function updateStatusText(editor)
local texts = { "", "", "" }
if ide.frame and editor then
local pos = editor:GetCurrentPos()
local line = editor:LineFromPosition(pos)
local col = 1 + pos - editor:PositionFromLine(line)
texts = {
iff(editor:GetOvertype(), TR("OVR"), TR("INS")),
iff(editor:GetReadOnly(), TR("R/O"), TR("R/W")),
TR("Ln: %d"):format(line + 1).." "..TR("Col: %d"):format(col) }
end
if ide.frame then
for n = 1, 3 do
if (texts[n] ~= statusTextTable[n]) then
statusBar:SetStatusText(texts[n], n+1)
statusTextTable[n] = texts[n]
end
end
end
end
local function updateBraceMatch(editor)
local pos = editor:GetCurrentPos()
local posp = pos > 0 and pos-1
local char = editor:GetCharAt(pos)
local charp = posp and editor:GetCharAt(posp)
local match = { [string.byte("<")] = true,
[string.byte(">")] = true,
[string.byte("(")] = true,
[string.byte(")")] = true,
[string.byte("{")] = true,
[string.byte("}")] = true,
[string.byte("[")] = true,
[string.byte("]")] = true,
}
pos = (match[char] and pos) or (charp and match[charp] and posp)
if (pos) then
-- don't match brackets in markup comments
local style = bit.band(editor:GetStyleAt(pos), 31)
if MarkupIsSpecial and MarkupIsSpecial(style)
or editor.spec.iscomment[style] then return end
local pos2 = editor:BraceMatch(pos)
if (pos2 == wxstc.wxSTC_INVALID_POSITION) then
editor:BraceBadLight(pos)
else
editor:BraceHighlight(pos,pos2)
end
editor.matchon = true
elseif(editor.matchon) then
editor:BraceBadLight(wxstc.wxSTC_INVALID_POSITION)
editor:BraceHighlight(wxstc.wxSTC_INVALID_POSITION,-1)
editor.matchon = false
end
end
local function getFileTitle (editor)
if not editor or not openDocuments[editor:GetId()] then return GetIDEString("editor") end
local id = editor:GetId()
local filePath = openDocuments[id].filePath
local fileName = openDocuments[id].fileName
if not filePath or not fileName then return GetIDEString("editor") end
return GetIDEString("editor").." ["..filePath.."]"
end
-- Check if file is altered, show dialog to reload it
local 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(
TR("File '%s' no longer exists."):format(fileName),
GetIDEString("editormessage"),
wx.wxOK + wx.wxCENTRE, ide.frame)
elseif not editor:GetReadOnly() and modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
local ret = wx.wxMessageBox(
TR("File '%s' has been modified on disk."):format(fileName)
.."\n"..TR("Do you want to reload it?"),
GetIDEString("editormessage"),
wx.wxYES_NO + wx.wxCENTRE, ide.frame)
if ret ~= wx.wxYES or LoadFile(filePath, editor, true) then
openDocuments[id].modTime = GetFileModTime(filePath)
end
end
end
end
end
-- ----------------------------------------------------------------------------
-- Get/Set notebook editor page, use nil for current page, returns nil if none
function GetEditor(selection)
if selection == nil then
selection = notebook:GetSelection()
end
local editor
if (selection >= 0) and (selection < notebook:GetPageCount())
and (notebook:GetPage(selection):GetClassInfo():GetClassName()=="wxStyledTextCtrl") 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)
updateStatusText(editor) -- update even if nil
statusBar:SetStatusText("",1)
ide.frame:SetTitle(getFileTitle(editor))
if editor then
if funclist:IsEmpty() then funclist:Append(TR("Jump to a function definition..."), 0) end
funclist:SetSelection(0)
editor:SetFocus()
editor:SetSTCFocus(true)
local id = editor:GetId()
FileTreeMarkSelected(openDocuments[id] and openDocuments[id].filePath or '')
else
FileTreeMarkSelected('')
end
SetAutoRecoveryMark()
end
function GetEditorFileAndCurInfo(nochecksave)
local editor = GetEditor()
if (not (editor and (nochecksave or SaveIfModified(editor)))) then
return
end
local id = editor:GetId()
local filepath = openDocuments[id].filePath
if not filepath then return end
local fn = wx.wxFileName(filepath)
fn:Normalize()
local info = {}
info.pos = editor:GetCurrentPos()
info.line = editor:GetCurrentLine()
info.sel = editor:GetSelectedText()
info.sel = info.sel and info.sel:len() > 0 and info.sel or nil
info.selword = info.sel and info.sel:match("([^a-zA-Z_0-9]+)") or info.sel
return fn,info
end
-- Set if the document is modified and update the notebook page text
function SetDocumentModified(id, modified)
if not openDocuments[id] then return end
local pageText = openDocuments[id].fileName or ide.config.default.fullname
if modified then
pageText = "* "..pageText
end
openDocuments[id].isModified = modified
notebook:SetPageText(openDocuments[id].index, pageText)
end
function EditorAutoComplete(editor)
if not (editor and editor.spec) then return end
local pos = editor:GetCurrentPos()
-- don't do auto-complete in comments or strings.
-- the current position and the previous one have default style (0),
-- so we need to check two positions back.
local style = pos >= 2 and bit.band(editor:GetStyleAt(pos-2),31) or 0
if editor.spec.iscomment[style] or editor.spec.isstring[style] then return end
-- retrieve the current line and get a string to the current cursor position in the line
local line = editor:GetCurrentLine()
local linetx = editor:GetLine(line)
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
local lt = linetx:sub(1,localpos)
lt = lt:gsub("%s*(["..editor.spec.sep.."])%s*", "%1")
-- strip closed brace scopes
lt = lt:gsub("%b()","")
lt = lt:gsub("%b[]","")
lt = lt:gsub("%b{}","")
-- match from starting brace
lt = lt:match("[^%[%(%{%s]*$")
-- know now which string is to be completed
local userList = CreateAutoCompList(editor,lt)
-- don't show the list if the only option is what's already typed
if userList and #userList > 0 and userList ~= lt then
editor:UserListShow(1, userList)
elseif editor:AutoCompActive() then
editor:AutoCompCancel()
end
end
local function getValAtPosition(editor, pos)
local line = editor:LineFromPosition(pos)
local linetx = editor:GetLine(line)
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
local ident = "([a-zA-Z_][a-zA-Z_0-9%.%:]*)"
local linetxtopos = linetx:sub(1,localpos)
linetxtopos = linetxtopos..")"
linetxtopos = linetxtopos:match(ident .. "%b()$")
local selected = editor:GetSelectionStart() ~= editor:GetSelectionEnd()
and pos >= editor:GetSelectionStart() and pos <= editor:GetSelectionEnd()
-- check if we have a selected text or an identifier
-- for an identifier, check fragments on the left and on the right.
-- this is to match 'io' in 'i^o.print' and 'io.print' in 'io.pr^int'.
-- remove square brackets to make tbl[index].x show proper values.
local start = linetx:sub(1,localpos)
:gsub("%b[]", function(s) return ("."):rep(#s) end)
:find(ident.."$")
-- check if the style is the right one; this is to ignore
-- comments, strings, numbers (to avoid '1 = 1'), keywords, and such
if start and not selected then
local style = bit.band(editor:GetStyleAt(linestart+start),31)
if editor.spec.iscomment[style]
or (MarkupIsAny and MarkupIsAny(style)) -- markup in comments
or editor.spec.isstring[style]
or style == wxstc.wxSTC_LUA_NUMBER
or style == wxstc.wxSTC_LUA_WORD then
-- don't do anything for strings or comments or numbers
return nil, linetxtopos
end
end
local right = linetx:sub(localpos+1,#linetx):match("^[a-zA-Z_0-9]*")
local var = selected and editor:GetSelectedText()
or (start and linetx:sub(start,localpos):gsub(":",".")..right or nil)
return var, linetxtopos
end
function EditorCallTip(editor, pos, x, y)
-- don't show anything if the calltip is active; this may happen after
-- typing function name, while the mouse is over a different function.
if editor:CallTipActive() then return end
local var, linetxtopos = getValAtPosition(editor, pos)
local tip = linetxtopos and GetTipInfo(editor,linetxtopos.."(",false)
if ide.debugger and ide.debugger.server then
if var then
local limit = 128
ide.debugger.quickeval(var, function(val)
if #val > limit then val = val:sub(1, limit-3).."..." end
-- check if the mouse position is specified and the mouse has moved,
-- then don't show the tooltip as it's already too late for it.
if x and y then
local mpos = wx.wxGetMousePosition()
if mpos.x ~= x or mpos.y ~= y then return end
end
editor:CallTipShow(pos, val) end)
end
elseif tip then
-- only shorten if shown on mouse-over. Use shortcut to get full info.
local shortento = 450
local showtooltip = ide.frame.menuBar:FindItem(ID_SHOWTOOLTIP)
local suffix = "...\n"
..TR("Use '%s' to see full description."):format(showtooltip:GetLabel())
if x and y and #tip > shortento then
tip = tip:sub(1, shortento-#suffix):gsub("%W*%w*$","")..suffix
end
editor:CallTipShow(pos, tip)
end
end
function EditorIsModified(editor)
local modified = false
if editor then
local id = editor:GetId()
modified = openDocuments[id]
and (openDocuments[id].isModified or not openDocuments[id].filePath)
end
return modified
end
-- ----------------------------------------------------------------------------
-- Create an editor
function CreateEditor()
local editor = wxstc.wxStyledTextCtrl(notebook, editorID,
wx.wxDefaultPosition, wx.wxSize(0, 0),
wx.wxBORDER_STATIC)
editorID = editorID + 1 -- increment so they're always unique
editor.matchon = false
editor.assignscache = false
editor:SetBufferedDraw(true)
editor:StyleClearAll()
editor:SetFont(ide.font.eNormal)
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.eNormal)
editor:SetTabWidth(ide.config.editor.tabwidth or 4)
editor:SetIndent(ide.config.editor.tabwidth or 4)
editor:SetUseTabs(ide.config.editor.usetabs and true or false)
editor:SetIndentationGuides(true)
editor:SetViewWhiteSpace(ide.config.editor.whitespace and true or false)
if (ide.config.editor.usewrap) then
editor:SetWrapMode(wxstc.wxSTC_WRAP_WORD)
editor:SetWrapStartIndent(0)
editor:SetWrapVisualFlagsLocation(wxstc.wxSTC_WRAPVISUALFLAGLOC_END_BY_TEXT)
end
if (ide.config.editor.defaulteol == wxstc.wxSTC_EOL_CRLF
or ide.config.editor.defaulteol == wxstc.wxSTC_EOL_LF) then
editor:SetEOLMode(ide.config.editor.defaulteol)
-- else: keep wxStyledTextCtrl default behavior (CRLF on Windows, LF on Unix)
end
editor:SetCaretLineVisible(ide.config.editor.caretline and 1 or 0)
editor:SetVisiblePolicy(wxstc.wxSTC_VISIBLE_STRICT, 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(StylesGetMarker("currentline"))
editor:MarkerDefine(StylesGetMarker("breakpoint"))
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)
-- allow multiple selection and multi-cursor editing if supported
if ide.wxver >= "2.9.5" then
editor:SetMultipleSelection(1)
editor:SetAdditionalCaretsBlink(1)
editor:SetAdditionalSelectionTyping(1)
end
do
local fg, bg = wx.wxWHITE, wx.wxColour(128, 128, 128)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEROPEN, wxstc.wxSTC_MARK_BOXMINUS, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDER, wxstc.wxSTC_MARK_BOXPLUS, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERSUB, wxstc.wxSTC_MARK_VLINE, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERTAIL, wxstc.wxSTC_MARK_LCORNER, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEREND, wxstc.wxSTC_MARK_BOXPLUSCONNECTED, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDEROPENMID, wxstc.wxSTC_MARK_BOXMINUSCONNECTED, fg, bg)
editor:MarkerDefine(wxstc.wxSTC_MARKNUM_FOLDERMIDTAIL, wxstc.wxSTC_MARK_TCORNER, fg, bg)
bg:delete()
end
if ide.config.editor.calltipdelay and ide.config.editor.calltipdelay > 0 then
editor:SetMouseDwellTime(ide.config.editor.calltipdelay)
end
editor:AutoCompSetIgnoreCase(ide.config.acandtip.ignorecase)
if (ide.config.acandtip.strategy > 0) then
editor:AutoCompSetAutoHide(0)
editor:AutoCompStops([[ \n\t=-+():.,;*/!"'$%&~'#°^@?´`<>][|}{]])
end
-- GotoPos should work by itself, but it doesn't (wx 2.9.5).
-- This is likely because the editor window hasn't been refreshed yet,
-- so its LinesOnScreen method returns 0, which skews the calculations.
-- To avoid this, the caret line is made visible at the first opportunity.
do
local redolater = false
function editor:GotoPosDelayed(pos)
local badtime = self:LinesOnScreen() <= 0 -- -1 on OSX, 0 on Windows
if pos then
self:GotoPos(pos)
redolater = badtime
elseif not badtime and redolater then
redolater = nil
-- reset the left margin first to make sure that the position
-- is set "from the left" to get the best content displayed.
self:SetXOffset(0)
self:GotoPos(self:GetCurrentPos())
end
end
end
editor.ev = {}
editor:Connect(wxstc.wxEVT_STC_MARGINCLICK,
function (event)
local line = editor:LineFromPosition(event:GetPosition())
local margin = event:GetMargin()
if margin == 1 then
DebuggerToggleBreakpoint(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_MODIFIED,
function (event)
if (editor.assignscache and editor:GetCurrentLine() ~= editor.assignscache.line) then
editor.assignscache = false
end
local evtype = event:GetModificationType()
if (bit.band(evtype,wxstc.wxSTC_MOD_INSERTTEXT) ~= 0) then
SetAutoRecoveryMark()
table.insert(editor.ev,{event:GetPosition(),event:GetLinesAdded()})
DynamicWordsAdd("post",editor,nil,editor:LineFromPosition(event:GetPosition()),event:GetLinesAdded())
end
if (bit.band(evtype,wxstc.wxSTC_MOD_DELETETEXT) ~= 0) then
SetAutoRecoveryMark()
table.insert(editor.ev,{event:GetPosition(),0})
DynamicWordsAdd("post",editor,nil,editor:LineFromPosition(event:GetPosition()),0)
end
if ide.config.acandtip.nodynwords then return end
-- only required to track changes
if (bit.band(evtype,wxstc.wxSTC_MOD_BEFOREDELETE) ~= 0) then
local numlines = 0
event:GetText():gsub("(\r?\n)",function() numlines = numlines + 1 end)
DynamicWordsRem("pre",editor,nil,editor:LineFromPosition(event:GetPosition()), numlines)
end
if (bit.band(evtype,wxstc.wxSTC_MOD_BEFOREINSERT) ~= 0) then
DynamicWordsRem("pre",editor,nil,editor:LineFromPosition(event:GetPosition()), 0)
end
end)
editor:Connect(wxstc.wxEVT_STC_CHARADDED,
function (event)
-- auto-indent
local ch = event:GetKey()
local eol = editor:GetEOLMode()
local pos = editor:GetCurrentPos()
local line = editor:GetCurrentLine()
local linetx = editor:GetLine(line)
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
local linetxtopos = linetx:sub(1,localpos)
if (ch == char_LF) then
if (line > 0) then
local indent = editor:GetLineIndentation(line - 1)
local linedone = editor:GetLine(line - 1)
-- if the indentation is 0 and the current line is not empty
-- then take indentation from the current line (instead of the
-- previous one). This may happen when CR is hit at the beginning
-- of a line (rather than at the end).
if indent == 0 and not linetx:match("^[\010\013]*$") then
indent = editor:GetLineIndentation(line)
end
local tw = editor:GetTabWidth()
local ut = editor:GetUseTabs()
if ide.config.editor.smartindent
and editor.spec.isdecindent and editor.spec.isincindent then
local closed, blockend = editor.spec.isdecindent(linedone)
local opened = editor.spec.isincindent(linedone)
-- if the current block is already indented, skip reverse indenting
if (line > 1) and (closed > 0 or blockend > 0)
and editor:GetLineIndentation(line-2) > indent then
-- adjust opened first; this is needed when use ENTER after })
if blockend == 0 then opened = opened + closed end
closed, blockend = 0, 0
end
editor:SetLineIndentation(line-1, indent - tw * closed)
indent = indent + tw * (opened - blockend)
if indent < 0 then indent = 0 end
end
editor:SetLineIndentation(line, indent)
indent = ut and (indent / tw) or indent
editor:GotoPos(editor:GetCurrentPos()+indent)
end
elseif ch == ("("):byte() then
local tip = GetTipInfo(editor,linetxtopos,ide.config.acandtip.shorttip)
if tip then
if editor:CallTipActive() then editor:CallTipCancel() end
editor:CallTipShow(pos,tip)
end
elseif ide.config.autocomplete then -- code completion prompt
local trigger = linetxtopos:match("["..editor.spec.sep.."%w_]+$")
if (trigger and (#trigger > 1 or trigger:match("["..editor.spec.sep.."]"))) then
ide.frame:AddPendingEvent(wx.wxCommandEvent(
wx.wxEVT_COMMAND_MENU_SELECTED, ID_AUTOCOMPLETE))
end
end
end)
editor:Connect(wxstc.wxEVT_STC_DWELLSTART,
function (event)
-- on Linux DWELLSTART event seems to be generated even for those
-- editor windows that are not active. What's worse, when generated
-- the event seems to report "old" position when retrieved using
-- event:GetX and event:GetY, so instead we use wxGetMousePosition.
local linux = ide.osname == 'Unix'
if linux and editor ~= GetEditor() then return end
-- check if this editor has focus; it may not when Stack/Watch window
-- is on top, but DWELL events are still triggered in this case.
-- Don't want to show calltip as it is still shown when the focus
-- is switched to a different application.
local focus = editor:FindFocus()
if focus and focus:GetId() ~= editor:GetId() then return end
-- event:GetX() and event:GetY() positions don't correspond to
-- the correct positions calculated using ScreenToClient (at least
-- on Windows and Linux), so use what's calculated.
local mpos = wx.wxGetMousePosition()
local cpos = editor:ScreenToClient(mpos)
local position = editor:PositionFromPointClose(cpos.x, cpos.y)
if position ~= wxstc.wxSTC_INVALID_POSITION then
EditorCallTip(editor, position, mpos.x, mpos.y)
end
event:Skip()
end)
editor:Connect(wxstc.wxEVT_STC_DWELLEND,
function (event)
if editor:CallTipActive() then editor:CallTipCancel() end
event:Skip()
end)
editor:Connect(wx.wxEVT_KILL_FOCUS,
function (event)
if editor:AutoCompActive() then editor:AutoCompCancel() end
event:Skip()
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 ()
SetDocumentModified(editor:GetId(), false)
end)
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTLEFT,
function ()
SetDocumentModified(editor:GetId(), true)
end)
-- "updateStatusText" should be called in UPDATEUI event, but it creates
-- several performance problems on Windows (using wx2.9.5+) when
-- brackets or backspace is used (very slow screen repaint with 0.5s delay).
-- Moving it to PAINTED event creates problems on OSX (using wx2.9.5+),
-- where refresh of R/W and R/O status in the status bar is delayed.
editor:Connect(wxstc.wxEVT_STC_PAINTED,
function ()
if ide.osname == 'Windows' then updateStatusText(editor) end
end)
editor:Connect(wxstc.wxEVT_STC_UPDATEUI,
function ()
if ide.osname ~= 'Windows' then updateStatusText(editor) end
editor:GotoPosDelayed()
updateBraceMatch(editor)
local minupdated
for _,iv in ipairs(editor.ev) do
local line = editor:LineFromPosition(iv[1])
if not minupdated or line < minupdated then minupdated = line end
IndicateFunctions(editor,line,line+iv[2])
end
local firstline = editor:DocLineFromVisible(editor:GetFirstVisibleLine())
local lastline = math.min(editor:GetLineCount(),
editor:DocLineFromVisible(editor:GetFirstVisibleLine() + editor:LinesOnScreen()))
MarkupStyle(editor,minupdated or firstline,lastline)
editor.ev = {}
end)
editor:Connect(wx.wxEVT_LEFT_DOWN,
function (event)
if MarkupHotspotClick then
local position = editor:PositionFromPointClose(event:GetX(),event:GetY())
if position ~= wxstc.wxSTC_INVALID_POSITION then
if MarkupHotspotClick(position, editor) then return end
end
end
event:Skip()
end)
local inhandler = false
editor:Connect(wx.wxEVT_SET_FOCUS,
function (event)
event:Skip()
if inhandler or ide.exitingProgram then return end
inhandler = true
isFileAlteredOnDisk(editor)
inhandler = false
end)
editor:Connect(wx.wxEVT_KEY_DOWN,
function (event)
local keycode = event:GetKeyCode()
local mod = event:GetModifiers()
local first, last = 0, notebook:GetPageCount()-1
if keycode == wx.WXK_ESCAPE and frame:IsFullScreen() then
ShowFullScreen(false)
-- Ctrl-Home and Ctrl-End don't work on OSX with 2.9.5+; fix it
elseif ide.osname == 'Macintosh' and ide.wxver >= "2.9.5"
and (mod == wx.wxMOD_RAW_CONTROL or mod == (wx.wxMOD_RAW_CONTROL + wx.wxMOD_SHIFT))
and (keycode == wx.WXK_HOME or keycode == wx.WXK_END) then
local pos = keycode == wx.WXK_HOME and 0 or editor:GetLength()
if event:ShiftDown() -- mark selection and scroll to caret
then editor:SetCurrentPos(pos) editor:EnsureCaretVisible()
else editor:GotoPos(pos) end
elseif mod == wx.wxMOD_RAW_CONTROL and keycode == wx.WXK_PAGEUP
or mod == (wx.wxMOD_RAW_CONTROL + wx.wxMOD_SHIFT) and keycode == wx.WXK_TAB then
if notebook:GetSelection() == first
then notebook:SetSelection(last)
else notebook:AdvanceSelection(false) end
elseif mod == wx.wxMOD_RAW_CONTROL
and (keycode == wx.WXK_PAGEDOWN or keycode == wx.WXK_TAB) then
if notebook:GetSelection() == last
then notebook:SetSelection(first)
else notebook:AdvanceSelection(true) end
elseif (keycode == wx.WXK_DELETE or keycode == wx.WXK_BACK)
and (mod == wx.wxMOD_NONE) then
-- Delete and Backspace behave the same way for selected text
if #(editor:GetSelectedText()) > 0 then
editor:SetTargetStart(editor:GetSelectionStart())
editor:SetTargetEnd(editor:GetSelectionEnd())
else
local pos = editor:GetCurrentPos()
if keycode == wx.WXK_BACK then
pos = pos - 1
if pos < 0 then return end
end
-- check if the modification is to one of "invisible" characters.
-- if not, proceed with "normal" processing as there are other
-- events that may depend on Backspace, for example, re-calculating
-- auto-complete suggestions.
local style = bit.band(editor:GetStyleAt(pos), 31)
if not MarkupIsSpecial or not MarkupIsSpecial(style) then
event:Skip()
return
end
editor:SetTargetStart(pos)
editor:SetTargetEnd(pos+1)
end
editor:ReplaceTarget("")
elseif ide.osname == "Unix" and ide.wxver >= "2.9.5"
and keycode == ('T'):byte() and mod == wx.wxMOD_CONTROL then
ide.frame:AddPendingEvent(wx.wxCommandEvent(
wx.wxEVT_COMMAND_MENU_SELECTED, ID_SHOWTOOLTIP))
else
if ide.osname == 'Macintosh' and mod == wx.wxMOD_META then
return -- ignore a key press if Command key is also pressed
end
event:Skip()
end
end)
local value
editor:Connect(wx.wxEVT_CONTEXT_MENU,
function (event)
local menu = wx.wxMenu()
menu:Append(ID_UNDO, TR("&Undo"))
menu:Append(ID_REDO, TR("&Redo"))
menu:AppendSeparator()
menu:Append(ID_CUT, TR("Cu&t"))
menu:Append(ID_COPY, TR("&Copy"))
menu:Append(ID_PASTE, TR("&Paste"))
menu:Append(ID_SELECTALL, TR("Select &All"))
menu:AppendSeparator()
menu:Append(ID_QUICKADDWATCH, TR("Add Watch Expression"))
menu:Append(ID_QUICKEVAL, TR("Evaluate in Console"))
menu:Append(ID_ADDTOSCRATCHPAD, TR("Add to Scratchpad"))
local point = editor:ScreenToClient(event:GetPosition())
local pos = editor:PositionFromPointClose(point.x, point.y)
value = pos ~= wxstc.wxSTC_INVALID_POSITION and getValAtPosition(editor, pos) or nil
menu:Enable(ID_QUICKADDWATCH, value ~= nil)
menu:Enable(ID_QUICKEVAL, value ~= nil)
local debugger = ide.debugger
menu:Enable(ID_ADDTOSCRATCHPAD, debugger.scratchpad
and debugger.scratchpad.editors and not debugger.scratchpad.editors[editor])
-- cancel calltip as it interferes with popup menu
if editor:CallTipActive() then editor:CallTipCancel() end
editor:PopupMenu(menu)
end)
editor:Connect(ID_QUICKADDWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function(event) DebuggerAddWatch(value) end)
editor:Connect(ID_QUICKEVAL, wx.wxEVT_COMMAND_MENU_SELECTED,
function(event) ShellExecuteCode(value) end)
editor:Connect(ID_ADDTOSCRATCHPAD, wx.wxEVT_COMMAND_MENU_SELECTED,
function(event) DebuggerScratchpadOn(editor) end)
return editor
end
-- ----------------------------------------------------------------------------
-- Add an editor to the notebook
function AddEditor(editor, name)
if notebook:AddPage(editor, name, true) then
local id = editor:GetId()
local document = {}
document.editor = editor
document.index = notebook:GetPageIndex(editor)
document.fileName = nil
document.filePath = nil
document.modTime = nil
document.isModified = false
openDocuments[id] = document
return document
end
return
end
function GetSpec(ext,forcespec)
local spec = forcespec
-- search proper spec
-- allow forcespec for "override"
if ext and not spec then
for _,curspec in pairs(ide.specs) do
local exts = curspec.exts
if (exts) then
for _,curext in ipairs(exts) do
if (curext == ext) then
spec = curspec
break
end
end
if (spec) then
break
end
end
end
end
return spec
end
function IndicateFunctions(editor, lines, linee)
if (not (edcfg.showfncall and editor.spec and editor.spec.isfncall)) then return end
local es = editor:GetEndStyled()
local lines = lines or 0
local linee = linee or editor:GetLineCount()-1
if (lines < 0) then return end
local isfncall = editor.spec.isfncall
local isinvalid = {}
for i,v in pairs(editor.spec.iscomment) do isinvalid[i] = v end
for i,v in pairs(editor.spec.iskeyword0) do isinvalid[i] = v end
for i,v in pairs(editor.spec.isstring) do isinvalid[i] = v end
local INDICS_MASK = wxstc.wxSTC_INDICS_MASK
local INDIC0_MASK = wxstc.wxSTC_INDIC0_MASK
for line=lines,linee do
local tx = editor:GetLine(line)
local ls = editor:PositionFromLine(line)
local from = 1
local off = -1
editor:StartStyling(ls,INDICS_MASK)
editor:SetStyling(#tx,0)
while from do
tx = from==1 and tx or string.sub(tx,from)
local f,t,w = isfncall(tx)
if (f) then
local p = ls+f+off
local s = bit.band(editor:GetStyleAt(p),31)
editor:StartStyling(p,INDICS_MASK)
editor:SetStyling(#w,isinvalid[s] and 0 or (INDIC0_MASK + 1))
off = off + t
end
from = t and (t+1)
end
end
editor:StartStyling(es,31)
end
function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
local lexerstyleconvert = nil
local spec = forcespec or GetSpec(ext)
-- found a spec setup lexers and keywords
if spec then
editor:SetLexer(spec.lexer or wxstc.wxSTC_LEX_NULL)
lexerstyleconvert = spec.lexerstyleconvert
if (spec.keywords) then
for i,words in ipairs(spec.keywords) do
editor:SetKeyWords(i-1,words)
end
end
if (spec.api == "lua") then
-- Get the items in the global "wx" table for autocompletion
if not wxkeywords then
local keyword_table = {}
for index in pairs(wx) do
table.insert(keyword_table, "wx."..index.." ")
end
for index in pairs(wxstc) do
table.insert(keyword_table, "wxstc."..index.." ")
end
table.sort(keyword_table)
wxkeywords = table.concat(keyword_table)
end
local offset = spec.keywords and #spec.keywords or 5
editor:SetKeyWords(offset, wxkeywords)
end
editor.api = GetApi(spec.apitype or "none")
editor.spec = spec
else
editor:SetLexer(wxstc.wxSTC_LEX_NULL)
editor:SetKeyWords(0, "")
editor.api = GetApi("none")
editor.spec = ide.specs.none
end
-- need to set folding property after lexer is set, otherwise
-- the folds are not shown (wxwidgets 2.9.5)
editor:SetProperty("fold", "1")
editor:SetProperty("fold.compact", ide.config.editor.foldcompact and "1" or "0")
editor:SetProperty("fold.comment", "1")
-- quickfix to prevent weird looks, otherwise need to update styling mechanism for cpp
-- cpp "greyed out" styles are styleid + 64
editor:SetProperty("lexer.cpp.track.preprocessor", "0")
editor:SetProperty("lexer.cpp.update.preprocessor", "0")
StylesApplyToEditor(styles or ide.config.styles, editor,
font or ide.font.eNormal,fontitalic or ide.font.eItalic,lexerstyleconvert)
end
----------------------------------------------------
-- function list for current file
local function refreshFunctionList(event)
event:Skip()
local editor = GetEditor()
if (editor and not (editor.spec and editor.spec.isfndef)) then return end
-- parse current file and update list
-- first populate with the current label to minimize flicker
-- then populate the list and update the label
local current = funclist:GetCurrentSelection()
local label = funclist:GetString(current)
local default = funclist:GetString(0)
funclist:Clear()
funclist:Append(current ~= wx.wxNOT_FOUND and label or default, 0)
funclist:SetSelection(0)
local lines = 0
local linee = (editor and editor:GetLineCount() or 0)-1
for line=lines,linee do
local tx = editor:GetLine(line)
local s,_,cap,l = editor.spec.isfndef(tx)
if (s) then
local ls = editor:PositionFromLine(line)
local style = bit.band(editor:GetStyleAt(ls+s),31)
if not (editor.spec.iscomment[style] or editor.spec.isstring[style]) then
funclist:Append((l and " " or "")..cap,line)
end
end
end
funclist:SetString(0, default)
funclist:SetSelection(current ~= wx.wxNOT_FOUND and current or 0)
end
-- wx.wxEVT_SET_FOCUS is not triggered for wxChoice on Mac (wx 2.8.12),
-- so use wx.wxEVT_LEFT_DOWN instead; none of the events are triggered for
-- wxChoice on Linux (wx 2.9.5+), so use EVT_ENTER_WINDOW attached to the
-- toolbar itself until something better is available.
if ide.osname == 'Unix' then
ide.frame.toolBar:Connect(wx.wxEVT_ENTER_WINDOW, refreshFunctionList)
else
local event = ide.osname == 'Macintosh' and wx.wxEVT_LEFT_DOWN or wx.wxEVT_SET_FOCUS
funclist:Connect(event, refreshFunctionList)
end
funclist:Connect(wx.wxEVT_COMMAND_CHOICE_SELECTED,
function (event)
-- test if updated
-- jump to line
event:Skip()
local l = event:GetClientData()
if (l and l > 0) then
local editor = GetEditor()
editor:GotoLine(l)
editor:SetFocus()
editor:SetSTCFocus(true)
end
end)