|
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, |
|
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) |
|
|
|
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 |
|
|
|
|
|
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)) |
|
|
|
|
|
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 |
|
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 |
|
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 |
|
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 |
|
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, |
|
|
|
|
|
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, |
|
|
|
|
|
OnTextChanged = function(search_box) |
|
local text = search_box:GetText() |
|
if text ~= "" then |
|
edit:SelectFirstOccurence(text, "ignore_case") |
|
else |
|
self:ClearHighlights() |
|
end |
|
end, |
|
|
|
|
|
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) |
|
end |
|
|