myspace / CommonLua /X /XTextEditorCodePlugin.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
14.5 kB
local MeasureText = UIL.MeasureText
local MeasureToCharStart = UIL.MeasureToCharStart
local StretchText = UIL.StretchText
local DrawSolidRect = UIL.DrawSolidRect
DefineClass.XCodeEditorPlugin = {
__parents = { "XTextEditorPlugin" },
SelectionColor = RGB(78, 140, 187),
KeywordColor = RGB(75, 105, 198),
CommentColor = RGB(0, 128, 0),
QuoteColor = RGB(190, 150, 150),
NumberColor = RGB(255, 141, 141),
ErrorColor = RGB(255, 0, 0),
SingleInstance = false,
highlighted_text = false,
highlight_positions = {},
error_line = false,
error_text = false,
error_needs_drawn = false, -- runtime state while drawing
dim_text = false,
color_string = false,
comment_out = false,
comment_close_string = false,
}
function XCodeEditorPlugin:SetError(line, text)
self.error_line = line
self.error_text = text
end
function XCodeEditorPlugin:VerticalSpaceAfterLine(edit, line)
if self.error_line and line == Min(self.error_line, #edit.lines) then
return edit.font_height
end
end
function XCodeEditorPlugin:OnBeginDraw(edit)
self.color_string = false
self.comment_out = false
self.comment_close_string = false
self.error_needs_drawn = self.error_line ~= false
end
function XCodeEditorPlugin:OnDrawText(edit, line_idx, text, target_box, font, text_color)
if self.error_line and line_idx == Min(self.error_line, #edit.lines) then
local width, height = MeasureText(self.error_text, font)
local pt = target_box:min() + point(0, edit:GetFontHeight())
if pt:y() + height <= edit.content_box:maxy() then
StretchText(self.error_text, sizebox(pt + point(5, 0), point(width, height)), font, self.ErrorColor)
local sx = target_box:sizex()
DrawSolidRect(sizebox(pt, point(sx, 1)), self.ErrorColor)
DrawSolidRect(sizebox(pt + point( 0, -3), point(1, 3)), self.ErrorColor)
DrawSolidRect(sizebox(pt + point(sx - 1, -3), point(1, 3)), self.ErrorColor)
self.error_needs_drawn = false
end
end
end
function XCodeEditorPlugin:OnEndDraw(edit)
-- the error is outside of the viewport
if self.error_needs_drawn then
local win_box = edit.box
local height = edit:GetFontHeight()
local start_idx = edit:LineIdxFromScreenY(win_box:miny())
local target_box
local padding_x1, padding_y1, padding_x2, padding_y2 = ScaleXY(edit.scale, edit.Padding:xyxy())
local size = point(win_box:sizex() + padding_x1 + padding_x2, height)
if self.error_line < start_idx then
target_box = sizebox(win_box:min() - point(padding_x1, padding_y1), size + point(0, padding_y1))
else
target_box = sizebox(point(win_box:minx() - padding_x1, win_box:maxy() - height), size + point(0, padding_y2))
end
-- force fit the box with the error message in the UI viewport
local win = edit
while win do
target_box = FitBoxInBox(target_box, win.box)
win = win.parent
end
local color = GetDarkModeSetting() and const.clrWhite or const.clrBlack
DrawSolidRect(target_box, InterpolateRGB(edit.Background, color, 1, 10))
-- draw error
local font = edit:GetFontId()
local width, height = MeasureText(self.error_text, font)
StretchText(self.error_text, sizebox(target_box:min() + point(5, 0), point(width, height)), font, self.ErrorColor)
self.error_needs_drawn = false
end
end
function XCodeEditorPlugin:ModColor(color)
if self.dim_text then
local r, g, b = GetRGB(color)
local apply_modifier = GetDarkModeSetting() and
function(v) return v * 75 / 100 end or
function(v) return v + (255 - v) * 40 / 100 end
return RGB(apply_modifier(r), apply_modifier(g), apply_modifier(b))
end
return color
end
function XCodeEditorPlugin:SetDimText(dim)
self.dim_text = dim
end
local var_or_func_pattern = "([%a%d_]*)([^%a%d_]*)"
local lua_keywords = {
["goto"] = true,
["and"] = true,
["break"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["then"] = true,
["true"] = true,
["until"] = true,
["while"] = true,
}
function XCodeEditorPlugin:PreProcessCharacter(other, character, comment_position, current, num_bytes)
if not self.comment_out then
if character == '"' then
if not self.color_string then self.color_string = '"'
elseif self.color_string == '"' then self.color_string = false end
elseif character == "'" then
if not self.color_string then self.color_string = "'"
elseif self.color_string == "'" then self.color_string = false end
end
if comment_position and num_bytes == comment_position then
local pos = comment_position + 2
if other:sub(pos, pos) == "[" then
pos = pos + 1
local gap = ""
while other:sub(pos, pos) == "=" do
gap = gap .. "="
pos = pos + 1
end
if other:sub(pos, pos) == "[" then
self.comment_close_string = string.format("]%s]", gap)
end
end
self.comment_out = true
end
end
end
function XCodeEditorPlugin:PostProcessCharacter(other, character, comment_position, current, num_bytes)
local comment_close = self.comment_close_string
if character == "]" and comment_close and current >= #comment_close and other:sub(current - #comment_close + 1):starts_with(comment_close) then
comment_position = other:find_lower("--", current)
self.comment_close_string = false
self.comment_out = false
end
end
function XCodeEditorPlugin:OnDrawLineOutsideView(edit, line_idx, text, above_view)
if above_view then
for word, other in text:gmatch(var_or_func_pattern) do
if other and other ~= " " then
local comment_position = other:find_lower("--")
local current, num_bytes = 1, 0
local character = utf8.sub(other, current, current)
while #character > 0 do
num_bytes = num_bytes + #character
self:PreProcessCharacter(other, character, comment_position, current, num_bytes)
self:PostProcessCharacter(other, character, comment_position, current, num_bytes)
current = current + 1
character = utf8.sub(other, current, current)
end
end
end
end
end
function XCodeEditorPlugin:OnBeforeDrawText(edit, line_idx, text, target_box, font, text_color)
for idx, pos in ipairs(self.highlight_positions[line_idx]) do
local x1 = MeasureToCharStart(text, font, pos + 1)
local x2 = MeasureToCharStart(text, font, pos + utf8.len(self.highlighted_text) + 1)
DrawSolidRect(box(target_box:minx() + x1, target_box:miny(), target_box:minx() + x2, target_box:maxy()), self.SelectionColor)
end
if not self.comment_close_string then
self.comment_out = false
end
local pos = 0
local text_start = target_box:minx()
for word, other in text:gmatch(var_or_func_pattern) do
local len_word = utf8.len(word)
local len_other = utf8.len(other)
local x1 = MeasureToCharStart(text, font, pos + 1)
local x2 = MeasureToCharStart(text, font, pos + len_word + 1)
local word_box = box(text_start + x1, target_box:miny(), text_start + x2, target_box:maxy())
local new_text_color
if not self.comment_out and not self.color_string then
if other and other:starts_with('(') and word ~= "function" then
new_text_color = RGB(160, 170, 150)
elseif tonumber(word) ~= nil then
new_text_color = self.NumberColor
else
new_text_color = lua_keywords[word] and self.KeywordColor or text_color
end
end
StretchText(word, word_box, font, self:ModColor(self.comment_out and self.CommentColor or self.color_string and self.QuoteColor or new_text_color))
if other and other ~= " " then
local comment_position = other:find_lower("--")
local current, num_bytes = 1, 0
local character = utf8.sub(other, current, current)
while #character > 0 do
num_bytes = num_bytes + #character
self:PreProcessCharacter(other, character, comment_position, current, num_bytes)
local x3 = MeasureToCharStart(text, font, pos + len_word + current)
local other_box = box(text_start + x3, target_box:miny(), text_start + x3, target_box:maxy())
if character == "." and tonumber(word) ~= nil then
StretchText(character, other_box, font, self:ModColor(self.NumberColor))
else
StretchText(character, other_box, font, self:ModColor(self.comment_out and self.CommentColor or self.color_string and self.QuoteColor or (character == '"' or character == "'") and self.QuoteColor or text_color))
end
self:PostProcessCharacter(other, character, comment_position, current, num_bytes)
current = current + 1
character = utf8.sub(other, current, current)
end
end
pos = pos + len_word + len_other
end
return true
end
function XCodeEditorPlugin:OnWordSelection(edit, word_to_mark)
self.highlighted_text = word_to_mark
self.highlight_positions = {}
for idx, line in ipairs(edit.lines) do
self.highlight_positions[idx] = {}
local pos = 0
for word, other in line:gmatch(var_or_func_pattern) do
local len = utf8.len(word)
if word == word_to_mark then
local current_pos = #(self.highlight_positions[idx] or empty_table)
self.highlight_positions[idx][current_pos + 1] = pos
end
pos = pos + len + utf8.len(other)
end
end
end
function XCodeEditorPlugin:OnSelectHighlight(edit, search_text, ignore_case)
self.highlighted_text = search_text
self.highlight_positions = {}
for idx, line in ipairs(edit.lines) do
self.highlight_positions[idx] = {}
local pos, len = 1, utf8.len(line)
local text = ignore_case and line:lower() or line
local end_pos
while pos <= len do
pos, end_pos = string.find(text, search_text, pos, true)
if not pos then break end
local current_pos = #(self.highlight_positions[idx] or empty_table)
self.highlight_positions[idx][current_pos + 1] = pos - 1
pos = end_pos + 1
end
end
end
function XCodeEditorPlugin:ClearHighlights()
self.highlight_positions = {}
self.highlighted_text = false
end
XCodeEditorPlugin.OnTextChanged = XCodeEditorPlugin.ClearHighlights
XCodeEditorPlugin.OnKillFocus = XCodeEditorPlugin.ClearHighlights
function XCodeEditorPlugin:OnShortcut(edit, shortcut, source, ...)
local start_line, start_char, end_line, end_char = edit:GetSelectionSortedBounds()
if start_line and end_line then
if shortcut == "Tab" and edit.AllowTabs then
edit:SetCursor(start_line, 0, false)
if end_char == 0 then
end_line = end_line - 1
end
edit:SetCursor(end_line, utf8.len(edit.lines[end_line]), "select", "include last endline")
local new_text = ""
for i = start_line, end_line do
new_text = new_text.."\t"..edit.lines[i]
end
edit:EditOperation(new_text, nil, nil, "keep_selection")
return "break"
elseif shortcut == "Shift-Tab" then
edit:SetCursor(start_line, 0, false)
if end_char == 0 then
end_line = end_line - 1
end
edit:SetCursor(end_line, utf8.len(edit.lines[end_line]), "select", "include last endline")
local new_text = ""
local has_changes = false
for i = start_line, end_line do
if string.sub(edit.lines[i], 1, 1) == "\t" then
new_text = new_text..string.sub(edit.lines[i], 2, edit.lines[i].length)
has_changes = true
else
new_text = new_text..edit.lines[i]
end
end
if has_changes then
edit:EditOperation(new_text, nil, nil, "keep_selection")
return "break"
end
end
end
if shortcut == "Ctrl-F" and edit.Multiline then
self:ActivateSearch(edit)
return "break"
elseif shortcut == "Ctrl-L" then
local line = edit.cursor_line
edit:SetCursor(line, 0)
edit:SetCursor(line, utf8.len(edit.lines[line]), "select", "include last endline")
edit:EditOperation()
return "break"
elseif shortcut == "Ctrl-Shift-Up" then
local line1, char1, line2, char2 = edit:GetSelectionSortedBounds()
local line = line1 or edit.cursor_line
if line > 1 then
if line1 then -- move selection
if char2 ~= 0 then line2 = line2 + 1 end
edit:ExchangeLines(line1 - 1, line1, line2, line1)
edit:SetCursor(line1 - 1, 0)
edit:SetCursor(line2 - 1, 0, "select")
else -- move current line
edit:ExchangeLines(line - 1, line, line + 1, line)
end
end
return "break"
elseif shortcut == "Ctrl-Shift-Down" then
local line1, char1, line2, char2 = edit:GetSelectionSortedBounds()
local line = line2 or edit.cursor_line
if line < #edit.lines or char2 == 0 then
if line2 then -- move selection
if char2 ~= 0 then line2 = line2 + 1 end
edit:ExchangeLines(line1, line2, line2 + 1, line1)
edit:SetCursor(line1 + 1, 0)
edit:SetCursor(line2 + 1, 0, "select")
else -- move current line
edit:ExchangeLines(line, line + 1, line + 2, line)
end
end
return "break"
elseif shortcut == "Escape" then
self:ClearHighlights()
edit:ClearSelection()
return "break"
end
end
function XCodeEditorPlugin:ActivateSearch(edit)
local search_box = XEdit:new({
Margins = box(5, 5, 5, 5),
MinWidth = 150,
MaxWidth = 150,
HAlign = "right",
VAlign = "top",
BorderWidth = 1,
AutoSelectAll = true,
-- close with Escape and Enter
OnKbdChar = function(self, char, ...)
if char == "\r" then
self:SetFocus(false)
return "break"
end
return XEdit.OnKbdChar(self, char, ...)
end,
OnShortcut = function(self, shortcut, ...)
if shortcut == "Escape" then
self:SetFocus(false)
return "break"
end
return XEdit.OnShortcut(self, shortcut, ...)
end,
OnKillFocus = function(self)
if self.window_state ~= "destroying" then
self:Close()
end
end,
-- search and highlight
OnTextChanged = function(search_box)
local text = search_box:GetText()
if text ~= "" then
edit:SelectFirstOccurence(text, "ignore_case")
else
self:ClearHighlights()
end
end,
-- hack(s) to leave the search box in the top-right corner regardless of scroll
parent_offset = false,
OnLayoutComplete = function(self)
self.parent_offset = self.box:min() + point(edit.OffsetX, edit.OffsetY) - edit.box:min()
self:SetBox()
end,
SetBox = function(self, ...)
if self.parent_offset then
local x, y = (edit.box:min() + self.parent_offset):xy()
local w, h = self.box:sizexyz()
XEdit.SetBox(self, x, y, w, h)
else
XEdit.SetBox(self, ...)
end
end,
SetLayoutSpace = function(self, ...)
self.parent_offset = false
return XEdit.SetLayoutSpace(self, ...)
end
}, edit)
local text = edit:GetSelectedText()
local idx = text.find(text, "[\r\n]")
if idx then
text = text:sub(1, idx - 1)
end
search_box:Open()
search_box:SetText(text)
search_box:SetFocus(true)
Msg("XWindowRecreated", search_box) -- dark mode stuff
end