if not WeakAuras.IsLibsOK() then return end ---@type string local AddonName = ... ---@class OptionsPrivate local OptionsPrivate = select(2, ...) -- Lua APIs local pairs, type, ipairs = pairs, type, ipairs local gsub = gsub -- WoW APIs local CreateFrame = CreateFrame local AceGUI = LibStub("AceGUI-3.0") local SharedMedia = LibStub("LibSharedMedia-3.0") local LibDD = LibStub:GetLibrary("LibUIDropDownMenu-4.0") local LAAC = LibStub("LibAPIAutoComplete-1.0") local IndentationLib = IndentationLib ---@class WeakAuras local WeakAuras = WeakAuras local L = WeakAuras.L local textEditor local editor_themes = { ["Standard"] = { ["Table"] = "|c00ff3333", ["Arithmetic"] = "|c00ff3333", ["Relational"] = "|c00ff3333", ["Logical"] = "|c004444ff", ["Special"] = "|c00ff3333", ["Keyword"] = "|c004444ff", ["Comment"] = "|c0000aa00", ["Number"] = "|c00ff9900", ["String"] = "|c00999999" }, ["Monokai"] = { ["Table"] = "|c00ffffff", ["Arithmetic"] = "|c00f92672", ["Relational"] = "|c00ff3333", ["Logical"] = "|c00f92672", ["Special"] = "|c0066d9ef", ["Keyword"] = "|c00f92672", ["Comment"] = "|c0075715e", ["Number"] = "|c00ae81ff", ["String"] = "|c00e6db74" }, ["Obsidian"] = { ["Table"] = "|c00AFC0E5", ["Arithmetic"] = "|c00E0E2E4", ["Relational"] = "|c00B3B689", ["Logical"] = "|c0093C763", ["Special"] = "|c00AFC0E5", ["Keyword"] = "|c0093C763", ["Comment"] = "|c0066747B", ["Number"] = "|c00FFCD22", ["String"] = "|c00EC7600" } } if not WeakAurasSaved.editor_tab_spaces then WeakAurasSaved.editor_tab_spaces = 4 end if not WeakAurasSaved.editor_font_size then WeakAurasSaved.editor_font_size = 12 end -- set default font size if missing local color_scheme = {[0] = "|r"} local function set_scheme() if not WeakAurasSaved.editor_theme then WeakAurasSaved.editor_theme = "Monokai" end local theme = editor_themes[WeakAurasSaved.editor_theme] color_scheme[IndentationLib.tokens.TOKEN_SPECIAL] = theme["Special"] color_scheme[IndentationLib.tokens.TOKEN_KEYWORD] = theme["Keyword"] color_scheme[IndentationLib.tokens.TOKEN_COMMENT_SHORT] = theme["Comment"] color_scheme[IndentationLib.tokens.TOKEN_COMMENT_LONG] = theme["Comment"] color_scheme[IndentationLib.tokens.TOKEN_NUMBER] = theme["Number"] color_scheme[IndentationLib.tokens.TOKEN_STRING] = theme["String"] color_scheme["..."] = theme["Table"] color_scheme["{"] = theme["Table"] color_scheme["}"] = theme["Table"] color_scheme["["] = theme["Table"] color_scheme["]"] = theme["Table"] color_scheme["+"] = theme["Arithmetic"] color_scheme["-"] = theme["Arithmetic"] color_scheme["/"] = theme["Arithmetic"] color_scheme["*"] = theme["Arithmetic"] color_scheme[".."] = theme["Arithmetic"] color_scheme["=="] = theme["Relational"] color_scheme["<"] = theme["Relational"] color_scheme["<="] = theme["Relational"] color_scheme[">"] = theme["Relational"] color_scheme[">="] = theme["Relational"] color_scheme["~="] = theme["Relational"] color_scheme["and"] = theme["Logical"] color_scheme["or"] = theme["Logical"] color_scheme["not"] = theme["Logical"] end -- Define the premade snippets local premadeSnippets = { { name = "Basic function", snippet = [=[ function() return end]=] }, { name = "Custom Activation", snippet = [=[ function(trigger) return trigger[1] and (trigger[2] or trigger[3]) end]=] }, { name = "Trigger: CLEU", snippet = [=[ function(event, timestamp, subEvent, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, ...) return end]=] }, { name = "Simple throttle", snippet = [=[ if not aura_env.last or aura_env.last < GetTime() - 1 then aura_env.last = GetTime() end]=] }, { name = "Trigger State Updater", snippet = [=[ function(allstates, event, ...) allstates:Update("", { progressType = "static"||"timed", value = , total = , duration = , expirationTime = , autoHide = true, name = , icon = , stacks = , index = , }) -- allstates:Remove("") -- allstates:RemoveAll() end]=] }, } local function ConstructTextEditor(frame) local group = AceGUI:Create("WeakAurasInlineGroup") group.frame:SetParent(frame) group.frame:SetPoint("TOPLEFT", frame, "TOPLEFT", 17, -63); group.frame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -17, 46); group.frame:Hide() group:SetLayout("flow") local editor = AceGUI:Create("MultiLineEditBox") editor.editBox.group = group editor:SetFullWidth(true) editor:SetFullHeight(true) editor:DisableButton(true) local fontPath = SharedMedia:Fetch("font", "Fira Mono Medium") if (fontPath) then editor.editBox:SetFont(fontPath, WeakAurasSaved.editor_font_size, "") end group:AddChild(editor) editor.frame:SetClipsChildren(true) local originalOnCursorChanged = editor.editBox:GetScript("OnCursorChanged") editor.editBox:SetScript("OnCursorChanged", function(self, ...) -- WORKAROUND the editbox sends spurious OnCursorChanged events if its resized -- That makes AceGUI scroll the editbox to make the cursor visible, leading to unintended -- movements. Prevent all of that by checking if the edit box has focus, as otherwise the cursor -- is invisible, and we don't care about making it visible if not self:HasFocus() then return end originalOnCursorChanged(self, ...) end) -- The indention lib overrides GetText, but for the line number -- display we ned the original, so save it here. local originalGetText = editor.editBox.GetText local originalSetText = editor.editBox.SetText set_scheme() LAAC:enable(editor.editBox) IndentationLib.enable(editor.editBox, color_scheme, WeakAurasSaved.editor_tab_spaces) local cancel = CreateFrame("Button", nil, group.frame, "UIPanelButtonTemplate") cancel:SetScript( "OnClick", function() group:CancelClose() end ) cancel:SetPoint("BOTTOMRIGHT", -20, -24) cancel:SetFrameLevel(cancel:GetFrameLevel() + 1) cancel:SetHeight(20) cancel:SetWidth(100) cancel:SetText(L["Cancel"]) local close = CreateFrame("Button", nil, group.frame, "UIPanelButtonTemplate") close:SetScript( "OnClick", function() group:Close() end ) close:SetPoint("RIGHT", cancel, "LEFT", -10, 0) close:SetFrameLevel(close:GetFrameLevel() + 1) close:SetHeight(20) close:SetWidth(100) close:SetText(L["Done"]) local settings_frame = CreateFrame("Button", "WASettingsButton", close, "UIPanelButtonTemplate") settings_frame:SetPoint("RIGHT", close, "LEFT", -10, 0) settings_frame:SetHeight(20) settings_frame:SetWidth(100) settings_frame:SetText(L["Settings"]) settings_frame:RegisterForClicks("LeftButtonUp") local helpButton = CreateFrame("Button", nil, group.frame, "UIPanelButtonTemplate") helpButton:SetPoint("BOTTOMLEFT", 0, -24) helpButton:SetFrameLevel(cancel:GetFrameLevel() + 1) helpButton:SetHeight(20) helpButton:SetWidth(100) helpButton:SetText(L["Help"]) local dropdown = LibDD:Create_UIDropDownMenu("SettingsMenuFrame", settings_frame) local function settings_dropdown_initialize(frame, level, menu) if level == 1 then for k, v in pairs(editor_themes) do local item = { text = k, isNotRadio = false, checked = function() return WeakAurasSaved.editor_theme == k end, func = function() WeakAurasSaved.editor_theme = k set_scheme() editor.editBox:SetText(editor.editBox:GetText()) end } LibDD:UIDropDownMenu_AddButton(item, level) end LibDD:UIDropDownMenu_AddButton( { text = L["Bracket Matching"], isNotRadio = true, checked = function() return WeakAurasSaved.editor_bracket_matching end, func = function() WeakAurasSaved.editor_bracket_matching = not WeakAurasSaved.editor_bracket_matching end }, level) LibDD:UIDropDownMenu_AddButton( { text = L["Indent Size"], hasArrow = true, notCheckable = true, menuList = "spaces" }, level) LibDD:UIDropDownMenu_AddButton( { text = WeakAuras.newFeatureString .. L["Font Size"], hasArrow = true, notCheckable = true, menuList = "sizes" }, level) elseif menu == "spaces" then local spaces = {2,4} for _, i in pairs(spaces) do LibDD:UIDropDownMenu_AddButton( { text = i, isNotRadio = false, checked = function() return WeakAurasSaved.editor_tab_spaces == i end, func = function() WeakAurasSaved.editor_tab_spaces = i IndentationLib.enable(editor.editBox, color_scheme, WeakAurasSaved.editor_tab_spaces) editor.editBox:SetText(editor.editBox:GetText().."\n") IndentationLib.indentEditbox(editor.editBox) end }, level) end elseif menu == "sizes" then local sizes = {10, 12, 14, 16} for _, i in pairs(sizes) do LibDD:UIDropDownMenu_AddButton( { text = i, isNotRadio = false, checked = function() return WeakAurasSaved.editor_font_size == i end, func = function() WeakAurasSaved.editor_font_size = i editor.editBox:SetFont(fontPath, WeakAurasSaved.editor_font_size, "") end }, level) end end end LibDD:UIDropDownMenu_Initialize(dropdown, settings_dropdown_initialize, "MENU") settings_frame:SetScript( "OnClick", function(self, button, down) LibDD:ToggleDropDownMenu(1, nil, dropdown, settings_frame, 0, 0) end ) -- Make Snippets button (top right, near the line number) local snippetsButton = CreateFrame("Button", "WASnippetsButton", group.frame, "UIPanelButtonTemplate") snippetsButton:SetPoint("BOTTOMRIGHT", editor.frame, "TOPRIGHT", -20, -10) snippetsButton:SetFrameLevel(group.frame:GetFrameLevel() + 2) snippetsButton:SetHeight(20) snippetsButton:SetWidth(100) snippetsButton:SetText(L["Snippets"]) snippetsButton:RegisterForClicks("LeftButtonUp") -- Get the saved snippets from SavedVars WeakAurasOptionsSaved.savedSnippets = WeakAurasOptionsSaved.savedSnippets or {} local savedSnippets = WeakAurasOptionsSaved.savedSnippets -- function to build snippet selection list local function UpdateSnippets(frame) -- release first before rebuilding frame:ReleaseChildren() table.sort( savedSnippets, function(a, b) return a.name < b.name end ) local heading1 = AceGUI:Create("Heading") heading1:SetText(L["Premade Snippets"]) heading1:SetRelativeWidth(0.7) frame:AddChild(heading1) -- Iterate premade snippets and make buttons for them for order, snippet in ipairs(premadeSnippets) do local button = AceGUI:Create("WeakAurasSnippetButton") button:SetTitle(snippet.name) button:SetDescription(snippet.snippet) button:SetCallback( "OnClick", function() editor.editBox:Insert(snippet.snippet) editor:SetFocus() end ) button:SetRelativeWidth(1) frame:AddChild(button) end local heading2 = AceGUI:Create("Heading") heading2:SetText(L["Your Saved Snippets"]) heading2:SetRelativeWidth(1) frame:AddChild(heading2) -- iterate saved snippets and make buttons for order, snippet in ipairs(savedSnippets) do local button = AceGUI:Create("WeakAurasSnippetButton") local snippetInsert = gsub(snippet.snippet, "|", "||") button:SetTitle(snippet.name) button:SetDescription(snippetInsert) button:SetEditable(true) button:SetRelativeWidth(1) button:SetNew(snippet.new) snippet.new = false button:SetCallback( "OnClick", function() editor.editBox:Insert(snippetInsert) editor:SetFocus() end ) button.deleteButton:SetScript( "OnClick", function() table.remove(savedSnippets, order) UpdateSnippets(frame) end ) button:SetCallback( "OnEnterPressed", function() local newName = button.renameEditBox:GetText() if newName and #newName > 0 then local found = false for _, snippet in ipairs(savedSnippets) do if snippet.name == newName then found = true break end end if not found then savedSnippets[order].name = newName UpdateSnippets(frame) end end end ) frame:AddChild(button) end end local apiSearchFrame -- Make sidebar for snippets local snippetsFrame = CreateFrame("Frame", "WeakAurasSnippets", group.frame, "PortraitFrameTemplate") ButtonFrameTemplate_HidePortrait(snippetsFrame) snippetsFrame:SetPoint("TOPLEFT", group.frame, "TOPRIGHT", 20, 0) snippetsFrame:SetPoint("BOTTOMLEFT", group.frame, "BOTTOMRIGHT", 20, 0) snippetsFrame:SetWidth(250) if snippetsFrame.Bg then local color = CreateColorFromHexString("ff1f1e21") -- PANEL_BACKGROUND_COLOR local r, g, b = color:GetRGB() snippetsFrame.Bg:SetColorTexture(r, g, b, 0.8) end -- Add button to save new snippet local AddSnippetButton = CreateFrame("Button", nil, snippetsFrame, "UIPanelButtonTemplate") AddSnippetButton:SetPoint("TOPLEFT", snippetsFrame, "TOPLEFT", 13, -25) AddSnippetButton:SetPoint("TOPRIGHT", snippetsFrame, "TOPRIGHT", -13, -25) AddSnippetButton:SetHeight(20) AddSnippetButton:SetText(L["Add Snippet"]) AddSnippetButton:RegisterForClicks("LeftButtonUp") -- house the buttons in a scroll frame -- All AceGUI from this point, so that buttons can be released and reused local snippetsScrollContainer = AceGUI:Create("SimpleGroup") snippetsScrollContainer:SetFullWidth(true) snippetsScrollContainer:SetFullHeight(true) snippetsScrollContainer:SetLayout("Fill") snippetsScrollContainer.frame:SetParent(snippetsFrame) snippetsScrollContainer.frame:SetPoint("TOPLEFT", snippetsFrame, "TOPLEFT", 17, -50) snippetsScrollContainer.frame:SetPoint("BOTTOMRIGHT", snippetsFrame, "BOTTOMRIGHT", -10, 10) local snippetsScroll = AceGUI:Create("ScrollFrame") snippetsScroll:SetLayout("List") snippetsScrollContainer:AddChild(snippetsScroll) snippetsScroll:FixScroll(true) snippetsScroll.scrollframe:SetScript( "OnScrollRangeChanged", function(frame) frame.obj:DoLayout() end ) snippetsFrame:Hide() -- Toggle the side bar on click snippetsButton:SetScript( "OnClick", function(self, button, down) if not snippetsFrame:IsShown() then snippetsFrame:Show() if apiSearchFrame and apiSearchFrame:IsShown() then apiSearchFrame:Hide() end UpdateSnippets(snippetsScroll) else snippetsFrame:Hide() end end ) AddSnippetButton:SetScript( "OnClick", function(self) local snippet = editor.editBox:GetText() if snippet and #snippet > 0 then local baseName, name, index = "New Snippet", "New Snippet", 0 local snippetExists = function(name) for _, snippet in ipairs(savedSnippets) do if snippet.name == name then return true end end end while snippetExists(name) do index = index + 1 name = format("%s %d", baseName, index) end table.insert(savedSnippets, {name = name, snippet = snippet, new = true}) UpdateSnippets(snippetsScroll) end end ) -- Make ApiSearch button local apiSearchButton = CreateFrame("Button", "WAAPISearchButton", group.frame, "UIPanelButtonTemplate") apiSearchButton:SetPoint("BOTTOMRIGHT", editor.frame, "TOPRIGHT", -20, 15) apiSearchButton:SetFrameLevel(group.frame:GetFrameLevel() + 2) apiSearchButton:SetHeight(20) apiSearchButton:SetWidth(100) apiSearchButton:SetText(L["Search API"]) apiSearchButton:RegisterForClicks("LeftButtonUp") -- Make sidebar for apiSearch apiSearchFrame = CreateFrame("Frame", "WeakAurasAPISearchFrame", group.frame, "PortraitFrameTemplate") ButtonFrameTemplate_HidePortrait(apiSearchFrame) apiSearchFrame:SetWidth(350) if apiSearchFrame.Bg then local color = CreateColorFromHexString("ff1f1e21") -- PANEL_BACKGROUND_COLOR local r, g, b = color:GetRGB() apiSearchFrame.Bg:SetColorTexture(r, g, b, 0.8) end local makeAPISearch local APISearchTextChangeDelay = 0.3 local APISearchCTimer -- filter line local filterInput = CreateFrame("EditBox", "WeakAurasAPISearchFilterInput", apiSearchFrame, "SearchBoxTemplate") filterInput:SetScript("OnTextChanged", function(self) SearchBoxTemplate_OnTextChanged(self) if APISearchCTimer then APISearchCTimer:Cancel() end APISearchCTimer = C_Timer.NewTimer( APISearchTextChangeDelay, function() makeAPISearch(filterInput:GetText()) end ) end) filterInput:SetHeight(15) filterInput:SetPoint("TOPLEFT", apiSearchFrame, "TOPLEFT", 17, -30) filterInput:SetPoint("TOPRIGHT", apiSearchFrame, "TOPRIGHT", -10, -30) filterInput:SetFont(STANDARD_TEXT_FONT, 10, "") local apiSearchScrollContainer = AceGUI:Create("SimpleGroup") apiSearchScrollContainer:SetFullWidth(true) apiSearchScrollContainer:SetFullHeight(true) apiSearchScrollContainer:SetLayout("Fill") apiSearchScrollContainer.frame:SetParent(apiSearchFrame) apiSearchScrollContainer.frame:SetPoint("TOPLEFT", apiSearchFrame, "TOPLEFT", 17, -50) apiSearchScrollContainer.frame:SetPoint("BOTTOMRIGHT", apiSearchFrame, "BOTTOMRIGHT", -10, 10) local apiSearchScroll = AceGUI:Create("ScrollFrame") apiSearchScroll:SetLayout("List") apiSearchScrollContainer:AddChild(apiSearchScroll) apiSearchScroll:FixScroll(true) apiSearchScroll.scrollframe:SetScript( "OnScrollRangeChanged", function(frame) frame.obj:DoLayout() end ) local snippetOnClickCallback = function(self) if self.isSystem then filterInput:SetText(self.name) else self.editor.editBox:Insert(self.name) self.editor:SetFocus() end end local function loadBlizzardAPIDocumentation() local apiAddonName = "Blizzard_APIDocumentation" local _, loaded = C_AddOns.IsAddOnLoaded(apiAddonName) if not loaded then C_AddOns.LoadAddOn(apiAddonName) end if #APIDocumentation.systems == 0 then APIDocumentation_LoadUI() end end local function addLine(results, apiInfo) local name if apiInfo.Type == "System" then name = apiInfo.Namespace elseif apiInfo.Type == "Function" then name = apiInfo:GetFullName() elseif apiInfo.Type == "Event" then name = apiInfo.LiteralName end table.insert(results, { name = name, apiInfo = apiInfo }) end local function APIListSystems() local results = {} for i, systemInfo in ipairs(APIDocumentation.systems) do if systemInfo.Namespace and #systemInfo.Functions > 0 then addLine(results, systemInfo) end end table.sort(results, function(a, b) return a.name < b.name end) return results end local function APISearch(word) local lowerWord = word:lower() local results = {} -- if search match name of namespace, show all functions & events for the namespace, and also show all other functions & events matching the search -- if search is composed with name of a namespace and a word separated by a dot, show matching function for matching namespace local nsName, rest = lowerWord:match("^([%w%_]+)(.*)") local funcName = rest and rest:match("^%.([%w%_]+)") for _, systemInfo in ipairs(APIDocumentation.systems) do -- search for namespaceName or namespaceName.functionName local systemMatch = nsName and #nsName >= 4 and systemInfo.Namespace and systemInfo.Namespace:lower():match(nsName) for _, apiInfo in ipairs(systemInfo.Functions) do if systemMatch then if funcName then if apiInfo:MatchesSearchString(funcName) then addLine(results, apiInfo) end else addLine(results, apiInfo) end else if apiInfo:MatchesSearchString(lowerWord) then addLine(results, apiInfo) end end end if systemMatch and rest == "" then for _, apiInfo in ipairs(systemInfo.Events) do addLine(results, apiInfo) end else for _, apiInfo in ipairs(systemInfo.Events) do if apiInfo:MatchesSearchString(lowerWord) then addLine(results, apiInfo) end end end end return results end local lastSearch = nil makeAPISearch = function(apiToSearchFor) loadBlizzardAPIDocumentation() local results if not apiToSearchFor or #apiToSearchFor < 4 then if lastSearch == "" then return end results = APIListSystems() lastSearch = "" else if lastSearch == apiToSearchFor then return end results = APISearch(apiToSearchFor) lastSearch = apiToSearchFor end apiSearchScroll:ReleaseChildren() for _, element in ipairs(results) do local apiInfo = element.apiInfo if apiInfo then local button = AceGUI:Create("WeakAurasSnippetButton") button:SetTitle(element.name) button:SetEditable(false) button:SetHeight(20) button:SetRelativeWidth(1) if apiInfo.Type ~= "System" and apiInfo.GetDetailedOutputLines then local desc = table.concat(apiInfo:GetDetailedOutputLines(), "\n") button:SetDescription(desc) else button:SetDescription() end button.name = element.name button.editor = editor button.isSystem = apiInfo.Type == "System" button:SetCallback("OnClick", snippetOnClickCallback) apiSearchScroll:AddChild(button) end end end apiSearchFrame:Hide() -- Toggle the side bar on click apiSearchButton:SetScript( "OnClick", function() if apiSearchFrame:IsShown() then apiSearchFrame:Hide() else apiSearchFrame:Show() apiSearchFrame:ClearAllPoints() apiSearchFrame:SetPoint("TOPLEFT", group.frame, "TOPRIGHT", 20, 0) apiSearchFrame:SetPoint("BOTTOMLEFT", group.frame, "BOTTOMRIGHT", 20, 0) filterInput:SetFocus() if snippetsFrame and snippetsFrame:IsShown() then snippetsFrame:Hide() end end end ) editor.editBox.timeMachine = {} editor.editBox.timeMachinePos = 1 local TimeMachineMaximumRollback = 10 editor.editBox:HookScript( "OnKeyDown", function(self, key) -- CTRL + S saves and closes if IsControlKeyDown() and key == "S" then group:Close() elseif key == "Z" and IsControlKeyDown() then self:SetPropagateKeyboardInput(false) if self.timeMachine[self.timeMachinePos + 1] then self.timeMachinePos = self.timeMachinePos + 1 self.skipOnTextChanged = true originalSetText(self, self.timeMachine[self.timeMachinePos][1]) self:SetCursorPosition(self.timeMachine[self.timeMachinePos][2]) end elseif key == "Y" and IsControlKeyDown() then self:SetPropagateKeyboardInput(false) if self.timeMachine[self.timeMachinePos - 1] then self.timeMachinePos = self.timeMachinePos - 1 self.skipOnTextChanged = true originalSetText(self, self.timeMachine[self.timeMachinePos][1]) self:SetCursorPosition(self.timeMachine[self.timeMachinePos][2]) end end end ) editor.editBox:HookScript( "OnTextChanged", function(self, userInput) local str = editor.editBox:GetText() if not str or str:trim() == "" or editor.combinedText == true then self.group.editorError:SetText("") else local func, errorString if (self.group.enclose) then func, errorString = OptionsPrivate.Private.LoadFunction("return function() " .. str .. "\n end", self.group.data.id, true) else func, errorString = OptionsPrivate.Private.LoadFunction("return " .. str, self.group.data.id, true) end if not errorString and self.group.validator then errorString = self.group.validator(func) end if errorString then if self.url then helpButton:Show() end self.group.editorError:Show() self.group.editorError:SetText(errorString) else self.group.editorError:SetText("") end end if not userInput then return end if self.skipOnTextChanged then self.skipOnTextChanged = false return end local cursorPosition = self:GetCursorPosition() local text = originalGetText(self) if IndentationLib then text, cursorPosition = IndentationLib.stripWowColorsWithPos(text, cursorPosition) end if self.timeMachine[1] and text == self.timeMachine[1][1] then return end -- if cursor is not at position 1, remove elements before cursor for i = 2, self.timeMachinePos do table.remove(self.timeMachine, 1) end -- insert current text table.insert(self.timeMachine, 1, {text, cursorPosition - 1}) -- timeMachine is limited to a number of TimeMachineMaximumRollback elements for i = #self.timeMachine, TimeMachineMaximumRollback + 1, -1 do table.remove(self.timeMachine, i) end self.timeMachinePos = 1 end ) -- bracket matching editor.editBox:HookScript( "OnChar", function(_, char) if not IsControlKeyDown() and WeakAurasSaved.editor_bracket_matching then if char == "(" then editor.editBox:Insert(")") editor.editBox:SetCursorPosition(editor.editBox:GetCursorPosition() - 1) elseif char == "{" then editor.editBox:Insert("}") editor.editBox:SetCursorPosition(editor.editBox:GetCursorPosition() - 1) elseif char == "[" then editor.editBox:Insert("]") editor.editBox:SetCursorPosition(editor.editBox:GetCursorPosition() - 1) end end end ) local editorError = group.frame:CreateFontString(nil, "OVERLAY") editorError:SetFont(STANDARD_TEXT_FONT, 12, "") editorError:SetJustifyH("LEFT") editorError:SetJustifyV("TOP") editorError:SetTextColor(1, 0, 0) editorError:SetPoint("LEFT", helpButton, "RIGHT", 0, 4) editorError:SetPoint("RIGHT", settings_frame, "LEFT") group.editorError = editorError local editorLine = CreateFrame("EditBox", nil, group.frame, "InputBoxTemplate") -- Set script on enter pressed.. editorLine:SetPoint("RIGHT", snippetsButton, "LEFT", -10, 0) editorLine:SetFont(STANDARD_TEXT_FONT, 10, "") editorLine:SetJustifyH("RIGHT") editorLine:SetWidth(30) editorLine:SetHeight(20) editorLine:SetNumeric(true) editorLine:SetTextInsets(0, 5, 0, 0) editorLine:SetAutoFocus(false) local editorLineText = group.frame:CreateFontString(nil, "OVERLAY") editorLineText:SetFont(STANDARD_TEXT_FONT, 10) editorLineText:SetTextColor(1, 1, 1) editorLineText:SetText(L["Line"]) editorLineText:SetPoint("RIGHT", editorLine, "LEFT", -8, 0) helpButton:SetScript("OnClick", function() OptionsPrivate.ToggleTip(helpButton, group.url, L["Help"], "") end) local oldOnCursorChanged = editor.editBox:GetScript("OnCursorChanged") editor.editBox:SetScript( "OnCursorChanged", function(...) oldOnCursorChanged(...) local cursorPosition = editor.editBox:GetCursorPosition() local next = -1 local line = 0 while (next and cursorPosition >= next) do next = originalGetText(editor.editBox):find("[\n]", next + 1) line = line + 1 end editorLine:SetNumber(line) end ) editorLine:SetScript( "OnEnterPressed", function() local newLine = editorLine:GetNumber() local newPosition = 0 while (newLine > 1 and newPosition) do newPosition = originalGetText(editor.editBox):find("[\n]", newPosition + 1) newLine = newLine - 1 end if (newPosition) then editor.editBox:SetCursorPosition(newPosition) editor.editBox:SetFocus() end end ) function group.Open(self, data, path, enclose, multipath, reloadOptions, setOnParent, url, validator) self.data = data self.path = path self.multipath = multipath self.reloadOptions = reloadOptions self.setOnParent = setOnParent self.url = url self.enclose = enclose self.validator = validator if url then helpButton:Show() else helpButton:Hide() end if (frame.window == "texture") then local texturepicker = OptionsPrivate.TexturePicker(frame, true) if texturepicker then texturepicker:CancelClose() end elseif (frame.window == "icon") then local iconpicker = OptionsPrivate.IconPicker(frame, true) if iconpicker then iconpicker:CancelClose() end end frame.window = "texteditor" frame:UpdateFrameVisible() local title = (type(data.id) == "string" and data.id or L["Temporary Group"]) .. " -" if (not multipath) then for index, field in pairs(path) do if (type(field) == "number") then field = "Trigger " .. field end title = title .. " " .. field:sub(1, 1):upper() .. field:sub(2) end end editor:SetLabel(title) editor.editBox.timeMachine = {} editor.editBox.timeMachinePos = 1 editor.editBox:SetScript( "OnEscapePressed", function() -- catch it so that escape doesn't default to losing focus (after which another escape would close config) end ) if setOnParent then editor:SetText(OptionsPrivate.Private.ValueFromPath(data, path) or "") else local singleText local sameTexts = true local combinedText = "" for child in OptionsPrivate.Private.TraverseLeafsOrAura(data) do local text if multipath then text = path[child.id] and OptionsPrivate.Private.ValueFromPath(child, path[child.id]) else text = OptionsPrivate.Private.ValueFromPath(child, path) end if text then if not (singleText) then singleText = text else if singleText ~= text then sameTexts = false end end if combinedText ~= "" then combinedText = combinedText .. "\n\n" end combinedText = combinedText .. L["-- Do not remove this comment, it is part of this aura: "] .. child.id .. "\n" combinedText = combinedText .. (text or "") end end if (sameTexts) then editor:SetText(singleText or "") editor.combinedText = false else editor:SetText(combinedText) editor.combinedText = true end end editor:SetFocus() end function group.CancelClose(self) editor:ClearFocus() frame:HideTip() frame.window = "default" frame:UpdateFrameVisible() end local function extractTexts(input) local texts = {} local currentPos, id, startIdLine, startId, endId, endIdLine while (true) do startIdLine, startId = string.find(input, L["-- Do not remove this comment, it is part of this aura: "], currentPos, true) if (not startId) then break end endId, endIdLine = string.find(input, "\n", startId, true) if (not endId) then break end if (currentPos) then local trimmedPosition = startIdLine - 1 while (string.sub(input, trimmedPosition, trimmedPosition) == "\n") do trimmedPosition = trimmedPosition - 1 end texts[id] = string.sub(input, currentPos, trimmedPosition) end id = string.sub(input, startId + 1, endId - 1) currentPos = endIdLine + 1 end if (id) then texts[id] = string.sub(input, currentPos, string.len(input)) end return texts end function group.Close(self) if self.setOnParent then OptionsPrivate.Private.ValueToPath(self.data, self.path, editor:GetText()) WeakAuras.Add(self.data) else local textById = editor.combinedText and extractTexts(editor:GetText()) for child in OptionsPrivate.Private.TraverseLeafsOrAura(self.data) do local text = editor.combinedText and (textById[child.id] or "") or editor:GetText() OptionsPrivate.Private.ValueToPath(child, self.multipath and self.path[child.id] or self.path, text) WeakAuras.Add(child) OptionsPrivate.ClearOptions(child.id) end end WeakAuras.ClearAndUpdateOptions(self.data.id) editor:ClearFocus() frame.window = "default" frame:UpdateFrameVisible() WeakAuras.FillOptions() end return group end function OptionsPrivate.TextEditor(frame, noConstruct) textEditor = textEditor or (not noConstruct and ConstructTextEditor(frame)) return textEditor end