You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3755 lines
132 KiB
3755 lines
132 KiB
--========================================================--
|
|
-- Scorpio CodeEditor --
|
|
-- --
|
|
-- Author : kurapica125@outlook.com --
|
|
-- Create Date : 2020/05/16 --
|
|
--========================================================--
|
|
|
|
--========================================================--
|
|
Scorpio "Scorpio.Widget.CodeEditor" "1.0.0"
|
|
--========================================================--
|
|
|
|
-----------------------------------------------------------
|
|
-- Text Editor Widget --
|
|
-----------------------------------------------------------
|
|
__Sealed__() class "CodeEditor" (function(_ENV)
|
|
inherit "InputScrollFrame"
|
|
|
|
__Sealed__()
|
|
class "OperationStack" (function(_ENV)
|
|
export { yield = coroutine.yield }
|
|
|
|
local FIELD_BASE = -1
|
|
local FIELD_CURSOR = -2
|
|
local FIELD_COUNT = -3
|
|
local FIELD_MAX = -4
|
|
|
|
-----------------------------------------------------------
|
|
-- property --
|
|
-----------------------------------------------------------
|
|
--- The max count of the stack
|
|
property "MaxCount" { field = FIELD_MAX, type = Number, default = -1 }
|
|
|
|
-----------------------------------------------------------
|
|
-- method --
|
|
-----------------------------------------------------------
|
|
function Push(self, oper, text, start, stop)
|
|
local max = self.MaxCount
|
|
if max == 0 then return end
|
|
|
|
local base = self[FIELD_BASE]
|
|
|
|
self[FIELD_CURSOR] = self[FIELD_CURSOR] + 1
|
|
self[FIELD_COUNT] = self[FIELD_CURSOR]
|
|
|
|
base = base + (self[FIELD_COUNT] - 1) * 4 + 1
|
|
|
|
self[base + 0] = oper
|
|
self[base + 1] = text
|
|
self[base + 2] = start
|
|
self[base + 3] = stop
|
|
|
|
-- Reduce the stack
|
|
if max > 0 and self[FIELD_COUNT] > max then
|
|
local last = base + (self[FIELD_COUNT] - max) * 4
|
|
for i = base + 1, last do
|
|
self[i] = nil
|
|
end
|
|
self[FIELD_BASE]= last
|
|
self[FIELD_COUNT] = max
|
|
self[FIELD_CURSOR]= max
|
|
end
|
|
end
|
|
|
|
function Undo(self)
|
|
if self[FIELD_CURSOR] > 1 then
|
|
self[FIELD_CURSOR] = self[FIELD_CURSOR] - 1
|
|
|
|
local base = self[FIELD_BASE] + (self[FIELD_CURSOR] - 1) * 4 + 1
|
|
return self[base], self[base + 1], self[base + 2], self[base + 3]
|
|
end
|
|
end
|
|
|
|
function Redo(self)
|
|
if self[FIELD_CURSOR] < self[FIELD_COUNT] then
|
|
self[FIELD_CURSOR] = self[FIELD_CURSOR] + 1
|
|
|
|
local base = self[FIELD_BASE] + (self[FIELD_CURSOR] - 1) * 4 + 1
|
|
return self[base], self[base + 1], self[base + 2], self[base + 3]
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------
|
|
-- constructor --
|
|
-----------------------------------------------------------
|
|
function __new(_, maxcount)
|
|
return {
|
|
[FIELD_BASE] = 0,
|
|
[FIELD_COUNT] = 0,
|
|
[FIELD_CURSOR] = 0,
|
|
[FIELD_MAX] = maxcount or -1,
|
|
}, true
|
|
end
|
|
end)
|
|
|
|
export {
|
|
tinsert = table.insert,
|
|
tremove = table.remove,
|
|
tblconcat = table.concat,
|
|
strbyte = string.byte,
|
|
strchar = string.char,
|
|
strrep = string.rep,
|
|
strtrim = Toolset.trim,
|
|
decode = Text.UTF8Encoding.Decode,
|
|
|
|
BYTE_WORD_KIND = 0,
|
|
BYTE_PUNC_KIND = 1,
|
|
BYTE_SPACE_KIND = 2,
|
|
|
|
DOUBLE_CLICK_INTERVAL = 0.6,
|
|
_FIRST_WAITTIME = 0.3,
|
|
_CONTINUE_WAITTIME = 0.05,
|
|
}
|
|
|
|
_Byte = setmetatable({
|
|
-- LineBreak
|
|
LINEBREAK_N = strbyte("\n"),
|
|
LINEBREAK_R = strbyte("\r"),
|
|
|
|
-- Space
|
|
SPACE = strbyte(" "),
|
|
TAB = strbyte("\t"),
|
|
|
|
-- UnderLine
|
|
UNDERLINE = strbyte("_"),
|
|
|
|
-- Number
|
|
ZERO = strbyte("0"),
|
|
NINE = strbyte("9"),
|
|
|
|
-- String
|
|
SINGLE_QUOTE = strbyte("'"),
|
|
DOUBLE_QUOTE = strbyte('"'),
|
|
|
|
-- Operator
|
|
PLUS = strbyte("+"),
|
|
MINUS = strbyte("-"),
|
|
ASTERISK = strbyte("*"),
|
|
SLASH = strbyte("/"),
|
|
PERCENT = strbyte("%"),
|
|
|
|
-- Compare
|
|
LESSTHAN = strbyte("<"),
|
|
GREATERTHAN = strbyte(">"),
|
|
EQUALS = strbyte("="),
|
|
|
|
-- Parentheses
|
|
LEFTBRACKET = strbyte("["),
|
|
RIGHTBRACKET = strbyte("]"),
|
|
LEFTPAREN = strbyte("("),
|
|
RIGHTPAREN = strbyte(")"),
|
|
LEFTWING = strbyte("{"),
|
|
RIGHTWING = strbyte("}"),
|
|
|
|
-- Punctuation
|
|
PERIOD = strbyte("."),
|
|
BACKSLASH = strbyte("\\"),
|
|
COMMA = strbyte(","),
|
|
SEMICOLON = strbyte(";"),
|
|
COLON = strbyte(":"),
|
|
TILDE = strbyte("~"),
|
|
HASH = strbyte("#"),
|
|
|
|
-- WOW
|
|
VERTICAL = strbyte("|"),
|
|
r = strbyte("r"),
|
|
c = strbyte("c"),
|
|
}, {
|
|
__index = function(self, key)
|
|
if type(key) == "string" and key:len() == 1 then
|
|
local val = strbyte(key)
|
|
rawset(self, key, val)
|
|
return val
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- Operation
|
|
_Operation = {
|
|
INIT = 0,
|
|
CHANGE_CURSOR = 1,
|
|
INPUTCHAR = 2,
|
|
INPUTTAB = 3,
|
|
DELETE = 4,
|
|
BACKSPACE = 5,
|
|
ENTER = 6,
|
|
PASTE = 7,
|
|
CUT = 8,
|
|
INDENTFORMAT = 9,
|
|
DELETE_LINE = 10,
|
|
DUPLICATE_LINE = 11,
|
|
UNDO_SAVE = 12,
|
|
}
|
|
|
|
_KEY_OPER = {
|
|
PAGEUP = _Operation.CHANGE_CURSOR,
|
|
PAGEDOWN = _Operation.CHANGE_CURSOR,
|
|
HOME = _Operation.CHANGE_CURSOR,
|
|
END = _Operation.CHANGE_CURSOR,
|
|
UP = _Operation.CHANGE_CURSOR,
|
|
DOWN = _Operation.CHANGE_CURSOR,
|
|
RIGHT = _Operation.CHANGE_CURSOR,
|
|
LEFT = _Operation.CHANGE_CURSOR,
|
|
TAB = _Operation.INPUTTAB,
|
|
DELETE = _Operation.DELETE,
|
|
BACKSPACE = _Operation.BACKSPACE,
|
|
ENTER = _Operation.ENTER,
|
|
}
|
|
|
|
-- SkipKey
|
|
_SkipKey = {
|
|
-- Control keys
|
|
LALT = true,
|
|
LCTRL = true,
|
|
LSHIFT = true,
|
|
RALT = true,
|
|
RCTRL = true,
|
|
RSHIFT = true,
|
|
-- other nouse keys
|
|
ESCAPE = true,
|
|
CAPSLOCK = true,
|
|
PRINTSCREEN = true,
|
|
INSERT = true,
|
|
UNKNOWN = true,
|
|
}
|
|
|
|
_Puncs = Dictionary(XList(128):Map(string.char):Filter("x=>x:match('[%p]+')"):Filter("x=>x~='_'"):Map(string.byte), true)
|
|
_Spaces = Dictionary(XList(128):Map(string.char):Filter("x=>x:match('[%s]+')"):Map(string.byte), true)
|
|
_Temp = {}
|
|
|
|
-- Auto Pairs
|
|
_AutoPairs = {
|
|
[_Byte.LEFTBRACKET] = _Byte.RIGHTBRACKET, -- []
|
|
[_Byte.LEFTPAREN] = _Byte.RIGHTPAREN, -- ()
|
|
[_Byte.LEFTWING] = _Byte.RIGHTWING, --{}
|
|
[_Byte.SINGLE_QUOTE] = true, -- ''
|
|
[_Byte.DOUBLE_QUOTE] = true, -- ""
|
|
[_Byte.RIGHTBRACKET] = false,
|
|
[_Byte.RIGHTPAREN] = false,
|
|
[_Byte.RIGHTWING] = false,
|
|
}
|
|
|
|
------------------------------------------------------
|
|
-- Test FontString
|
|
------------------------------------------------------
|
|
_TestFontString = UIParent:CreateFontString()
|
|
_TestFontString:Hide()
|
|
_TestFontString:SetWordWrap(true)
|
|
|
|
------------------------------------------------------
|
|
-- Short Key Block
|
|
------------------------------------------------------
|
|
_BtnBlockUp = CreateFrame("Button", "Scorpio_TextEditor_UpBlock", UIParent, "SecureActionButtonTemplate")
|
|
_BtnBlockDown = CreateFrame("Button", "Scorpio_TextEditor_DownBlock", UIParent, "SecureActionButtonTemplate")
|
|
_BtnBlockUp:Hide()
|
|
_BtnBlockDown:Hide()
|
|
|
|
------------------------------------------------------
|
|
-- Key Scanner
|
|
------------------------------------------------------
|
|
_KeyScan = CreateFrame("Frame", nil, UIParent)
|
|
_KeyScan:Hide()
|
|
_KeyScan:SetPropagateKeyboardInput(true)
|
|
_KeyScan:EnableKeyboard(true)
|
|
_KeyScan:SetFrameStrata("TOOLTIP")
|
|
_KeyScan.ActiveKeys = {}
|
|
|
|
------------------------------------------------------
|
|
-- Inpput Helpers
|
|
------------------------------------------------------
|
|
local function getPrevVerticalCount(str, pos)
|
|
local sum = 0
|
|
|
|
while pos > 0 do
|
|
local byte = strbyte(str, pos)
|
|
if byte ~= _Byte.VERTICAL then break end
|
|
sum = sum + 1
|
|
pos = pos - 1
|
|
end
|
|
|
|
return sum
|
|
end
|
|
|
|
local function skipColor(str, pos)
|
|
while true do
|
|
local byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.VERTICAL then
|
|
local nbyte = strbyte(str, pos + 1)
|
|
if nbyte == _Byte.c then
|
|
-- Color start
|
|
pos = pos + 10
|
|
elseif nbyte == _Byte.r then
|
|
-- Color end
|
|
pos = pos + 2
|
|
else
|
|
-- must be ||
|
|
return pos, true
|
|
end
|
|
else
|
|
return pos
|
|
end
|
|
end
|
|
end
|
|
|
|
local function checkUTF8(str, pos)
|
|
local code, len = decode(str, pos)
|
|
return len and (pos + len - 1) or pos
|
|
end
|
|
|
|
local function skipPrevColor(str, pos)
|
|
while pos > 0 do
|
|
local byte = strbyte(str, pos)
|
|
if byte == _Byte.r then
|
|
if getPrevVerticalCount(str, pos - 1) % 2 == 1 then
|
|
pos = pos - 2
|
|
else
|
|
return pos
|
|
end
|
|
elseif pos >= 10 and strbyte(str, pos - 8) == _Byte.c and getPrevVerticalCount(str, pos - 9) % 2 == 1 then
|
|
pos = pos - 10
|
|
else
|
|
return pos
|
|
end
|
|
end
|
|
end
|
|
|
|
local function checkPrevUTF8(str, pos)
|
|
while pos > 0 do
|
|
local byte = strbyte(str, pos)
|
|
if byte < 0x80 then
|
|
if byte == _Byte.VERTICAL then
|
|
return pos - 1
|
|
end
|
|
return pos -- 1-byte
|
|
elseif byte >= 0xC0 then
|
|
return pos
|
|
end
|
|
|
|
pos = pos - 1
|
|
end
|
|
end
|
|
|
|
local function removeColor(str)
|
|
local start = 1
|
|
local pos = 1
|
|
local npos, isv
|
|
local count = 0
|
|
|
|
wipe(_Temp)
|
|
|
|
while pos <= #str do
|
|
npos, isv = skipColor(str, pos)
|
|
|
|
if npos ~= pos then
|
|
count = count + 1
|
|
_Temp[count] = str:sub(start, pos - 1)
|
|
start = npos
|
|
end
|
|
|
|
pos = npos + (isv and 2 or 1)
|
|
end
|
|
|
|
count = count + 1
|
|
_Temp[count] = str:sub(start, -1)
|
|
|
|
local result = tblconcat(_Temp)
|
|
wipe(_Temp)
|
|
|
|
return result
|
|
end
|
|
|
|
local function updateLineNum(self, try)
|
|
local editor = self.__Editor
|
|
local linenum = self.__LineNum
|
|
|
|
if not linenum:IsShown() then return end
|
|
|
|
local font, height, flag= editor:GetFont()
|
|
local spacing = editor:GetSpacing()
|
|
local left, right = editor:GetTextInsets()
|
|
local lineWidth = editor:GetWidth() - left - right
|
|
local lineHeight = height + spacing
|
|
|
|
-- Wait for one phase
|
|
if not font then return not try and Next(updateLineNum, self, true) end
|
|
|
|
local text = editor:GetText()
|
|
local index = 0
|
|
local count = 0
|
|
local extra = 0
|
|
|
|
local lines = linenum.Lines or {}
|
|
linenum.Lines = lines
|
|
|
|
linenum:SetHeight(editor:GetHeight() + 10)
|
|
|
|
_TestFontString:SetFont(font, height, flag)
|
|
_TestFontString:SetSpacing(spacing)
|
|
_TestFontString:SetIndentedWordWrap(editor:GetIndentedWordWrap())
|
|
_TestFontString:SetWidth(lineWidth)
|
|
|
|
for _, line in strsplit(text, "\n") do
|
|
index = index + 1
|
|
count = count + 1
|
|
|
|
lines[count] = index
|
|
|
|
_TestFontString:SetText(line)
|
|
|
|
extra = _TestFontString:GetStringHeight() / lineHeight
|
|
extra = floor(extra) - 1
|
|
|
|
for i = 1, extra do
|
|
count = count + 1
|
|
lines[count] = ""
|
|
end
|
|
end
|
|
|
|
for i = #lines, count + 1, -1 do
|
|
lines[i] = nil
|
|
end
|
|
|
|
linenum:SetText(tblconcat(lines, "\n"))
|
|
end
|
|
|
|
local function replaceBlock(str, startp, endp, replace)
|
|
return startp and (str:sub(1, startp - 1) .. replace .. str:sub(endp + 1, -1)) or str
|
|
end
|
|
|
|
local function getLines(str, startp, endp)
|
|
endp = endp and (endp + 1) or endp == nil and (startp + 1) or endp
|
|
|
|
-- get prev LineBreak
|
|
while startp > 0 do
|
|
local byte = strbyte(str, startp)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
startp = startp - 1
|
|
end
|
|
|
|
startp = startp + 1
|
|
|
|
if not endp then return startp end
|
|
|
|
-- get next LineBreak
|
|
while true do
|
|
local byte = strbyte(str, endp)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
endp = endp + 1
|
|
end
|
|
|
|
endp = endp - 1
|
|
|
|
-- get block
|
|
return startp, endp
|
|
end
|
|
|
|
local function getByteType(byte)
|
|
return _Puncs[byte] and BYTE_PUNC_KIND or _Spaces[byte] and BYTE_SPACE_KIND or byte and BYTE_WORD_KIND
|
|
end
|
|
|
|
local function getWord(str, pos, notail, nopre)
|
|
local startp, endp = getLines(str, pos)
|
|
if startp > endp then return end
|
|
|
|
local pretype, tailtype
|
|
|
|
-- Check the match type
|
|
if not notail then
|
|
tailtype = getByteType(strbyte(str, (skipColor(str, pos + 1)))) or -1
|
|
end
|
|
|
|
if tailtype == 0 or nopre then
|
|
pretype = 0
|
|
else
|
|
pretype = getByteType(strbyte(str, skipPrevColor(str, pos))) or -1
|
|
end
|
|
|
|
-- Match the word
|
|
local prev, tail = pos + 1, pos
|
|
|
|
if not nopre then
|
|
repeat
|
|
prev = skipPrevColor(str, prev - 1)
|
|
until not prev or prev < startp or getByteType(strbyte(str, prev)) ~= pretype
|
|
prev = prev and (prev + 1) or 1
|
|
end
|
|
|
|
if not notail then
|
|
local isv
|
|
repeat
|
|
tail, isv = skipColor(str, tail + (isv and 2 or 1))
|
|
until not tail or tail > endp or getByteType(strbyte(str, tail)) ~= tailtype
|
|
tail = tail - 1
|
|
end
|
|
|
|
return prev, tail
|
|
end
|
|
|
|
local function inString(str, pos)
|
|
local startp, endp = getLines(str, pos)
|
|
if startp > endp then return false end
|
|
|
|
local byte = strbyte(str, startp)
|
|
local isString = 0
|
|
local preEscape = false
|
|
|
|
while byte and startp <= pos do
|
|
if not preEscape then
|
|
if byte == _Byte.SINGLE_QUOTE then
|
|
if isString == 0 then
|
|
isString = 1
|
|
elseif isString == 1 then
|
|
isString = 0
|
|
end
|
|
elseif byte == _Byte.DOUBLE_QUOTE then
|
|
if isString == 0 then
|
|
isString = 2
|
|
elseif isString == 2 then
|
|
isString = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
if byte == _Byte.BACKSLASH then
|
|
preEscape = not preEscape
|
|
else
|
|
preEscape = false
|
|
end
|
|
|
|
startp = startp + 1
|
|
byte = strbyte(str, startp)
|
|
end
|
|
|
|
return isString == 1 and _Byte.SINGLE_QUOTE or isString == 2 and _Byte.DOUBLE_QUOTE
|
|
end
|
|
|
|
local function endPrevKey(self)
|
|
if self._SKIPCURCHGARROW then
|
|
self._SKIPCURCHGARROW = nil
|
|
self._SKIPCURCHG = nil
|
|
end
|
|
self._InPasting = false
|
|
self._DownOffset = false
|
|
end
|
|
|
|
local function isKeyPressed(self, key)
|
|
return _KeyScan.FocusEditor == self and _KeyScan.ActiveKeys[key] or false
|
|
end
|
|
|
|
local function getLinesByReturn(str, startp, returnCnt)
|
|
local byte
|
|
local handledReturn = 0
|
|
local endp = startp + 1
|
|
|
|
returnCnt = returnCnt or 0
|
|
|
|
-- get prev LineBreak
|
|
while startp > 0 do
|
|
byte = strbyte(str, startp)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
startp = startp - 1
|
|
end
|
|
|
|
startp = startp + 1
|
|
|
|
-- get next LineBreak
|
|
while true do
|
|
byte = strbyte(str, endp)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
returnCnt = returnCnt - 1
|
|
|
|
if returnCnt < 0 then
|
|
break
|
|
end
|
|
|
|
handledReturn = handledReturn + 1
|
|
end
|
|
|
|
endp = endp + 1
|
|
end
|
|
|
|
endp = endp - 1
|
|
|
|
-- get block
|
|
return startp, endp, handledReturn
|
|
end
|
|
|
|
local function getPrevLinesByReturn(str, startp, returnCnt)
|
|
local byte
|
|
local handledReturn = 0
|
|
local endp = startp + 1
|
|
|
|
returnCnt = returnCnt or 0
|
|
|
|
-- get prev LineBreak
|
|
while true do
|
|
byte = strbyte(str, endp)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
endp = endp + 1
|
|
end
|
|
|
|
endp = endp - 1
|
|
|
|
local prevReturn
|
|
|
|
-- get prev LineBreak
|
|
while startp > 0 do
|
|
byte = strbyte(str, startp)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
returnCnt = returnCnt - 1
|
|
|
|
if returnCnt < 0 then
|
|
break
|
|
end
|
|
|
|
prevReturn = startp
|
|
|
|
handledReturn = handledReturn + 1
|
|
end
|
|
|
|
startp = startp - 1
|
|
end
|
|
|
|
if not prevReturn or prevReturn > startp + 1 then
|
|
startp = startp + 1
|
|
end
|
|
|
|
-- get block
|
|
return startp, endp, handledReturn
|
|
end
|
|
|
|
local function getOffsetByCursorPos(str, cursorPos)
|
|
if not cursorPos or cursorPos <= 0 then return 0 end
|
|
|
|
local startp = getLines(str, cursorPos, false)
|
|
local byteCnt, isv = 0
|
|
|
|
while startp <= cursorPos do
|
|
startp, isv = skipColor(str, startp)
|
|
|
|
byteCnt = byteCnt + 1
|
|
startp = startp + (isv and 2 or 1)
|
|
end
|
|
|
|
return byteCnt
|
|
end
|
|
|
|
local function getCursorPosByOffset(str, cursorPos, offset)
|
|
local startp, endp = getLines(str, cursorPos)
|
|
startp = startp - 1
|
|
|
|
local byteCnt, isv = 0
|
|
|
|
while byteCnt < offset and startp <= endp do
|
|
startp, isv = skipColor(str, startp)
|
|
|
|
byteCnt = byteCnt + 1
|
|
startp = startp + (isv and 2 or 1)
|
|
end
|
|
|
|
return startp
|
|
end
|
|
|
|
_IndentFunc = _IndentFunc or {}
|
|
_ShiftIndentFunc = _ShiftIndentFunc or {}
|
|
|
|
do
|
|
setmetatable(_IndentFunc, {
|
|
__index = function(self, key)
|
|
if tonumber(key) then
|
|
local tab = floor(tonumber(key))
|
|
|
|
if tab > 0 then
|
|
if not rawget(self, key) then
|
|
rawset(self, key, function(str)
|
|
return strrep(" ", tab) .. str
|
|
end)
|
|
end
|
|
|
|
return rawget(self, key)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
setmetatable(_ShiftIndentFunc, {
|
|
__index = function(self, key)
|
|
if tonumber(key) then
|
|
local tab = floor(tonumber(key))
|
|
|
|
if tab > 0 then
|
|
if not rawget(self, key) then
|
|
rawset(self, key, function(str)
|
|
local _, len = str:find("^%s+")
|
|
|
|
if len and len > 0 then
|
|
return strrep(" ", len - tab) .. str:sub(len + 1, -1)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return rawget(self, key)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
wipe(_IndentFunc)
|
|
wipe(_ShiftIndentFunc)
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- Code Helpers
|
|
------------------------------------------------------
|
|
_INPUTCHAR = _Operation.INPUTCHAR
|
|
_BACKSPACE = _Operation.BACKSPACE
|
|
|
|
_UTF8_Three_Char = 224
|
|
_UTF8_Two_Char = 192
|
|
|
|
_IndentNone = 0
|
|
_IndentRight = 1
|
|
_IndentLeft = 2
|
|
_IndentBoth = 3
|
|
|
|
_EndColor = "|r"
|
|
|
|
-- Token
|
|
_Token = {
|
|
UNKNOWN = 0,
|
|
LINEBREAK = 1,
|
|
SPACE = 2,
|
|
OPERATOR = 3,
|
|
LEFTBRACKET = 4,
|
|
RIGHTBRACKET = 5,
|
|
LEFTPAREN = 6,
|
|
RIGHTPAREN = 7,
|
|
LEFTWING = 8,
|
|
RIGHTWING = 9,
|
|
COMMA = 10,
|
|
SEMICOLON = 11,
|
|
COLON = 12,
|
|
HASH = 13,
|
|
NUMBER = 14,
|
|
COLORCODE_START = 15,
|
|
COLORCODE_END = 16,
|
|
COMMENT = 17,
|
|
STRING = 18,
|
|
ASSIGNMENT = 19,
|
|
EQUALITY = 20,
|
|
PERIOD = 21,
|
|
DOUBLEPERIOD = 22,
|
|
TRIPLEPERIOD = 23,
|
|
LT = 24,
|
|
LTE = 25,
|
|
GT = 26,
|
|
GTE = 27,
|
|
NOTEQUAL = 28,
|
|
TILDE = 29,
|
|
IDENTIFIER = 30,
|
|
VERTICAL = 31,
|
|
}
|
|
|
|
_WordWrap = {
|
|
[0] = _IndentNone, -- UNKNOWN
|
|
_IndentNone, -- LINEBREAK
|
|
_IndentNone, -- SPACE
|
|
_IndentBoth, -- OPERATOR
|
|
_IndentNone, -- LEFTBRACKET
|
|
_IndentNone, -- RIGHTBRACKET
|
|
_IndentNone, -- LEFTPAREN
|
|
_IndentNone, -- RIGHTPAREN
|
|
_IndentNone, -- LEFTWING
|
|
_IndentNone, -- RIGHTWING
|
|
_IndentRight, -- COMMA
|
|
_IndentNone, -- SEMICOLON
|
|
_IndentNone, -- COLON
|
|
_IndentLeft, -- HASH
|
|
_IndentNone, -- NUMBER
|
|
_IndentNone, -- COLORCODE_START
|
|
_IndentNone, -- COLORCODE_END
|
|
_IndentNone, -- COMMENT
|
|
_IndentNone, -- STRING
|
|
_IndentBoth, -- ASSIGNMENT
|
|
_IndentBoth, -- EQUALITY
|
|
_IndentNone, -- PERIOD
|
|
_IndentBoth, -- DOUBLEPERIOD
|
|
_IndentNone, -- TRIPLEPERIOD
|
|
_IndentBoth, -- LT
|
|
_IndentBoth, -- LTE
|
|
_IndentBoth, -- GT
|
|
_IndentBoth, -- GTE
|
|
_IndentBoth, -- NOTEQUAL
|
|
_IndentNone, -- TILDE
|
|
_IndentNone, -- IDENTIFIER
|
|
_IndentNone, -- VERTICAL
|
|
}
|
|
|
|
-- Words
|
|
_KeyWord = {
|
|
["and"] = _IndentNone,
|
|
["break"] = _IndentNone,
|
|
["do"] = _IndentRight,
|
|
["else"] = _IndentBoth,
|
|
["elseif"] = _IndentLeft,
|
|
["end"] = _IndentLeft,
|
|
["false"] = _IndentNone,
|
|
["for"] = _IndentNone,
|
|
["function"] = _IndentRight,
|
|
["if"] = _IndentNone,
|
|
["in"] = _IndentNone,
|
|
["local"] = _IndentNone,
|
|
["nil"] = _IndentNone,
|
|
["not"] = _IndentNone,
|
|
["or"] = _IndentNone,
|
|
["repeat"] = _IndentRight,
|
|
["return"] = _IndentNone,
|
|
["then"] = _IndentRight,
|
|
["true"] = _IndentNone,
|
|
["until"] = _IndentLeft,
|
|
["while"] = _IndentNone,
|
|
-- Loop
|
|
["class"] = _IndentNone,
|
|
["inherit"] = _IndentNone,
|
|
["import"] = _IndentNone,
|
|
["event"] = _IndentNone,
|
|
["property"] = _IndentNone,
|
|
["namespace"] = _IndentNone,
|
|
["enum"] = _IndentNone,
|
|
["struct"] = _IndentNone,
|
|
["interface"] = _IndentNone,
|
|
["extend"] = _IndentNone,
|
|
}
|
|
|
|
-- Special
|
|
_Special = {
|
|
-- LineBreak
|
|
[_Byte.LINEBREAK_N] = _Token.LINEBREAK,
|
|
[_Byte.LINEBREAK_R] = _Token.LINEBREAK,
|
|
|
|
-- Space
|
|
[_Byte.SPACE] = _Token.SPACE,
|
|
[_Byte.TAB] = _Token.SPACE,
|
|
|
|
-- String
|
|
[_Byte.SINGLE_QUOTE] = -1,
|
|
[_Byte.DOUBLE_QUOTE] = -1,
|
|
|
|
-- Operator
|
|
[_Byte.MINUS] = -1, -- need check
|
|
[_Byte.PLUS] = _Token.OPERATOR,
|
|
[_Byte.SLASH] = _Token.OPERATOR,
|
|
[_Byte.ASTERISK] = _Token.OPERATOR,
|
|
[_Byte.PERCENT] = _Token.OPERATOR,
|
|
|
|
-- Compare
|
|
[_Byte.LESSTHAN] = -1,
|
|
[_Byte.GREATERTHAN] = -1,
|
|
[_Byte.EQUALS] = -1,
|
|
|
|
-- Parentheses
|
|
[_Byte.LEFTBRACKET] = -1,
|
|
[_Byte.RIGHTBRACKET] = _Token.RIGHTBRACKET,
|
|
[_Byte.LEFTPAREN] = _Token.LEFTPAREN,
|
|
[_Byte.RIGHTPAREN] = _Token.RIGHTPAREN,
|
|
[_Byte.LEFTWING] = _Token.LEFTWING,
|
|
[_Byte.RIGHTWING] = _Token.RIGHTWING,
|
|
|
|
-- Punctuation
|
|
[_Byte.PERIOD] = -1,
|
|
[_Byte.COMMA] = _Token.COMMA,
|
|
[_Byte.SEMICOLON] = _Token.SEMICOLON,
|
|
[_Byte.COLON] = _Token.COLON,
|
|
[_Byte.TILDE] = -1,
|
|
[_Byte.HASH] = _Token.HASH,
|
|
|
|
-- WOW
|
|
[_Byte.VERTICAL] = -1,
|
|
}
|
|
|
|
-- Code Auto Completion
|
|
_List = ListFrame("Scorpio_CodeEditor_AutoComplete", UIParent)
|
|
_List:SetFrameStrata("TOOLTIP")
|
|
_List:SetWidth(250)
|
|
_List:Hide()
|
|
|
|
_AutoCacheKeys = List()
|
|
_AutoCacheItems = {}
|
|
_AutoWordWeightCache = {}
|
|
_AutoWordMap = {}
|
|
_Recycle = {}
|
|
_RecycleLast = 0
|
|
|
|
_AutoCheckKey = ""
|
|
_AutoCheckWord = ""
|
|
|
|
_BackAutoCache = {}
|
|
|
|
_CommonAutoCompleteList = {}
|
|
|
|
local function compare(t1, t2)
|
|
t1 = t1 or ""
|
|
t2 = t2 or ""
|
|
|
|
local ut1 = strupper(t1)
|
|
local ut2 = strupper(t2)
|
|
|
|
if ut1 == ut2 then
|
|
return t1 < t2
|
|
else
|
|
return ut1 < ut2
|
|
end
|
|
end
|
|
|
|
local function compareWeight(t1, t2)
|
|
return (_AutoWordWeightCache[t1] or 0) < (_AutoWordWeightCache[t2] or 0)
|
|
end
|
|
|
|
local function getIndex(list, name, sIdx, eIdx)
|
|
if not sIdx then
|
|
if not next(list) then return 0 end
|
|
sIdx = 1
|
|
eIdx = #list
|
|
|
|
-- Border check
|
|
if compare(name, list[sIdx]) then
|
|
return 0
|
|
elseif compare(list[eIdx], name) then
|
|
return eIdx
|
|
end
|
|
end
|
|
|
|
if sIdx == eIdx then return sIdx end
|
|
|
|
local f = floor((sIdx + eIdx) / 2)
|
|
|
|
if compare(name, list[f+1]) then
|
|
return getIndex(list, name, sIdx, f)
|
|
else
|
|
return getIndex(list, name, f+1, eIdx)
|
|
end
|
|
end
|
|
|
|
local function transMatchWord(w)
|
|
return "(["..w:lower()..w:upper().."])([%w_]-)"
|
|
end
|
|
|
|
local function applyColor(...)
|
|
local ret = ""
|
|
local word = ""
|
|
local pos = 0
|
|
|
|
local weight = 0
|
|
local n = select('#', ...)
|
|
|
|
for i = 1, n do
|
|
word = select(i, ...)
|
|
|
|
if i % 2 == 1 then
|
|
pos = floor((i+1)/2)
|
|
ret = ret .. Color.WHITE .. word .. Color.CLOSE
|
|
|
|
if word ~= _AutoCheckKey:sub(pos, pos) then
|
|
weight = weight + 1
|
|
end
|
|
else
|
|
ret = ret .. Color.GRAY .. word .. Color.CLOSE
|
|
|
|
if i < n then
|
|
weight = weight + word:len()
|
|
end
|
|
end
|
|
end
|
|
|
|
_AutoWordWeightCache[_AutoCheckWord] = weight
|
|
_AutoWordMap[_AutoCheckWord] = ret
|
|
|
|
return ret
|
|
end
|
|
|
|
local function initCommonList()
|
|
local index = 0
|
|
|
|
for i, v in ipairs{ "math", "string", "bit", "table", "coroutine" } do
|
|
index = i
|
|
_CommonAutoCompleteList[i] = v
|
|
end
|
|
|
|
for k, v in pairs(_G) do
|
|
if type(k) == "string" and (type(v) == "function" and not k:find("_") or type(v) == "table" and k:match("^C_")) then
|
|
index = index + 1
|
|
_CommonAutoCompleteList[index] = k
|
|
|
|
if index % 10 == 0 then
|
|
Continue()
|
|
end
|
|
end
|
|
end
|
|
|
|
Continue()
|
|
List(_CommonAutoCompleteList):QuickSort(compare)
|
|
end
|
|
|
|
local function applyAutoComplete(self)
|
|
local owner = self.__Owner
|
|
_List:Hide()
|
|
|
|
if _CommonAutoCompleteList[1] or owner.AutoCompleteList[1] then
|
|
-- Handle the auto complete
|
|
local fullText = self:GetText()
|
|
local startp, endp = getWord(fullText, self:GetCursorPosition(), true)
|
|
local word = startp and fullText:sub(startp, endp)
|
|
|
|
if startp then
|
|
local sp, ep = getWord(fullText, startp - 1, true)
|
|
|
|
if sp and sp == ep then
|
|
local byte = strbyte(fullText, sp)
|
|
|
|
if byte == _Byte.PERIOD or byte == _Byte.COLON then
|
|
-- handle it later
|
|
if true then return end
|
|
local p = getWord(fullText, sp - 1, true)
|
|
end
|
|
end
|
|
end
|
|
|
|
word = word and removeColor(word)
|
|
|
|
wipe(_AutoCacheKeys)
|
|
wipe(_AutoWordMap)
|
|
wipe(_AutoWordWeightCache)
|
|
|
|
if word and word:match("^[%w_]+$") then
|
|
word = word:sub(1, 16)
|
|
_AutoCheckKey = word
|
|
|
|
word = word:lower()
|
|
|
|
-- Match the auto complete list
|
|
local uword = "^" .. word:gsub("[%w_]", transMatchWord) .. "$"
|
|
local header = word:sub(1, 1)
|
|
|
|
if not header or #header == 0 then return end
|
|
|
|
local lst = owner.AutoCompleteList
|
|
local sIdx = getIndex(lst, header)
|
|
|
|
if sIdx == 0 then sIdx = 1 end
|
|
|
|
for i = sIdx, #lst do
|
|
local value = lst[i]
|
|
if #value == 0 or compare(header, value:sub(1, 1)) then break end
|
|
|
|
_AutoCheckWord = value
|
|
|
|
if _AutoCheckWord:match(uword) then
|
|
_AutoCheckWord:gsub(uword, applyColor)
|
|
|
|
tinsert(_AutoCacheKeys, _AutoCheckWord)
|
|
end
|
|
end
|
|
|
|
lst = _CommonAutoCompleteList
|
|
sIdx = getIndex(lst, header)
|
|
|
|
if sIdx == 0 then sIdx = 1 end
|
|
|
|
for i = sIdx, #lst do
|
|
local value = lst[i]
|
|
if not _AutoWordMap[value] then
|
|
if #value == 0 or compare(header, value:sub(1, 1)) then break end
|
|
|
|
_AutoCheckWord = value
|
|
|
|
if _AutoCheckWord:match(uword) then
|
|
_AutoCheckWord:gsub(uword, applyColor)
|
|
|
|
tinsert(_AutoCacheKeys, _AutoCheckWord)
|
|
end
|
|
end
|
|
end
|
|
|
|
_AutoCacheKeys:QuickSort(compareWeight)
|
|
|
|
if #_AutoCacheKeys == 1 and _AutoCacheKeys[1] == _AutoCheckKey then
|
|
wipe(_AutoCacheKeys)
|
|
end
|
|
end
|
|
|
|
for i, v in ipairs(_AutoCacheKeys) do
|
|
local item = _AutoCacheItems[i]
|
|
if not item then
|
|
if _RecycleLast > 0 then
|
|
item= _Recycle[_RecycleLast]
|
|
_Recycle[_RecycleLast] = nil
|
|
_RecycleLast = _RecycleLast - 1
|
|
else
|
|
item= {}
|
|
end
|
|
end
|
|
|
|
item.checkvalue = v
|
|
item.text = _AutoWordMap[v]
|
|
--item.tiptitle = text.tiptitle
|
|
--item.tiptext = text.tiptext
|
|
|
|
_AutoCacheItems[i] = item
|
|
end
|
|
|
|
for i = #_AutoCacheItems, #_AutoCacheKeys + 1, -1 do
|
|
_RecycleLast = _RecycleLast + 1
|
|
_Recycle[_RecycleLast] = _AutoCacheItems[i]
|
|
_AutoCacheItems[i] = nil
|
|
end
|
|
|
|
-- Refresh the item
|
|
_List.RawItems = _AutoCacheItems
|
|
end
|
|
end
|
|
|
|
local autoCompleteEditor
|
|
local autoCompleteTime
|
|
local taskStarted
|
|
local autoComplete_x
|
|
local autoComplete_y
|
|
local autoComplete_w
|
|
local autoComplete_h
|
|
|
|
local function processAutoComplete()
|
|
while autoCompleteEditor do
|
|
if autoCompleteEditor and autoCompleteTime <= GetTime() then
|
|
applyAutoComplete( autoCompleteEditor )
|
|
|
|
if #_AutoCacheItems > 0 and autoCompleteEditor:HasFocus() then
|
|
_List.CurrentEditor = autoCompleteEditor
|
|
|
|
local owner = autoCompleteEditor.__Owner
|
|
local linenum = owner.__LineNum
|
|
local marginx = linenum:IsShown() and linenum:GetWidth() or 0
|
|
|
|
-- Handle the auto complete
|
|
_List:ClearAllPoints()
|
|
_List:SetPoint("TOPLEFT", owner, autoComplete_x + marginx, autoComplete_y - autoComplete_h + Style[owner].ScrollBar.value)
|
|
_List:Show()
|
|
_List.SelectedIndex = 1
|
|
else
|
|
_List.CurrentEditor = nil
|
|
_List:Hide()
|
|
end
|
|
|
|
autoCompleteEditor = nil
|
|
end
|
|
|
|
Next()
|
|
end
|
|
taskStarted = false
|
|
end
|
|
|
|
local function registerAutoComplete(self, x, y, w, h)
|
|
autoCompleteEditor = self
|
|
autoCompleteTime = GetTime() + self.__Owner.AutoCompleteDelay
|
|
autoComplete_x = x
|
|
autoComplete_y = y
|
|
autoComplete_w = w
|
|
autoComplete_h = h
|
|
|
|
if not taskStarted then
|
|
taskStarted = true
|
|
-- Tiny cost for all editor
|
|
Continue(processAutoComplete)
|
|
end
|
|
end
|
|
|
|
local function initDefinition(self)
|
|
self._IdentifierCache = self._IdentifierCache or {}
|
|
wipe(self._IdentifierCache)
|
|
|
|
local owner = self.__Owner
|
|
|
|
owner:ClearAutoCompleteList()
|
|
|
|
for k in pairs(_KeyWord) do
|
|
self._IdentifierCache[k] = true
|
|
owner:InsertAutoCompleteWord(k)
|
|
end
|
|
|
|
-- Operation Keep List
|
|
self._OperationStack = OperationStack(owner.MaxOperationCount)
|
|
|
|
-- Clear now operation
|
|
self._OperationOnLine = nil
|
|
self._OperationBackUpOnLine = nil
|
|
self._OperationStartOnLine = nil
|
|
self._OperationEndOnLine = nil
|
|
end
|
|
|
|
-- Token
|
|
local function nextNumber(str, pos, noPeriod, cursorPos, trueWord, newPos)
|
|
pos = pos or 1
|
|
newPos = newPos or 0
|
|
cursorPos = cursorPos or 0
|
|
|
|
-- just match, don't care error
|
|
local e = 0
|
|
local startPos = pos
|
|
|
|
local cnt = 0
|
|
local isHex = true
|
|
|
|
-- Number
|
|
while true do
|
|
local npos = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
local byte = strbyte(str, pos)
|
|
if not byte then break end
|
|
|
|
cnt = cnt + 1
|
|
|
|
if cnt == 1 and byte ~= _Byte.ZERO then isHex = false end
|
|
if isHex and cnt == 2 and byte ~= _Byte.x and byte ~= _Byte.X then isHex = false end
|
|
|
|
if isHex then
|
|
if cnt > 2 then
|
|
if (byte >= _Byte.ZERO and byte <= _Byte.NINE) or (byte >= _Byte.a and byte <= _Byte.f) or (byte >= _Byte.A and byte <= _Byte.F) then
|
|
-- continue
|
|
else
|
|
break
|
|
end
|
|
end
|
|
else
|
|
if byte >= _Byte.ZERO and byte <= _Byte.NINE then
|
|
if e == 1 then e = 2 end
|
|
elseif byte == _Byte.E or byte == _Byte.e then
|
|
if e == 0 then e = 1 else break end
|
|
elseif not noPeriod and e == 0 and byte == _Byte.PERIOD then
|
|
-- '.' only work before 'e'
|
|
noPeriod = true
|
|
elseif e == 1 and (byte == _Byte.MINUS or byte == _Byte.PLUS) then
|
|
e = 2
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
end
|
|
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
|
|
return pos, trueWord, newPos
|
|
end
|
|
|
|
local function nextComment(str, pos, cursorPos, trueWord, newPos)
|
|
pos = pos or 1
|
|
newPos = newPos or 0
|
|
cursorPos = cursorPos or 0
|
|
|
|
local markLen = 0
|
|
local dblBrak = false
|
|
local startPos = pos
|
|
|
|
-- Skip the color part
|
|
local npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
local byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.LEFTBRACKET then
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
|
|
while true do
|
|
npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.EQUALS then
|
|
markLen = markLen + 1
|
|
elseif byte == _Byte.LEFTBRACKET then
|
|
dblBrak = true
|
|
else
|
|
break
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
if dblBrak then break end
|
|
end
|
|
end
|
|
|
|
if dblBrak then
|
|
--[==[...]==]
|
|
while true do
|
|
npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.RIGHTBRACKET then
|
|
local len = 0
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
|
|
while true do
|
|
npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.EQUALS then
|
|
len = len + 1
|
|
else
|
|
break
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
end
|
|
|
|
if not byte then
|
|
break
|
|
elseif len == markLen and byte == _Byte.RIGHTBRACKET then
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + (isv and 2 or 1)
|
|
end
|
|
|
|
pos = pos + (isv and 2 or 1)
|
|
end
|
|
else
|
|
--...
|
|
while true do
|
|
npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + (isv and 2 or 1)
|
|
end
|
|
|
|
pos = pos + (isv and 2 or 1)
|
|
end
|
|
end
|
|
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
|
|
return pos, trueWord, newPos
|
|
end
|
|
|
|
local function nextString(str, pos, mark, cursorPos, trueWord, newPos)
|
|
pos = pos or 1
|
|
cursorPos = cursorPos or 0
|
|
newPos = newPos or 0
|
|
|
|
local preEscape = false
|
|
local startPos = pos
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
|
|
while true do
|
|
local npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
local byte = strbyte(str, pos)
|
|
|
|
if not byte or byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
break
|
|
end
|
|
|
|
if not preEscape and byte == mark then
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
break
|
|
end
|
|
|
|
if byte == _Byte.BACKSLASH then
|
|
preEscape = not preEscape
|
|
else
|
|
preEscape = false
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + (isv and 2 or 1)
|
|
end
|
|
|
|
pos = pos + (isv and 2 or 1)
|
|
end
|
|
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
|
|
return pos, trueWord, newPos
|
|
end
|
|
|
|
local function nextIdentifier(str, pos, cursorPos, trueWord, newPos)
|
|
pos = pos or 1
|
|
cursorPos = cursorPos or 0
|
|
newPos = newPos or 0
|
|
|
|
local startPos = pos
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + 1
|
|
|
|
while true do
|
|
local npos, isv = skipColor(str, pos)
|
|
if npos ~= pos then
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
pos = npos
|
|
startPos = pos
|
|
end
|
|
|
|
local byte = strbyte(str, pos)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.SPACE or byte == _Byte.TAB then
|
|
break
|
|
elseif _Special[byte] then
|
|
break
|
|
end
|
|
|
|
if pos <= cursorPos then
|
|
newPos = newPos + 1
|
|
end
|
|
|
|
pos = pos + (isv and 2 or 1)
|
|
end
|
|
|
|
trueWord = trueWord and trueWord .. str:sub(startPos, pos - 1)
|
|
|
|
return pos, trueWord, newPos
|
|
end
|
|
|
|
local function nextToken(str, pos, cursorPos, needTrueWord)
|
|
pos = pos or 1
|
|
cursorPos = cursorPos or 0
|
|
|
|
if not str then return nil, pos end
|
|
|
|
local byte = strbyte(str, pos)
|
|
local start = pos
|
|
|
|
if not byte then return nil, pos end
|
|
|
|
-- Space
|
|
if byte == _Byte.SPACE or byte == _Byte.TAB then
|
|
while true do
|
|
pos = pos + 1
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte or (byte ~= _Byte.SPACE and byte ~= _Byte.TAB) then
|
|
return _Token.SPACE, pos, needTrueWord and str:sub(start, pos - 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Special character
|
|
if _Special[byte] then
|
|
if _Special[byte] >= 0 then
|
|
return _Special[byte], pos + 1, needTrueWord and str:sub(start, pos)
|
|
elseif _Special[byte] == -1 then
|
|
if byte == _Byte.VERTICAL then
|
|
-- '|'
|
|
pos = pos + 1
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.c then
|
|
--[[for i = pos + 1, pos + 8 do
|
|
byte = strbyte(str, i)
|
|
|
|
if i <= pos + 2 then
|
|
if byte ~= _Byte.f then
|
|
return _Token.UNKNOWN, pos
|
|
end
|
|
else
|
|
if not ( ( byte >= _Byte.ZERO and byte <= _Byte.NINE ) or ( byte >= _Byte.a and byte <= _Byte.f ) ) then
|
|
return _Token.UNKNOWN, pos
|
|
end
|
|
end
|
|
end--]]
|
|
|
|
-- mark as '|cff20ff20'
|
|
return _Token.COLORCODE_START, pos + 9, ""
|
|
elseif byte == _Byte.r then
|
|
-- mark '|r'
|
|
return _Token.COLORCODE_END, pos + 1, ""
|
|
elseif byte == _Byte.VERTICAL then
|
|
return _Token.VERTICAL, pos + 1
|
|
else
|
|
-- don't know
|
|
return _Token.UNKNOWN, pos
|
|
end
|
|
elseif byte == _Byte.MINUS then
|
|
-- '-'
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.MINUS then
|
|
-- '--'
|
|
return _Token.COMMENT, nextComment(str, pos + 1, cursorPos, needTrueWord and "--", 2)
|
|
else
|
|
-- '-'
|
|
return _Token.OPERATOR, pos, "-", 1
|
|
end
|
|
elseif byte == _Byte.SINGLE_QUOTE or byte == _Byte.DOUBLE_QUOTE then
|
|
-- ' || "
|
|
return _Token.STRING, nextString(str, pos, byte, cursorPos, needTrueWord and "")
|
|
elseif byte == _Byte.LEFTBRACKET then
|
|
local chkPos = pos
|
|
local dblBrak = false
|
|
|
|
-- '['
|
|
pos = pos + 1
|
|
|
|
while true do
|
|
pos = skipColor(str, pos)
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte then
|
|
break
|
|
elseif byte == _Byte.EQUALS then
|
|
elseif byte == _Byte.LEFTBRACKET then
|
|
dblBrak = true
|
|
break
|
|
else
|
|
break
|
|
end
|
|
|
|
pos = pos + 1
|
|
end
|
|
|
|
if dblBrak then
|
|
return _Token.STRING, nextComment(str, chkPos, cursorPos, needTrueWord and "")
|
|
else
|
|
return _Token.LEFTBRACKET, chkPos + 1, "[", 1
|
|
end
|
|
elseif byte == _Byte.EQUALS then
|
|
-- '='
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.EQUALS then
|
|
return _Token.EQUALITY, pos + 1, "==", 2
|
|
else
|
|
return _Token.ASSIGNMENT, pos, "=", 1
|
|
end
|
|
elseif byte == _Byte.PERIOD then
|
|
-- '.'
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if not byte then
|
|
return _Token.PERIOD, pos, ".", 1
|
|
elseif byte == _Byte.PERIOD then
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.PERIOD then
|
|
return _Token.TRIPLEPERIOD, pos + 1, "...", 3
|
|
else
|
|
return _Token.DOUBLEPERIOD, pos, "..", 2
|
|
end
|
|
elseif byte >= _Byte.ZERO and byte <= _Byte.NINE then
|
|
return _Token.NUMBER, nextNumber(str, pos, true, cursorPos, needTrueWord and ".", 1)
|
|
else
|
|
return _Token.PERIOD, pos, ".", 1
|
|
end
|
|
elseif byte == _Byte.LESSTHAN then
|
|
-- '<'
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.EQUALS then
|
|
return _Token.LTE, pos + 1, "<=", 2
|
|
else
|
|
return _Token.LT, pos, "<", 1
|
|
end
|
|
elseif byte == _Byte.GREATERTHAN then
|
|
-- '>'
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.EQUALS then
|
|
return _Token.GTE, pos + 1, ">=", 2
|
|
else
|
|
return _Token.GT, pos, ">", 1
|
|
end
|
|
elseif byte == _Byte.TILDE then
|
|
-- '~'
|
|
pos = skipColor(str, pos + 1)
|
|
byte = strbyte(str, pos)
|
|
|
|
if byte == _Byte.EQUALS then
|
|
return _Token.NOTEQUAL, pos + 1, "~=", 2
|
|
else
|
|
return _Token.TILDE, pos, "~", 1
|
|
end
|
|
else
|
|
return _Token.UNKNOWN, pos
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Number
|
|
if byte >= _Byte.ZERO and byte <= _Byte.NINE then
|
|
return _Token.NUMBER, nextNumber(str, pos, nil, cursorPos, needTrueWord and "")
|
|
end
|
|
|
|
-- Identifier
|
|
return _Token.IDENTIFIER, nextIdentifier(str, pos, cursorPos, needTrueWord and "")
|
|
end
|
|
|
|
-- Color
|
|
local function formatColor(self, str, cursorPos)
|
|
local pos = 1
|
|
|
|
local token
|
|
local content = {}
|
|
local cindex = 0
|
|
local nextPos
|
|
local trueWord
|
|
local word
|
|
local newPos
|
|
|
|
local owner = self.__Owner
|
|
local defaultColor = tostring(owner.DefaultColor)
|
|
local commentColor = tostring(owner.CommentColor)
|
|
local stringColor = tostring(owner.StringColor)
|
|
local numberColor = tostring(owner.NumberColor)
|
|
local instructionColor = tostring(owner.InstructionColor)
|
|
local functionColor = tostring(owner.FunctionColor)
|
|
local attrcolor = tostring(owner.AttributeColor)
|
|
|
|
cursorPos = cursorPos or 0
|
|
|
|
local chkLength = 0
|
|
local newCurPos = 0
|
|
local prevIdentifier
|
|
|
|
local skipNextColorEnd = false
|
|
|
|
while true do
|
|
token, nextPos, trueWord, newPos = nextToken(str, pos, cursorPos, true)
|
|
if not token then break end
|
|
|
|
word = trueWord or str:sub(pos, nextPos - 1)
|
|
newPos = newPos or word:len()
|
|
cindex = cindex + 1
|
|
|
|
if token == _Token.COLORCODE_START or token == _Token.COLORCODE_END then
|
|
-- clear prev colorcode
|
|
content[cindex] = ""
|
|
elseif token == _Token.IDENTIFIER then
|
|
if _KeyWord[word] then
|
|
prevIdentifier = nil
|
|
content[cindex] = instructionColor .. word .. _EndColor
|
|
else
|
|
prevIdentifier = cindex
|
|
content[cindex] = defaultColor .. word .. _EndColor
|
|
end
|
|
elseif token == _Token.NUMBER then
|
|
prevIdentifier = nil
|
|
content[cindex] = numberColor .. word .. _EndColor
|
|
elseif token == _Token.STRING then
|
|
prevIdentifier = nil
|
|
content[cindex] = stringColor .. word .. _EndColor
|
|
elseif token == _Token.COMMENT then
|
|
prevIdentifier = nil
|
|
content[cindex] = commentColor .. word .. _EndColor
|
|
else
|
|
content[cindex] = word
|
|
if token == _Token.SPACE or token == _Token.LEFTPAREN then
|
|
if prevIdentifier and token == _Token.LEFTPAREN then
|
|
-- Replace the function call's color
|
|
local c = content[prevIdentifier]:sub(#defaultColor + 1, -3)
|
|
if c:match("^__.*__$") then
|
|
content[prevIdentifier] = attrcolor .. c .. _EndColor
|
|
else
|
|
content[prevIdentifier] = functionColor .. c .. _EndColor
|
|
end
|
|
prevIdentifier = nil
|
|
end
|
|
else
|
|
prevIdentifier = nil
|
|
end
|
|
end
|
|
|
|
-- Check cursor position
|
|
if chkLength < cursorPos then
|
|
chkLength = chkLength + nextPos - pos
|
|
|
|
if chkLength >= cursorPos then
|
|
if content[cindex]:len() > 0 and strbyte(content[cindex], 1) == _Byte.VERTICAL and strbyte(content[cindex], 2) ~= _Byte.VERTICAL then
|
|
if chkLength == cursorPos then
|
|
newCurPos = newCurPos + newPos + 12
|
|
else
|
|
newCurPos = newCurPos + newPos + 10
|
|
end
|
|
elseif token == _Token.COLORCODE_END then
|
|
-- skip
|
|
else
|
|
newCurPos = newCurPos + newPos
|
|
end
|
|
else
|
|
newCurPos = newCurPos + content[cindex]:len()
|
|
end
|
|
end
|
|
|
|
pos = nextPos
|
|
end
|
|
|
|
return tblconcat(content), newCurPos
|
|
end
|
|
|
|
-- Indent
|
|
local function formatIndent(self, str, cursorPos)
|
|
local pos = 1
|
|
|
|
local token
|
|
local content = {}
|
|
local cindex = 0
|
|
local indent = 0
|
|
local nextPos
|
|
local word
|
|
local rightSpace = false
|
|
local index
|
|
local prevIndent = 0
|
|
local startIndent = 0
|
|
local prevToken
|
|
local trueWord
|
|
local oposQueue = cursorPos and Queue()
|
|
|
|
local tab = self.__Owner.TabWidth
|
|
|
|
while true do
|
|
prevToken = token
|
|
token, nextPos, trueWord, newPos = nextToken(str, pos, cursorPos, true)
|
|
if not token then break end
|
|
|
|
word = str:sub(pos, nextPos - 1)
|
|
trueWord = trueWord or word
|
|
|
|
if cursorPos then oposQueue:Enqueue(pos, nextPos - 1) end
|
|
|
|
-- Format Indent
|
|
if token == _Token.LEFTWING then
|
|
indent = indent + 1
|
|
startIndent = startIndent + 1
|
|
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = false
|
|
elseif token == _Token.RIGHTWING then
|
|
indent = indent - 1
|
|
if startIndent > 0 then
|
|
startIndent = startIndent - 1
|
|
else
|
|
prevIndent = prevIndent + 1
|
|
end
|
|
if content[cindex] == strrep(" ", tab * (indent+1)) then
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
end
|
|
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = false
|
|
elseif token == _Token.LINEBREAK then
|
|
if rightSpace then
|
|
content[cindex] = content[cindex]:gsub("^(.-)%s*$", "%1")
|
|
if content[cindex] == "" then
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
end
|
|
end
|
|
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
cindex = cindex + 1
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
rightSpace = true
|
|
elseif token == _Token.SPACE then
|
|
if not rightSpace then
|
|
cindex = cindex + 1
|
|
content[cindex] = " "
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
else
|
|
if cursorPos then oposQueue:Enqueue(0) end
|
|
end
|
|
rightSpace = true
|
|
elseif token == _Token.IDENTIFIER then
|
|
if _KeyWord[trueWord] then
|
|
if _KeyWord[trueWord] == _IndentNone then
|
|
indent = indent
|
|
elseif _KeyWord[trueWord] == _IndentRight then
|
|
indent = indent + 1
|
|
startIndent = startIndent + 1
|
|
elseif _KeyWord[trueWord] == _IndentLeft then
|
|
indent = indent - 1
|
|
if startIndent > 0 then
|
|
startIndent = startIndent - 1
|
|
else
|
|
prevIndent = prevIndent + 1
|
|
end
|
|
|
|
if prevToken == _Token.COLORCODE_START then
|
|
index = cindex - 1
|
|
else
|
|
index = cindex
|
|
end
|
|
|
|
if content[index] == strrep(" ", tab * (indent+1)) then
|
|
content[index] = strrep(" ", tab * indent)
|
|
end
|
|
elseif _KeyWord[trueWord] == _IndentBoth then
|
|
indent = indent
|
|
if startIndent == 0 then
|
|
prevIndent = prevIndent + 1
|
|
startIndent = startIndent + 1
|
|
end
|
|
|
|
if prevToken == _Token.COLORCODE_START then
|
|
index = cindex - 1
|
|
else
|
|
index = cindex
|
|
end
|
|
|
|
if content[index] == strrep(" ", tab * indent) then
|
|
content[index] = strrep(" ", tab * (indent-1))
|
|
end
|
|
end
|
|
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
if not self._IdentifierCache[word] then
|
|
self._IdentifierCache[word] = true
|
|
self.__Owner:InsertAutoCompleteWord(word)
|
|
end
|
|
end
|
|
rightSpace = false
|
|
elseif _WordWrap[token] == _IndentNone then
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = false
|
|
elseif _WordWrap[token] == _IndentRight then
|
|
cindex = cindex + 1
|
|
content[cindex] = word .. " "
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = true
|
|
elseif _WordWrap[token] == _IndentLeft then
|
|
if rightSpace then
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = " " .. word
|
|
end
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = false
|
|
elseif _WordWrap[token] == _IndentBoth then
|
|
if rightSpace then
|
|
cindex = cindex + 1
|
|
content[cindex] = word .. " "
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = " " .. word .. " "
|
|
end
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = true
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
if cursorPos then oposQueue:Enqueue(cindex) end
|
|
|
|
rightSpace = false
|
|
end
|
|
|
|
pos = nextPos
|
|
end
|
|
|
|
-- Get the new cursor pos
|
|
pos = 0
|
|
local previdx = 0
|
|
while cursorPos do
|
|
local s, e, idx = oposQueue:Dequeue(3)
|
|
|
|
if not s then
|
|
-- Meet the last position
|
|
cursorPos = pos
|
|
break
|
|
end
|
|
|
|
if e <= cursorPos then
|
|
while previdx < idx do
|
|
previdx = previdx + 1
|
|
pos = pos + #content[previdx]
|
|
end
|
|
else
|
|
if idx > 0 then
|
|
pos = pos + cursorPos - s + 1
|
|
end
|
|
|
|
cursorPos = pos
|
|
break
|
|
end
|
|
end
|
|
|
|
return tblconcat(content), indent, prevIndent, cursorPos
|
|
end
|
|
|
|
local function formatColor4Line(self, startp, endp)
|
|
local cursorPos = self:GetCursorPosition()
|
|
local text = self:GetText()
|
|
local byte
|
|
local line
|
|
|
|
local owner = self.__Owner
|
|
local commentColor = tostring(owner.CommentColor)
|
|
local stringColor = tostring(owner.StringColor)
|
|
|
|
startp = startp or cursorPos
|
|
endp = endp or cursorPos
|
|
|
|
-- Color the line
|
|
startp, endp = getLines(text, startp, endp)
|
|
|
|
-- check prev comment
|
|
local preColorPos = startp - 1
|
|
local token, nextPos
|
|
|
|
while preColorPos > 0 do
|
|
byte = strbyte(text, preColorPos)
|
|
|
|
if byte == _Byte.VERTICAL and strbyte(text, preColorPos - 1) ~= _Byte.VERTICAL then
|
|
-- '|'
|
|
byte = strbyte(text, preColorPos + 1)
|
|
|
|
if byte == _Byte.c then
|
|
if commentColor == text:sub(preColorPos, preColorPos + 9) or stringColor == text:sub(preColorPos, preColorPos + 9) then
|
|
-- check multi-lines comment or string
|
|
token, nextPos = nextToken(text, preColorPos + 10)
|
|
|
|
if token == _Token.COMMENT or token == _Token.STRING then
|
|
if nextPos < startp and nextPos < endp then
|
|
break -- no need to think about prev multi-lines comment and string
|
|
end
|
|
|
|
while token and (nextPos <= endp or nextPos <= startp) do
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
|
|
byte = strbyte(text, nextPos)
|
|
|
|
if not byte or (nextPos - 1 > endp and nextPos - 1 > startp) then
|
|
line, cursorPos = formatColor(self, text:sub(preColorPos, nextPos - 1), cursorPos - preColorPos + 1)
|
|
|
|
self:SetText(replaceBlock(text, preColorPos, nextPos - 1, line))
|
|
|
|
cursorPos = preColorPos + cursorPos - 1
|
|
|
|
return SetCursorPosition(self, cursorPos)
|
|
end
|
|
|
|
token, nextPos = nextToken(text, nextPos)
|
|
|
|
while token do
|
|
if token == _Token.COLORCODE_START or token == _Token.COLORCODE_END then
|
|
line, cursorPos = formatColor(self, text:sub(preColorPos, endp), cursorPos - preColorPos + 1)
|
|
|
|
self:SetText(replaceBlock(text, preColorPos, endp, line))
|
|
|
|
cursorPos = preColorPos + cursorPos - 1
|
|
|
|
return SetCursorPosition(self, cursorPos)
|
|
elseif token == _Token.IDENTIFIER or token == _Token.NUMBER or token == _Token.STRING or token == _Token.COMMENT then
|
|
while token and token ~= _Token.COLORCODE_END do
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
|
|
line, cursorPos = formatColor(self, text:sub(preColorPos, nextPos - 1), cursorPos - preColorPos + 1)
|
|
|
|
self:SetText(replaceBlock(text, preColorPos, nextPos - 1, line))
|
|
|
|
cursorPos = preColorPos + cursorPos - 1
|
|
|
|
return SetCursorPosition(self, cursorPos)
|
|
end
|
|
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
else
|
|
break
|
|
end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
preColorPos = preColorPos - 1
|
|
end
|
|
|
|
nextPos = startp
|
|
token, nextPos = nextToken(text, nextPos)
|
|
|
|
while token and (nextPos <= endp or nextPos <= startp) do
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
|
|
if nextPos - 1 > endp and nextPos - 1 > startp then
|
|
line, cursorPos = formatColor(self, text:sub(startp, nextPos - 1), cursorPos - startp + 1)
|
|
|
|
self:SetText(replaceBlock(text, startp, nextPos - 1, line))
|
|
|
|
return SetCursorPosition(self, startp + cursorPos - 1)
|
|
end
|
|
|
|
while true do
|
|
if not token then
|
|
line, cursorPos = formatColor(self, text:sub(startp, endp), cursorPos - startp + 1)
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, line))
|
|
|
|
return SetCursorPosition(self, startp + cursorPos - 1)
|
|
elseif token == _Token.COLORCODE_START or token == _Token.COLORCODE_END then
|
|
line, cursorPos = formatColor(self, text:sub(startp, endp), cursorPos - startp + 1)
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, line))
|
|
|
|
return SetCursorPosition(self, startp + cursorPos - 1)
|
|
elseif token == _Token.IDENTIFIER or token == _Token.NUMBER or token == _Token.STRING or token == _Token.COMMENT then
|
|
while token and token ~= _Token.COLORCODE_END do
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
|
|
line, cursorPos = formatColor(self, text:sub(startp, nextPos - 1), cursorPos - startp + 1)
|
|
|
|
self:SetText(replaceBlock(text, startp, nextPos - 1, line))
|
|
|
|
return SetCursorPosition(self, startp + cursorPos - 1)
|
|
end
|
|
|
|
token, nextPos = nextToken(text, nextPos)
|
|
end
|
|
end
|
|
|
|
local function formatAll(self, str)
|
|
local owner = self.__Owner
|
|
local pos = 1
|
|
local tab = owner.TabWidth
|
|
|
|
local token
|
|
local content = {}
|
|
local cindex = 0
|
|
local nextPos
|
|
local trueWord
|
|
local word
|
|
|
|
local defaultColor = tostring(owner.DefaultColor)
|
|
local commentColor = tostring(owner.CommentColor)
|
|
local stringColor = tostring(owner.StringColor)
|
|
local numberColor = tostring(owner.NumberColor)
|
|
local instructionColor = tostring(owner.InstructionColor)
|
|
local functionColor = tostring(owner.FunctionColor)
|
|
local attrcolor = tostring(owner.AttributeColor)
|
|
|
|
local indent = 0
|
|
local rightSpace = false
|
|
local index
|
|
local prevIndent = 0
|
|
local startIndent = 0
|
|
local prevToken
|
|
local prevIdentifier
|
|
|
|
while true do
|
|
prevToken = token
|
|
token, nextPos, trueWord = nextToken(str, pos, nil, true)
|
|
if not token then break end
|
|
|
|
word = trueWord or str:sub(pos, nextPos - 1)
|
|
|
|
if token == _Token.COLORCODE_START or token == _Token.COLORCODE_END then
|
|
-- clear prev colorcode
|
|
cindex = cindex + 1
|
|
content[cindex] = ""
|
|
elseif token == _Token.LEFTWING then
|
|
indent = indent + 1
|
|
startIndent = startIndent + 1
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
rightSpace = false
|
|
prevIdentifier = nil
|
|
elseif token == _Token.RIGHTWING then
|
|
indent = indent - 1
|
|
if startIndent > 0 then
|
|
startIndent = startIndent - 1
|
|
else
|
|
prevIndent = prevIndent + 1
|
|
end
|
|
if content[cindex] == strrep(" ", tab * (indent+1)) then
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
end
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
rightSpace = false
|
|
prevIdentifier = nil
|
|
elseif token == _Token.LINEBREAK then
|
|
if rightSpace then
|
|
content[cindex] = content[cindex]:gsub("^(.-)%s*$", "%1")
|
|
if content[cindex] == "" then
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
end
|
|
end
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
cindex = cindex + 1
|
|
content[cindex] = strrep(" ", tab * indent)
|
|
rightSpace = true
|
|
prevIdentifier = nil
|
|
elseif token == _Token.SPACE then
|
|
if not rightSpace then
|
|
cindex = cindex + 1
|
|
content[cindex] = " "
|
|
end
|
|
rightSpace = true
|
|
elseif token == _Token.IDENTIFIER then
|
|
if _KeyWord[word] then
|
|
if _KeyWord[word] == _IndentNone then
|
|
indent = indent
|
|
elseif _KeyWord[word] == _IndentRight then
|
|
indent = indent + 1
|
|
startIndent = startIndent + 1
|
|
elseif _KeyWord[word] == _IndentLeft then
|
|
indent = indent - 1
|
|
if startIndent > 0 then
|
|
startIndent = startIndent - 1
|
|
else
|
|
prevIndent = prevIndent + 1
|
|
end
|
|
|
|
if prevToken == _Token.COLORCODE_START then
|
|
index = cindex - 1
|
|
else
|
|
index = cindex
|
|
end
|
|
|
|
if content[index] == strrep(" ", tab * (indent+1)) then
|
|
content[index] = strrep(" ", tab * indent)
|
|
end
|
|
elseif _KeyWord[word] == _IndentBoth then
|
|
indent = indent
|
|
if startIndent == 0 then
|
|
prevIndent = prevIndent + 1
|
|
startIndent = startIndent + 1
|
|
end
|
|
|
|
if prevToken == _Token.COLORCODE_START then
|
|
index = cindex - 1
|
|
else
|
|
index = cindex
|
|
end
|
|
|
|
if content[index] == strrep(" ", tab * indent) then
|
|
content[index] = strrep(" ", tab * (indent-1))
|
|
end
|
|
end
|
|
cindex = cindex + 1
|
|
content[cindex] = instructionColor .. word .. _EndColor
|
|
prevIdentifier = nil
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = defaultColor .. word .. _EndColor
|
|
prevIdentifier = cindex
|
|
|
|
word = removeColor(word)
|
|
if not self._IdentifierCache[word] then
|
|
self._IdentifierCache[word] = true
|
|
owner:InsertAutoCompleteWord(word)
|
|
end
|
|
end
|
|
rightSpace = false
|
|
else
|
|
if prevIdentifier and token == _Token.LEFTPAREN then
|
|
-- Replace the function call's color
|
|
local c = content[prevIdentifier]:sub(#defaultColor + 1, -3)
|
|
if c:match("^__.*__$") then
|
|
content[prevIdentifier] = attrcolor .. c .. _EndColor
|
|
else
|
|
content[prevIdentifier] = functionColor .. c .. _EndColor
|
|
end
|
|
prevIdentifier = nil
|
|
else
|
|
prevIdentifier = nil
|
|
end
|
|
|
|
if token == _Token.NUMBER then
|
|
cindex = cindex + 1
|
|
content[cindex] = numberColor .. word .. _EndColor
|
|
elseif token == _Token.STRING then
|
|
cindex = cindex + 1
|
|
content[cindex] = stringColor .. word .. _EndColor
|
|
elseif token == _Token.COMMENT then
|
|
cindex = cindex + 1
|
|
content[cindex] = commentColor .. word .. _EndColor
|
|
else
|
|
cindex = cindex + 1
|
|
content[cindex] = word
|
|
end
|
|
|
|
if _WordWrap[token] == _IndentNone then
|
|
rightSpace = false
|
|
elseif _WordWrap[token] == _IndentRight then
|
|
content[#content] = content[#content] .. " "
|
|
rightSpace = true
|
|
elseif _WordWrap[token] == _IndentLeft then
|
|
if not rightSpace then
|
|
content[#content] = " " .. content[#content]
|
|
end
|
|
rightSpace = false
|
|
elseif _WordWrap[token] == _IndentBoth then
|
|
if rightSpace then
|
|
content[cindex] = content[cindex] .. " "
|
|
else
|
|
content[cindex] = " " .. content[cindex] .. " "
|
|
end
|
|
rightSpace = true
|
|
else
|
|
rightSpace = false
|
|
end
|
|
end
|
|
|
|
pos = nextPos
|
|
end
|
|
|
|
return tblconcat(content)
|
|
end
|
|
|
|
local function refreshText(self)
|
|
self:SetText(self:GetText())
|
|
end
|
|
|
|
local function moveOnList(self, offset, key)
|
|
local min, max = 1, _List.ItemCount
|
|
local index = _List.SelectedIndex
|
|
|
|
local first = true
|
|
self:SetAltArrowKeyMode(true)
|
|
|
|
repeat
|
|
index = index + offset
|
|
if index >= min and index <= max then
|
|
_List.SelectedIndex = index
|
|
_List:RefreshScrollView()
|
|
else
|
|
break
|
|
end
|
|
|
|
Delay(first and 0.3 or 0.1)
|
|
first = false
|
|
until not isKeyPressed(self, key)
|
|
|
|
self:SetAltArrowKeyMode(false)
|
|
end
|
|
|
|
function _List:OnItemClick(key)
|
|
local editor = _List.CurrentEditor
|
|
if not editor then return _List:Hide() end
|
|
|
|
local ct = editor:GetText()
|
|
local startp, endp = getWord(ct, editor:GetCursorPosition(), true)
|
|
|
|
wipe(_BackAutoCache)
|
|
|
|
if key then
|
|
for _, v in ipairs(_AutoCacheItems) do
|
|
tinsert(_BackAutoCache, v.checkvalue)
|
|
end
|
|
|
|
_BackAutoCache[0] = _List.SelectedIndex
|
|
|
|
_List:Hide()
|
|
|
|
editor:SetText(replaceBlock(ct, startp, endp, key))
|
|
|
|
SetCursorPosition(editor.__Owner, startp + key:len() - 1)
|
|
|
|
formatColor4Line(editor, startp, startp + key:len() - 1)
|
|
else
|
|
_List:Hide()
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- Key Scan Helper
|
|
------------------------------------------------------
|
|
local function saveOperation(self, isundo)
|
|
if not self._OperationOnLine then return end
|
|
|
|
-- check change
|
|
if self:GetText() ~= self._OperationBackUpOnLine then
|
|
self._OperationStack:Push(self._OperationOnLine, self._OperationBackUpOnLine, self._OperationStartOnLine, self._OperationEndOnLine)
|
|
|
|
if isundo then
|
|
self._OperationStack:Push(_Operation.UNDO_SAVE, self:GetText(), self._HighlightStart, self._HighlightEnd)
|
|
end
|
|
end
|
|
|
|
self._OperationOnLine = nil
|
|
self._OperationBackUpOnLine = nil
|
|
self._OperationStartOnLine = nil
|
|
self._OperationEndOnLine = nil
|
|
end
|
|
|
|
local function newOperation(self, oper)
|
|
if self._OperationOnLine == oper then return end
|
|
|
|
-- save last operation
|
|
saveOperation(self)
|
|
|
|
self._OperationOnLine = oper
|
|
|
|
self._OperationBackUpOnLine = self:GetText()
|
|
self._OperationStartOnLine = self._HighlightStart
|
|
self._OperationEndOnLine = self._HighlightEnd
|
|
end
|
|
|
|
local function undo(self)
|
|
saveOperation(self, true)
|
|
|
|
local oper, text, start, stop = self._OperationStack:Undo()
|
|
if oper then
|
|
self:SetText(text)
|
|
SetCursorPosition(self.__Owner, stop)
|
|
HighlightText(self.__Owner, start, stop)
|
|
end
|
|
end
|
|
|
|
local function redo(self)
|
|
local oper, text, start, stop = self._OperationStack:Redo()
|
|
if oper then
|
|
self:SetText(text)
|
|
SetCursorPosition(self.__Owner, stop)
|
|
HighlightText(self.__Owner, start, stop)
|
|
end
|
|
end
|
|
|
|
local function asyncDelete(self)
|
|
local first = true
|
|
local str = self:GetText()
|
|
local nextPos, isv
|
|
local pos = self:GetCursorPosition() + 1
|
|
|
|
while self._DELETE do
|
|
|
|
if first and self._HighlightStart ~= self._HighlightEnd then
|
|
pos = self._HighlightStart + 1
|
|
nextPos = self._HighlightEnd
|
|
else
|
|
nextPos = pos
|
|
|
|
-- yap, I should do this myself
|
|
if IsControlKeyDown() then
|
|
-- delete words
|
|
local s, e = getWord(str, nextPos, nil, true)
|
|
nextPos = e or pos
|
|
else
|
|
-- delete char
|
|
nextPos, isv= skipColor(str, nextPos)
|
|
if isv then
|
|
nextPos = nextPos + 1
|
|
else
|
|
nextPos = checkUTF8(str, nextPos)
|
|
end
|
|
end
|
|
end
|
|
|
|
if pos > str:len() then break end
|
|
|
|
str = replaceBlock(str, pos, nextPos, "")
|
|
|
|
self:SetText(str)
|
|
|
|
SetCursorPosition(self.__Owner, pos - 1)
|
|
|
|
-- Do for long press
|
|
if first then
|
|
Delay(_FIRST_WAITTIME)
|
|
first = false
|
|
else
|
|
Delay(_CONTINUE_WAITTIME)
|
|
end
|
|
end
|
|
|
|
return formatColor4Line(self)
|
|
end
|
|
|
|
local function asyncBackdpace(self)
|
|
local first = true
|
|
local str = self:GetText()
|
|
local prevPos
|
|
local pos = self:GetCursorPosition()
|
|
local isDelteLine = false
|
|
|
|
while self._BACKSPACE do
|
|
if pos == 0 then break end
|
|
|
|
if first and self._HighlightStart ~= self._HighlightEnd then
|
|
pos = self._HighlightEnd
|
|
prevPos = self._HighlightStart + 1
|
|
else
|
|
prevPos = pos
|
|
|
|
-- yap, I should do this myself
|
|
if IsControlKeyDown() then
|
|
local s, e = getWord(str, prevPos, true)
|
|
prevPos = s or pos
|
|
else
|
|
prevPos = checkPrevUTF8(str, skipPrevColor(str, prevPos))
|
|
|
|
-- Auto pairs check
|
|
local byte = strbyte(str, prevPos)
|
|
|
|
if _AutoPairs[byte] then
|
|
local n = skipColor(str, pos + 1)
|
|
if _AutoPairs[byte] == true then
|
|
if strbyte(str, n) == byte then
|
|
pos = n
|
|
end
|
|
elseif strbyte(str, n) == _AutoPairs[byte] then
|
|
pos = n
|
|
end
|
|
elseif byte == _Byte.LINEBREAK_N or byte == _Byte.LINEBREAK_R then
|
|
isDelteLine = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Delete
|
|
str = replaceBlock(str, prevPos, pos, "")
|
|
|
|
self:SetText(str)
|
|
|
|
pos = prevPos - 1
|
|
SetCursorPosition(self.__Owner, pos)
|
|
|
|
if isDelteLine then
|
|
isDelteLine = false
|
|
updateLineNum(self.__Owner)
|
|
end
|
|
|
|
-- Do for long press
|
|
if first then
|
|
Delay(_FIRST_WAITTIME)
|
|
first = false
|
|
else
|
|
Delay(_CONTINUE_WAITTIME)
|
|
end
|
|
end
|
|
|
|
applyAutoComplete(self)
|
|
return Next(formatColor4Line, self)
|
|
end
|
|
|
|
local function formatAllIndent(self)
|
|
-- format all codes for indent and keep the cursor position
|
|
newOperation(self, _Operation.INDENTFORMAT)
|
|
|
|
local str, _, _, cursor = formatIndent(self, self:GetText(), self:GetCursorPosition())
|
|
self:SetText(str)
|
|
SetCursorPosition(self.__Owner, cursor)
|
|
end
|
|
|
|
_KeyScan:SetScript("OnKeyDown", function (self, key)
|
|
if not key or _SkipKey[key] then return end
|
|
|
|
if self.FocusEditor then
|
|
local editor = self.FocusEditor
|
|
local owner = editor.__Owner
|
|
local cursorPos = editor:GetCursorPosition()
|
|
|
|
local oper = _KEY_OPER[key]
|
|
|
|
if oper then
|
|
local text = editor:GetText()
|
|
|
|
if oper == _Operation.CHANGE_CURSOR then
|
|
local handled = false --editor:Fire("OnDirectionKey", _DirectionKeyEventArgs)
|
|
|
|
if _List:IsShown() then
|
|
local offset = 0
|
|
|
|
handled = true
|
|
|
|
if key == "PAGEUP" then
|
|
offset = - _List.DisplayCount
|
|
elseif key == "PAGEDOWN" then
|
|
offset = _List.DisplayCount
|
|
elseif key == "HOME" then
|
|
offset = 1 - _List.SelectedIndex
|
|
elseif key == "END" then
|
|
offset = _List.ItemCount - _List.SelectedIndex
|
|
elseif key == "UP" then
|
|
offset = -1
|
|
elseif key == "DOWN" then
|
|
offset = 1
|
|
else
|
|
handled = false
|
|
end
|
|
|
|
if offset ~= 0 then
|
|
Continue(moveOnList, editor, offset, key)
|
|
end
|
|
end
|
|
|
|
if handled then
|
|
self.ActiveKeys[key] = true
|
|
return self:SetPropagateKeyboardInput(false)
|
|
end
|
|
|
|
if key == "PAGEUP" then
|
|
local bar = owner:GetChild("ScrollBar")
|
|
local skipLine = floor(owner:GetHeight() / bar:GetValueStep())
|
|
local startp, endp, line = getPrevLinesByReturn(text, cursorPos, skipLine)
|
|
|
|
if line == 0 then return end
|
|
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
end
|
|
|
|
editor:SetCursorPosition(getCursorPosByOffset(text, startp, getOffsetByCursorPos(text, cursorPos)))
|
|
return
|
|
elseif key == "PAGEDOWN" then
|
|
local bar = owner:GetChild("ScrollBar")
|
|
local skipLine = floor(owner:GetHeight() / bar:GetValueStep())
|
|
local startp, endp, line = getLinesByReturn(text, cursorPos, skipLine)
|
|
|
|
if line == 0 then return end
|
|
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
end
|
|
|
|
editor:SetCursorPosition(getCursorPosByOffset(text, endp, getOffsetByCursorPos(text, cursorPos)))
|
|
return
|
|
elseif key == "HOME" then
|
|
local startp, endp = getLines(text, cursorPos)
|
|
local byte
|
|
|
|
if startp - 1 == cursorPos then return end
|
|
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
end
|
|
|
|
local byte = strbyte(text, startp)
|
|
while _Spaces[byte] do
|
|
startp = startp + 1
|
|
byte = strbyte(text, startp)
|
|
end
|
|
|
|
if startp <= cursorPos then
|
|
self.ActiveKeys[key] = true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
editor:SetCursorPosition(startp - 1)
|
|
end
|
|
return
|
|
elseif key == "END" then
|
|
local startp, endp = getLines(editor:GetText(), cursorPos)
|
|
|
|
if endp == cursorPos then return end
|
|
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
end
|
|
|
|
return
|
|
elseif key == "UP" then
|
|
local _, _, line = getPrevLinesByReturn(editor:GetText(), cursorPos, 1)
|
|
|
|
if line > 0 then
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
editor._SKIPCURCHGARROW = true
|
|
end
|
|
end
|
|
|
|
return
|
|
elseif key == "DOWN" then
|
|
local text = editor:GetText()
|
|
local _, _, line= getLinesByReturn(text, cursorPos, 2)
|
|
|
|
if line > 0 then
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
editor._SKIPCURCHGARROW = true
|
|
end
|
|
|
|
if line == 1 then
|
|
-- Check a special error
|
|
local startp, endp = getLines(text, cursorPos)
|
|
local offset, isv = 0
|
|
|
|
startp, isv = skipColor(text, startp)
|
|
|
|
while startp <= cursorPos do
|
|
offset = offset + 1
|
|
startp, isv = skipColor(text, startp + (isv and 2 or 1))
|
|
end
|
|
|
|
editor._DownOffset = offset
|
|
end
|
|
end
|
|
|
|
return
|
|
elseif key == "RIGHT" then
|
|
if cursorPos < text:len() then
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
local skipCtrl = false
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
editor._SKIPCURCHGARROW = true
|
|
elseif editor._HighlightStart ~= editor._HighlightEnd then
|
|
editor._SKIPCURCHG = nil
|
|
editor._SKIPCURCHGARROW = nil
|
|
skipCtrl = true
|
|
self.ActiveKeys[key] = true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
SetCursorPosition(editor.__Owner, editor._HighlightEnd)
|
|
end
|
|
|
|
if not skipCtrl and IsControlKeyDown() then
|
|
local text = editor:GetText()
|
|
local s, e = getWord(text, cursorPos, nil, true)
|
|
|
|
if s and e then
|
|
self.ActiveKeys[key] = true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
editor:SetCursorPosition(e)
|
|
end
|
|
end
|
|
end
|
|
|
|
return
|
|
elseif key == "LEFT" then
|
|
if cursorPos > 0 then
|
|
endPrevKey(editor)
|
|
saveOperation(editor)
|
|
|
|
local skipCtrl = false
|
|
|
|
if IsShiftKeyDown() then
|
|
editor._SKIPCURCHG = cursorPos
|
|
editor._SKIPCURCHGARROW = true
|
|
elseif editor._HighlightStart ~= editor._HighlightEnd then
|
|
editor._SKIPCURCHG = nil
|
|
editor._SKIPCURCHGARROW = nil
|
|
skipCtrl = true
|
|
self.ActiveKeys[key] = true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
SetCursorPosition(editor.__Owner, editor._HighlightStart)
|
|
end
|
|
|
|
if not skipCtrl and IsControlKeyDown() then
|
|
local text = editor:GetText()
|
|
local s, e = getWord(text, cursorPos, true)
|
|
|
|
if s and e then
|
|
self.ActiveKeys[key] = true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
editor:SetCursorPosition(s - 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
if key == "TAB" then
|
|
endPrevKey(editor)
|
|
return newOperation(editor, _Operation.INPUTTAB)
|
|
end
|
|
|
|
if key == "DELETE" then
|
|
if not editor._DELETE and not IsShiftKeyDown() and (editor._HighlightStart ~= editor._HighlightEnd or cursorPos < text:len()) then
|
|
endPrevKey(editor)
|
|
editor._DELETE = true
|
|
newOperation(editor, _Operation.DELETE)
|
|
self.ActiveKeys[key]= true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
return Continue(asyncDelete, editor)
|
|
end
|
|
return
|
|
end
|
|
|
|
if key == "BACKSPACE" then
|
|
if not editor._BACKSPACE and cursorPos > 0 then
|
|
endPrevKey(editor)
|
|
editor._BACKSPACE = cursorPos
|
|
newOperation(editor, _Operation.BACKSPACE)
|
|
self.ActiveKeys[key]= true
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
return Continue(asyncBackdpace, editor)
|
|
end
|
|
return
|
|
end
|
|
|
|
if key == "ENTER" then
|
|
endPrevKey(editor)
|
|
-- editor._SKIPCURCHG = true
|
|
return newOperation(editor, _Operation.ENTER)
|
|
end
|
|
end
|
|
|
|
endPrevKey(editor)
|
|
|
|
-- Don't consider multi-modified keys
|
|
if IsShiftKeyDown() then
|
|
-- shift+
|
|
if IsControlKeyDown() then
|
|
self:SetPropagateKeyboardInput(false)
|
|
|
|
if key == "D" then
|
|
-- duplicate line
|
|
local text = editor:GetText()
|
|
local startp, endp = getLines(text, editor._HighlightStart, editor._HighlightEnd)
|
|
local line = "\n" .. text:sub(startp, endp)
|
|
|
|
newOperation(editor, _Operation.DUPLICATE_LINE)
|
|
editor:SetText(replaceBlock(text, endp + 1, endp, line))
|
|
SetCursorPosition(editor.__Owner, endp + #line)
|
|
|
|
formatColor4Line(editor)
|
|
return saveOperation(editor)
|
|
elseif key == "K" then
|
|
-- Delete line
|
|
local text = editor:GetText()
|
|
local startp, endp = getLines(text, editor._HighlightStart, editor._HighlightEnd)
|
|
|
|
if startp and endp then
|
|
if startp == 1 and startp > endp then return end
|
|
|
|
newOperation(editor, _Operation.DELETE_LINE)
|
|
if endp >= startp then
|
|
-- Delete the current line
|
|
editor:SetText(replaceBlock(text, startp, endp + 1, ""))
|
|
SetCursorPosition(editor.__Owner, startp - 1)
|
|
|
|
formatColor4Line(editor)
|
|
return saveOperation(editor)
|
|
else
|
|
-- Delete the line break
|
|
editor:SetText(replaceBlock(text, startp - 1, startp - 1, ""))
|
|
SetCursorPosition(editor.__Owner, startp - 2)
|
|
|
|
formatColor4Line(editor)
|
|
return saveOperation(editor)
|
|
end
|
|
end
|
|
end
|
|
|
|
return
|
|
end
|
|
elseif IsAltKeyDown() then
|
|
return OnAltKey(editor.__Owner, key)
|
|
elseif IsControlKeyDown() then
|
|
if key == "A" then
|
|
return owner:HighlightText()
|
|
elseif key == "V" then
|
|
editor._InPasting = true
|
|
return newOperation(editor, _Operation.PASTE)
|
|
elseif key == "C" then
|
|
-- do nothing
|
|
return
|
|
elseif key == "Z" then
|
|
return undo(editor)
|
|
elseif key == "Y" then
|
|
return redo(editor)
|
|
elseif key == "X" then
|
|
if editor._HighlightStart ~= editor._HighlightEnd then
|
|
newOperation(editor, _Operation.CUT)
|
|
end
|
|
return
|
|
elseif key == "K" then
|
|
-- Format the text
|
|
self.ActiveKeys[key]= true
|
|
self:SetPropagateKeyboardInput(false)
|
|
return formatAllIndent(editor)
|
|
else
|
|
return OnControlKey(editor.__Owner, key)
|
|
end
|
|
elseif key:find("^F%d+") == 1 then
|
|
return OnFunctionKey(editor.__Owner, key)
|
|
end
|
|
|
|
return newOperation(editor, _Operation.INPUTCHAR)
|
|
end
|
|
end)
|
|
|
|
_KeyScan:SetScript("OnKeyUp", function (self, key)
|
|
self:SetPropagateKeyboardInput(true)
|
|
|
|
self.ActiveKeys[key] = nil
|
|
|
|
if self.FocusEditor then
|
|
if key == "DELETE" then
|
|
self.FocusEditor._DELETE = nil
|
|
end
|
|
if key == "BACKSPACE" then
|
|
self.FocusEditor._BACKSPACE = nil
|
|
end
|
|
end
|
|
end)
|
|
|
|
------------------------------------------------------
|
|
-- event
|
|
------------------------------------------------------
|
|
--- Fired when alt + key is pressed
|
|
event "OnAltKey"
|
|
|
|
--- Fired when ctrl + key is pressed
|
|
event "OnControlKey"
|
|
|
|
--- Fired when function key is pressed
|
|
event "OnFunctionKey"
|
|
|
|
--- Fired when the enter is pressed(only works when the multiline is turn off)
|
|
event "OnEnterPressed"
|
|
|
|
------------------------------------------------------
|
|
-- Method
|
|
------------------------------------------------------
|
|
--- Refresh the editor layout
|
|
__AsyncSingle__()
|
|
function RefreshLayout(self)
|
|
Next()
|
|
|
|
local editor = self.__Editor
|
|
local linenum = self.__LineNum
|
|
|
|
local font, height, flag= editor:GetFont()
|
|
local spacing = editor:GetSpacing()
|
|
|
|
while not font do
|
|
Next()
|
|
font, height, flag = editor:GetFont()
|
|
spacing = editor:GetSpacing()
|
|
end
|
|
|
|
linenum:SetFont(font, height, flag)
|
|
linenum:SetSpacing(spacing)
|
|
|
|
_TestFontString:SetFont(font, height, flag)
|
|
_TestFontString:SetText("XXXX")
|
|
|
|
linenum:SetWidth(_TestFontString:GetStringWidth() + 8)
|
|
linenum:SetText("")
|
|
|
|
local l, r, t, b = editor:GetTextInsets()
|
|
l = (linenum:IsShown() and linenum:GetWidth() or 0) + 5
|
|
Style[editor].textInsets= Inset(l, r, t, b)
|
|
|
|
linenum:ClearAllPoints()
|
|
linenum:SetPoint("LEFT")
|
|
linenum:SetPoint("TOP", editor, "TOP", 0, - t - spacing)
|
|
|
|
Style[self].ScrollBar.valueStep = height + spacing
|
|
|
|
return updateLineNum(self)
|
|
end
|
|
|
|
--- Highlight the text
|
|
function HighlightText(self, startp, endp)
|
|
local editor = self.__Editor
|
|
|
|
local text
|
|
startp = startp or 0
|
|
if not endp then
|
|
text = editor:GetText()
|
|
endp = #text
|
|
end
|
|
|
|
if endp < startp then startp, endp = endp, startp end
|
|
|
|
editor._HighlightStart = startp
|
|
editor._HighlightEnd = endp
|
|
|
|
if startp ~= endp then
|
|
text = text or editor:GetText()
|
|
editor._HighlightText = text:sub(startp + 1, endp)
|
|
else
|
|
editor._HighlightText = ""
|
|
end
|
|
|
|
return editor:HighlightText(startp, endp)
|
|
end
|
|
|
|
--- Set the cursor position
|
|
function SetCursorPosition(self, pos)
|
|
local editor = self.__Editor or self
|
|
editor._OldCursorPosition = pos
|
|
editor:SetCursorPosition(pos)
|
|
return editor.__Owner:HighlightText(pos, pos)
|
|
end
|
|
|
|
--- Set The Text
|
|
function SetText(self, text)
|
|
text = text and tostring(text) or ""
|
|
self = self.__Editor
|
|
|
|
initDefinition(self)
|
|
|
|
self:SetText(formatAll(self, text:gsub("\124", "\124\124")))
|
|
SetCursorPosition(self, 0)
|
|
|
|
newOperation(self, _Operation.INIT)
|
|
end
|
|
|
|
--- Get the text
|
|
function GetText(self)
|
|
return removeColor(self.__Editor:GetText() or ""):gsub("\124\124", "\124")
|
|
end
|
|
|
|
--- Clear the auto complete list
|
|
function ClearAutoCompleteList(self)
|
|
wipe(self.AutoCompleteList)
|
|
end
|
|
|
|
--- Insert word to the auto complete list
|
|
function InsertAutoCompleteWord(self, word)
|
|
if type(word) == "string" and strtrim(word) ~= "" then
|
|
word = strtrim(word)
|
|
word = removeColor(word)
|
|
|
|
local lst = self.AutoCompleteList
|
|
local idx = getIndex(lst, word)
|
|
|
|
if lst[idx] == word then return end
|
|
|
|
tinsert(lst, idx + 1, word)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- Property
|
|
------------------------------------------------------
|
|
--- The tab width, default 4
|
|
property "TabWidth" { type = NaturalNumber, default = 4, handler = refreshText }
|
|
|
|
--- Whether show the line num
|
|
property "ShowLineNum" { type = Bool, default = true, handler = function(self, flag) Style[self].ScrollChild.LineNum.visible = flag; Style[self].LineHolder.visible = flag; self:RefreshLayout() end }
|
|
|
|
--- The default text color
|
|
property "DefaultColor" { type = ColorType, handler = refreshText, default = Color(1, 1, 1) }
|
|
|
|
--- The comment color
|
|
property "CommentColor" { type = ColorType, handler = refreshText, default = Color(0.5, 0.5, 0.5) }
|
|
|
|
--- The string color
|
|
property "StringColor" { type = ColorType, handler = refreshText, default = Color(0, 1, 0) }
|
|
|
|
--- The number color
|
|
property "NumberColor" { type = ColorType, handler = refreshText, default = Color(1, 1, 0) }
|
|
|
|
--- The instruction color
|
|
property "InstructionColor" { type = ColorType, handler = refreshText, default = Color(1, 0.39, 0.09) }
|
|
|
|
--- The function
|
|
property "FunctionColor" { type = ColorType, handler = refreshText, default = Color(0.33, 1, 0.9) }
|
|
|
|
--- The attribute color
|
|
property "AttributeColor" { type = ColorType, handler = refreshText, default = Color(0.52, 0.12, 0.47) }
|
|
|
|
--- The custom auto complete list
|
|
property "AutoCompleteList" { type = System.Collections.List, default = function() return System.Collections.List() end, handler = function(self, value) return value and value:QuickSort(compare) end }
|
|
|
|
--- The delay to show the auto complete
|
|
property "AutoCompleteDelay"{ type = Number, default = 0.5 }
|
|
|
|
--- The max count of the undo list, -1 means no limit, 0 mean no undo/redo operation
|
|
property "MaxOperationCount"{ type = Number, default = -1 }
|
|
|
|
------------------------------------------------------
|
|
-- Event Handler
|
|
------------------------------------------------------
|
|
local function blockShortKey()
|
|
SetOverrideBindingClick(_BtnBlockDown, false, "DOWN", _BtnBlockDown:GetName(), "LeftButton")
|
|
SetOverrideBindingClick(_BtnBlockUp, false, "UP", _BtnBlockUp:GetName(), "LeftButton")
|
|
end
|
|
|
|
local function unBlockShortKey()
|
|
ClearOverrideBindings(_BtnBlockDown)
|
|
ClearOverrideBindings(_BtnBlockUp)
|
|
end
|
|
|
|
local function updateCursorAsync(self)
|
|
return SetCursorPosition(self.__Owner, self:GetCursorPosition())
|
|
end
|
|
|
|
local function onChar(self, char)
|
|
if self._InPasting or not self:HasFocus() then return true end
|
|
|
|
-- Auto Pairs
|
|
local auto = char and _AutoPairs[strbyte(char)]
|
|
local startp, endp
|
|
|
|
if auto then
|
|
-- { [ ( " '
|
|
local cursor = self:GetCursorPosition()
|
|
local text = self:GetText()
|
|
local inner = self._HighlightText
|
|
local rchar = auto == true and char or strchar(auto)
|
|
|
|
if inner ~= "" then
|
|
-- Has High Light Text, Just do the auto pairs
|
|
self:SetText(replaceBlock(text, cursor + 1, cursor, inner .. rchar))
|
|
|
|
local stop = cursor + #inner
|
|
SetCursorPosition(self.__Owner, stop)
|
|
-- HighlightText(self.__Owner, cursor, stop)
|
|
startp, endp = cursor, stop
|
|
else
|
|
local next = skipColor(text, cursor + 1)
|
|
|
|
if not inString(text, auto == true and (cursor -1) or cursor) or strbyte(text, next) == auto then
|
|
if strbyte(text, next) ~= strbyte(char) then
|
|
self:SetText(replaceBlock(text, cursor + 1, cursor, rchar))
|
|
SetCursorPosition(self.__Owner, cursor)
|
|
end
|
|
elseif auto == true then
|
|
if strbyte(text, next) == strbyte(char) then
|
|
self:SetText(replaceBlock(text, cursor + 1, next, ""))
|
|
SetCursorPosition(self.__Owner, cursor)
|
|
end
|
|
end
|
|
end
|
|
elseif auto == false then
|
|
-- ) ] }
|
|
local cursor = self:GetCursorPosition()
|
|
local text = self:GetText()
|
|
local next = skipColor(text, cursor + 1)
|
|
|
|
if strbyte(text, next) == strbyte(char) then
|
|
self:SetText(replaceBlock(text, cursor + 1, next, ""))
|
|
SetCursorPosition(self.__Owner, cursor)
|
|
end
|
|
end
|
|
|
|
self._InCharComposition = false
|
|
|
|
return formatColor4Line(self, startp, endp)
|
|
end
|
|
|
|
local function onCharComposition(self)
|
|
-- Avoid handle cursor change when input with IME
|
|
self._InCharComposition = true
|
|
end
|
|
|
|
local function onCursorChanged(self, x, y, w, h)
|
|
local oper = self._OperationOnLine
|
|
|
|
_List:Hide()
|
|
|
|
if self._InCharComposition then return end
|
|
|
|
if oper == _INPUTCHAR or oper == _BACKSPACE then
|
|
-- Prepare the auto complete but with a delay
|
|
registerAutoComplete(self, x, y, w, h)
|
|
end
|
|
|
|
local cursorPos = self:GetCursorPosition()
|
|
|
|
if cursorPos == self._OldCursorPosition and self._OperationOnLine ~= _Operation.CUT then
|
|
return
|
|
end
|
|
|
|
local owner = self.__Owner
|
|
self._OldCursorPosition = cursorPos
|
|
|
|
if self._InPasting then
|
|
self._InPasting = nil
|
|
local startp, endp = self._HighlightStart, cursorPos
|
|
HighlightText(owner, cursorPos, cursorPos)
|
|
|
|
return formatColor4Line(self, startp, endp)
|
|
elseif self._MouseDownShf == false then
|
|
-- First CursorChanged after mouse down if not press shift
|
|
HighlightText(owner, cursorPos, cursorPos)
|
|
|
|
self._MouseDownCur = cursorPos
|
|
self._MouseDownShf = nil
|
|
elseif self._MouseDownCur then
|
|
if self._MouseDownCur ~= cursorPos then
|
|
-- Hightlight all
|
|
if self._HighlightStart and self._HighlightEnd and self._HighlightStart ~= self._HighlightEnd then
|
|
if self._HighlightStart == self._MouseDownCur then
|
|
HighlightText(owner, cursorPos, self._HighlightEnd)
|
|
elseif self._HighlightEnd == self._MouseDownCur then
|
|
HighlightText(owner, cursorPos, self._HighlightStart)
|
|
else
|
|
HighlightText(owner, self._MouseDownCur, cursorPos)
|
|
end
|
|
else
|
|
HighlightText(owner, self._MouseDownCur, cursorPos)
|
|
end
|
|
|
|
self._MouseDownCur = cursorPos
|
|
end
|
|
elseif self._BACKSPACE or self._DELETE then
|
|
-- Skip
|
|
elseif self._SKIPCURCHG then
|
|
if tonumber(self._SKIPCURCHG) then
|
|
if self._HighlightStart and self._HighlightEnd and self._HighlightStart ~= self._HighlightEnd then
|
|
if self._HighlightStart == self._SKIPCURCHG then
|
|
HighlightText(owner, cursorPos, self._HighlightEnd)
|
|
elseif self._HighlightEnd == self._SKIPCURCHG then
|
|
HighlightText(owner, cursorPos, self._HighlightStart)
|
|
else
|
|
HighlightText(owner, self._SKIPCURCHG, cursorPos)
|
|
end
|
|
else
|
|
HighlightText(owner, self._SKIPCURCHG, cursorPos)
|
|
end
|
|
end
|
|
|
|
if not self._SKIPCURCHGARROW then
|
|
self._SKIPCURCHG = nil
|
|
else
|
|
self._SKIPCURCHG = cursorPos
|
|
end
|
|
elseif self._DownOffset then
|
|
local text = self:GetText()
|
|
local startp, endp = getLines(text, cursorPos)
|
|
|
|
local offset, isv = 0
|
|
|
|
startp, isv = skipColor(text, startp)
|
|
|
|
while startp < endp and offset < self._DownOffset do
|
|
offset = offset + 1
|
|
if offset == self._DownOffset then break end
|
|
startp = skipColor(text, startp + (isv and 2 or 1))
|
|
end
|
|
|
|
self._DownOffset = false
|
|
|
|
if startp ~= cursorPos then
|
|
return SetCursorPosition(owner, startp)
|
|
end
|
|
|
|
HighlightText(owner, cursorPos, cursorPos)
|
|
else
|
|
HighlightText(owner, cursorPos, cursorPos)
|
|
end
|
|
|
|
if self._OperationOnLine == _Operation.CUT then
|
|
formatColor4Line(self)
|
|
saveOperation(self)
|
|
end
|
|
end
|
|
|
|
local function onEditFocusGained(self, ...)
|
|
if _KeyScan.FocusEditor then
|
|
endPrevKey(_KeyScan.FocusEditor)
|
|
end
|
|
|
|
_KeyScan.FocusEditor = self
|
|
_KeyScan:Show()
|
|
|
|
NoCombat(blockShortKey)
|
|
end
|
|
|
|
local function onEditFocusLost(self, ...)
|
|
if _KeyScan.FocusEditor ~= self then return end
|
|
|
|
endPrevKey(self)
|
|
_KeyScan.FocusEditor = nil
|
|
_KeyScan:Hide()
|
|
|
|
NoCombat(unBlockShortKey)
|
|
|
|
_List:Hide()
|
|
end
|
|
|
|
local function onEnterPressed(self)
|
|
--- Handle the auto complete list
|
|
if _List:IsShown() then
|
|
local startp, endp, str
|
|
local text = self:GetText()
|
|
|
|
wipe(_BackAutoCache)
|
|
|
|
startp, endp = getWord(text, self:GetCursorPosition(), true)
|
|
str = _List.SelectedValue
|
|
|
|
if not IsControlKeyDown() and startp and str then
|
|
for _, item in ipairs(_List.RawItems) do
|
|
tinsert(_BackAutoCache, item.checkvalue)
|
|
end
|
|
|
|
_BackAutoCache[0] = _List.SelectedIndex
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
SetCursorPosition(self.__Owner, startp + str:len() - 1)
|
|
|
|
formatColor4Line(self, startp, startp + str:len() - 1)
|
|
|
|
return
|
|
else
|
|
_List:Hide()
|
|
end
|
|
end
|
|
|
|
if not self:IsMultiLine() then return OnEnterPressed(self.__Owner) end
|
|
|
|
-- The default behavior
|
|
if not IsControlKeyDown() then
|
|
self:Insert("\n")
|
|
else
|
|
local _, endp = getLines(self:GetText(), self:GetCursorPosition())
|
|
SetCursorPosition(self.__Owner, endp)
|
|
self:Insert("\n")
|
|
end
|
|
|
|
Next(updateCursorAsync, self)
|
|
|
|
-- On New Line
|
|
local cursorPos = self:GetCursorPosition()
|
|
local text = self:GetText()
|
|
local tabWidth = self.__Owner.TabWidth
|
|
local lstartp, lendp = getLines(text, cursorPos - 1)
|
|
local lstr = lstartp and text:sub(lstartp, lendp)
|
|
local _, len, indent, startp, endp, str, lprevIndent, oprevIndent, prevIndent, lindent, llen
|
|
|
|
lstr = lstr or ""
|
|
_, llen = lstr:find("^%s+")
|
|
llen = llen or 0
|
|
|
|
lstr, lindent, lprevIndent = formatIndent(self, lstr:sub(llen+1, -1))
|
|
|
|
oprevIndent = lprevIndent
|
|
|
|
if lprevIndent == 0 then
|
|
if lindent == 0 then
|
|
self:Insert(strrep(" ", llen))
|
|
elseif lindent > 0 then
|
|
self:Insert(strrep(" ", floor(llen/tabWidth)*tabWidth + tabWidth * lindent))
|
|
end
|
|
else
|
|
startp, endp, str, len = lstartp, lendp, lstr, llen
|
|
|
|
while startp > 1 do
|
|
startp, endp = getLines(text, startp - 2)
|
|
str = startp and text:sub(startp, endp)
|
|
|
|
if startp < endp then
|
|
_, len = str:find("^%s+")
|
|
len = len or 0
|
|
|
|
if len < str:len() then
|
|
str, indent, prevIndent = formatIndent(self, str:sub(len+1, -1))
|
|
|
|
lprevIndent = lprevIndent - indent
|
|
|
|
if lprevIndent <= 0 then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if lprevIndent <= 0 then
|
|
lstr = strrep(" ", floor(len / tabWidth) * tabWidth) .. lstr
|
|
self:SetText(replaceBlock(text, lstartp, lendp, lstr))
|
|
SetCursorPosition(self.__Owner, lstartp + lstr:len())
|
|
self:Insert(strrep(" ", floor(len / tabWidth) * tabWidth + tabWidth * (lindent + oprevIndent)))
|
|
else
|
|
self:SetText(replaceBlock(text, lstartp, lendp, lstr))
|
|
SetCursorPosition(self.__Owner, lstartp + lstr:len())
|
|
self:Insert(strrep(" ", tabWidth * (lindent + oprevIndent)))
|
|
end
|
|
end
|
|
|
|
formatColor4Line(self, lstartp)
|
|
return updateLineNum(self.__Owner)
|
|
end
|
|
|
|
local function onMouseUpAsync(self, btn)
|
|
local prev, curr = self._MouseDownCur, self:GetCursorPosition()
|
|
self._MouseDownCur = nil
|
|
self._MouseDownShf = nil
|
|
|
|
if self._CheckDblClk then
|
|
-- Select the Words
|
|
local prev = self._MouseDownTime
|
|
self._CheckDblClk = false
|
|
self._MouseDownTime = false
|
|
|
|
if prev and (GetTime() - prev) < DOUBLE_CLICK_INTERVAL then
|
|
local startp, endp = getWord(self:GetText(), self:GetCursorPosition())
|
|
if startp and endp then
|
|
SetCursorPosition(self.__Owner, endp)
|
|
return Next(HighlightText, self.__Owner, startp - 1, endp)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onMouseUp(self, btn)
|
|
return Next(onMouseUpAsync, self, btn)
|
|
end
|
|
|
|
local function onMouseDown(self, btn)
|
|
self._MouseDownCur = self:GetCursorPosition()
|
|
|
|
--- Reset the state
|
|
saveOperation(self)
|
|
endPrevKey(self)
|
|
|
|
-- Check Double Click to select word
|
|
if IsShiftKeyDown() then
|
|
self._MouseDownShf = true
|
|
self._MouseDownTime = false
|
|
self._CheckDblClk = false
|
|
else
|
|
self._MouseDownShf = false
|
|
|
|
local now = GetTime()
|
|
if self._MouseDownTime and (now - self._MouseDownTime) < DOUBLE_CLICK_INTERVAL then
|
|
self._CheckDblClk = true
|
|
else
|
|
self._CheckDblClk = false
|
|
self._MouseDownTime = now
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onSizeChanged(self)
|
|
return updateLineNum(self.__Owner)
|
|
end
|
|
|
|
local function onShow(self)
|
|
return self.__Owner:RefreshLayout()
|
|
end
|
|
|
|
local function onTabPressed(self)
|
|
local owner = self.__Owner
|
|
|
|
local text = self:GetText()
|
|
|
|
-- Handle the auto complete
|
|
local startp, endp, str
|
|
|
|
if _List:IsShown() then
|
|
wipe(_BackAutoCache)
|
|
|
|
startp, endp = getWord(text, self:GetCursorPosition(), true)
|
|
str = _List.SelectedValue
|
|
|
|
if startp and str then
|
|
for _, item in ipairs(_List.RawItems) do
|
|
tinsert(_BackAutoCache, item.checkvalue)
|
|
end
|
|
|
|
_BackAutoCache[0] = _List.SelectedIndex
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
SetCursorPosition(self.__Owner, startp + str:len() - 1)
|
|
|
|
formatColor4Line(self, startp, startp + str:len() - 1)
|
|
|
|
return true
|
|
else
|
|
_List:Hide()
|
|
end
|
|
elseif #_BackAutoCache > 0 then
|
|
startp, endp = getWord(text, self:GetCursorPosition(), true)
|
|
str = startp and removeColor(text:sub(startp, endp))
|
|
|
|
if str == _BackAutoCache[_BackAutoCache[0]] then
|
|
_BackAutoCache[0] = _BackAutoCache[0] + 1
|
|
|
|
if _BackAutoCache[0] > #_BackAutoCache then
|
|
_BackAutoCache[0] = 1
|
|
end
|
|
|
|
str = _BackAutoCache[_BackAutoCache[0]]
|
|
|
|
if str then
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
SetCursorPosition(self.__Owner, startp + str:len() - 1)
|
|
|
|
formatColor4Line(self, startp, startp + str:len() - 1)
|
|
|
|
return true
|
|
else
|
|
wipe(_BackAutoCache)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Handle the default behavior
|
|
local startp, endp, str, lineBreak
|
|
local shiftDown = IsShiftKeyDown()
|
|
local cursorPos = self:GetCursorPosition()
|
|
local tabWidth = owner.TabWidth
|
|
|
|
if self._HighlightStart and self._HighlightEnd and self._HighlightEnd > self._HighlightStart then
|
|
startp, endp = getLines(text, self._HighlightStart, self._HighlightEnd)
|
|
str = text:sub(startp, endp)
|
|
|
|
if str:find("\n") then
|
|
lineBreak = "\n"
|
|
elseif str:find("\r") then
|
|
lineBreak = "\r"
|
|
else
|
|
lineBreak = false
|
|
end
|
|
|
|
if lineBreak then
|
|
if shiftDown then
|
|
str = str:gsub("[^".. lineBreak .."]+", _ShiftIndentFunc[tabWidth])
|
|
else
|
|
str = str:gsub("[^".. lineBreak .."]+", _IndentFunc[tabWidth])
|
|
end
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
SetCursorPosition(owner, startp + str:len() - 1)
|
|
|
|
HighlightText(owner, startp - 1, startp + str:len() - 1)
|
|
else
|
|
self:SetText(replaceBlock(text, self._HighlightStart + 1, self._HighlightEnd, strrep(" ", tabWidth)))
|
|
SetCursorPosition(owner, self._HighlightStart + tabWidth)
|
|
end
|
|
else
|
|
startp, endp = getLines(text, cursorPos)
|
|
str = text:sub(startp, endp)
|
|
|
|
if shiftDown then
|
|
local _, len = str:find("^%s+")
|
|
|
|
if len and len > 0 then
|
|
if startp + len - 1 >= cursorPos then
|
|
str = strrep(" ", len - tabWidth) .. str:sub(len + 1, -1)
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
if cursorPos - tabWidth >= startp - 1 then
|
|
SetCursorPosition(owner, cursorPos - tabWidth)
|
|
else
|
|
SetCursorPosition(owner, startp - 1)
|
|
end
|
|
else
|
|
cursorPos = startp - 1 + floor((cursorPos - startp) / tabWidth) * tabWidth
|
|
|
|
SetCursorPosition(owner, cursorPos)
|
|
end
|
|
end
|
|
else
|
|
local byte = strbyte(text, cursorPos + 1)
|
|
|
|
if byte == _Byte.RIGHTBRACKET or byte == _Byte.RIGHTPAREN then
|
|
saveOperation(self)
|
|
|
|
SetCursorPosition(owner, cursorPos + 1)
|
|
else
|
|
local len = tabWidth - (cursorPos - startp + 1) % tabWidth
|
|
|
|
str = str:sub(1, cursorPos - startp + 1) .. strrep(" ", len) .. str:sub(cursorPos - startp + 2, -1)
|
|
|
|
self:SetText(replaceBlock(text, startp, endp, str))
|
|
|
|
SetCursorPosition(owner, cursorPos + len)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onEscapePressed(self)
|
|
if _List:IsShown() then
|
|
_List:Hide()
|
|
return true
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------
|
|
-- Constructor
|
|
------------------------------------------------------
|
|
__Template__{
|
|
LineHolder = Frame,
|
|
|
|
{
|
|
ScrollChild = {
|
|
LineNum = FontString,
|
|
}
|
|
}
|
|
}
|
|
function __ctor(self)
|
|
local editor = self:GetScrollChild():GetChild("EditBox")
|
|
local linenum = self:GetScrollChild():GetChild("LineNum")
|
|
local lineHolder = self:GetChild("LineHolder")
|
|
|
|
lineHolder:SetPoint("TOPLEFT")
|
|
lineHolder:SetPoint("BOTTOMLEFT")
|
|
lineHolder:SetPoint("RIGHT", linenum, "RIGHT")
|
|
|
|
-- Don't use the styles on the line num because there are too many changes on it
|
|
linenum:SetJustifyV("TOP")
|
|
linenum:SetJustifyH("CENTER")
|
|
|
|
editor.OnEscapePressed:SetInitFunction(onEscapePressed)
|
|
|
|
editor.OnChar = editor.OnChar + onChar
|
|
editor.OnCharComposition= editor.OnCharComposition + onCharComposition
|
|
editor.OnCursorChanged = editor.OnCursorChanged + onCursorChanged
|
|
editor.OnEditFocusGained= editor.OnEditFocusGained + onEditFocusGained
|
|
editor.OnEditFocusLost = editor.OnEditFocusLost + onEditFocusLost
|
|
editor.OnEnterPressed = editor.OnEnterPressed + onEnterPressed
|
|
editor.OnMouseDown = editor.OnMouseDown + onMouseDown
|
|
editor.OnMouseUp = editor.OnMouseUp + onMouseUp
|
|
editor.OnTabPressed = editor.OnTabPressed + onTabPressed
|
|
editor.OnShow = editor.OnShow + onShow
|
|
editor.OnSizeChanged = editor.OnSizeChanged + onSizeChanged
|
|
|
|
editor.__Owner = self
|
|
|
|
self.__Editor = editor
|
|
self.__LineNum = linenum
|
|
|
|
initDefinition(editor)
|
|
|
|
if not _CommonAutoCompleteList[1] then
|
|
Next(initCommonList)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-----------------------------------------------------------
|
|
-- UI Property --
|
|
-----------------------------------------------------------
|
|
--- the font settings
|
|
UI.Property {
|
|
name = "Font",
|
|
type = FontType,
|
|
require = CodeEditor,
|
|
get = function(self) return Style[self].ScrollChild.EditBox.font end,
|
|
set = function(self, font) Style[self].ScrollChild.EditBox.font = font; self:RefreshLayout() end,
|
|
override = { "FontObject" },
|
|
}
|
|
|
|
--- the Font object
|
|
UI.Property {
|
|
name = "FontObject",
|
|
type = FontObject,
|
|
require = CodeEditor,
|
|
get = function(self) return Style[self].ScrollChild.EditBox.fontObject end,
|
|
set = function(self, font) Style[self].ScrollChild.EditBox.fontObject = font; self:RefreshLayout() end,
|
|
override = { "Font" },
|
|
}
|
|
|
|
--- whether the text wrap will be indented
|
|
UI.Property {
|
|
name = "Indented",
|
|
type = Boolean,
|
|
require = CodeEditor,
|
|
default = false,
|
|
get = function(self) return Style[self].ScrollChild.EditBox.indented end,
|
|
set = function(self, val) Style[self].ScrollChild.EditBox.indented = val; self:RefreshLayout() end,
|
|
}
|
|
|
|
--- the fontstring's amount of spacing between lines
|
|
UI.Property {
|
|
name = "Spacing",
|
|
type = Number,
|
|
require = CodeEditor,
|
|
default = 0,
|
|
get = function(self) return Style[self].ScrollChild.EditBox.spacing end,
|
|
set = function(self, val) Style[self].ScrollChild.EditBox.spacing = val; self:RefreshLayout() end,
|
|
}
|
|
|
|
--- the insets from the edit box's edges which determine its interactive text area
|
|
UI.Property {
|
|
name = "TextInsets",
|
|
type = Inset,
|
|
require = CodeEditor,
|
|
get = function(self) return Style[self].ScrollChild.EditBox.textInsets end,
|
|
set = function(self, val) Style[self].ScrollChild.EditBox.textInsets = val; self:RefreshLayout() end,
|
|
}
|
|
|
|
-----------------------------------------------------------
|
|
-- CodeEditor Style - Default --
|
|
-----------------------------------------------------------
|
|
Style.UpdateSkin("Default", {
|
|
[CodeEditor] = {
|
|
maxLetters = 0,
|
|
countInvisibleLetters = false,
|
|
textInsets = Inset(5, 5, 3, 3),
|
|
|
|
LineHolder = {
|
|
enableMouse = false,
|
|
frameStrata = "FULLSCREEN",
|
|
|
|
MiddleBGTexture = {
|
|
color = Color(0.12, 0.12, 0.12, 0.8),
|
|
setAllPoints = true,
|
|
alphaMode = "ADD",
|
|
}
|
|
},
|
|
}
|
|
})
|
|
|