myspace / CommonLua /X /XTextEditorPlugins.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
9.18 kB
-- Small XTextEditor plugins should be implemented here
----- XSpellcheckPlugin
DefineClass.XSpellcheckPlugin = {
__parents = { "XTextEditorPlugin" },
UnderlineColor = RGB(255, 20, 20),
}
local word_chars = "[_%w\127-\255\39]" -- count any utf8 extented character as a word character
local nonword_chars = "[^_%w\127-\255\39]"
local word_pattern = "(" .. word_chars .. "*)" .. "(" .. nonword_chars .. "*)"
local whitespace = "[\9\32]"
local multiple_whitespace = "[\9\32][\9\32]+"
local MeasureToCharStart = UIL.MeasureToCharStart
local StretchText = UIL.StretchText
function XSpellcheckPlugin:Init()
LoadDictionary()
end
function XSpellcheckPlugin:OnDrawText(edit, line_idx, text, target_box, font, text_color)
local y = target_box:maxy() - 4
local position_in_text = 0
local substitutions = { {"β€˜", "'"}, {"’", "'"}, {"β€œ", "\""}, {"”", "\""}, {"–", "-"}, {"β€”", "-"}, {"…", "."}, {"βˆ’", "-"}}
for i = 1, #substitutions do
text = text:gsub(substitutions[i][1], substitutions[i][2])
end
-- underline whitespace at the begining of the line
if #text >= 1 and text:sub(1,1):find(whitespace) then
self:Underline(text, target_box:minx(), y, 1, 2, font)
end
-- underline whitespace at the end of the line; lines always end with a space, so the second to last character in the string should be checked
if #text >= 1 and text:sub(#text - 1,#text - 1):find(whitespace) then
self:Underline(text, target_box:minx(), y, #text- 1, #text, font)
end
local untagged, tag, first, last = string.nexttag(text, 1)
-- Check for unmatched closing tag bracket (>) at the beginning of the text and if found, skip the dictionary checks for the text before it
local unmatched_closing_tag = false
local close_tag_idx
if not tag then
local open_tag_idx = string.find(untagged, "<")
close_tag_idx = string.find(untagged, ">")
-- Find the farthest unmatched closing tag bracket (>)
while open_tag_idx and close_tag_idx do
local next_close_tag = string.find(untagged, ">", close_tag_idx + 1)
if next_close_tag and next_close_tag < open_tag_idx then
close_tag_idx = next_close_tag
else
break
end
end
unmatched_closing_tag = (close_tag_idx and not open_tag_idx) or (close_tag_idx and open_tag_idx and close_tag_idx < open_tag_idx)
end
-- try this - if the current position_in_text is < and there's no closing tag until the end skip/break
for word, non_word in text:gmatch(word_pattern) do
-- Check for unmatched opening tag bracket (<) at the end of the text and if found, skip the dictionary checks for the text after it
if string.sub(text, position_in_text, position_in_text) == "<" and not string.find(text, ">", position_in_text + 1) then
break
end
local current_word_end_pos = position_in_text + utf8.len(word)
local new_position_in_text = current_word_end_pos + utf8.len(non_word)
if tag and position_in_text >= first and current_word_end_pos <= last then
-- we have reached a word/text enclosed in < and > - don't check it against the dictionary
if position_in_text < last and new_position_in_text >= last then
-- find the next word in tags
untagged, tag, first, last = string.nexttag(text, new_position_in_text)
end
-- Enter only if we're outside of the unmatched closing tag (if any)
elseif not (unmatched_closing_tag and position_in_text <= close_tag_idx) then
word = word:gsub("^'", ""):gsub("'$", "") -- remove leading and trailing ' characters
local lowercase_word = word:lower()
if not WordInDictionary(word, lowercase_word) then
self:Underline(text, target_box:minx(), y, position_in_text + 1, current_word_end_pos + 1, font)
end
local comma_pos = non_word:find("[,;].*")
local comma, other_chars = non_word:match("([,;])(.*)")
-- , or ; is underlined only if it is not followed by space and is at the end of line
-- however, every line ends with a space and if there's a space after the , or ;, it needs to be checked for eol
if comma and (not other_chars or not other_chars:starts_with("<")) and
(not other_chars or other_chars:sub(1, 1) ~= " " or
(other_chars:sub(1, 1) == " " and current_word_end_pos + comma_pos == text:len()))
then
local underline_start = current_word_end_pos + comma_pos
self:Underline(text, target_box:minx(), y, underline_start, underline_start + 1, font)
end
-- . or : is underlined only if it is not followed by space
local dot_pos = non_word:find("[%.:][^%s]*$")
local dot_idx = dot_pos and current_word_end_pos + dot_pos
if dot_idx and dot_idx ~= text:len() then
local next_char = text:sub(dot_idx + 1, dot_idx + 1)
if next_char ~= "\"" and next_char ~= "\'" and next_char ~= "." and next_char ~= "<" then
self:Underline(text, target_box:minx(), y, dot_idx, dot_idx + 1, font)
end
end
-- multiple whitespace handling
local whitespace_start, whitespace_end = non_word:find(multiple_whitespace)
if whitespace_start then
self:Underline(text, target_box:minx(), y, current_word_end_pos + whitespace_start, current_word_end_pos + whitespace_end + 1, font)
end
end
position_in_text = new_position_in_text
end
end
function XSpellcheckPlugin:Underline(text, x, y, underline_start, underline_end, font)
local x_start = MeasureToCharStart(text, font, underline_start)
local x_end = MeasureToCharStart(text, font, underline_end)
UIL.DrawSolidRect(box(x + x_start, y, x + x_end, y + 2), self.UnderlineColor)
end
function XSpellcheckPlugin:OnRightButtonDown(edit, pt)
local word = edit:GetWordUnderCursor(pt) or ""
local lowercase_word = word:lower()
if not WordInDictionary(word, lowercase_word) then
CreateRealTimeThread(function()
local title = "Add to dictionary"
local text = "Do you want to save '"..word.."' to the dictionary?"
local dialog = StdMessageDialog:new({}, edit.desktop, { question = true, title = title, text = text })
dialog:Open()
dialog:SetZOrder(BaseLoadingScreen.ZOrder + 2) -- above the bug report dialog...
local result, win = dialog:Wait()
if result == "ok" then
local new_entry = word:match("%l") and lowercase_word or word
SpellcheckDict[new_entry] = true
WriteToDictionary(SpellcheckDict)
end
end)
return "break"
end
end
----- XExternalTextEditorPlugin
if FirstLoad then
g_ExternalTextEditorActiveCtrl = false
end
DefineClass.XExternalTextEditorPlugin = {
__parents = { "XTextEditorPlugin" },
}
function XExternalTextEditorPlugin:Init()
g_ExternalTextEditorActiveCtrl = false
end
function XExternalTextEditorPlugin:Done()
if g_ExternalTextEditorActiveCtrl then
g_ExternalTextEditorActiveCtrl = false
end
end
function XExternalTextEditorPlugin:OpenEditor(edit)
g_ExternalTextEditorActiveCtrl = edit
AsyncCreatePath("AppData/editorplugin/")
local file_path = "AppData/editorplugin/" .. config.DefaultExternalTextEditorTempFile
AsyncStringToFile(file_path, edit:GetText())
local cmd = config.DefaultExternelTextEditorCmd
and string.format(config.DefaultExternelTextEditorCmd, ConvertToOSPath(file_path))
or string.format("\"%s\" %s",config.DefaultExternelTextEditorPath, ConvertToOSPath(file_path))
os.execute(cmd)
end
function XExternalTextEditorPlugin:OnShortcut(edit, shortcut, source, ...)
if shortcut == "Ctrl-E" then
self:OpenEditor(edit)
return true
end
end
function XExternalTextEditorPlugin:OnTextChanged(edit)
-- update external file
if g_ExternalTextEditorActiveCtrl == edit then
local file_path = "AppData/editorplugin/" .. config.DefaultExternalTextEditorTempFile
AsyncStringToFile(file_path, edit:GetText())
end
end
function XExternalTextEditorPlugin.ApplyEdit(file, change)
if change == "Modified" then
local err, content = AsyncFileToString(file)
if not err then
if g_ExternalTextEditorActiveCtrl then
g_ExternalTextEditorActiveCtrl:SetText(content)
end
end
end
end
----- XHighlightTextPlugin
DefineClass.XHighlightTextPlugin = {
__parents = { "XTextEditorPlugin" },
HighlightColor = RGB(188, 168, 70),
SingleInstance = false,
highlighted_text = false,
ignore_case = true,
}
local function find_next(str_lower, str, substr, start_pos)
local idx = string.find(str_lower, substr, start_pos, true)
if idx then
return str:sub(start_pos, idx - 1), str:sub(idx, idx + #substr - 1)
else
return str:sub(start_pos)
end
end
function XHighlightTextPlugin:OnAfterDrawText(edit, line_idx, text, target_box, font, text_color)
if not self.highlighted_text or self.highlighted_text == "" then return end
local pos = 1
local text_start = target_box:minx()
local lower_text = self.ignore_case and text:lower() or text
local other, word
repeat
other, word = find_next(lower_text, text, self.highlighted_text, pos)
if word then
local len_word = utf8.len(word)
local len_other = utf8.len(other)
local x1 = MeasureToCharStart(text, font, pos + len_other)
local x2 = MeasureToCharStart(text, font, pos + len_word + len_other)
local word_box = box(text_start + x1, target_box:miny(), text_start + x2, target_box:maxy())
StretchText(word, word_box, font, self.HighlightColor)
pos = pos + len_word + len_other
end
until not word
return true
end