From 93d9e61a033a5bd240134543fa523cab37333ed4 Mon Sep 17 00:00:00 2001 From: Jipok Date: Sun, 5 Dec 2021 20:30:03 +0500 Subject: [PATCH 001/409] Copy/cut whole line if selection empty --- data/core/commands/doc.lua | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index c3063f97..6cdd74a2 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -47,17 +47,26 @@ end local function cut_or_copy(delete) local full_text = "" + local text = "" for idx, line1, col1, line2, col2 in doc():get_selections() do if line1 ~= line2 or col1 ~= col2 then - local text = doc():get_text(line1, col1, line2, col2) + text = doc():get_text(line1, col1, line2, col2) + full_text = full_text == "" and text or (full_text .. "\n" .. text) if delete then doc():delete_to_cursor(idx, 0) end - full_text = full_text == "" and text or (full_text .. "\n" .. text) - doc().cursor_clipboard[idx] = text else - doc().cursor_clipboard[idx] = "" + text = "\n" .. doc().lines[line1]:gsub("\n", "") + full_text = full_text == "" and text or (full_text .. text) + if delete then + if line1 < #doc().lines then + doc():remove(line1, 1, line1 + 1, 1) + else + doc():remove(line1 - 1, math.huge, line1, math.huge) + end + end end + doc().cursor_clipboard[idx] = text end doc().cursor_clipboard["full"] = full_text system.set_clipboard(full_text) @@ -85,6 +94,13 @@ local function set_cursor(x, y, snap_type) end local selection_commands = { + ["doc:select-none"] = function() + local line, col = doc():get_selection() + doc():set_selection(line, col) + end +} + +local commands = { ["doc:cut"] = function() cut_or_copy(true) end, @@ -93,13 +109,6 @@ local selection_commands = { cut_or_copy(false) end, - ["doc:select-none"] = function() - local line, col = doc():get_selection() - doc():set_selection(line, col) - end -} - -local commands = { ["doc:undo"] = function() doc():undo() end, @@ -393,27 +402,27 @@ local commands = { os.remove(filename) core.log("Removed \"%s\"", filename) end, - - ["doc:select-to-cursor"] = function(x, y, clicks) + + ["doc:select-to-cursor"] = function(x, y, clicks) local line1, col1 = select(3, doc():get_selection()) local line2, col2 = dv():resolve_screen_position(x, y) dv().mouse_selecting = { line1, col1, nil } doc():set_selection(line2, col2, line1, col1) end, - + ["doc:set-cursor"] = function(x, y) - set_cursor(x, y, "set") + set_cursor(x, y, "set") end, - - ["doc:set-cursor-word"] = function(x, y) - set_cursor(x, y, "word") - end, - - ["doc:set-cursor-line"] = function(x, y, clicks) - set_cursor(x, y, "lines") + + ["doc:set-cursor-word"] = function(x, y) + set_cursor(x, y, "word") end, - - ["doc:split-cursor"] = function(x, y, clicks) + + ["doc:set-cursor-line"] = function(x, y, clicks) + set_cursor(x, y, "lines") + end, + + ["doc:split-cursor"] = function(x, y, clicks) local line, col = dv():resolve_screen_position(x, y) doc():add_selection(line, col, line, col) end, From acc7ceefd48097dadea36b7ef2ccaa6971501618 Mon Sep 17 00:00:00 2001 From: Jipok Date: Mon, 6 Dec 2021 17:48:30 +0500 Subject: [PATCH 002/409] Correct paste after 'Cut/copy whole line' --- data/core/commands/doc.lua | 18 ++++++++++++++---- data/core/doc/init.lua | 7 ++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 6cdd74a2..c903f0ea 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -52,12 +52,15 @@ local function cut_or_copy(delete) if line1 ~= line2 or col1 ~= col2 then text = doc():get_text(line1, col1, line2, col2) full_text = full_text == "" and text or (full_text .. "\n" .. text) + doc().cursor_clipboard_whole_line[idx] = false if delete then doc():delete_to_cursor(idx, 0) end - else - text = "\n" .. doc().lines[line1]:gsub("\n", "") - full_text = full_text == "" and text or (full_text .. text) + else -- Cut/copy whole line + text = doc().lines[line1] + full_text = full_text == "" and text or (full_text .. text) + doc().cursor_clipboard_whole_line[idx] = true + core.lines_in_clipboard = full_text if delete then if line1 < #doc().lines then doc():remove(line1, 1, line1 + 1, 1) @@ -122,10 +125,17 @@ local commands = { -- If the clipboard has changed since our last look, use that instead if doc().cursor_clipboard["full"] ~= clipboard then doc().cursor_clipboard = {} + doc().cursor_clipboard_whole_line = {} end for idx, line1, col1, line2, col2 in doc():get_selections() do local value = doc().cursor_clipboard[idx] or clipboard - doc():text_input(value:gsub("\r", ""), idx) + if doc().cursor_clipboard_whole_line[idx] == true then + doc():insert(line1, 1, value:gsub("\r", "")) + elseif (core.lines_in_clipboard == clipboard) and (not doc().cursor_clipboard[idx]) then + doc():insert(line1, 1, clipboard:gsub("\r", "")) + else + doc():text_input(value:gsub("\r", ""), idx) + end end end, diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index f324b6d3..3aa76a62 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -34,6 +34,7 @@ function Doc:reset() self.lines = { "\n" } self.selections = { 1, 1, 1, 1 } self.cursor_clipboard = {} + self.cursor_clipboard_whole_line = {} self.undo_stack = { idx = 1 } self.redo_stack = { idx = 1 } self.clean_change_id = 1 @@ -356,7 +357,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time) -- splice lines into line array common.splice(self.lines, line, 1, lines) - + -- keep cursors where they should be for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do if cline1 < line then break end @@ -388,7 +389,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) -- splice line into line array common.splice(self.lines, line1, line2 - line1 + 1, { before .. after }) - + -- move all cursors back if they share a line with the removed text for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do if cline1 < line2 then break end @@ -458,7 +459,7 @@ end function Doc:replace(fn) local has_selection, n = false, 0 for idx, line1, col1, line2, col2 in self:get_selections(true) do - if line1 ~= line2 or col1 ~= col2 then + if line1 ~= line2 or col1 ~= col2 then n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn) has_selection = true end From 4eee123efff4b28feb23b78ea8677ba4e9e8fcd6 Mon Sep 17 00:00:00 2001 From: Jipok Date: Wed, 8 Dec 2021 17:34:10 +0500 Subject: [PATCH 003/409] Make cursor_clipboard globa, not per doc --- data/core/commands/doc.lua | 35 ++++++++++++++++++++++------------- data/core/doc/init.lua | 4 +--- data/core/init.lua | 2 ++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index c903f0ea..cca32ca8 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -48,19 +48,20 @@ end local function cut_or_copy(delete) local full_text = "" local text = "" + core.cursor_clipboard = {} + core.cursor_clipboard_whole_line = {} for idx, line1, col1, line2, col2 in doc():get_selections() do if line1 ~= line2 or col1 ~= col2 then text = doc():get_text(line1, col1, line2, col2) - full_text = full_text == "" and text or (full_text .. "\n" .. text) - doc().cursor_clipboard_whole_line[idx] = false + full_text = full_text == "" and text or (full_text .. " " .. text) + core.cursor_clipboard_whole_line[idx] = false if delete then doc():delete_to_cursor(idx, 0) end else -- Cut/copy whole line text = doc().lines[line1] full_text = full_text == "" and text or (full_text .. text) - doc().cursor_clipboard_whole_line[idx] = true - core.lines_in_clipboard = full_text + core.cursor_clipboard_whole_line[idx] = true if delete then if line1 < #doc().lines then doc():remove(line1, 1, line1 + 1, 1) @@ -69,9 +70,9 @@ local function cut_or_copy(delete) end end end - doc().cursor_clipboard[idx] = text + core.cursor_clipboard[idx] = text end - doc().cursor_clipboard["full"] = full_text + core.cursor_clipboard["full"] = full_text system.set_clipboard(full_text) end @@ -123,16 +124,24 @@ local commands = { ["doc:paste"] = function() local clipboard = system.get_clipboard() -- If the clipboard has changed since our last look, use that instead - if doc().cursor_clipboard["full"] ~= clipboard then - doc().cursor_clipboard = {} - doc().cursor_clipboard_whole_line = {} + if core.cursor_clipboard["full"] ~= clipboard then + core.cursor_clipboard = {} + core.cursor_clipboard_whole_line = {} end + local value, whole_line for idx, line1, col1, line2, col2 in doc():get_selections() do - local value = doc().cursor_clipboard[idx] or clipboard - if doc().cursor_clipboard_whole_line[idx] == true then + if #core.cursor_clipboard_whole_line == (#doc().selections/4) then + value = core.cursor_clipboard[idx] + whole_line = core.cursor_clipboard_whole_line[idx] == true + else + value = clipboard + whole_line = clipboard:find("\n") ~= nil + end + if whole_line then doc():insert(line1, 1, value:gsub("\r", "")) - elseif (core.lines_in_clipboard == clipboard) and (not doc().cursor_clipboard[idx]) then - doc():insert(line1, 1, clipboard:gsub("\r", "")) + if col1 == 1 then + doc():move_to_cursor(idx, #value) + end else doc():text_input(value:gsub("\r", ""), idx) end diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 3aa76a62..7928d2a5 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -33,8 +33,6 @@ end function Doc:reset() self.lines = { "\n" } self.selections = { 1, 1, 1, 1 } - self.cursor_clipboard = {} - self.cursor_clipboard_whole_line = {} self.undo_stack = { idx = 1 } self.redo_stack = { idx = 1 } self.clean_change_id = 1 @@ -199,7 +197,7 @@ function Doc:add_selection(line1, col1, line2, col2, swap) end function Doc:set_selection(line1, col1, line2, col2, swap) - self.selections, self.cursor_clipboard = {}, {} + self.selections = {} self:set_selections(1, line1, col1, line2, col2, swap) end diff --git a/data/core/init.lua b/data/core/init.lua index aadccc66..f6cd91d1 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -648,6 +648,8 @@ function core.init() core.clip_rect_stack = {{ 0,0,0,0 }} core.log_items = {} core.docs = {} + core.cursor_clipboard = {} + core.cursor_clipboard_whole_line = {} core.window_mode = "normal" core.threads = setmetatable({}, { __mode = "k" }) core.blink_start = system.get_time() From 4a563ddea1dca63f089ebd30e73644db567be77c Mon Sep 17 00:00:00 2001 From: Jipok Date: Fri, 10 Dec 2021 19:23:49 +0500 Subject: [PATCH 004/409] Delete old forgotten self.cursor_clipboard --- data/core/doc/init.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 7928d2a5..2c27fd31 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -207,12 +207,10 @@ function Doc:merge_cursors(idx) if self.selections[i] == self.selections[j] and self.selections[i+1] == self.selections[j+1] then common.splice(self.selections, i, 4) - common.splice(self.cursor_clipboard, i, 1) break end end end - if #self.selections <= 4 then self.cursor_clipboard = {} end end local function selection_iterator(invariant, idx) From 6a135f7c06bc5d17f1df7bc24c44b13075050963 Mon Sep 17 00:00:00 2001 From: Jipok Date: Fri, 10 Dec 2021 19:25:28 +0500 Subject: [PATCH 005/409] Make pasting multiple lines from clipboard same way as a single line --- data/core/commands/doc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index cca32ca8..159e03cd 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -135,7 +135,7 @@ local commands = { whole_line = core.cursor_clipboard_whole_line[idx] == true else value = clipboard - whole_line = clipboard:find("\n") ~= nil + whole_line = false end if whole_line then doc():insert(line1, 1, value:gsub("\r", "")) From 7381a13d6fc6e664e7a5b10409cf290edf9c1319 Mon Sep 17 00:00:00 2001 From: Jipok Date: Sun, 12 Dec 2021 02:23:47 +0500 Subject: [PATCH 006/409] Revert "Make pasting multiple lines from clipboard same way as a single line" This reverts commit 6a135f7c06bc5d17f1df7bc24c44b13075050963. --- data/core/commands/doc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 159e03cd..cca32ca8 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -135,7 +135,7 @@ local commands = { whole_line = core.cursor_clipboard_whole_line[idx] == true else value = clipboard - whole_line = false + whole_line = clipboard:find("\n") ~= nil end if whole_line then doc():insert(line1, 1, value:gsub("\r", "")) From ab4ecd515bb69a4cfacf06a2af9fbcf375827ec5 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 18 Dec 2021 10:51:44 +0800 Subject: [PATCH 007/409] multiple improvements to logging - added style.log table - removed contextmenu - use ctrl+click to copy individual log entries - use icon instead of + or - for log items in logview --- data/core/command.lua | 2 +- data/core/commands/log.lua | 26 ++++++++++++++++++++++++++ data/core/init.lua | 22 ++++++++++++++-------- data/core/keymap.lua | 3 ++- data/core/logview.lua | 17 ++++++++--------- data/core/style.lua | 5 +++++ data/plugins/contextmenu.lua | 18 ------------------ 7 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 data/core/commands/log.lua diff --git a/data/core/command.lua b/data/core/command.lua index 2cf851da..07b30fb8 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -64,7 +64,7 @@ end function command.add_defaults() local reg = { "core", "root", "command", "doc", "findreplace", - "files", "drawwhitespace", "dialog" + "files", "drawwhitespace", "dialog", "log" } for _, name in ipairs(reg) do require("core.commands." .. name) diff --git a/data/core/commands/log.lua b/data/core/commands/log.lua new file mode 100644 index 00000000..940ffca4 --- /dev/null +++ b/data/core/commands/log.lua @@ -0,0 +1,26 @@ +local core = require "core" +local command = require "core.command" + +command.add("core.logview", { + ["log:expand-item"] = function() + if core.active_view.hovered_item then + core.active_view:expand_item(core.active_view.hovered_item) + end + end, + ["log:copy-entry"] = function() + if core.active_view.hovered_item then + system.set_clipboard(core.get_log(core.active_view.hovered_item)) + end + end +}) + +command.add(nil, { + ["log:open-as-doc"] = function() + local doc = core.open_doc("logs.txt") + core.root_view:open_doc(doc) + doc:insert(1, 1, core.get_log()) + end, + ["log:copy-to-clipboard"] = function() + system.set_clipboard(core.get_log()) + end +}) diff --git a/data/core/init.lua b/data/core/init.lua index aadccc66..14408393 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1034,15 +1034,21 @@ function core.get_views_referencing_doc(doc) end -local function log(icon, icon_color, fmt, ...) +local function log(level, show, fmt, ...) local text = string.format(fmt, ...) - if icon then - core.status_view:show_message(icon, icon_color, text) + if show then + local s = style.log[level] + core.status_view:show_message(s.icon, s.color, text) end local info = debug.getinfo(2, "Sl") local at = string.format("%s:%d", info.short_src, info.currentline) - local item = { text = text, time = os.time(), at = at } + local item = { + level = level, + text = text, + time = os.time(), + at = at + } table.insert(core.log_items, item) if #core.log_items > config.max_log_items then table.remove(core.log_items, 1) @@ -1052,17 +1058,17 @@ end function core.log(...) - return log("i", style.text, ...) + return log("INFO", true, ...) end function core.log_quiet(...) - return log(nil, nil, ...) + return log("INFO", false, ...) end function core.error(...) - return log("!", style.accent, ...) + return log("ERROR", true, ...) end @@ -1075,7 +1081,7 @@ function core.get_log(i) return table.concat(r, "\n") end local item = type(i) == "number" and core.log_items[i] or i - local text = string.format("[%s] %s at %s", os.date(nil, item.time), item.text, item.at) + local text = string.format("%s [%s] %s at %s", os.date(nil, item.time), item.level, item.text, item.at) if item.info then text = string.format("%s\n%s\n", text, item.info) end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index b076629b..440e518d 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -235,7 +235,8 @@ keymap.add_direct { ["pagedown"] = "doc:move-to-next-page", ["shift+1lclick"] = "doc:select-to-cursor", - ["ctrl+1lclick"] = "doc:split-cursor", + ["ctrl+1lclick"] = { "doc:split-cursor", "log:copy-entry" }, + ["lclick"] = "log:expand-item", ["1lclick"] = "doc:set-cursor", ["2lclick"] = "doc:set-cursor-word", ["3lclick"] = "doc:set-cursor-line", diff --git a/data/core/logview.lua b/data/core/logview.lua index 1ea0e43e..5be2aaca 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -36,6 +36,7 @@ local LogView = View:extend() LogView.context = "session" + function LogView:new() LogView.super.new(self) self.last_item = core.log_items[#core.log_items] @@ -91,14 +92,6 @@ function LogView:on_mouse_moved(px, py, ...) end -function LogView:on_mouse_pressed(button, mx, my, clicks) - if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end - if self.hovered_item then - self:expand_item(self.hovered_item) - end -end - - function LogView:update() local item = core.log_items[#core.log_items] if self.last_item ~= item then @@ -144,7 +137,13 @@ function LogView:draw() x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh) x = x + style.padding.x - x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh) + x = common.draw_text( + style.icon_font, + style.log[item.level].color, + style.log[item.level].icon, + "left", + x, y, w, lh + ) x = x + style.padding.x w = w - (x - self:get_content_offset()) diff --git a/data/core/style.lua b/data/core/style.lua index 3b0d9e35..86f88414 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -66,4 +66,9 @@ style.syntax["function"] = { common.color "#93DDFA" } style.syntax_fonts = {} -- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options) +style.log = { + INFO = { icon = "i", color = style.text }, + ERROR = { icon = "!", color = style.accent } +} + return style diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua index 4b34dfd5..017846ae 100644 --- a/data/plugins/contextmenu.lua +++ b/data/plugins/contextmenu.lua @@ -42,24 +42,6 @@ keymap.add { ["menu"] = "context:show" } -local function copy_log() - local item = core.active_view.hovered_item - if item then - system.set_clipboard(core.get_log(item)) - end -end - -local function open_as_doc() - local doc = core.open_doc("logs.txt") - core.root_view:open_doc(doc) - doc:insert(1, 1, core.get_log()) -end - -menu:register("core.logview", { - { text = "Copy entry", command = copy_log }, - { text = "Open as file", command = open_as_doc } -}) - if require("plugins.scale") then menu:register("core.docview", { { text = "Cut", command = "doc:cut" }, From b5dff196f6440d542b4b7c92ffe278e7ce559762 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 18 Dec 2021 20:10:25 +0800 Subject: [PATCH 008/409] make error icon red --- data/core/style.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/style.lua b/data/core/style.lua index 86f88414..c988b2ef 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -68,7 +68,7 @@ style.syntax_fonts = {} style.log = { INFO = { icon = "i", color = style.text }, - ERROR = { icon = "!", color = style.accent } + ERROR = { icon = "!", color = { common.color "#ff6961" } } } return style From fd3b4334ce20163edb9279a74c7068cbffab42c0 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 18 Dec 2021 20:11:24 +0800 Subject: [PATCH 009/409] add clipping to drawing log items --- data/core/logview.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data/core/logview.lua b/data/core/logview.lua index 5be2aaca..9f45a160 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -130,11 +130,8 @@ function LogView:draw() local th = style.font:get_height() local lh = th + style.padding.y -- for one line - for _, item, x, y, w in self:each_item() do - x = x + style.padding.x - - local time = os.date(nil, item.time) - x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh) + for _, item, x, y, w, h in self:each_item() do + core.push_clip_rect(x, y, w, h) x = x + style.padding.x x = common.draw_text( @@ -164,6 +161,8 @@ function LogView:draw() end _, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh) end + + core.pop_clip_rect() end end From 31df408d93c057eaaa3dfae002db64f1430a6447 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 18 Dec 2021 20:11:50 +0800 Subject: [PATCH 010/409] make timestamp fix sized --- data/core/logview.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/core/logview.lua b/data/core/logview.lua index 9f45a160..90ce564f 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -142,6 +142,13 @@ function LogView:draw() x, y, w, lh ) x = x + style.padding.x + + -- timestamps are always 15% of the width + local tw = w * 15 / 100 + local time = os.date(nil, item.time) + common.draw_text(style.font, style.dim, time, "center", x, y, tw, lh) + x = x + tw + style.padding.x + w = w - (x - self:get_content_offset()) if is_expanded(item) then From becdb99222192a71db2b9c63569f2a635420fbfd Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 18 Dec 2021 22:42:33 +0800 Subject: [PATCH 011/409] center icons to accomodate for size difference --- data/core/logview.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/core/logview.lua b/data/core/logview.lua index 90ce564f..6485c2f6 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -130,6 +130,10 @@ function LogView:draw() local th = style.font:get_height() local lh = th + style.padding.y -- for one line + local iw = math.max( + style.icon_font:get_width(style.log.ERROR.icon), + style.icon_font:get_width(style.log.INFO.icon) + ) for _, item, x, y, w, h in self:each_item() do core.push_clip_rect(x, y, w, h) x = x + style.padding.x @@ -138,8 +142,8 @@ function LogView:draw() style.icon_font, style.log[item.level].color, style.log[item.level].icon, - "left", - x, y, w, lh + "center", + x, y, iw, lh ) x = x + style.padding.x From 1526cd176c6c119b7bb738f1779a86d1addd0f88 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 19 Dec 2021 09:36:24 +0800 Subject: [PATCH 012/409] move selection logic to mouse click --- data/core/commands/log.lua | 12 ------------ data/core/keymap.lua | 3 +-- data/core/logview.lua | 28 +++++++++++++++++++++------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/data/core/commands/log.lua b/data/core/commands/log.lua index 940ffca4..c0111b57 100644 --- a/data/core/commands/log.lua +++ b/data/core/commands/log.lua @@ -1,18 +1,6 @@ local core = require "core" local command = require "core.command" -command.add("core.logview", { - ["log:expand-item"] = function() - if core.active_view.hovered_item then - core.active_view:expand_item(core.active_view.hovered_item) - end - end, - ["log:copy-entry"] = function() - if core.active_view.hovered_item then - system.set_clipboard(core.get_log(core.active_view.hovered_item)) - end - end -}) command.add(nil, { ["log:open-as-doc"] = function() diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 440e518d..b076629b 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -235,8 +235,7 @@ keymap.add_direct { ["pagedown"] = "doc:move-to-next-page", ["shift+1lclick"] = "doc:select-to-cursor", - ["ctrl+1lclick"] = { "doc:split-cursor", "log:copy-entry" }, - ["lclick"] = "log:expand-item", + ["ctrl+1lclick"] = "doc:split-cursor", ["1lclick"] = "doc:set-cursor", ["2lclick"] = "doc:set-cursor-word", ["3lclick"] = "doc:set-cursor-line", diff --git a/data/core/logview.lua b/data/core/logview.lua index 6485c2f6..d7b07e33 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -1,5 +1,6 @@ local core = require "core" local common = require "core.common" +local keymap = require "core.keymap" local style = require "core.style" local View = require "core.view" @@ -78,17 +79,30 @@ function LogView:each_item() end -function LogView:on_mouse_moved(px, py, ...) - LogView.super.on_mouse_moved(self, px, py, ...) - local hovered = false - for _, item, x, y, w, h in self:each_item() do +function LogView:on_mouse_pressed(button, px, py, clicks) + if LogView.super.on_mouse_pressed(self, button, px, py, clicks) then + return true + end + + local index, selected + for i, item, x, y, w, h in self:each_item() do if px >= x and py >= y and px < x + w and py < y + h then - hovered = true - self.hovered_item = item + index = i + selected = item break end end - if not hovered then self.hovered_item = nil end + + if selected then + if keymap.modkeys["ctrl"] then + system.set_clipboard(core.get_log(selected)) + core.status_view:show_message("i", style.text, "copied entry #"..index.." to clipboard.") + else + self:expand_item(selected) + end + end + + return true end From 695c7bf78163e3e554533f25a65c0b5d57b0f211 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 19 Dec 2021 09:36:43 +0800 Subject: [PATCH 013/409] add instruction when logview is open --- data/core/logview.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/core/logview.lua b/data/core/logview.lua index d7b07e33..18299ac5 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -44,6 +44,8 @@ function LogView:new() self.expanding = {} self.scrollable = true self.yoffset = 0 + + core.status_view:show_message("i", style.text, "ctrl+click to copy entry") end From 23f83857c5634a3db3c9945261145d0b9f4f6705 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 24 Nov 2021 05:03:42 +0100 Subject: [PATCH 014/409] Don't search if there are no files --- data/core/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/core/init.lua b/data/core/init.lua index 4a16e6b7..6f3754e4 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -191,6 +191,7 @@ end local function file_search(files, info) local filename, type = info.filename, info.type local inf, sup = 1, #files + if sup <= 0 then return 1, false end while sup - inf > 8 do local curr = math.floor((inf + sup) / 2) if system.path_compare(filename, type, files[curr].filename, files[curr].type) then From e512c576379fd806f83a89ef0a274f6ed95920d7 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 23 Nov 2021 22:35:11 -0500 Subject: [PATCH 015/409] Added an exclusion for lineguide in the commandview. --- data/plugins/lineguide.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 61debbff..41109e4f 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -2,20 +2,22 @@ local config = require "core.config" local style = require "core.style" local DocView = require "core.docview" +local CommandView = require "core.commandview" local draw_overlay = DocView.draw_overlay function DocView:draw_overlay(...) - local ns = ("n"):rep(config.line_limit) - local ss = self:get_font():subpixel_scale() - local offset = self:get_font():get_width_subpixel(ns) / ss - local x = self:get_line_screen_position(1) + offset - local y = self.position.y - local w = math.ceil(SCALE * 1) - local h = self.size.y - - local color = style.guide or style.selection - renderer.draw_rect(x, y, w, h, color) - + if not self:is(CommandView) then + local ns = ("n"):rep(config.line_limit) + local ss = self:get_font():subpixel_scale() + local offset = self:get_font():get_width_subpixel(ns) / ss + local x = self:get_line_screen_position(1) + offset + local y = self.position.y + local w = math.ceil(SCALE * 1) + local h = self.size.y + + local color = style.guide or style.selection + renderer.draw_rect(x, y, w, h, color) + end draw_overlay(self, ...) end From 405bd1c2bd5c18b32e7a81adf1857897cec42d27 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 26 Nov 2021 13:45:13 +0100 Subject: [PATCH 016/409] Fix logic in project's file insertion The function "file_search" in core.init was sometimes giving a wrong index value, off by one. The problem happened for example when the entry to search was "less than" the first entry, the function returned a value of two instead of one as expected. The bug was easily observed creating a new directory with a name that comes as the first in alphabetical order within the project. --- data/core/init.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 6f3754e4..f5d38ac0 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -191,7 +191,6 @@ end local function file_search(files, info) local filename, type = info.filename, info.type local inf, sup = 1, #files - if sup <= 0 then return 1, false end while sup - inf > 8 do local curr = math.floor((inf + sup) / 2) if system.path_compare(filename, type, files[curr].filename, files[curr].type) then @@ -200,12 +199,12 @@ local function file_search(files, info) inf = curr end end - repeat + while inf <= sup and not system.path_compare(filename, type, files[inf].filename, files[inf].type) do if files[inf].filename == filename then return inf, true end inf = inf + 1 - until inf > sup or system.path_compare(filename, type, files[inf].filename, files[inf].type) + end return inf, false end From 37c00c877a5e21827b00c8f134da7ba7dc507abd Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 20 Dec 2021 11:03:49 +0100 Subject: [PATCH 017/409] Ensure TreeView cache entry is removed on delete Address issue: https://github.com/lite-xl/lite-xl/issues/689 Attempt to provide a more accurate fix to commit: 59f64088e1e88f2f2a29e4d5418ccdf781fdc12e For this latter what happens is that any change inside a directory cause the corresponding entry to be folded in the TreeView. The new change is more accurate because we remove only the stale entry corresponding to the delete event and we do not reset the cache of the parent directory using the modify event. --- data/core/init.lua | 5 +++-- data/plugins/treeview.lua | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index f5d38ac0..9e4676f0 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1164,8 +1164,8 @@ end -- no-op but can be overrided by plugins -function core.on_dirmonitor_modify() -end +function core.on_dirmonitor_modify() end +function core.on_dirmonitor_delete() end function core.on_dir_change(watch_id, action, filepath) @@ -1174,6 +1174,7 @@ function core.on_dir_change(watch_id, action, filepath) core.dir_rescan_add_job(dir, filepath) if action == "delete" then project_scan_remove_file(dir, filepath) + core.on_dirmonitor_delete(dir, filepath) elseif action == "create" then project_scan_add_file(dir, filepath) core.on_dirmonitor_modify(dir, filepath); diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 77b6732f..2909768c 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -42,6 +42,19 @@ function TreeView:new() self.target_size = default_treeview_size self.cache = {} self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 } + self:add_core_hooks() +end + + +function TreeView:add_core_hooks() + -- When a file or directory is deleted we delete the corresponding cache entry + -- because if the entry is recreated we may use wrong information from cache. + local on_delete = core.on_dirmonitor_delete + core.on_dirmonitor_delete = function(dir, filepath) + local cache = self.cache[dir.name] + if cache then cache[filepath] = nil end + on_delete(dir, filepath) + end end From 29318be9c71e1be290e7507e9f8b1c9445aad1b0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 11 Dec 2021 03:43:33 +0100 Subject: [PATCH 018/409] Consume unmatched character correctly We must consume the whole UTF-8 character, not just a single byte. --- data/core/tokenizer.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index d95baeb1..57c17a0b 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -237,8 +237,13 @@ function tokenizer.tokenize(incoming_syntax, text, state) -- consume character if we didn't match if not matched then - push_token(res, "normal", text:sub(i, i)) - i = i + 1 + local n = 0 + -- reach the next character + while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do + n = n + 1 + end + push_token(res, "normal", text:sub(i, i + n)) + i = i + n + 1 end end From c353dd6eda02b873de847c29ddb5df9c45c0b6c0 Mon Sep 17 00:00:00 2001 From: Jipok Date: Mon, 20 Dec 2021 16:20:06 +0500 Subject: [PATCH 019/409] Add for config.highlight_current_line new variant: no_selection --- data/core/config.lua | 1 + data/core/docview.lua | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/data/core/config.lua b/data/core/config.lua index 71e83994..a887d579 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -13,6 +13,7 @@ config.undo_merge_timeout = 0.3 config.max_undos = 10000 config.max_tabs = 8 config.always_show_tabs = true +-- Possible values: false, true, "no_selection" config.highlight_current_line = true config.line_height = 1.2 config.indent_size = 2 diff --git a/data/core/docview.lua b/data/core/docview.lua index a4587670..5bed6215 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -331,13 +331,22 @@ end function DocView:draw_line_body(idx, x, y) -- draw highlight if any selection ends on this line local draw_highlight = false - for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do - if line1 == idx then - draw_highlight = true - break + local hcl = config.highlight_current_line + if hcl ~= false then + for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do + if line1 == idx then + if hcl == "no_selection" then + if (line1 ~= line2) or (col1 ~= col2) then + draw_highlight = false + break + end + end + draw_highlight = true + break + end end end - if draw_highlight and config.highlight_current_line and core.active_view == self then + if draw_highlight and core.active_view == self then self:draw_line_highlight(x + self.scroll.x, y) end From 3109263c5d5ca0009c604ad8de1f51ac9d38bb78 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 20 Dec 2021 14:42:48 +0100 Subject: [PATCH 020/409] Call dmon_unwatch when changing project Fix a conspicuous omission to call the dmon_unwatch function when changing project directory. This uncovered a bug or a quirk of the dmon library where the watch_ids can change as a result of calling dmon_unwatch because they are just indexes on a contiguous array. Use a workaround to always unwatch the first valid watch_id N times. --- data/core/commands/core.lua | 5 ++++- data/core/init.lua | 16 ++++++++++++++++ src/api/system.c | 8 ++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index ad0d4b10..16b371f3 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -164,7 +164,10 @@ command.add(nil, { core.error("Cannot open folder %q", text) return end - core.confirm_close_docs(core.docs, core.open_folder_project, text) + core.confirm_close_docs(core.docs, function(dirpath) + core.close_current_project() + core.open_folder_project(dirpath) + end, text) end, suggest_directory) end, diff --git a/data/core/init.lua b/data/core/init.lua index 9e4676f0..928df227 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -64,6 +64,22 @@ function core.set_project_dir(new_dir, change_project_fn) return false end +function core.close_current_project() + -- When using system.unwatch_dir we need to pass the watch_id provided by dmon. + -- In reality when unwatching a directory the dmon library shifts the other watch_id + -- values so the actual watch_id changes. To workaround this problem we assume the + -- first watch_id is always 1 and the watch_id are continguous and we unwatch the + -- first watch_id repeateadly up to the number of watch_ids. + local watch_id_max = 0 + for _, project_dir in ipairs(core.project_directories) do + if project_dir.watch_id and project_dir.watch_id > watch_id_max then + watch_id_max = project_dir.watch_id + end + end + for i = 1, watch_id_max do + system.unwatch_dir(1) + end +end function core.open_folder_project(dir_path_abs) if core.set_project_dir(dir_path_abs, core.on_quit_project) then diff --git a/src/api/system.c b/src/api/system.c index 0ebf78f1..4c14843b 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -723,6 +723,13 @@ static int f_watch_dir(lua_State *L) { return 1; } +static int f_unwatch_dir(lua_State *L) { + dmon_watch_id watch_id; + watch_id.id = luaL_checkinteger(L, 1); + dmon_unwatch(watch_id); + return 0; +} + #if __linux__ static int f_watch_dir_add(lua_State *L) { dmon_watch_id watch_id; @@ -826,6 +833,7 @@ static const luaL_Reg lib[] = { { "fuzzy_match", f_fuzzy_match }, { "set_window_opacity", f_set_window_opacity }, { "watch_dir", f_watch_dir }, + { "unwatch_dir", f_unwatch_dir }, { "path_compare", f_path_compare }, #if __linux__ { "watch_dir_add", f_watch_dir_add }, From 50247fcd929dd931e98de1b4c0c7f2e91fc3fab3 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 20 Dec 2021 16:19:15 +0100 Subject: [PATCH 021/409] Move to 2.0.4 version number --- changelog.md | 11 +++++++++++ meson.build | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index c81c7dbe..06a75b70 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ This files document the changes done in Lite XL for each release. +### 2.0.4 + +Fix some bugs related to newly introduced directory monitoring using the dmon library. + +Fix a problem with plain text search using Lua patterns by error. + +Fix a problem with visualization of UTF-8 characters that caused garbage characters +visualization. + +Other fixes and improvements contributed by @Guldoman. + ### 2.0.3 Replace periodic rescan of project folder with a notification based system using the diff --git a/meson.build b/meson.build index 613204b7..ccdfdd09 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('lite-xl', ['c', 'cpp'], - version : '2.0.3', + version : '2.0.4', license : 'MIT', meson_version : '>= 0.54', default_options : ['c_std=gnu11', 'cpp_std=c++03'] From 6c1c983d1c8c33ec9f606edddf209b4d5b7367cf Mon Sep 17 00:00:00 2001 From: Jipok Date: Tue, 21 Dec 2021 02:20:31 +0500 Subject: [PATCH 022/409] rootview.lua: Refactor Node:draw_tab --- data/core/node.lua | 74 +++++++++++++++++++++++------------------- data/core/rootview.lua | 6 ++-- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/data/core/node.lua b/data/core/node.lua index bced052d..52fc3d6d 100644 --- a/data/core/node.lua +++ b/data/core/node.lua @@ -260,8 +260,8 @@ end local function close_button_location(x, w) local cw = style.icon_font:get_width("C") - local pad = style.padding.y - return x + w - pad - cw, cw, pad + local pad = style.padding.x / 2 + return x + w - cw - pad, cw, pad end @@ -476,51 +476,59 @@ function Node:update() end end -function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone) +function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h) + local text = view and view:get_name() or "" + local dots_width = font:get_width("…") + local align = "center" + if font:get_width(text) > w then + align = "left" + for i = 1, #text do + local reduced_text = text:sub(1, #text - i) + if font:get_width(reduced_text) + dots_width <= w then + text = reduced_text .. "…" + break + end + end + end + local color = style.dim + if is_active then color = style.text end + if is_hovered then color = style.text end + common.draw_text(font, color, text, align, x, y, w, h) +end + +function Node:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone) + -- Tabs deviders local ds = style.divider_size - local dots_width = style.font:get_width("…") local color = style.dim local padding_y = style.padding.y - renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim) + renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y*2, style.dim) if standalone then renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2) end + -- Full border if is_active then color = style.text renderer.draw_rect(x, y, w, h, style.background) renderer.draw_rect(x + w, y, ds, h, style.divider) renderer.draw_rect(x - ds, y, ds, h, style.divider) end - local cx, cw, cspace = close_button_location(x, w) + return x + ds, y, w - ds*2, h +end + +function Node:draw_tab(view, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone) + x, y, w, h = self:draw_tab_borders(view, is_active, is_hovered, x, y, w, h, standalone) + -- Close button + local cx, cw, cpad = close_button_location(x, w) local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button) if show_close_button then local close_style = is_close_hovered and style.text or style.dim - common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h) + common.draw_text(style.icon_font, close_style, "C", nil, cx, y, cw, h) end - if is_hovered then - color = style.text - end - local padx = style.padding.x - -- Normally we should substract "cspace" from text_avail_width and from the - -- clipping width. It is the padding space we give to the left and right of the - -- close button. However, since we are using dots to terminate filenames, we - -- choose to ignore "cspace" accepting that the text can possibly "touch" the - -- close button. - local text_avail_width = cx - x - padx - core.push_clip_rect(x, y, cx - x, h) - x, w = x + padx, w - padx * 2 - local align = "center" - if style.font:get_width(text) > text_avail_width then - align = "left" - for i = 1, #text do - local reduced_text = text:sub(1, #text - i) - if style.font:get_width(reduced_text) + dots_width <= text_avail_width then - text = reduced_text .. "…" - break - end - end - end - common.draw_text(style.font, color, text, align, x, y, w, h) + -- Title + x = x + cpad + w = cx - x + core.push_clip_rect(x, y, w, h) + self:draw_tab_title(view, style.font, is_active, is_hovered, x, y, w, h) core.pop_clip_rect() end @@ -547,7 +555,7 @@ function Node:draw_tabs() for i = self.tab_offset, self.tab_offset + tabs_number - 1 do local view = self.views[i] local x, y, w, h = self:get_tab_rect(i) - self:draw_tab(view:get_name(), view == self.active_view, + self:draw_tab(view, view == self.active_view, i == self.hovered_tab, i == self.hovered_close, x, y, w, h) end @@ -688,7 +696,7 @@ function Node:get_split_type(mouse_x, mouse_y) local local_mouse_x = mouse_x - x local local_mouse_y = mouse_y - y - + if local_mouse_y < 0 then return "tab" else diff --git a/data/core/rootview.lua b/data/core/rootview.lua index fb735fe3..9be386f5 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -254,7 +254,7 @@ function RootView:on_mouse_moved(x, y, dx, dy) self.root_node:on_mouse_moved(x, y, dx, dy) self.overlapping_node = self.root_node:get_child_overlapping_point(x, y) - + local div = self.root_node:get_divider_overlapping_point(x, y) local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y) if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then @@ -381,8 +381,8 @@ function RootView:draw_grabbed_tab() local _,_, w, h = dn.node:get_tab_rect(dn.idx) local x = self.mouse.x - w / 2 local y = self.mouse.y - h / 2 - local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or "" - self.root_node:draw_tab(text, true, true, false, x, y, w, h, true) + local view = dn.node.views[dn.idx] + self.root_node:draw_tab(view, true, true, false, x, y, w, h, true) end From 8d7867d118689098003ce844d96728366bbba341 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:36:32 +0800 Subject: [PATCH 023/409] adapt style.good and style.error --- data/core/style.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/style.lua b/data/core/style.lua index c988b2ef..acda297e 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -67,8 +67,8 @@ style.syntax_fonts = {} -- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options) style.log = { - INFO = { icon = "i", color = style.text }, - ERROR = { icon = "!", color = { common.color "#ff6961" } } + INFO = { icon = "i", color = style.good }, + ERROR = { icon = "!", color = style.error } } return style From 54f6579e9dbb448cd6b71fae67925cd99792afce Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:38:25 +0800 Subject: [PATCH 024/409] change INFO to use style.text --- data/core/style.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/style.lua b/data/core/style.lua index 94af2a54..470a0904 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -73,7 +73,7 @@ style.syntax_fonts = {} -- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options) style.log = { - INFO = { icon = "i", color = style.good }, + INFO = { icon = "i", color = style.text }, ERROR = { icon = "!", color = style.error } } From 773a85cd2d6d95d27c3404961630babfd1ba29f2 Mon Sep 17 00:00:00 2001 From: Jipok Date: Wed, 22 Dec 2021 01:30:55 +0500 Subject: [PATCH 025/409] Support for remaped special keys(Fix #757) --- src/api/system.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/api/system.c b/src/api/system.c index cbddd5f9..ecd68270 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -109,9 +109,14 @@ static const char *get_key_name(const SDL_Event *e, char *buf) { /* We need to correctly handle non-standard layouts such as dvorak. Therefore, if a Latin letter(code<128) is pressed in the current layout, then we transmit it as it is. But we also need to support shortcuts in - other languages, so for non-Latin characters we pass the scancode that - matches the letter in the QWERTY layout. */ - if (e->key.keysym.sym < 128) + other languages, so for non-Latin characters(code>128) we pass the + scancode based name that matches the letter in the QWERTY layout. + + In SDL, the codes of all special buttons such as control, shift, arrows + and others, are shifted using bit mask 1<<30(SDLK_SCANCODE_MASK) outside + the unicode range(>0x10FFFF). User can remap these buttons, so we need + to give correct name, not scancode based.*/ + if ((e->key.keysym.sym < 128) || (e->key.keysym.sym & SDLK_SCANCODE_MASK)) strcpy(buf, SDL_GetKeyName(e->key.keysym.sym)); else strcpy(buf, SDL_GetScancodeName(scancode)); From 978550d2a282b8aff1789e5fbbc92f6c28930230 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 21 Dec 2021 16:21:37 -0500 Subject: [PATCH 026/409] Restores external pastes to be normal pastes. --- data/core/commands/doc.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index cca32ca8..8e2078f3 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -124,7 +124,8 @@ local commands = { ["doc:paste"] = function() local clipboard = system.get_clipboard() -- If the clipboard has changed since our last look, use that instead - if core.cursor_clipboard["full"] ~= clipboard then + local external_paste = core.cursor_clipboard["full"] ~= clipboard + if external_paste then core.cursor_clipboard = {} core.cursor_clipboard_whole_line = {} end @@ -135,7 +136,7 @@ local commands = { whole_line = core.cursor_clipboard_whole_line[idx] == true else value = clipboard - whole_line = clipboard:find("\n") ~= nil + whole_line = not external_paste and clipboard:find("\n") ~= nil end if whole_line then doc():insert(line1, 1, value:gsub("\r", "")) From 8f06ef9b8100a0f59aa1fca706dcb84c91316833 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 22 Dec 2021 09:55:47 +0800 Subject: [PATCH 027/409] ensure date is rendered properly --- data/core/logview.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/logview.lua b/data/core/logview.lua index 18299ac5..99ab0a93 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -150,6 +150,8 @@ function LogView:draw() style.icon_font:get_width(style.log.ERROR.icon), style.icon_font:get_width(style.log.INFO.icon) ) + + local tw = style.font:get_width(os.date()) for _, item, x, y, w, h in self:each_item() do core.push_clip_rect(x, y, w, h) x = x + style.padding.x @@ -164,9 +166,8 @@ function LogView:draw() x = x + style.padding.x -- timestamps are always 15% of the width - local tw = w * 15 / 100 local time = os.date(nil, item.time) - common.draw_text(style.font, style.dim, time, "center", x, y, tw, lh) + common.draw_text(style.font, style.dim, time, "left", x, y, tw, lh) x = x + tw + style.padding.x w = w - (x - self:get_content_offset()) From 2f65d5a26ff7a3da24ef37e4f312c3d665b70c4c Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 22 Dec 2021 10:53:53 +0800 Subject: [PATCH 028/409] make the date field consistent --- data/core/logview.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/logview.lua b/data/core/logview.lua index 99ab0a93..aa1ad84a 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -140,7 +140,8 @@ local function draw_text_multiline(font, text, x, y, color) return resx, y end - +-- this is just to get a date string that's consistent +local datestr = os.date() function LogView:draw() self:draw_background(style.background) @@ -151,7 +152,7 @@ function LogView:draw() style.icon_font:get_width(style.log.INFO.icon) ) - local tw = style.font:get_width(os.date()) + local tw = style.font:get_width(datestr) for _, item, x, y, w, h in self:each_item() do core.push_clip_rect(x, y, w, h) x = x + style.padding.x From 00e2e281d3181055c435e1cdb9ed5d141976d5f8 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 22 Dec 2021 10:54:25 +0800 Subject: [PATCH 029/409] remove unsaved flag from log.txt --- data/core/commands/log.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/core/commands/log.lua b/data/core/commands/log.lua index c0111b57..8a5b7f3d 100644 --- a/data/core/commands/log.lua +++ b/data/core/commands/log.lua @@ -7,6 +7,8 @@ command.add(nil, { local doc = core.open_doc("logs.txt") core.root_view:open_doc(doc) doc:insert(1, 1, core.get_log()) + doc.new_file = false + doc:clean() end, ["log:copy-to-clipboard"] = function() system.set_clipboard(core.get_log()) From a19baeacb11afe40566b13e6c43e8ab39cc108fb Mon Sep 17 00:00:00 2001 From: Jipok Date: Wed, 22 Dec 2021 23:34:09 +0500 Subject: [PATCH 030/409] Support portable user config(Fix #762) --- data/core/start.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/start.lua b/data/core/start.lua index f3bc89c6..482f8bd0 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -12,8 +12,9 @@ else local prefix = EXEDIR:match("^(.+)[/\\]bin$") DATADIR = prefix and (prefix .. '/share/lite-xl') or (EXEDIR .. '/data') end -USERDIR = (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl") - or (HOME and (HOME .. '/.config/lite-xl') or (EXEDIR .. '/user')) +USERDIR = (system.get_file_info(EXEDIR .. '/user') and (EXEDIR .. '/user')) + or ((os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. "/lite-xl")) + or (HOME and (HOME .. '/.config/lite-xl')) package.path = DATADIR .. '/?.lua;' .. package.path package.path = DATADIR .. '/?/init.lua;' .. package.path From 66bc55148805f681a5866464f3443449ece8460d Mon Sep 17 00:00:00 2001 From: Lorenzo Orsatti <49567430+lorsatti@users.noreply.github.com> Date: Wed, 22 Dec 2021 22:18:22 +0100 Subject: [PATCH 031/409] Add CFBundleIdentifier to Info.plist.in The CFBundleIdentifier key is necessary to associate lite-xl application to all files with a certain extension. --- resources/macos/Info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/macos/Info.plist.in b/resources/macos/Info.plist.in index 4d715f2f..2cb6208e 100644 --- a/resources/macos/Info.plist.in +++ b/resources/macos/Info.plist.in @@ -8,6 +8,8 @@ lite-xl CFBundleIconFile icon.icns + CFBundleIdentifier + com.lite-xl CFBundleName Lite XL CFBundlePackageType From 9e7bdf49e9b4b5e38bc2a95d7e7aa6b607c86468 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 22 Dec 2021 23:39:26 +0100 Subject: [PATCH 032/409] Revert "Merge pull request #697 from Guldoman/treeview_remove_changed" This reverts commit 4e078cc2171f1c01150b09db7d091a2dfdbf715a, reversing changes made to 0c488c94920a1100986fa73e11ccc4a2c5cd5667. --- data/core/init.lua | 2 +- data/plugins/treeview.lua | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index bb3647ec..a85b5d0d 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1158,7 +1158,7 @@ end -- no-op but can be overrided by plugins -function core.on_dirmonitor_modify(dir, filepath) +function core.on_dirmonitor_modify() end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index f7c7f5ba..b6de33b2 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -45,14 +45,6 @@ function TreeView:new() self.item_icon_width = 0 self.item_text_spacing = 0 - - local on_dirmonitor_modify = core.on_dirmonitor_modify - function core.on_dirmonitor_modify(dir, filepath) - if self.cache[dir.name] then - self.cache[dir.name][filepath] = nil - end - on_dirmonitor_modify(dir, filepath) - end end From 9155be7a2268548bb488d6e294dd2ee043453c28 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 20 Dec 2021 11:03:49 +0100 Subject: [PATCH 033/409] Ensure TreeView cache entry is removed on delete Address issue: https://github.com/lite-xl/lite-xl/issues/689 Attempt to provide a more accurate fix to commit: 59f64088e1e88f2f2a29e4d5418ccdf781fdc12e For this latter what happens is that any change inside a directory cause the corresponding entry to be folded in the TreeView. The new change is more accurate because we remove only the stale entry corresponding to the delete event and we do not reset the cache of the parent directory using the modify event. --- data/core/init.lua | 5 +++-- data/plugins/treeview.lua | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index a85b5d0d..0b4c355f 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1158,8 +1158,8 @@ end -- no-op but can be overrided by plugins -function core.on_dirmonitor_modify() -end +function core.on_dirmonitor_modify() end +function core.on_dirmonitor_delete() end function core.on_dir_change(watch_id, action, filepath) @@ -1168,6 +1168,7 @@ function core.on_dir_change(watch_id, action, filepath) core.dir_rescan_add_job(dir, filepath) if action == "delete" then project_scan_remove_file(dir, filepath) + core.on_dirmonitor_delete(dir, filepath) elseif action == "create" then project_scan_add_file(dir, filepath) core.on_dirmonitor_modify(dir, filepath); diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index b6de33b2..4d1207f2 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -45,6 +45,19 @@ function TreeView:new() self.item_icon_width = 0 self.item_text_spacing = 0 + self:add_core_hooks() +end + + +function TreeView:add_core_hooks() + -- When a file or directory is deleted we delete the corresponding cache entry + -- because if the entry is recreated we may use wrong information from cache. + local on_delete = core.on_dirmonitor_delete + core.on_dirmonitor_delete = function(dir, filepath) + local cache = self.cache[dir.name] + if cache then cache[filepath] = nil end + on_delete(dir, filepath) + end end From eac82e69fb1016fb351349be919539989425d530 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 22 Dec 2021 23:43:56 +0100 Subject: [PATCH 034/409] Add parameters to `core.on_dirmonitor_{modify,delete}` --- data/core/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 0b4c355f..c76c6366 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1158,8 +1158,8 @@ end -- no-op but can be overrided by plugins -function core.on_dirmonitor_modify() end -function core.on_dirmonitor_delete() end +function core.on_dirmonitor_modify(dir, filepath) end +function core.on_dirmonitor_delete(dir, filepath) end function core.on_dir_change(watch_id, action, filepath) From a122d7e916162a1fe5d9e43cc86d1fc4002c817e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 23 Dec 2021 00:06:24 +0100 Subject: [PATCH 035/409] Correct `get_key_name` comment --- src/api/system.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/system.c b/src/api/system.c index ecd68270..0da6cbbe 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -113,9 +113,9 @@ static const char *get_key_name(const SDL_Event *e, char *buf) { scancode based name that matches the letter in the QWERTY layout. In SDL, the codes of all special buttons such as control, shift, arrows - and others, are shifted using bit mask 1<<30(SDLK_SCANCODE_MASK) outside - the unicode range(>0x10FFFF). User can remap these buttons, so we need - to give correct name, not scancode based.*/ + and others, are masked with SDLK_SCANCODE_MASK, which moves them outside + the unicode range (>0x10FFFF). Users can remap these buttons, so we need + to return the correct name, not scancode based. */ if ((e->key.keysym.sym < 128) || (e->key.keysym.sym & SDLK_SCANCODE_MASK)) strcpy(buf, SDL_GetKeyName(e->key.keysym.sym)); else From d16e46dba505d5862af67b6a2f8075668664cd36 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 23 Dec 2021 23:26:52 +0100 Subject: [PATCH 036/409] Update website location --- resources/linux/org.lite_xl.lite_xl.appdata.xml | 4 ++-- scripts/innosetup/innosetup.iss.in | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/linux/org.lite_xl.lite_xl.appdata.xml b/resources/linux/org.lite_xl.lite_xl.appdata.xml index c5895178..49c23430 100644 --- a/resources/linux/org.lite_xl.lite_xl.appdata.xml +++ b/resources/linux/org.lite_xl.lite_xl.appdata.xml @@ -17,11 +17,11 @@ The editor window - https://lite-xl.github.io/assets/img/screenshots/editor.png + https://lite-xl.com/assets/img/editor.png - https://lite-xl.github.io + https://lite-xl.com lite-xl diff --git a/scripts/innosetup/innosetup.iss.in b/scripts/innosetup/innosetup.iss.in index 2b669fc0..31ca8008 100644 --- a/scripts/innosetup/innosetup.iss.in +++ b/scripts/innosetup/innosetup.iss.in @@ -1,7 +1,7 @@ #define MyAppName "Lite XL" #define MyAppVersion "@PROJECT_VERSION@" #define MyAppPublisher "Lite XL Team" -#define MyAppURL "https://lite-xl.github.io" +#define MyAppURL "https://lite-xl.com" #define MyAppExeName "lite-xl.exe" #define BuildDir "@PROJECT_BUILD_DIR@" #define SourceDir "@PROJECT_SOURCE_DIR@" From 84a390632311637ac57666f09cd7211c8a7e0a0e Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Fri, 24 Dec 2021 15:04:52 +0800 Subject: [PATCH 037/409] fix FontGroup __gc method --- src/api/renderer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/renderer.c b/src/api/renderer.c index bd001cdf..0fc21707 100644 --- a/src/api/renderer.c +++ b/src/api/renderer.c @@ -92,7 +92,7 @@ static int f_font_copy(lua_State *L) { return 1; } -static int f_font_group(lua_State* L) { +static int f_font_group(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_setmetatable(L, API_TYPE_FONT); return 1; @@ -106,8 +106,10 @@ static int f_font_set_tab_size(lua_State *L) { } static int f_font_gc(lua_State *L) { + if (lua_istable(L, 1)) return 0; // do not run if its FontGroup RenFont** self = luaL_checkudata(L, 1, API_TYPE_FONT); ren_font_free(*self); + return 0; } From 16df6d8bce461f4e95dfa81419a39256e81651f3 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Fri, 24 Dec 2021 21:32:28 +0800 Subject: [PATCH 038/409] add option for initial size --- data/plugins/treeview.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index f7c7f5ba..2cd110e6 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -10,7 +10,11 @@ local ContextMenu = require "core.contextmenu" local RootView = require "core.rootview" -local default_treeview_size = 200 * SCALE +config.plugins.treeview = common.merge({ + size = 200 * SCALE +}, config.plugins.treeview) + + local tooltip_offset = style.font:get_height() local tooltip_border = 1 local tooltip_delay = 0.5 @@ -39,7 +43,7 @@ function TreeView:new() self.scrollable = true self.visible = true self.init_size = true - self.target_size = default_treeview_size + self.target_size = config.plugins.treeview.size self.cache = {} self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 } @@ -405,7 +409,7 @@ if config.plugins.toolbarview ~= false and toolbar_plugin then toolbar_view = ToolbarView() treeview_node:split("down", toolbar_view, {y = true}) local min_toolbar_width = toolbar_view:get_min_width() - view:set_target_size("x", math.max(default_treeview_size, min_toolbar_width)) + view:set_target_size("x", math.max(config.plugins.treeview.size, min_toolbar_width)) command.add(nil, { ["toolbar:toggle"] = function() toolbar_view:toggle_visible() From 4d31b1bc404a56a1160f75ed82adec24042d676e Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 25 Dec 2021 12:57:00 +0800 Subject: [PATCH 039/409] add toggle-block-comment --- data/core/commands/doc.lua | 48 ++++++++++++++++++++++++++++++++++ data/core/keymap.lua | 1 + data/plugins/language_c.lua | 1 + data/plugins/language_cpp.lua | 1 + data/plugins/language_css.lua | 1 + data/plugins/language_html.lua | 31 +++++++++++----------- data/plugins/language_js.lua | 1 + data/plugins/language_lua.lua | 1 + data/plugins/language_xml.lua | 1 + 9 files changed, 71 insertions(+), 15 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 8e2078f3..1e65ad11 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -292,6 +292,54 @@ local commands = { end end, + ["doc:toggle-block-comments"] = function() + local comment = doc().syntax.multiline_comment + if not comment then return end + + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + local text = doc():get_text(line1, col1, line2, col2) + + -- might need to deal with unicode later... + local cs, cse = text:find(comment[1], 1, true) + local ce = text:find(comment[2], #text - #comment[2] + 1, true) + + local new_col1, new_col2 = col1, col2 + -- uncomment + if cs and ce then + if line1 == line2 then + new_col2 = new_col2 - #table.concat(comment) + else + new_col2 = new_col2 - #comment[2] + end + + -- remove 1 whitespace if possible + if text:sub(cse + 1, cse + 1) == " " then + cse = cse + 1 + if line1 == line2 then + new_col2 = new_col2 - 1 + end + end + if text:sub(ce - 1, ce - 1) == " " then + ce = ce - 1 + new_col2 = new_col2 - 1 + end + + text = text:sub(cse + 1, ce - 1) + -- comment + else + text = comment[1] .. " " .. text .. " " .. comment[2] + if line1 == line2 then + new_col2 = new_col2 + #table.concat(comment) + 2 + else + new_col2 = new_col2 + #comment[2] + 1 + end + end + doc():remove(line1, col1, line2, col2) + doc():insert(line1, col1, text) + doc():set_selections(idx, line1, new_col1, line2, new_col2) + end + end, + ["doc:toggle-line-comments"] = function() local comment = doc().syntax.comment if not comment then return end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 404cff39..6716fca9 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -215,6 +215,7 @@ keymap.add_direct { ["ctrl+l"] = "doc:select-lines", ["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" }, ["ctrl+/"] = "doc:toggle-line-comments", + ["ctrl+shift+/"] = "doc:toggle-block-comments", ["ctrl+up"] = "doc:move-lines-up", ["ctrl+down"] = "doc:move-lines-down", ["ctrl+shift+d"] = "doc:duplicate-lines", diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index 88cc7c5a..ecbeed7a 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -5,6 +5,7 @@ syntax.add { name = "C", files = { "%.c$", "%.h$", "%.inl$" }, comment = "//", + multiline_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index a945fd1f..03554a48 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -10,6 +10,7 @@ syntax.add { "%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$" }, comment = "//", + multiline_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_css.lua b/data/plugins/language_css.lua index 395e375c..871598a2 100644 --- a/data/plugins/language_css.lua +++ b/data/plugins/language_css.lua @@ -4,6 +4,7 @@ local syntax = require "core.syntax" syntax.add { name = "CSS", files = { "%.css$" }, + multiline_comment = { "/*", "*/" }, patterns = { { pattern = "\\.", type = "normal" }, { pattern = "//.-\n", type = "comment" }, diff --git a/data/plugins/language_html.lua b/data/plugins/language_html.lua index 1f4515bc..e22e8897 100644 --- a/data/plugins/language_html.lua +++ b/data/plugins/language_html.lua @@ -4,28 +4,29 @@ local syntax = require "core.syntax" syntax.add { name = "HTML", files = { "%.html?$" }, + multiline_comment = { "" }, patterns = { - { - pattern = { + { + pattern = { "<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" .. "['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>", - "<%s*/[sS][cC][rR][iI][pP][tT]>" - }, - syntax = ".js", - type = "function" - }, - { - pattern = { - "<%s*[sS][cC][rR][iI][pP][tT]%s*>", - "<%s*/%s*[sS][cC][rR][iI][pP][tT]>" + "<%s*/[sS][cC][rR][iI][pP][tT]>" }, syntax = ".js", type = "function" }, - { - pattern = { - "<%s*[sS][tT][yY][lL][eE][^>]*>", - "<%s*/%s*[sS][tT][yY][lL][eE]%s*>" + { + pattern = { + "<%s*[sS][cC][rR][iI][pP][tT]%s*>", + "<%s*/%s*[sS][cC][rR][iI][pP][tT]>" + }, + syntax = ".js", + type = "function" + }, + { + pattern = { + "<%s*[sS][tT][yY][lL][eE][^>]*>", + "<%s*/%s*[sS][tT][yY][lL][eE]%s*>" }, syntax = ".css", type = "function" diff --git a/data/plugins/language_js.lua b/data/plugins/language_js.lua index 94ab8499..d23e412b 100644 --- a/data/plugins/language_js.lua +++ b/data/plugins/language_js.lua @@ -5,6 +5,7 @@ syntax.add { name = "JavaScript", files = { "%.js$", "%.json$", "%.cson$" }, comment = "//", + multiline_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_lua.lua b/data/plugins/language_lua.lua index 5c770d43..79a8289b 100644 --- a/data/plugins/language_lua.lua +++ b/data/plugins/language_lua.lua @@ -6,6 +6,7 @@ syntax.add { files = "%.lua$", headers = "^#!.*[ /]lua", comment = "--", + multiline_comment = { "--[[", "]]" }, patterns = { { pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" }, diff --git a/data/plugins/language_xml.lua b/data/plugins/language_xml.lua index c858d3cf..971a53de 100644 --- a/data/plugins/language_xml.lua +++ b/data/plugins/language_xml.lua @@ -5,6 +5,7 @@ syntax.add { name = "XML", files = { "%.xml$" }, headers = "<%?xml", + multiline_comment = { "" }, patterns = { { pattern = { "" }, type = "comment" }, { pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" }, From 69a857bbbfd4ab12dc5ef463508dc44c69c67e93 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 26 Dec 2021 15:05:27 +0800 Subject: [PATCH 040/409] fallback to toggle-line-comment and vice versa if needed --- data/core/commands/doc.lua | 16 +++++++++++++--- data/plugins/language_c.lua | 2 +- data/plugins/language_cpp.lua | 2 +- data/plugins/language_css.lua | 2 +- data/plugins/language_html.lua | 2 +- data/plugins/language_js.lua | 2 +- data/plugins/language_lua.lua | 2 +- data/plugins/language_xml.lua | 2 +- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 1e65ad11..edf36bbd 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -293,8 +293,13 @@ local commands = { end, ["doc:toggle-block-comments"] = function() - local comment = doc().syntax.multiline_comment - if not comment then return end + local comment = doc().syntax.block_comment + if not comment then + if doc().syntax.comment then + command.perform "doc:toggle-line-comments" + end + return + end for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do local text = doc():get_text(line1, col1, line2, col2) @@ -342,7 +347,12 @@ local commands = { ["doc:toggle-line-comments"] = function() local comment = doc().syntax.comment - if not comment then return end + if not comment then + if doc().syntax.block_comment then + command.perform "doc:toggle-block-comments" + end + return + end local indentation = doc():get_indent_string() local comment_text = comment .. " " for idx, line1, _, line2 in doc_multiline_selections(true) do diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index ecbeed7a..c4d3b07b 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -5,7 +5,7 @@ syntax.add { name = "C", files = { "%.c$", "%.h$", "%.inl$" }, comment = "//", - multiline_comment = { "/*", "*/" }, + block_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index 03554a48..2b3a104f 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -10,7 +10,7 @@ syntax.add { "%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$" }, comment = "//", - multiline_comment = { "/*", "*/" }, + block_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_css.lua b/data/plugins/language_css.lua index 871598a2..96c3eb8d 100644 --- a/data/plugins/language_css.lua +++ b/data/plugins/language_css.lua @@ -4,7 +4,7 @@ local syntax = require "core.syntax" syntax.add { name = "CSS", files = { "%.css$" }, - multiline_comment = { "/*", "*/" }, + block_comment = { "/*", "*/" }, patterns = { { pattern = "\\.", type = "normal" }, { pattern = "//.-\n", type = "comment" }, diff --git a/data/plugins/language_html.lua b/data/plugins/language_html.lua index e22e8897..ff61fa7f 100644 --- a/data/plugins/language_html.lua +++ b/data/plugins/language_html.lua @@ -4,7 +4,7 @@ local syntax = require "core.syntax" syntax.add { name = "HTML", files = { "%.html?$" }, - multiline_comment = { "" }, + block_comment = { "" }, patterns = { { pattern = { diff --git a/data/plugins/language_js.lua b/data/plugins/language_js.lua index d23e412b..2e6d5d87 100644 --- a/data/plugins/language_js.lua +++ b/data/plugins/language_js.lua @@ -5,7 +5,7 @@ syntax.add { name = "JavaScript", files = { "%.js$", "%.json$", "%.cson$" }, comment = "//", - multiline_comment = { "/*", "*/" }, + block_comment = { "/*", "*/" }, patterns = { { pattern = "//.-\n", type = "comment" }, { pattern = { "/%*", "%*/" }, type = "comment" }, diff --git a/data/plugins/language_lua.lua b/data/plugins/language_lua.lua index 79a8289b..993e53c8 100644 --- a/data/plugins/language_lua.lua +++ b/data/plugins/language_lua.lua @@ -6,7 +6,7 @@ syntax.add { files = "%.lua$", headers = "^#!.*[ /]lua", comment = "--", - multiline_comment = { "--[[", "]]" }, + block_comment = { "--[[", "]]" }, patterns = { { pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" }, diff --git a/data/plugins/language_xml.lua b/data/plugins/language_xml.lua index 971a53de..297b6d73 100644 --- a/data/plugins/language_xml.lua +++ b/data/plugins/language_xml.lua @@ -5,7 +5,7 @@ syntax.add { name = "XML", files = { "%.xml$" }, headers = "<%?xml", - multiline_comment = { "" }, + block_comment = { "" }, patterns = { { pattern = { "" }, type = "comment" }, { pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" }, From 33f7fe4fdaf1a640dfa33b1805aa948816e0222f Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 26 Dec 2021 15:12:28 +0800 Subject: [PATCH 041/409] toggle comment for whole line if nothing is selected --- data/core/commands/doc.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index edf36bbd..977f233f 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -302,6 +302,11 @@ local commands = { end for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + -- if nothing is selected, toggle the whole line + if line1 == line2 and col1 == col2 then + col1 = 1 + col2 = #doc().lines[line1] + end local text = doc():get_text(line1, col1, line2, col2) -- might need to deal with unicode later... From 1f0785b73ff0cffd0926c3086baa408a82f01722 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 28 Dec 2021 10:59:01 +0100 Subject: [PATCH 042/409] Scan project folder after project module is loaded Otherwise the initial scan of the project folder is done without taking into account the config.ignore_files directives. --- data/core/init.lua | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 928df227..60c80ec1 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -52,13 +52,15 @@ local function update_recents_project(action, dir_path_abs) end -function core.set_project_dir(new_dir, change_project_fn) +function core.set_project_dir(new_dir, change_project_fn, defer_add) local chdir_ok = pcall(system.chdir, new_dir) if chdir_ok then if change_project_fn then change_project_fn() end core.project_dir = common.normalize_volume(new_dir) core.project_directories = {} - core.add_project_directory(new_dir) + if not defer_add then + core.add_project_directory(new_dir) + end return true end return false @@ -679,7 +681,9 @@ function core.init() core.blink_timer = core.blink_start local project_dir_abs = system.absolute_path(project_dir) - local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs) + -- We prevent set_project_dir below to effectively add and scan the directory becaese tha + -- project module and its ignore files is not yet loaded. + local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs, nil, true) if set_project_ok then if project_dir_explicit then update_recents_project("add", project_dir_abs) @@ -689,7 +693,7 @@ function core.init() update_recents_project("remove", project_dir) end project_dir_abs = system.absolute_path(".") - if not core.set_project_dir(project_dir_abs) then + if not core.set_project_dir(project_dir_abs, nil, true) then system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd") os.exit(1) end @@ -726,6 +730,9 @@ function core.init() end local got_project_error = not core.load_project_module() + -- We add the project directory now because the project's module is loaded. + core.add_project_directory(project_dir_abs) + -- We assume we have just a single project directory here. Now that StatusView -- is there show max files warning if needed. if core.project_directories[1].files_limit then From 0f1b84040dd823823c17e925e1ed10380b7737c1 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 28 Dec 2021 12:25:48 +0100 Subject: [PATCH 043/409] No longer store autocomplete options in config Plugins should not store private stuff in core.config because this latter can be reloaded due to user changing preferences. --- data/plugins/autocomplete.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index fde9487e..d324733f 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -10,7 +10,7 @@ local RootView = require "core.rootview" local DocView = require "core.docview" local Doc = require "core.doc" -config.plugins.autocomplete = { +local autocomplete_options = { -- Amount of characters that need to be written for autocomplete min_len = 3, -- The max amount of visible items @@ -192,7 +192,7 @@ local function update_suggestions() -- fuzzy match, remove duplicates and store items = common.fuzzy_match(items, partial) local j = 1 - for i = 1, config.plugins.autocomplete.max_suggestions do + for i = 1, autocomplete_options.max_suggestions do suggestions[i] = items[j] while items[j] and items[i].text == items[j].text do items[i].info = items[i].info or items[j].info @@ -235,7 +235,7 @@ local function get_suggestions_rect(av) max_width = math.max(max_width, w) end - local ah = config.plugins.autocomplete.max_height + local ah = autocomplete_options.max_height local max_items = #suggestions if max_items > ah then @@ -294,7 +294,7 @@ local function draw_suggestions_box(av) return end - local ah = config.plugins.autocomplete.max_height + local ah = autocomplete_options.max_height -- draw background rect local rx, ry, rw, rh = get_suggestions_rect(av) @@ -355,7 +355,7 @@ local function show_autocomplete() -- update partial symbol and suggestions partial = get_partial_symbol() - if #partial >= config.plugins.autocomplete.min_len or triggered_manually then + if #partial >= autocomplete_options.min_len or triggered_manually then update_suggestions() if not triggered_manually then @@ -469,7 +469,7 @@ function autocomplete.complete(completions, on_close) end function autocomplete.can_complete() - if #partial >= config.plugins.autocomplete.min_len then + if #partial >= autocomplete_options.min_len then return true end return false From 05b003eeb523816f4e87991f0fd06be8d1353d1b Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 28 Dec 2021 14:32:39 +0100 Subject: [PATCH 044/409] Avoid references to project's dir in TreeView It is not a good practice to keep a reference to the project's directory object outside of the "core" module itself. The TreeView was using such a reference in the cache item for each file or directory entry. Replace the reference to the object with the absolute name of the project directory. --- data/core/init.lua | 9 +++++++++ data/plugins/treeview.lua | 9 +++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 60c80ec1..aac9a1b2 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -371,6 +371,15 @@ function core.add_project_directory(path) end +function core.project_dir_by_name(name) + for i = 1, #core.project_directories do + if core.project_directories[i].name == name then + return core.project_directories[i] + end + end +end + + function core.update_project_subdir(dir, filename, expanded) local index, n, file = project_subdir_bounds(dir, filename) if index then diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 2909768c..ffe93ca5 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -92,7 +92,7 @@ function TreeView:get_cached(dir, item, dirname) end t.name = basename t.type = item.type - t.dir = dir -- points to top level "dir" item + t.dir_name = dir.name -- points to top level "dir" item dir_cache[cache_name] = t end return t @@ -231,9 +231,10 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) create_directory_in(hovered_item) else hovered_item.expanded = not hovered_item.expanded - if hovered_item.dir.files_limit then - core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded) - core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded) + local hovered_dir = core.project_dir_by_name(hovered_item.dir_name) + if hovered_dir and hovered_dir.files_limit then + core.update_project_subdir(hovered_dir, hovered_item.filename, hovered_item.expanded) + core.project_subdir_set_show(hovered_dir, hovered_item.filename, hovered_item.expanded) end end else From 2cf3c6f7476d891cf3eb548409c109de315b3c6b Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 28 Dec 2021 12:32:25 +0100 Subject: [PATCH 045/409] Ensure project reload when changing project module Changes in project's module required an application restart to work. Now the project will be re-scanned when the project's module changes. In addition ensure borderless window config is changed when changed in user's preferences. --- data/core/init.lua | 62 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index aac9a1b2..481fc431 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -368,6 +368,36 @@ function core.add_project_directory(path) core.project_files = dir.files end core.redraw = true + return dir +end + + +-- The function below is needed to reload the project directories +-- when the project's module changes. +local function rescan_project_directories() + local save_project_dirs = {} + local n = #core.project_directories + for i = 1, n do + local dir = core.project_directories[i] + save_project_dirs[i] = {name = dir.name, shown_subdir = dir.shown_subdir} + end + core.close_current_project() -- ensure we unwatch directories + core.project_directories = {} + for i = 1, n do -- add again the directories in the project + local dir = core.add_project_directory(save_project_dirs[i].name) + -- The shown_subdir is only used on linux for very large directories. + -- replay them on the newly scanned project. + for subdir, show in pairs(save_project_dirs[i].shown_subdir) do + for j = 1, #dir.files do + if dir.files[j].filename == subdir then + -- the instructions above match when happens in TreeView:on_mouse_pressed + core.update_project_subdir(dir, subdir, show) + core.project_subdir_set_show(dir, subdir, show) + break + end + end + end + end end @@ -613,15 +643,33 @@ local function whitespace_replacements() end -local function reload_on_user_module_save() +local function configure_borderless_window() + system.set_window_bordered(not config.borderless) + core.title_view:configure_hit_test(config.borderless) + core.title_view.visible = config.borderless +end + + +local function reload_customizations() + core.reload_module("core.style") + core.reload_module("core.config") + core.reload_module("core.keymap") + core.load_user_directory() + core.load_project_module() + rescan_project_directories() + configure_borderless_window() +end + + +local function add_config_files_hooks() -- auto-realod style when user's module is saved by overriding Doc:Save() local doc_save = Doc.save local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua") + local module_filename = system.absolute_path(".lite_project.lua") function Doc:save(filename, abs_filename) doc_save(self, filename, abs_filename) - if self.abs_filename == user_filename then - core.reload_module("core.style") - core.load_user_directory() + if self.abs_filename == user_filename or self.abs_filename == module_filename then + reload_customizations() end end end @@ -760,9 +808,7 @@ function core.init() command.perform("core:open-log") end - system.set_window_bordered(not config.borderless) - core.title_view:configure_hit_test(config.borderless) - core.title_view.visible = config.borderless + configure_borderless_window() if #plugins_refuse_list.userdir.plugins > 0 or #plugins_refuse_list.datadir.plugins > 0 then local opt = { @@ -786,7 +832,7 @@ function core.init() end) end - reload_on_user_module_save() + add_config_files_hooks() end From 8550049db81453ab2437a562b3d793a8a00f8275 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 28 Dec 2021 16:39:40 +0100 Subject: [PATCH 046/409] Draw NagView in overlay mode The NagView takes some actual space in the Y and when it appears it cause the documents' content to be displaced. The movement of the documents' content is annoying and should be avoided so we draw the NagView entirely in overlay mode using defer draw and we always keep its y size to zero to don't affect the other application contents. --- data/core/nagview.lua | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/data/core/nagview.lua b/data/core/nagview.lua index 3d448cd4..35ae9b43 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -16,6 +16,7 @@ local NagView = View:extend() function NagView:new() NagView.super.new(self) self.size.y = 0 + self.show_height = 0 self.force_focus = false self.queue = {} end @@ -50,16 +51,16 @@ function NagView:update() NagView.super.update(self) if core.active_view == self and self.title then - self:move_towards(self.size, "y", self:get_target_height()) + self:move_towards(self, "show_height", self:get_target_height()) self:move_towards(self, "underline_progress", 1) else - self:move_towards(self.size, "y", 0) + self:move_towards(self, "show_height", 0) end end -function NagView:draw_overlay() +function NagView:dim_window_content() local ox, oy = self:get_content_offset() - oy = oy + self.size.y + oy = oy + self.show_height local w, h = core.root_view.size.x, core.root_view.size.y - oy core.root_view:defer_draw(function() renderer.draw_rect(ox, oy, w, h, style.nagbar_dim) @@ -81,7 +82,7 @@ function NagView:each_option() bh = self:get_buttons_height() ox,oy = self:get_content_offset() ox = ox + self.size.x - oy = oy + self.size.y - bh - style.padding.y + oy = oy + self.show_height - bh - style.padding.y for i = #self.options, 1, -1 do opt = self.options[i] @@ -123,19 +124,21 @@ function NagView:on_text_input(text) end -function NagView:draw() - if self.size.y <= 0 or not self.title then return end +local function draw_nagview_message(self) + if self.show_height <= 0 or not self.title then return end - self:draw_overlay() - self:draw_background(style.nagbar) + self:dim_window_content() + -- draw message's background local ox, oy = self:get_content_offset() + renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar) + ox = ox + style.padding.x -- if there are other items, show it if #self.queue > 0 then local str = string.format("[%d]", #self.queue) - ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y) + ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height) ox = ox + style.padding.x end @@ -170,6 +173,10 @@ function NagView:draw() end end +function NagView:draw() + core.root_view:defer_draw(draw_nagview_message, self) +end + function NagView:get_message_height() local h = 0 for str in string.gmatch(self.message, "(.-)\n") do From fcb3c4108212daba34455502a543559fe5056381 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Tue, 28 Dec 2021 17:16:42 -0500 Subject: [PATCH 047/409] meson: lower the minimum buildsystem requirements No features of 0.54 are being used, so 0.50 should be perfectly fine. This drops the minimum requirement down to a version available in the latest Ubuntu LTS (20.04), which only has 0.53 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 87544c7f..e1244ac1 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('lite-xl', ['c'], version : '2.0.3', license : 'MIT', - meson_version : '>= 0.54', + meson_version : '>= 0.50', default_options : ['c_std=gnu11'] ) From 7a961c8c8e10e8b7accbe0dfed48afa8f1f30e8f Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Tue, 28 Dec 2021 17:20:23 -0500 Subject: [PATCH 048/409] meson: lower the minimum buildsystem requirements even more Only a couple trivial features from meson ~0.50 were being used, and none of them are really needed: - configure_file() with the install kwarg has always defaulted to inferring its value from whether an install_dir was defined. This is fine, we don't need to set `install: true` in that case. The kwarg was only even added to meson 0.50 for consistency and to allow conditionally overriding the file to not install, even when install_dir is set. This project does not need that feature. - path building could historically be done with the join_paths() function. Recent versions of meson (0.49) added cosmetic sugar in the form of string operator overloading to allow using the division operator on two strings. By removing this and using the backwards compatible form, we can support older versions of meson. - sdl2 dependency lookup with hardcoded config-tool method is very opinionated about the correct way to look up sdl2, but meson can try multiple methods if you permit it, and there is no reason to think that config-tool is the only one that returns correct results. By removing these features, the minimum can be dropped all the way down to a version that is available on the oldest supported versions of Ubuntu (18.04), Debian (oldoldstable / Stretch) and anywhere else of consequence. --- meson.build | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/meson.build b/meson.build index e1244ac1..44be29da 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('lite-xl', ['c'], version : '2.0.3', license : 'MIT', - meson_version : '>= 0.50', + meson_version : '>= 0.42', default_options : ['c_std=gnu11'] ) @@ -52,7 +52,7 @@ if not get_option('source-only') ) pcre2_dep = dependency('libpcre2-8') freetype_dep = dependency('freetype2') - sdl_dep = dependency('sdl2', method: 'config-tool') + sdl_dep = dependency('sdl2') lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep] endif #=============================================================================== @@ -94,17 +94,16 @@ endif install_data('licenses/licenses.md', install_dir : lite_docdir) -install_subdir('data' / 'core' , install_dir : lite_datadir, exclude_files : 'start.lua') +install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua') foreach data_module : ['fonts', 'plugins', 'colors'] - install_subdir('data' / data_module , install_dir : lite_datadir) + install_subdir(join_paths('data', data_module), install_dir : lite_datadir) endforeach configure_file( input : 'data/core/start.lua', output : 'start.lua', configuration : conf_data, - install : true, - install_dir : lite_datadir / 'core', + install_dir : join_paths(lite_datadir, 'core'), ) if not get_option('source-only') From 88ed312f6b6af17788e2aa2d73ccfe51f66d89e7 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 29 Dec 2021 16:00:53 +0100 Subject: [PATCH 049/409] Fix NagView missing mouse events --- data/core/nagview.lua | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/data/core/nagview.lua b/data/core/nagview.lua index 35ae9b43..fca6c306 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -104,13 +104,38 @@ function NagView:on_mouse_moved(mx, my, ...) end end +-- Used to store saved value for RootView.on_view_mouse_pressed +local on_view_mouse_pressed + + +local function capture_mouse_pressed(nag_view) + -- RootView is loaded locally to avoid NagView and RootView being + -- mutually recursive + local RootView = require "core.rootview" + on_view_mouse_pressed = RootView.on_view_mouse_pressed + RootView.on_view_mouse_pressed = function(button, x, y, clicks) + local handled = NagView.on_mouse_pressed(nag_view, button, x, y, clicks) + return handled or on_view_mouse_pressed(button, x, y, clicks) + end +end + + +local function release_mouse_pressed() + local RootView = require "core.rootview" + if on_view_mouse_pressed then + RootView.on_view_mouse_pressed = on_view_mouse_pressed + on_view_mouse_pressed = nil + end +end + + function NagView:on_mouse_pressed(button, mx, my, clicks) - if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end + if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end for i, _, x,y,w,h in self:each_option() do if mx >= x and my >= y and mx < x + w and my < y + h then self:change_hovered(i) command.perform "dialog:select" - break + return true end end end @@ -202,6 +227,12 @@ function NagView:next() self.force_focus = self.message ~= nil core.set_active_view(self.message ~= nil and self or core.next_active_view or core.last_active_view) + if self.message ~= nil and self then + -- We add a hook to manage all the mouse_pressed events. + capture_mouse_pressed(self) + else + release_mouse_pressed() + end end function NagView:show(title, message, options, on_select) From 46aaf57b45fd6a572c9b8c203d065395d9e80de1 Mon Sep 17 00:00:00 2001 From: Nightwing <94565465+Nightwing13@users.noreply.github.com> Date: Thu, 30 Dec 2021 06:26:58 +0900 Subject: [PATCH 050/409] [Fix] Pointer Bug in ToolBar plugin Fixes an issue where the pointer moves down when "open user module" button is spammed --- data/plugins/toolbarview.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/plugins/toolbarview.lua b/data/plugins/toolbarview.lua index bfd71138..debdd2df 100644 --- a/data/plugins/toolbarview.lua +++ b/data/plugins/toolbarview.lua @@ -84,11 +84,12 @@ end function ToolbarView:on_mouse_pressed(button, x, y, clicks) local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks) - if caught then return end + if caught then return false end core.set_active_view(core.last_active_view) if self.hovered_item then command.perform(self.hovered_item.command) end + return true end From 60322a93a877d54bd451edfe61381ea8f44a9993 Mon Sep 17 00:00:00 2001 From: Nightwing <94565465+Nightwing13@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:12:24 +0900 Subject: [PATCH 051/409] Update toolbarview.lua --- data/plugins/toolbarview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/toolbarview.lua b/data/plugins/toolbarview.lua index debdd2df..1a879877 100644 --- a/data/plugins/toolbarview.lua +++ b/data/plugins/toolbarview.lua @@ -84,7 +84,7 @@ end function ToolbarView:on_mouse_pressed(button, x, y, clicks) local caught = ToolbarView.super.on_mouse_pressed(self, button, x, y, clicks) - if caught then return false end + if caught then return caught end core.set_active_view(core.last_active_view) if self.hovered_item then command.perform(self.hovered_item.command) From adaf0235415c6c18fa907467e12a1f614fa2b001 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 29 Dec 2021 08:19:44 +0100 Subject: [PATCH 052/409] Always watch/unwatch subdirectories on all systems Simplifies and uniformize the logic on the Lua side for the setting of directories' watches. Now we always use the methods: systems.watch_dir_add / rm on all the project's directories at any depth when we are not in files limit mode. In files limited mode the functions systems.watch_dir_add/rm are called only on the expanded folders. The shown_subdir table is also updated only in files limited mode. On the C side, using the dmon library, we remove the recursive argument from the system.watch_dir and we always call it recursively except on Linux. At the same time the functions: systems.watch_dir_add / rm are provided but as dummy functions that does nothing except on Linux where they work as before to add / remove sub-directories in the inotify watch. In this was on the Lua side we always act we if the watches needed to be set for each sub-directory explicitly, independently of the system. The important improvement introduced is that we always avoid calling dmon_watch recursively on Linux. This latter thing is problematic with inotify and is therefore avoided on Linux. On the other side we simplifies the logic on the Lua side and remove conditions based on the OS used. --- data/core/init.lua | 100 +++++++++++++++++++++++++++++++++------------ src/api/system.c | 24 ++++++++++- 2 files changed, 95 insertions(+), 29 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 481fc431..803f1946 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -180,10 +180,9 @@ end function core.project_subdir_set_show(dir, filename, show) dir.shown_subdir[filename] = show - if dir.files_limit and PLATFORM == "Linux" then + if dir.files_limit and not dir.force_rescan then local fullpath = dir.name .. PATHSEP .. filename - local watch_fn = show and system.watch_dir_add or system.watch_dir_rm - local success = watch_fn(dir.watch_id, fullpath) + local success = (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) if not success then core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm") end @@ -228,9 +227,14 @@ end local function project_scan_add_entry(dir, fileinfo) + assert(not dir.force_rescan, "should be used on when force_rescan is unset") local index, match = file_search(dir.files, fileinfo) if not match then table.insert(dir.files, index, fileinfo) + if fileinfo.type == "dir" and not dir.files_limit then + -- ASSUMPTION: dir.force_rescan is FALSE + system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) + end dir.is_dirty = true end end @@ -252,7 +256,7 @@ local function files_list_match(a, i1, n, b) end -- arguments like for files_list_match -local function files_list_replace(as, i1, n, bs) +local function files_list_replace(as, i1, n, bs, insert_hook) local m = #bs local i, j = 1, 1 while i <= m or i <= n do @@ -262,6 +266,7 @@ local function files_list_replace(as, i1, n, bs) then table.insert(as, i1 + i, b) i, j, n = i + 1, j + 1, n + 1 + if insert_hook then insert_hook(b) end elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then table.remove(as, i1 + i) n = n - 1 @@ -297,7 +302,19 @@ local function rescan_project_subdir(dir, filename_rooted) end if not files_list_match(dir.files, index, n, new_files) then - files_list_replace(dir.files, index, n, new_files) + -- Since we are modifying the list of files we may add new directories and + -- when dir.files_limit is false we need to add a watch for each subdirectory. + -- We are therefore passing a insert hook function to the purpose of adding + -- a watch. + -- Note that the hook shold almost never be called, it happens only if + -- we missed some directory creation event from the directory monitoring which + -- almost never happens. With inotify is at least theoretically possible. + local need_subdir_watches = not dir.files_limit and not dir.force_rescan + files_list_replace(dir.files, index, n, new_files, need_subdir_watches and function(fileinfo) + if fileinfo.type == "dir" then + system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) + end + end) dir.is_dirty = true return true end @@ -316,35 +333,54 @@ local function add_dir_scan_thread(dir) end) end + +local function folder_add_subdirs_watch(dir) + for _, fileinfo in ipairs(dir.files) do + if fileinfo.type == "dir" then + system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) + end + end +end + + -- Populate a project folder top directory by scanning the filesystem. local function scan_project_folder(index) local dir = core.project_directories[index] - if PLATFORM == "Linux" then - local fstype = system.get_fs_type(dir.name) - dir.force_rescan = (fstype == "nfs" or fstype == "fuse") + local fstype = system.get_fs_type(dir.name) + dir.force_rescan = (fstype == "nfs" or fstype == "fuse") + if not dir.force_rescan then + dir.watch_id = system.watch_dir(dir.name) end local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred) + -- If dir.files_limit is set to TRUE it means that: + -- * we will not read recursively all the project files and we don't index them + -- * we read only the files for the subdirectories that are opened/expanded in the + -- TreeView + -- * we add a subdirectory watch only to the directories that are opened/expanded + -- * we set the values in the shown_subdir table + -- + -- If dir.files_limit is set to FALSE it means that: + -- * we will read recursively all the project files and we index them + -- * all the list of project files is always complete and kept updated when + -- changes happen on the disk + -- * all the subdirectories at any depth must have a watch using system.watch_dir_add + -- * we DO NOT set the values in the shown_subdir table + -- + -- * If force_rescan is set to TRUE no watch are used in any case. if not complete then dir.slow_filesystem = not complete and (entries_count <= config.max_project_files) dir.files_limit = true - if not dir.force_rescan then - -- Watch non-recursively on Linux only. - -- The reason is recursively watching with dmon on linux - -- doesn't work on very large directories. - dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux") - end if core.status_view then -- May be not yet initialized. show_max_files_warning(dir) end - else - if not dir.force_rescan then - dir.watch_id = system.watch_dir(dir.name, true) - end end dir.files = t if dir.force_rescan then add_dir_scan_thread(dir) else + if not dir.files_limit then + folder_add_subdirs_watch(dir) + end core.dir_rescan_add_job(dir, ".") end end @@ -385,15 +421,16 @@ local function rescan_project_directories() core.project_directories = {} for i = 1, n do -- add again the directories in the project local dir = core.add_project_directory(save_project_dirs[i].name) - -- The shown_subdir is only used on linux for very large directories. - -- replay them on the newly scanned project. - for subdir, show in pairs(save_project_dirs[i].shown_subdir) do - for j = 1, #dir.files do - if dir.files[j].filename == subdir then - -- the instructions above match when happens in TreeView:on_mouse_pressed - core.update_project_subdir(dir, subdir, show) - core.project_subdir_set_show(dir, subdir, show) - break + if dir.files_limit then + for subdir, show in pairs(save_project_dirs[i].shown_subdir) do + for j = 1, #dir.files do + if dir.files[j].filename == subdir then + -- The instructions below match when happens in TreeView:on_mouse_pressed. + -- We perform the operations only once iff the subdir is in dir.files. + core.update_project_subdir(dir, subdir, show) + core.project_subdir_set_show(dir, subdir, show) + break + end end end end @@ -411,9 +448,15 @@ end function core.update_project_subdir(dir, filename, expanded) + assert(dir.files_limit, "function should be called only when directory is in files limit mode") local index, n, file = project_subdir_bounds(dir, filename) if index then local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} + -- ASSUMPTION: core.update_project_subdir is called only when dir.files_limit is true + -- NOTE: we may add new directories below but we do not need to call + -- system.watch_dir_add because the current function is called only + -- in dir.files_limit mode and in this latter case we don't need to + -- add watch to new, unfolded, subdirectories. files_list_replace(dir.files, index, n, new_files) dir.is_dirty = true return true @@ -510,6 +553,9 @@ local function project_scan_remove_file(dir, filepath) local index, match = file_search(dir.files, fileinfo) if match then table.remove(dir.files, index) + if dir.files_limit and filetype == "dir" then + dir.shown_subdir[filepath] = nil + end dir.is_dirty = true return end diff --git a/src/api/system.c b/src/api/system.c index 4c14843b..955d0ee2 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -584,6 +584,11 @@ static int f_get_fs_type(lua_State *L) { lua_pushstring(L, "unknown"); return 1; } +#else +static int f_return_unknown(lua_State *L) { + lua_pushstring(L, "unknown"); + return 1; +} #endif @@ -715,8 +720,14 @@ static int f_set_window_opacity(lua_State *L) { static int f_watch_dir(lua_State *L) { const char *path = luaL_checkstring(L, 1); - const int recursive = lua_toboolean(L, 2); - uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0); + /* On linux we watch non-recursively and we add/remove each sub-directory explicitly + * using the function system.watch_dir_add/rm. On other systems we watch recursively + * and system.watch_dir_add/rm are dummy functions that always returns true. */ +#if __linux__ + const uint32_t dmon_flags = 0; +#else + const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE; +#endif dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL); if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); } lua_pushnumber(L, watch_id.id); @@ -746,6 +757,11 @@ static int f_watch_dir_rm(lua_State *L) { lua_pushboolean(L, dmon_watch_rm(watch_id, subdir)); return 1; } +#else +static int f_return_true(lua_State *L) { + lua_pushboolean(L, 1); + return 1; +} #endif #ifdef _WIN32 @@ -839,6 +855,10 @@ static const luaL_Reg lib[] = { { "watch_dir_add", f_watch_dir_add }, { "watch_dir_rm", f_watch_dir_rm }, { "get_fs_type", f_get_fs_type }, +#else + { "watch_dir_add", f_return_true }, + { "watch_dir_rm", f_return_true }, + { "get_fs_type", f_return_unknown }, #endif { NULL, NULL } }; From fd074ff39a69238bb72b37373babd6043e30a5d8 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 30 Dec 2021 12:11:01 +0100 Subject: [PATCH 053/409] Fix problem when opening project's module document It wasn't fine to call core.open_doc without filename argument and later call Doc:save without providing both the filename and the absolute filename. It was giving a Doc in an inconsistent status where self.filename was set but not self.abs_filename. Added an asset to detect early the problem if ever happens again. In turn the problem prevented the project's module hook to work if the file was newly created. --- data/core/commands/core.lua | 11 +++-------- data/core/doc/init.lua | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 16b371f3..3242e2ef 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -141,14 +141,9 @@ command.add(nil, { end, ["core:open-project-module"] = function() - local filename = ".lite_project.lua" - if system.get_file_info(filename) then - core.root_view:open_doc(core.open_doc(filename)) - else - local doc = core.open_doc() - core.root_view:open_doc(doc) - doc:save(filename) - end + local doc = core.open_doc(".lite_project.lua") + core.root_view:open_doc(doc) + doc:save() end, ["core:change-project-folder"] = function() diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 06cde9f9..7b97db35 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -85,6 +85,8 @@ function Doc:save(filename, abs_filename) assert(self.filename, "no filename set to default to") filename = self.filename abs_filename = self.abs_filename + else + assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path") end local fp = assert( io.open(filename, "wb") ) for _, line in ipairs(self.lines) do From 9578359b2b2f0402b840c86f33a9bba13593c586 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 30 Dec 2021 15:45:09 +0100 Subject: [PATCH 054/409] Remove inotify recursive directory monitoring We no longer use in Lite XL recursive directory monitoring as it was a source of problems. To enforce this at compile time we use the preprocessor to remove the implementation of recursive monitoring for the Linux implementation only. --- lib/dmon/dmon.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 2bc9e0c3..f04e27d1 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -169,6 +169,8 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id); # include # include # include +/* Recursive removed for Lite XL when using inotify. */ +# define LITE_XL_DISABLE_INOTIFY_RECURSIVE #elif DMON_OS_MACOS # include # include @@ -710,6 +712,10 @@ typedef struct dmon__state { static bool _dmon_init; static dmon__state _dmon; +/* Implementation of recursive monitoring was removed on Linux for the Lite XL + * application. It is never used with recent version of Lite XL starting from 2.0.5 + * and recursive monitoring with inotify was always problematic and half-broken. */ +#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask, bool followlinks, dmon__watch_state* watch) { @@ -764,6 +770,7 @@ _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t m } closedir(dir); } +#endif _DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd) { @@ -777,6 +784,7 @@ _DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int return NULL; } +#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE _DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* dirname) { struct dirent* entry; @@ -809,6 +817,7 @@ _DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* } closedir(dir); } +#endif _DMON_PRIVATE void dmon__inotify_process_events(void) { @@ -919,6 +928,7 @@ _DMON_PRIVATE void dmon__inotify_process_events(void) } if (ev->mask & IN_CREATE) { +# ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE if (ev->mask & IN_ISDIR) { if (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) { char watchdir[DMON_MAX_PATH]; @@ -948,6 +958,7 @@ _DMON_PRIVATE void dmon__inotify_process_events(void) ev = &_dmon.events[i]; // gotta refresh the pointer because it may be relocated } } +# endif watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data); } else if (ev->mask & IN_MODIFY) { @@ -1158,11 +1169,12 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, stb_sb_push(watch->wds, wd); // recursive mode: enumarate all child directories and add them to watch +#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE if (flags & DMON_WATCHFLAGS_RECURSIVE) { dmon__watch_recursive(watch->rootdir, watch->fd, inotify_mask, (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) ? true : false, watch); } - +#endif pthread_mutex_unlock(&_dmon.mutex); return dmon__make_id(id); From 68aea8851014206654f1195dc0e50799718110e3 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 30 Dec 2021 22:24:43 +0100 Subject: [PATCH 055/409] Fix error with ignore_files There was a double error because the config.ignore_files was used at two differect places in different ways. Now we apply coherently the original rule to apply config.ignore_files to the basename of each file or directory. --- data/core/init.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 803f1946..24083f0a 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -111,15 +111,20 @@ local function compare_file(a, b) end +local function fileinfo_pass_filter(info) + local basename = common.basename(info.filename) + return (info.size < config.file_size_limit * 1e6 and + not common.match_pattern(basename, config.ignore_files)) +end + + -- compute a file's info entry completed with "filename" to be used -- in project scan or falsy if it shouldn't appear in the list. local function get_project_file_info(root, file) local info = system.get_file_info(root .. file) if info then info.filename = strip_leading_path(file) - return (info.size < config.file_size_limit * 1e6 and - not common.match_pattern(info.filename, config.ignore_files) - and info) + return fileinfo_pass_filter(info) and info end end @@ -564,12 +569,8 @@ end local function project_scan_add_file(dir, filepath) - for fragment in string.gmatch(filepath, "([^/\\]+)") do - if common.match_pattern(fragment, config.ignore_files) then - return - end - end local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath) + if not fileinfo_pass_filter(fileinfo) then return end if fileinfo then project_scan_add_entry(dir, fileinfo) end From 85d26adb6276fad3e3fe6091941c9b512fb10c55 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 30 Dec 2021 23:57:23 +0100 Subject: [PATCH 056/409] Fix project's module loading when changing project Fix a bag of subtle problem about when loading the project module. We need to load the project's module before to scan the project directory. --- data/core/init.lua | 50 ++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 24083f0a..922715c4 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -52,18 +52,14 @@ local function update_recents_project(action, dir_path_abs) end -function core.set_project_dir(new_dir, change_project_fn, defer_add) +function core.set_project_dir(new_dir, change_project_fn) local chdir_ok = pcall(system.chdir, new_dir) if chdir_ok then if change_project_fn then change_project_fn() end core.project_dir = common.normalize_volume(new_dir) core.project_directories = {} - if not defer_add then - core.add_project_directory(new_dir) - end - return true end - return false + return chdir_ok end function core.close_current_project() @@ -83,13 +79,22 @@ function core.close_current_project() end end + +local function reload_customizations() + core.reload_module("core.style") + core.reload_module("core.config") + core.reload_module("core.keymap") + core.load_user_directory() + core.load_project_module() +end + + function core.open_folder_project(dir_path_abs) if core.set_project_dir(dir_path_abs, core.on_quit_project) then core.root_view:close_all_docviews() + reload_customizations() update_recents_project("add", dir_path_abs) - if not core.load_project_module() then - command.perform("core:open-log") - end + core.add_project_directory(dir_path_abs) core.on_enter_project(dir_path_abs) end end @@ -570,10 +575,8 @@ end local function project_scan_add_file(dir, filepath) local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath) - if not fileinfo_pass_filter(fileinfo) then return end - if fileinfo then - project_scan_add_entry(dir, fileinfo) - end + if not fileinfo or not fileinfo_pass_filter(fileinfo) then return end + project_scan_add_entry(dir, fileinfo) end @@ -697,26 +700,16 @@ local function configure_borderless_window() end -local function reload_customizations() - core.reload_module("core.style") - core.reload_module("core.config") - core.reload_module("core.keymap") - core.load_user_directory() - core.load_project_module() - rescan_project_directories() - configure_borderless_window() -end - - local function add_config_files_hooks() -- auto-realod style when user's module is saved by overriding Doc:Save() local doc_save = Doc.save local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua") - local module_filename = system.absolute_path(".lite_project.lua") function Doc:save(filename, abs_filename) doc_save(self, filename, abs_filename) - if self.abs_filename == user_filename or self.abs_filename == module_filename then + if self.abs_filename == user_filename or self.abs_filename == core.project_module_filename then reload_customizations() + rescan_project_directories() + configure_borderless_window() end end end @@ -787,7 +780,7 @@ function core.init() local project_dir_abs = system.absolute_path(project_dir) -- We prevent set_project_dir below to effectively add and scan the directory becaese tha -- project module and its ignore files is not yet loaded. - local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs, nil, true) + local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs) if set_project_ok then if project_dir_explicit then update_recents_project("add", project_dir_abs) @@ -797,7 +790,7 @@ function core.init() update_recents_project("remove", project_dir) end project_dir_abs = system.absolute_path(".") - if not core.set_project_dir(project_dir_abs, nil, true) then + if not core.set_project_dir(project_dir_abs) then system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd") os.exit(1) end @@ -1028,6 +1021,7 @@ end function core.load_project_module() local filename = ".lite_project.lua" + core.project_module_filename = system.absolute_path(filename) if system.get_file_info(filename) then return core.try(function() local fn, err = loadfile(filename) From 03350cc14b44e8734af8419535a99bebea11fae6 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 31 Dec 2021 00:20:52 +0100 Subject: [PATCH 057/409] Restore config.plugins when reloading config Some plugins store options in: config.plugins. so we restore all the kay-values of config.plugins when reloading the user preferences. --- data/core/init.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 922715c4..5d51c4cb 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -81,11 +81,18 @@ end local function reload_customizations() - core.reload_module("core.style") - core.reload_module("core.config") - core.reload_module("core.keymap") - core.load_user_directory() - core.load_project_module() + core.reload_module("core.style") + core.reload_module("core.keymap") + local plugins_save = {} + for k, v in pairs(config.plugins) do + plugins_save[k] = v + end + core.reload_module("core.config") + for k, v in pairs(plugins_save) do + config.plugins[k] = v + end + core.load_user_directory() + core.load_project_module() end From 445c79bb52120fa874dbc711aa1d4390c4d02848 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 31 Dec 2021 00:22:49 +0100 Subject: [PATCH 058/409] Revert "No longer store autocomplete options in config" This reverts commit 0f1b84040dd823823c17e925e1ed10380b7737c1. The new mechanism to save config.plugins upon user's configuration reload let us stay compatible with existing plugins. --- data/plugins/autocomplete.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index d324733f..fde9487e 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -10,7 +10,7 @@ local RootView = require "core.rootview" local DocView = require "core.docview" local Doc = require "core.doc" -local autocomplete_options = { +config.plugins.autocomplete = { -- Amount of characters that need to be written for autocomplete min_len = 3, -- The max amount of visible items @@ -192,7 +192,7 @@ local function update_suggestions() -- fuzzy match, remove duplicates and store items = common.fuzzy_match(items, partial) local j = 1 - for i = 1, autocomplete_options.max_suggestions do + for i = 1, config.plugins.autocomplete.max_suggestions do suggestions[i] = items[j] while items[j] and items[i].text == items[j].text do items[i].info = items[i].info or items[j].info @@ -235,7 +235,7 @@ local function get_suggestions_rect(av) max_width = math.max(max_width, w) end - local ah = autocomplete_options.max_height + local ah = config.plugins.autocomplete.max_height local max_items = #suggestions if max_items > ah then @@ -294,7 +294,7 @@ local function draw_suggestions_box(av) return end - local ah = autocomplete_options.max_height + local ah = config.plugins.autocomplete.max_height -- draw background rect local rx, ry, rw, rh = get_suggestions_rect(av) @@ -355,7 +355,7 @@ local function show_autocomplete() -- update partial symbol and suggestions partial = get_partial_symbol() - if #partial >= autocomplete_options.min_len or triggered_manually then + if #partial >= config.plugins.autocomplete.min_len or triggered_manually then update_suggestions() if not triggered_manually then @@ -469,7 +469,7 @@ function autocomplete.complete(completions, on_close) end function autocomplete.can_complete() - if #partial >= autocomplete_options.min_len then + if #partial >= config.plugins.autocomplete.min_len then return true end return false From 99ddf1fb9274f2f1928b92abfe62c2172c19cdb3 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Fri, 31 Dec 2021 13:53:01 +0100 Subject: [PATCH 059/409] Migrate to Lua 5.4 --- .gitignore | 2 +- data/core/bit.lua | 30 ++++++++++++++ data/core/commands/findreplace.lua | 2 +- data/core/common.lua | 2 +- data/core/statusview.lua | 2 +- data/core/tokenizer.lua | 1 + data/plugins/projectsearch.lua | 2 +- meson.build | 9 +++-- src/api/process.c | 12 +++--- src/api/regex.c | 12 +++--- src/api/system.c | 64 +++++++++++++++--------------- subprojects/lua.wrap | 16 ++++++-- 12 files changed, 98 insertions(+), 56 deletions(-) create mode 100644 data/core/bit.lua diff --git a/.gitignore b/.gitignore index 41183a14..d1bba414 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build*/ .build*/ lhelper/ submodules/ -subprojects/lua/ +subprojects/lua*/ subprojects/reproc/ /appimage* .ccls-cache diff --git a/data/core/bit.lua b/data/core/bit.lua new file mode 100644 index 00000000..f357d5fd --- /dev/null +++ b/data/core/bit.lua @@ -0,0 +1,30 @@ +local bit = {} + +local LUA_NBITS = 32 +local ALLONES = (~(((~0) << (LUA_NBITS - 1)) << 1)) + +local function trim(x) + return (x & ALLONES) +end + +local function mask(n) + return (~((ALLONES << 1) << ((n) - 1))) +end + +function bit.extract(n, field, width) + local r = trim(field) + local f = width + r = (r >> f) & mask(width) + return r +end + +function bit.replace(n, v, field, width) + local r = trim(v); + local v = trim(field); + local f = width + local m = mask(width); + r = (r & ~(m << f)) | ((v & m) << f); + return r +end + +return bit \ No newline at end of file diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index 0801b745..db6a2dd6 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -55,7 +55,7 @@ end local function find(label, search_fn) last_view, last_sel = core.active_view, { core.active_view.doc:get_selection() } - local text = last_view.doc:get_text(unpack(last_sel)) + local text = last_view.doc:get_text(table.unpack(last_sel)) found_expression = false core.command_view:set_text(text, true) diff --git a/data/core/common.lua b/data/core/common.lua index df7d435a..bae6636a 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -42,7 +42,7 @@ end function common.distance(x1, y1, x2, y2) - return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2)) + return math.sqrt(((x2-x1) ^ 2)+((y2-y1) ^ 2)) end diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 330fcc76..1e5140f2 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -124,7 +124,7 @@ function StatusView:get_items() col > config.line_limit and style.accent or style.text, "col: ", col, style.text, self.separator, - string.format("%d%%", line / #dv.doc.lines * 100), + string.format("%.f%%", line / #dv.doc.lines * 100), }, { style.text, indent_label, indent_size, style.dim, self.separator2, style.text, diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 5fd8c69f..03769c46 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -1,5 +1,6 @@ local syntax = require "core.syntax" local common = require "core.common" +local bit32 = require "core.bit" local tokenizer = {} diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index d0d75d7f..d0d14014 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -176,7 +176,7 @@ function ResultsView:draw() local text if self.searching then if files_number then - text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...", + text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...", per * 100, self.last_file_idx, files_number, #self.results, self.query) else diff --git a/meson.build b/meson.build index 44be29da..867b72b9 100644 --- a/meson.build +++ b/meson.build @@ -47,9 +47,12 @@ if not get_option('source-only') libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) threads_dep = dependency('threads') - lua_dep = dependency('lua5.2', fallback: ['lua', 'lua_dep'], - default_options: ['shared=false', 'use_readline=false', 'app=false'] - ) + lua_dep = dependency('lua5.4', required : false) + if not lua_dep.found() + lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'], + default_options: ['line_editing=false', 'default_library=static'] + ) + endif pcre2_dep = dependency('libpcre2-8') freetype_dep = dependency('freetype2') sdl_dep = dependency('sdl2') diff --git a/src/api/process.c b/src/api/process.c index b961ca51..ac1b21e6 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -128,12 +128,12 @@ static int process_start(lua_State* L) { #if LUA_VERSION_NUM > 501 lua_len(L, 1); #else - lua_pushnumber(L, (int)lua_objlen(L, 1)); + lua_pushinteger(L, (int)lua_objlen(L, 1)); #endif size_t cmd_len = luaL_checknumber(L, -1); lua_pop(L, 1); size_t arg_len = lua_gettop(L); for (size_t i = 1; i <= cmd_len; ++i) { - lua_pushnumber(L, i); + lua_pushinteger(L, i); lua_rawget(L, 1); cmd[i-1] = luaL_checkstring(L, -1); } @@ -343,7 +343,7 @@ static int f_write(lua_State* L) { return luaL_error(L, "error writing to process: %s", strerror(errno)); } #endif - lua_pushnumber(L, length); + lua_pushinteger(L, length); return 1; } @@ -375,7 +375,7 @@ static int f_tostring(lua_State* L) { static int f_pid(lua_State* L) { process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); - lua_pushnumber(L, self->pid); + lua_pushinteger(L, self->pid); return 1; } @@ -383,7 +383,7 @@ static int f_returncode(lua_State *L) { process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); if (self->running) return 0; - lua_pushnumber(L, self->returncode); + lua_pushinteger(L, self->returncode); return 1; } @@ -404,7 +404,7 @@ static int f_wait(lua_State* L) { int timeout = luaL_optnumber(L, 2, 0); if (poll_process(self, timeout)) return 0; - lua_pushnumber(L, self->returncode); + lua_pushinteger(L, self->returncode); return 1; } diff --git a/src/api/regex.c b/src/api/regex.c index 9f6bd3ee..08f0f142 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -104,17 +104,17 @@ int luaopen_regex(lua_State *L) { lua_setfield(L, -2, "__name"); lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, "regex"); - lua_pushnumber(L, PCRE2_ANCHORED); + lua_pushinteger(L, PCRE2_ANCHORED); lua_setfield(L, -2, "ANCHORED"); - lua_pushnumber(L, PCRE2_ANCHORED) ; + lua_pushinteger(L, PCRE2_ANCHORED) ; lua_setfield(L, -2, "ENDANCHORED"); - lua_pushnumber(L, PCRE2_NOTBOL); + lua_pushinteger(L, PCRE2_NOTBOL); lua_setfield(L, -2, "NOTBOL"); - lua_pushnumber(L, PCRE2_NOTEOL); + lua_pushinteger(L, PCRE2_NOTEOL); lua_setfield(L, -2, "NOTEOL"); - lua_pushnumber(L, PCRE2_NOTEMPTY); + lua_pushinteger(L, PCRE2_NOTEMPTY); lua_setfield(L, -2, "NOTEMPTY"); - lua_pushnumber(L, PCRE2_NOTEMPTY_ATSTART); + lua_pushinteger(L, PCRE2_NOTEMPTY_ATSTART); lua_setfield(L, -2, "NOTEMPTY_ATSTART"); return 1; } diff --git a/src/api/system.c b/src/api/system.c index 0da6cbbe..13fd29aa 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -145,8 +145,8 @@ top: ren_resize_window(); lua_pushstring(L, "resized"); /* The size below will be in points. */ - lua_pushnumber(L, e.window.data1); - lua_pushnumber(L, e.window.data2); + lua_pushinteger(L, e.window.data1); + lua_pushinteger(L, e.window.data2); return 3; } else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) { rencache_invalidate(); @@ -179,8 +179,8 @@ top: SDL_GetWindowPosition(window, &wx, &wy); lua_pushstring(L, "filedropped"); lua_pushstring(L, e.drop.file); - lua_pushnumber(L, mx - wx); - lua_pushnumber(L, my - wy); + lua_pushinteger(L, mx - wx); + lua_pushinteger(L, my - wy); SDL_free(e.drop.file); return 4; @@ -220,17 +220,17 @@ top: if (e.button.button == 1) { SDL_CaptureMouse(1); } lua_pushstring(L, "mousepressed"); lua_pushstring(L, button_name(e.button.button)); - lua_pushnumber(L, e.button.x); - lua_pushnumber(L, e.button.y); - lua_pushnumber(L, e.button.clicks); + lua_pushinteger(L, e.button.x); + lua_pushinteger(L, e.button.y); + lua_pushinteger(L, e.button.clicks); return 5; case SDL_MOUSEBUTTONUP: if (e.button.button == 1) { SDL_CaptureMouse(0); } lua_pushstring(L, "mousereleased"); lua_pushstring(L, button_name(e.button.button)); - lua_pushnumber(L, e.button.x); - lua_pushnumber(L, e.button.y); + lua_pushinteger(L, e.button.x); + lua_pushinteger(L, e.button.y); return 4; case SDL_MOUSEMOTION: @@ -243,20 +243,20 @@ top: e.motion.yrel += event_plus.motion.yrel; } lua_pushstring(L, "mousemoved"); - lua_pushnumber(L, e.motion.x); - lua_pushnumber(L, e.motion.y); - lua_pushnumber(L, e.motion.xrel); - lua_pushnumber(L, e.motion.yrel); + lua_pushinteger(L, e.motion.x); + lua_pushinteger(L, e.motion.y); + lua_pushinteger(L, e.motion.xrel); + lua_pushinteger(L, e.motion.yrel); return 5; case SDL_MOUSEWHEEL: lua_pushstring(L, "mousewheel"); - lua_pushnumber(L, e.wheel.y); + lua_pushinteger(L, e.wheel.y); return 2; case SDL_USEREVENT: lua_pushstring(L, "dirchange"); - lua_pushnumber(L, e.user.code >> 16); + lua_pushinteger(L, e.user.code >> 16); switch (e.user.code & 0xffff) { case DMON_ACTION_DELETE: lua_pushstring(L, "delete"); @@ -371,10 +371,10 @@ static int f_get_window_size(lua_State *L) { int x, y, w, h; SDL_GetWindowSize(window, &w, &h); SDL_GetWindowPosition(window, &x, &y); - lua_pushnumber(L, w); - lua_pushnumber(L, h); - lua_pushnumber(L, x); - lua_pushnumber(L, y); + lua_pushinteger(L, w); + lua_pushinteger(L, h); + lua_pushinteger(L, x); + lua_pushinteger(L, y); return 4; } @@ -544,10 +544,10 @@ static int f_get_file_info(lua_State *L) { } lua_newtable(L); - lua_pushnumber(L, s.st_mtime); + lua_pushinteger(L, s.st_mtime); lua_setfield(L, -2, "modified"); - lua_pushnumber(L, s.st_size); + lua_pushinteger(L, s.st_size); lua_setfield(L, -2, "size"); if (S_ISREG(s.st_mode)) { @@ -639,7 +639,7 @@ static int f_set_clipboard(lua_State *L) { static int f_get_time(lua_State *L) { double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); - lua_pushnumber(L, n); + lua_pushinteger(L, n); return 1; } @@ -693,7 +693,7 @@ static int f_fuzzy_match(lua_State *L) { strTarget += increment; } if (ptnTarget >= ptn && *ptnTarget) { return 0; } - lua_pushnumber(L, score - (int)strLen * 10); + lua_pushinteger(L, score - (int)strLen * 10); return 1; } @@ -718,13 +718,13 @@ static void* api_require(const char* symbol) { P(error), P(gc), P(getallocf), P(getfield), P(gethook), P(gethookcount), P(gethookmask), P(getinfo), P(getlocal), P(getmetatable), P(getstack), P(gettable), P(gettop), P(getupvalue), - P(insert), P(isnumber), P(isstring), P(isuserdata), - P(load), P(newstate), P(newthread), P(newuserdata), P(next), + P(isnumber), P(isstring), P(isuserdata), + P(load), P(newstate), P(newthread), P(next), P(pushboolean), P(pushcclosure), P(pushfstring), P(pushinteger), P(pushlightuserdata), P(pushlstring), P(pushnil), P(pushnumber), P(pushstring), P(pushthread), P(pushvalue), P(pushvfstring), P(rawequal), P(rawget), P(rawgeti), - P(rawset), P(rawseti), P(remove), P(replace), P(resume), + P(rawset), P(rawseti), P(resume), P(setallocf), P(setfield), P(sethook), P(setlocal), P(setmetatable), P(settable), P(settop), P(setupvalue), P(status), P(tocfunction), P(tointegerx), P(tolstring), P(toboolean), @@ -737,12 +737,12 @@ static void* api_require(const char* symbol) { U(newstate), U(setfuncs), U(buffinit), U(addlstring), U(addstring), U(addvalue), U(pushresult), #if LUA_VERSION_NUM >= 502 - P(absindex), P(arith), P(callk), P(compare), P(getctx), P(getglobal), P(getuservalue), - P(len), P(pcallk), P(pushunsigned), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal), - P(iscfunction), P(setuservalue), P(tounsignedx), P(yieldk), - U(checkversion_), U(tolstring), U(checkunsigned), U(len), U(getsubtable), U(prepbuffsize), + P(absindex), P(arith), P(callk), P(compare), P(getglobal), + P(len), P(pcallk), P(rawgetp), P(rawlen), P(rawsetp), P(setglobal), + P(iscfunction), P(yieldk), + U(checkversion_), U(tolstring), U(len), U(getsubtable), U(prepbuffsize), U(pushresultsize), U(buffinitsize), U(checklstring), U(checkoption), U(gsub), U(loadbufferx), - U(loadfilex), U(optinteger), U(optlstring), U(optunsigned), U(requiref), U(traceback) + U(loadfilex), U(optinteger), U(optlstring), U(requiref), U(traceback) #else P(objlen) #endif @@ -797,7 +797,7 @@ static int f_watch_dir(lua_State *L) { uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0); dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL); if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); } - lua_pushnumber(L, watch_id.id); + lua_pushinteger(L, watch_id.id); return 1; } diff --git a/subprojects/lua.wrap b/subprojects/lua.wrap index 1be7895b..71a37d39 100644 --- a/subprojects/lua.wrap +++ b/subprojects/lua.wrap @@ -1,4 +1,12 @@ -[wrap-git] -directory = lua -url = https://github.com/franko/lua -revision = v5.2.4-7 +[wrap-file] +directory = lua-5.4.3 +source_url = https://www.lua.org/ftp/lua-5.4.3.tar.gz +source_filename = lua-5.4.3.tar.gz +source_hash = f8612276169e3bfcbcfb8f226195bfc6e466fe13042f1076cbde92b7ec96bbfb +patch_filename = lua_5.4.3-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-1/get_patch +patch_hash = 66794455e18d373041c8ffa9c23d1629b813c7a716f8905425d347937f5c8dc8 + +[provide] +lua-5.4 = lua_dep + From 73a867fc89a823eb3145c56d754712172181d6ea Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Fri, 31 Dec 2021 13:53:31 +0100 Subject: [PATCH 060/409] add Libraries label --- .github/labeler.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index 41f66c7c..7c0ceeb7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -33,3 +33,6 @@ "Category: C Core": - src/**/* + +"Category: Libraries": + - lib/**/* From e079ddfa3735e37add2798cface7d877af28832b Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 2 Jan 2022 19:11:48 +0800 Subject: [PATCH 061/409] refactor toggle-block-comments, make command spaces aware, set selections correctly --- data/core/commands/doc.lua | 61 +++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 977f233f..0b040d88 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -305,48 +305,41 @@ local commands = { -- if nothing is selected, toggle the whole line if line1 == line2 and col1 == col2 then col1 = 1 - col2 = #doc().lines[line1] + col2 = #doc().lines[line2] end - local text = doc():get_text(line1, col1, line2, col2) - -- might need to deal with unicode later... - local cs, cse = text:find(comment[1], 1, true) - local ce = text:find(comment[2], #text - #comment[2] + 1, true) + -- automatically skip spaces + local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S") + local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$") + col1 = col1 + (word_start and (word_start - 1) or 0) + col2 = word_end and word_end or col2 - local new_col1, new_col2 = col1, col2 - -- uncomment - if cs and ce then - if line1 == line2 then - new_col2 = new_col2 - #table.concat(comment) - else - new_col2 = new_col2 - #comment[2] + local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1]) + local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2) + + if block_start == comment[1] and block_end == comment[2] then + -- remove up to 1 whitespace after the comment + local start_len, stop_len = #comment[1], #comment[2] + if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then + start_len = start_len + 1 + end + if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then + stop_len = stop_len + 1 end - -- remove 1 whitespace if possible - if text:sub(cse + 1, cse + 1) == " " then - cse = cse + 1 - if line1 == line2 then - new_col2 = new_col2 - 1 - end - end - if text:sub(ce - 1, ce - 1) == " " then - ce = ce - 1 - new_col2 = new_col2 - 1 - end + doc():remove(line1, col1, line1, col1 + start_len) + col2 = col2 - (line1 == line2 and start_len or 0) + doc():remove(line2, col2 - stop_len, line2, col2) - text = text:sub(cse + 1, ce - 1) - -- comment + doc():set_selections(1, line1, col1, line2, col2 - stop_len) else - text = comment[1] .. " " .. text .. " " .. comment[2] - if line1 == line2 then - new_col2 = new_col2 + #table.concat(comment) + 2 - else - new_col2 = new_col2 + #comment[2] + 1 - end + doc():insert(line1, col1, comment[1] .. " ") + col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0) + doc():insert(line2, col2, " " .. comment[2]) + + col2 = col2 + #comment[2] + 1 + doc():set_selections(idx, line1, col1, line2, col2) end - doc():remove(line1, col1, line2, col2) - doc():insert(line1, col1, text) - doc():set_selections(idx, line1, new_col1, line2, new_col2) end end, From df0f6fb94cb0efae95b9a5c03e35abdc14951ef8 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 2 Jan 2022 19:18:08 +0800 Subject: [PATCH 062/409] make set_selections consistent --- data/core/commands/doc.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 0b040d88..6337ae89 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -337,8 +337,7 @@ local commands = { col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0) doc():insert(line2, col2, " " .. comment[2]) - col2 = col2 + #comment[2] + 1 - doc():set_selections(idx, line1, col1, line2, col2) + doc():set_selections(idx, line1, col1, line2, col2 + #comment[2] + 1) end end end, From bf578d5ee4485f79a40b2507fece8227c46fa730 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 3 Jan 2022 18:50:43 +0100 Subject: [PATCH 063/409] Fix logic for file create event When we get a file or directory creation event we need to ensure that all the parent directories pass the ignore_files test. --- data/core/init.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 5d51c4cb..4074043e 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -582,8 +582,16 @@ end local function project_scan_add_file(dir, filepath) local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath) - if not fileinfo or not fileinfo_pass_filter(fileinfo) then return end - project_scan_add_entry(dir, fileinfo) + if fileinfo then + repeat + filepath = common.dirname(filepath) + local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath) + if filepath and not parent_info then + return + end + until not parent_info + project_scan_add_entry(dir, fileinfo) + end end From f3cf7ac9c7d60a9d23f031ab526d0a279c2fa410 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 4 Jan 2022 18:06:30 +0100 Subject: [PATCH 064/409] Do not reload core.keymap module Avoid reloading the core.keymap module when user's config or project module change. The reason is the plugins like autocomplete can add keymaps and the additions from plugins would be lost. Close issue #793 --- data/core/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 4074043e..b97ab2d6 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -82,7 +82,6 @@ end local function reload_customizations() core.reload_module("core.style") - core.reload_module("core.keymap") local plugins_save = {} for k, v in pairs(config.plugins) do plugins_save[k] = v From 9929ca9c2d83231872a2b5fb05f4c27f41d092c0 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 5 Jan 2022 23:21:47 +0100 Subject: [PATCH 065/409] Fix logic with project directories suggestions Attempt to fix issue #791. The logic set with the previous commit for suggest_directory is similar to the one we use except the previous expression was false do to operator precedence for "and" versus "or". With the modification here, when opening a project directory, we suggest the recently used projects if the text is equal to dirname(project_dir) + "/" which happens to be the text the command view is initially set to. In addition we do the same if text is "". If the condition is not met we return the suggestions from common.dir_path_suggest to match the text entered. Works well on Linux but may not solve the problem on Windows, it should be tested. --- data/core/commands/core.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 3242e2ef..29626c86 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -10,8 +10,9 @@ local restore_title_view = false local function suggest_directory(text) text = common.home_expand(text) - return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir))) - and core.recent_projects or common.dir_path_suggest(text)) + local basedir = common.dirname(core.project_dir) + return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and + core.recent_projects or common.dir_path_suggest(text)) end command.add(nil, { @@ -149,7 +150,7 @@ command.add(nil, { ["core:change-project-folder"] = function() local dirname = common.dirname(core.project_dir) if dirname then - core.command_view:set_text(common.home_encode(dirname)) + core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end core.command_view:enter("Change Project Folder", function(text, item) text = system.absolute_path(common.home_expand(item and item.text or text)) @@ -169,7 +170,7 @@ command.add(nil, { ["core:open-project-folder"] = function() local dirname = common.dirname(core.project_dir) if dirname then - core.command_view:set_text(common.home_encode(dirname)) + core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end core.command_view:enter("Open Project", function(text, item) text = common.home_expand(item and item.text or text) From 1b57107352dc8c26999febb93e46cb5338a00255 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 5 Jan 2022 23:32:26 +0100 Subject: [PATCH 066/409] Fix problem with special file types For special file types like the ones in /dev/ the info entry's type is neither file neither dir. We prevent these kind of files from being listed in the project. --- data/core/init.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index b97ab2d6..aba10d8f 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -133,7 +133,9 @@ end -- in project scan or falsy if it shouldn't appear in the list. local function get_project_file_info(root, file) local info = system.get_file_info(root .. file) - if info then + -- info can be not nil but info.type may be nil if is neither a file neither + -- a directory, for example for /dev/* entries on linux. + if info and info.type then info.filename = strip_leading_path(file) return fileinfo_pass_filter(info) and info end From 1e7075ca9fb59c4f237d747ee7e7fd06baaf183a Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 5 Jan 2022 23:42:47 +0100 Subject: [PATCH 067/409] Do not force choosing project dir to suggestion When changing or opening a project directory do not take the selected item from suggestion but simply the entered text as it is. Otherwise the user may be unable to choose a directory if the text matches the beginning of suggestion. Close #791 --- data/core/commands/core.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 29626c86..971b95f1 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -152,8 +152,8 @@ command.add(nil, { if dirname then core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end - core.command_view:enter("Change Project Folder", function(text, item) - text = system.absolute_path(common.home_expand(item and item.text or text)) + core.command_view:enter("Change Project Folder", function(text) + text = system.absolute_path(common.home_expand(text)) if text == core.project_dir then return end local path_stat = system.get_file_info(text) if not path_stat or path_stat.type ~= 'dir' then @@ -172,8 +172,8 @@ command.add(nil, { if dirname then core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end - core.command_view:enter("Open Project", function(text, item) - text = common.home_expand(item and item.text or text) + core.command_view:enter("Open Project", function(text) + text = common.home_expand(text) local path_stat = system.get_file_info(text) if not path_stat or path_stat.type ~= 'dir' then core.error("Cannot open folder %q", text) From 7ded5c819929e6e06a9fc21cd964ebbfb22542d1 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 6 Jan 2022 18:00:15 +0100 Subject: [PATCH 068/409] Fix problem when reloading project directory --- data/core/init.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index aba10d8f..52f4112a 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -440,13 +440,27 @@ local function rescan_project_directories() for i = 1, n do -- add again the directories in the project local dir = core.add_project_directory(save_project_dirs[i].name) if dir.files_limit then - for subdir, show in pairs(save_project_dirs[i].shown_subdir) do + -- We need to sort the list of shown subdirectories so that higher level + -- directories are populated first. We use the function system.path_compare + -- because it order the entries in the appropriate order. + -- TODO: we may consider storing the table shown_subdir as a sorted table + -- since the beginning. + local subdir_list = {} + for subdir in pairs(save_project_dirs[i].shown_subdir) do + table.insert(subdir_list, subdir) + end + table.sort(subdir_list, function(a, b) return system.path_compare(a, "dir", b, "dir") end) + for _, subdir in ipairs(subdir_list) do + local show = save_project_dirs[i].shown_subdir[subdir] for j = 1, #dir.files do if dir.files[j].filename == subdir then -- The instructions below match when happens in TreeView:on_mouse_pressed. -- We perform the operations only once iff the subdir is in dir.files. - core.update_project_subdir(dir, subdir, show) + -- In theory set_show below may fail and return false but is it is listed + -- there it means it succeeded before so we are optimistically assume it + -- will not fail for the sake of simplicity. core.project_subdir_set_show(dir, subdir, show) + core.update_project_subdir(dir, subdir, show) break end end From 087314aea42e20f5fc9be990ed2451eaace69491 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Fri, 7 Jan 2022 17:31:24 +0800 Subject: [PATCH 069/409] fix lhelper script --- scripts/lhelper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lhelper.sh b/scripts/lhelper.sh index af6ae158..3122ff0d 100644 --- a/scripts/lhelper.sh +++ b/scripts/lhelper.sh @@ -72,4 +72,4 @@ main() { fi } -main +main "$@" From 143b389365aec6bd3b16800d9f54cb92f87f42e7 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 7 Jan 2022 10:40:26 +0100 Subject: [PATCH 070/409] Clear TreeView cache when closing project --- data/plugins/treeview.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index ffe93ca5..9f59bc80 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -402,6 +402,12 @@ function RootView:draw(...) menu:draw() end +local on_quit_project = core.on_quit_project +function core.on_quit_project() + view.cache = {} + on_quit_project() +end + local function is_project_folder(path) return common.basename(core.project_dir) == path end From fc809b3172b5c80f98d4f5496fa7c5a4cc903623 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 8 Jan 2022 18:45:38 +0800 Subject: [PATCH 071/409] comment the entire line when using block comment --- data/core/commands/doc.lua | 133 +++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 6337ae89..54b84921 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -97,6 +97,67 @@ local function set_cursor(x, y, snap_type) core.blink_reset() end +local function line_comment(comment, line1, line2) + local comment_text = comment .. " " + local uncomment = true + local start_offset = math.huge + for line = line1, line2 do + local text = doc().lines[line] + local s = text:find("%S") + local cs, ce = text:find(comment_text, s, true) + if s and cs ~= s then + uncomment = false + start_offset = math.min(start_offset, s) + end + end + for line = line1, line2 do + local text = doc().lines[line] + local s = text:find("%S") + if uncomment then + local cs, ce = text:find(comment_text, s, true) + if ce then + doc():remove(line, cs, line, ce + 1) + end + elseif s then + doc():insert(line, start_offset, comment_text) + end + end +end + +local function block_comment(comment, line1, col1, line2, col2) + -- automatically skip spaces + local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S") + local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$") + col1 = col1 + (word_start and (word_start - 1) or 0) + col2 = word_end and word_end or col2 + + local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1]) + local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2) + + if block_start == comment[1] and block_end == comment[2] then + -- remove up to 1 whitespace after the comment + local start_len, stop_len = #comment[1], #comment[2] + if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then + start_len = start_len + 1 + end + if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then + stop_len = stop_len + 1 + end + + doc():remove(line1, col1, line1, col1 + start_len) + col2 = col2 - (line1 == line2 and start_len or 0) + doc():remove(line2, col2 - stop_len, line2, col2) + + return line1, col1, line2, col2 - stop_len + else + doc():insert(line1, col1, comment[1] .. " ") + col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0) + doc():insert(line2, col2, " " .. comment[2]) + + return line1, col1, line2, col2 + #comment[2] + 1 + end +end + local selection_commands = { ["doc:select-none"] = function() local line, col = doc():get_selection() @@ -308,73 +369,27 @@ local commands = { col2 = #doc().lines[line2] end - -- automatically skip spaces - local word_start = doc():get_text(line1, col1, line1, math.huge):find("%S") - local word_end = doc():get_text(line2, 1, line2, col2):find("%s*$") - col1 = col1 + (word_start and (word_start - 1) or 0) - col2 = word_end and word_end or col2 - - local block_start = doc():get_text(line1, col1, line1, col1 + #comment[1]) - local block_end = doc():get_text(line2, col2 - #comment[2], line2, col2) - - if block_start == comment[1] and block_end == comment[2] then - -- remove up to 1 whitespace after the comment - local start_len, stop_len = #comment[1], #comment[2] - if doc():get_text(line1, col1 + #comment[1], line1, col1 + #comment[1] + 1):find("%s$") then - start_len = start_len + 1 - end - if doc():get_text(line2, col2 - #comment[2] - 1, line2, col2):find("^%s") then - stop_len = stop_len + 1 - end - - doc():remove(line1, col1, line1, col1 + start_len) - col2 = col2 - (line1 == line2 and start_len or 0) - doc():remove(line2, col2 - stop_len, line2, col2) - - doc():set_selections(1, line1, col1, line2, col2 - stop_len) - else - doc():insert(line1, col1, comment[1] .. " ") - col2 = col2 + (line1 == line2 and (#comment[1] + 1) or 0) - doc():insert(line2, col2, " " .. comment[2]) - - doc():set_selections(idx, line1, col1, line2, col2 + #comment[2] + 1) - end + line1, col1, line2, col2 = block_comment(comment, line1, col1, line2, col2) + doc():set_selections(idx, line1, col1, line2, col2) end end, ["doc:toggle-line-comments"] = function() local comment = doc().syntax.comment + local block = false if not comment then - if doc().syntax.block_comment then - command.perform "doc:toggle-block-comments" - end - return + comment = doc().syntax.block_comment + if not comment then return end + block = true end - local indentation = doc():get_indent_string() - local comment_text = comment .. " " - for idx, line1, _, line2 in doc_multiline_selections(true) do - local uncomment = true - local start_offset = math.huge - for line = line1, line2 do - local text = doc().lines[line] - local s = text:find("%S") - local cs, ce = text:find(comment_text, s, true) - if s and cs ~= s then - uncomment = false - start_offset = math.min(start_offset, s) - end - end - for line = line1, line2 do - local text = doc().lines[line] - local s = text:find("%S") - if uncomment then - local cs, ce = text:find(comment_text, s, true) - if ce then - doc():remove(line, cs, line, ce + 1) - end - elseif s then - doc():insert(line, start_offset, comment_text) + + for _, line1, _, line2 in doc_multiline_selections(true) do + if block then + for line = line1, line2 do + block_comment(comment, line, 1, line, #doc().lines[line]) end + else + line_comment(comment, line1, line2) end end end, From 31d448971aa992c75ee4d79d73e985f36927ce25 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 8 Jan 2022 12:59:15 -0500 Subject: [PATCH 072/409] Restored floating point time granularity. --- data/core/init.lua | 4 ++-- src/api/system.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index c76c6366..446d6b20 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -779,8 +779,8 @@ function core.confirm_close_docs(docs, close_fn, ...) end end -local temp_uid = (system.get_time() * 1000) % 0xffffffff -local temp_file_prefix = string.format(".lite_temp_%08x", temp_uid) +local temp_uid = math.floor(system.get_time() * 1000) % 0xffffffff +local temp_file_prefix = string.format(".lite_temp_%08x", tonumber(temp_uid)) local temp_file_counter = 0 local function delete_temp_files() diff --git a/src/api/system.c b/src/api/system.c index 13fd29aa..8abc5595 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -639,7 +639,7 @@ static int f_set_clipboard(lua_State *L) { static int f_get_time(lua_State *L) { double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); - lua_pushinteger(L, n); + lua_pushnumber(L, n); return 1; } From 5b154c189fd6213b7e9bd2f5197e2550607f86f6 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 3 Jan 2022 15:05:57 +0100 Subject: [PATCH 073/409] First version of paths in ignore_files Works correctly and the logic seems sound even if somewhat quirky. `^%.` match any file of directory whose basename begins with a dot. `^/node_modules$/"` match a directory named `node_modules` at the project's root. Note that the final '/' needs to be at the end. The '/' after the '^' needs to be there to trigger a match of the full path filename so we are sure it is at the root. PROBLEM: the '/' to trigger full path match could be in a pattern's special expression like: [^/] `^%.git$/` match any directory name '.git' anywhere in the project. `^/%.git$/` match a directory named '.git' only at the project's root. `^/subprojects/.+/` match any directory in a top-level folder named "subprojects". `^/build/` match any top level directory whose name begins with "build" PROBLEM: may be surprising, one may expects it matches only a directory named 'build'. It actually acts like it was `^/build.*/`. --- data/core/init.lua | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 52f4112a..fe4c75be 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -122,10 +122,37 @@ local function compare_file(a, b) end + + + + local function fileinfo_pass_filter(info) + if info.size >= config.file_size_limit * 1e6 then return false end local basename = common.basename(info.filename) - return (info.size < config.file_size_limit * 1e6 and - not common.match_pattern(basename, config.ignore_files)) + -- replace '\' with '/' for Windows where PATHSEP = '\' + local fullname = "/" .. info.filename:gsub("\\", "/") + local ipatterns = config.ignore_files + -- config.ignore_files could be a simple string... + if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end + for _, pattern in ipairs(ipatterns) do + local is_path_like = pattern:match("/[^/]") -- contains a slash but not at the end + local dir_pass = true + if pattern:match("(.+)/$") then + dir_pass = (info.type == "dir") + -- the final '/' should not participate to the match. + pattern = pattern:match("(.+)/$") + end + if is_path_like then + if fullname:match(pattern) and dir_pass then + return false + end + else + if basename:match(pattern) and dir_pass then + return false + end + end + end + return true end From 295c65da92cd7bb3690f04629ebbffda5ff6c6a0 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 3 Jan 2022 17:49:15 +0100 Subject: [PATCH 074/409] Use compiled ignore_files pattern Try to digest the ignore_files pattern before potentially processing a lot of files because it may be expensive. --- data/core/init.lua | 64 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index fe4c75be..c17f0498 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -122,34 +122,32 @@ local function compare_file(a, b) end +local function compile_ignore_files() + local ipatterns = config.ignore_files + local compiled = {} + -- config.ignore_files could be a simple string... + if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end + for i, pattern in ipairs(ipatterns) do + local match_dir = pattern:match("(.+)/$") + compiled[i] = { + use_path = pattern:match("/[^/]"), -- contains a slash but not at the end + match_dir = match_dir, + pattern = match_dir or pattern + } + end + return compiled +end - - -local function fileinfo_pass_filter(info) +local function fileinfo_pass_filter(info, ignore_compiled) if info.size >= config.file_size_limit * 1e6 then return false end local basename = common.basename(info.filename) -- replace '\' with '/' for Windows where PATHSEP = '\' local fullname = "/" .. info.filename:gsub("\\", "/") - local ipatterns = config.ignore_files - -- config.ignore_files could be a simple string... - if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end - for _, pattern in ipairs(ipatterns) do - local is_path_like = pattern:match("/[^/]") -- contains a slash but not at the end - local dir_pass = true - if pattern:match("(.+)/$") then - dir_pass = (info.type == "dir") - -- the final '/' should not participate to the match. - pattern = pattern:match("(.+)/$") - end - if is_path_like then - if fullname:match(pattern) and dir_pass then - return false - end - else - if basename:match(pattern) and dir_pass then - return false - end + for _, compiled in ipairs(ignore_compiled) do + local pass_dir = (not compiled.match_dir or info.type == "dir") + if (compiled.use_path and fullname or basename):match(compiled.pattern) and pass_dir then + return false end end return true @@ -158,13 +156,13 @@ end -- compute a file's info entry completed with "filename" to be used -- in project scan or falsy if it shouldn't appear in the list. -local function get_project_file_info(root, file) +local function get_project_file_info(root, file, ignore_compiled) local info = system.get_file_info(root .. file) -- info can be not nil but info.type may be nil if is neither a file neither -- a directory, for example for /dev/* entries on linux. if info and info.type then info.filename = strip_leading_path(file) - return fileinfo_pass_filter(info) and info + return fileinfo_pass_filter(info, ignore_compiled) and info end end @@ -186,15 +184,16 @@ end -- When recursing "root" will always be the same, only "path" will change. -- Returns a list of file "items". In eash item the "filename" will be the -- complete file path relative to "root" *without* the trailing '/'. -local function get_directory_files(dir, root, path, t, entries_count, recurse_pred, begin_hook) +local function get_directory_files(dir, root, path, t, ignore_compiled, entries_count, recurse_pred, begin_hook) if begin_hook then begin_hook() end + ignore_compiled = ignore_compiled or compile_ignore_files() local t0 = system.get_time() local all = system.list_dir(root .. path) or {} local t_elapsed = system.get_time() - t0 local dirs, files = {}, {} for _, file in ipairs(all) do - local info = get_project_file_info(root, path .. PATHSEP .. file) + local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) if info then table.insert(info.type == "dir" and dirs or files, info) entries_count = entries_count + 1 @@ -206,7 +205,7 @@ local function get_directory_files(dir, root, path, t, entries_count, recurse_pr for _, f in ipairs(dirs) do table.insert(t, f) if recurse_pred(dir, f.filename, entries_count, t_elapsed) then - local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred, begin_hook) + local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, ignore_compiled, entries_count, recurse_pred, begin_hook) recurse_complete = recurse_complete and complete entries_count = n else @@ -339,7 +338,7 @@ local function project_subdir_bounds(dir, filename) end local function rescan_project_subdir(dir, filename_rooted) - local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, 0, core.project_subdir_is_shown, coroutine.yield) + local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, nil, 0, core.project_subdir_is_shown, coroutine.yield) local index, n = 0, #dir.files if filename_rooted ~= "" then local filename = strip_leading_path(filename_rooted) @@ -396,7 +395,7 @@ local function scan_project_folder(index) if not dir.force_rescan then dir.watch_id = system.watch_dir(dir.name) end - local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred) + local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0, timed_max_files_pred) -- If dir.files_limit is set to TRUE it means that: -- * we will not read recursively all the project files and we don't index them -- * we read only the files for the subdirectories that are opened/expanded in the @@ -510,7 +509,7 @@ function core.update_project_subdir(dir, filename, expanded) assert(dir.files_limit, "function should be called only when directory is in files limit mode") local index, n, file = project_subdir_bounds(dir, filename) if index then - local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} + local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, nil, 0, core.project_subdir_is_shown) or {} -- ASSUMPTION: core.update_project_subdir is called only when dir.files_limit is true -- NOTE: we may add new directories below but we do not need to call -- system.watch_dir_add because the current function is called only @@ -623,11 +622,12 @@ end local function project_scan_add_file(dir, filepath) - local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath) + local ignore = compile_ignore_files() + local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath, ignore) if fileinfo then repeat filepath = common.dirname(filepath) - local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath) + local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath, ignore) if filepath and not parent_info then return end From a703840068c59758bfbde2e207d19d5c10600b2e Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 3 Jan 2022 23:43:12 +0100 Subject: [PATCH 075/409] Add some comments for ignore_files logic --- data/core/init.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index c17f0498..fcd66734 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -122,6 +122,7 @@ local function compare_file(a, b) end +-- inspect config.ignore_files patterns and prepare ready to use entries. local function compile_ignore_files() local ipatterns = config.ignore_files local compiled = {} @@ -131,8 +132,8 @@ local function compile_ignore_files() local match_dir = pattern:match("(.+)/$") compiled[i] = { use_path = pattern:match("/[^/]"), -- contains a slash but not at the end - match_dir = match_dir, - pattern = match_dir or pattern + match_dir = match_dir, -- to be used as a boolen value + pattern = match_dir or pattern -- get the actual pattern } end return compiled @@ -625,11 +626,13 @@ local function project_scan_add_file(dir, filepath) local ignore = compile_ignore_files() local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath, ignore) if fileinfo then + -- on Windows and MacOS we can get events from directories we are not following: + -- check if each parent directories pass the ignore_files rules. repeat filepath = common.dirname(filepath) local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath, ignore) if filepath and not parent_info then - return + return -- parent directory does match ignore_files rules: stop there end until not parent_info project_scan_add_entry(dir, fileinfo) From 5032e7352e0570644a35f805df1df9d203836677 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 4 Jan 2022 00:25:14 +0100 Subject: [PATCH 076/409] Write an initial project module if not present --- data/core/commands/core.lua | 3 +++ data/core/init.lua | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 971b95f1..2ea34b2d 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -142,6 +142,9 @@ command.add(nil, { end, ["core:open-project-module"] = function() + if not system.get_file_info(".lite_project.lua") then + core.try(core.write_init_project_module, ".lite_project.lua") + end local doc = core.open_doc(".lite_project.lua") core.root_view:open_doc(doc) doc:save() diff --git a/data/core/init.lua b/data/core/init.lua index fcd66734..67efe11e 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -715,6 +715,46 @@ local style = require "core.style" end +function core.write_init_project_module(init_filename) + local init_file = io.open(init_filename, "w") + if not init_file then error("cannot create file: \"" .. init_filename .. "\"") end + init_file:write([[ +-- Put project's module settings here. +-- This module will be loaded when opening a project, after the user module +-- configuration. +-- It will be automatically reloaded when saved. + +local config = require "core.config" + +-- you can add some patterns to ignore files within the project +-- config.ignore_files = {"^%.", } + +-- Patterns are normally applied to the file's or directory's name, without +-- its path. See below about how to include the path. +-- +-- Here some examples: +-- +-- "^%." match any file of directory whose basename begins with a dot. +-- +-- When there is an '/' at the end the pattern will only match directories. The final +-- '/' is removed from the pattern to match the file's or directory's name. +-- +-- "^%.git$/" match any directory named ".git" anywhere in the project. +-- +-- If a "/" appears anywhere in the pattern (except at the end) then the pattern +-- will be applied to the full path of the file or directory. An initial "/" will +-- be prepended to the file's or directory's path to indicate the project's root. +-- +-- "^/node_modules$/" match a directory named "node_modules" at the project's root. +-- "^/build/" match any top level directory whose name _begins_ with "build" +-- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects". + +-- You may activate some plugins on a pre-project base to override the user's settings. +-- config.plugins.trimwitespace = true +]]) + init_file:close() +end + function core.load_user_directory() return core.try(function() From 7473fbf32c35838511703a3ed79bb2a273812a89 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 6 Jan 2022 00:10:14 +0100 Subject: [PATCH 077/409] Fix undue asserts in dmon_extra Some asserts are placed in case that can effectively occur so we remove the assertion and we return false. In turn we adapt the logic accordingly so when false is returned to add a watch we do not open that directory. --- data/core/init.lua | 8 ++++---- data/plugins/treeview.lua | 8 +++++--- lib/dmon/dmon_extra.h | 4 ---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 67efe11e..05838192 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -224,14 +224,14 @@ end function core.project_subdir_set_show(dir, filename, show) - dir.shown_subdir[filename] = show if dir.files_limit and not dir.force_rescan then local fullpath = dir.name .. PATHSEP .. filename - local success = (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) - if not success then - core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm") + if not (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) then + return false end end + dir.shown_subdir[filename] = show + return true end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 9f59bc80..93c43189 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -230,12 +230,14 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) if keymap.modkeys["ctrl"] and button == "left" then create_directory_in(hovered_item) else - hovered_item.expanded = not hovered_item.expanded local hovered_dir = core.project_dir_by_name(hovered_item.dir_name) if hovered_dir and hovered_dir.files_limit then - core.update_project_subdir(hovered_dir, hovered_item.filename, hovered_item.expanded) - core.project_subdir_set_show(hovered_dir, hovered_item.filename, hovered_item.expanded) + if not core.project_subdir_set_show(hovered_dir, hovered_item.filename, not hovered_item.expanded) then + return + end + core.update_project_subdir(hovered_dir, hovered_item.filename, not hovered_item.expanded) end + hovered_item.expanded = not hovered_item.expanded end else core.try(function() diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h index 4b321034..cbacdc93 100644 --- a/lib/dmon/dmon_extra.h +++ b/lib/dmon/dmon_extra.h @@ -62,7 +62,6 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); dmon__strcat(fullpath, sizeof(fullpath), watchdir); if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) { - _DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir); if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -79,7 +78,6 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) // check that the directory is not already added for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) { if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) { - _DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir); if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -92,7 +90,6 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir); int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask); if (wd == -1) { - _DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno); if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -137,7 +134,6 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) } } if (i >= c) { - _DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir); if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; From 1520c12580d91a6455031b07efc2fc37deb8959a Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 8 Jan 2022 10:55:43 +0100 Subject: [PATCH 078/409] Remove DMON_LOG_ERROR to return an error code --- data/core/init.lua | 7 +++- lib/dmon/dmon.h | 78 +++++++++++++++++++++++++------------------ lib/dmon/dmon_extra.h | 2 ++ src/api/system.c | 13 ++++++-- 4 files changed, 64 insertions(+), 36 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 05838192..0d47af14 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -394,7 +394,12 @@ local function scan_project_folder(index) local fstype = system.get_fs_type(dir.name) dir.force_rescan = (fstype == "nfs" or fstype == "fuse") if not dir.force_rescan then - dir.watch_id = system.watch_dir(dir.name) + local watch_err + dir.watch_id, watch_err = system.watch_dir(dir.name) + if not dir.watch_id then + core.log("Watch directory %s: %s", dir.name, watch_err) + dir.force_rescan = true + end end local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0, timed_max_files_pred) -- If dir.files_limit is set to TRUE it means that: diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index f04e27d1..496463f1 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -44,9 +44,6 @@ // DMON_ASSERT: // define this to provide your own assert // default is 'assert' -// DMON_LOG_ERROR: -// define this to provide your own logging mechanism -// default implementation logs to stdout and breaks the program // DMON_LOG_DEBUG // define this to provide your own extra debug logging mechanism // default implementation logs to stdout in DEBUG and does nothing in other builds @@ -104,10 +101,21 @@ typedef enum dmon_action_t { DMON_ACTION_MOVE } dmon_action; +typedef enum dmon_error_enum { + DMON_SUCCESS = 0, + DMON_ERROR_WATCH_DIR, + DMON_ERROR_OPEN_DIR, + DMON_ERROR_MONITOR_FAIL, + DMON_ERROR_UNSUPPORTED_SYMLINK, + DMON_ERROR_END, +} dmon_error; + #ifdef __cplusplus extern "C" { #endif +DMON_API_DECL const char *dmon_error_str(dmon_error err); + DMON_API_DECL void dmon_init(void); DMON_API_DECL void dmon_deinit(void); @@ -115,7 +123,7 @@ DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir, void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, const char* rootdir, const char* filepath, const char* oldfilepath, void* user), - uint32_t flags, void* user_data); + uint32_t flags, void* user_data, dmon_error *error_code); DMON_API_DECL void dmon_unwatch(dmon_watch_id id); #ifdef __cplusplus @@ -171,6 +179,7 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id); # include /* Recursive removed for Lite XL when using inotify. */ # define LITE_XL_DISABLE_INOTIFY_RECURSIVE +# define DMON_LOG_DEBUG(s) #elif DMON_OS_MACOS # include # include @@ -191,11 +200,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id); # define DMON_ASSERT(e) assert(e) #endif -#ifndef DMON_LOG_ERROR -# include -# define DMON_LOG_ERROR(s) do { puts(s); DMON_ASSERT(0); } while(0) -#endif - #ifndef DMON_LOG_DEBUG # ifndef NDEBUG # include @@ -225,10 +229,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id); #include -#ifndef _DMON_LOG_ERRORF -# define _DMON_LOG_ERRORF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_ERROR(msg); } while(0); -#endif - #ifndef _DMON_LOG_DEBUGF # define _DMON_LOG_DEBUGF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_DEBUG(msg); } while(0); #endif @@ -358,6 +358,19 @@ static void * stb__sbgrowf(void *arr, int increment, int itemsize) // watcher callback (same as dmon.h's decleration) typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const char*, const char*, void*); +static const char *dmon__errors[] = { + "Success", + "Error watching directory", + "Error opening directory", + "Error enabling monitoring", + "Error support for symlink disabled", +}; + +DMON_API_IMPL const char *dmon_error_str(dmon_error err) { + DMON_ASSERT(err >= 0 && err < DMON_ERROR_END); + return dmon__errors[(int) err]; +} + #if DMON_OS_WINDOWS // IOCP (windows) #ifdef UNICODE @@ -503,13 +516,13 @@ _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) } DWORD wait_result = WaitForMultipleObjects(_dmon.num_watches, wait_handles, FALSE, 10); - DMON_ASSERT(wait_result != WAIT_FAILED); - if (wait_result != WAIT_TIMEOUT) { + // FIXME: do not check for WAIT_ABANDONED_, check if that can happen. + if (wait_result != WAIT_TIMEOUT && wait_result != WAIT_FAILED) { dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; - DMON_ASSERT(HasOverlappedIoCompleted(&watch->overlapped)); DWORD bytes; - if (GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) { + if (HasOverlappedIoCompleted(&watch->overlapped) && + GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) { char filepath[DMON_MAX_PATH]; PFILE_NOTIFY_INFORMATION notify; size_t offset = 0; @@ -598,7 +611,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, const char* dirname, const char* filename, const char* oldname, void* user), - uint32_t flags, void* user_data) + uint32_t flags, void* user_data, dmon_error *error_code) { DMON_ASSERT(watch_cb); DMON_ASSERT(rootdir && rootdir[0]); @@ -632,17 +645,17 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE; watch->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - DMON_ASSERT(watch->overlapped.hEvent != INVALID_HANDLE_VALUE); - if (!dmon__refresh_watch(watch)) { + if (watch->overlapped.hEvent == INVALID_HANDLE_VALUE || + !dmon__refresh_watch(watch)) { dmon__unwatch(watch); - DMON_LOG_ERROR("ReadDirectoryChanges failed"); + *error_code = DMON_ERROR_WATCH_DIR; LeaveCriticalSection(&_dmon.mutex); _InterlockedExchange(&_dmon.modify_watches, 0); return dmon__make_id(0); } } else { - _DMON_LOG_ERRORF("Could not open: %s", rootdir); + *error_code = DMON_ERROR_OPEN_DIR; LeaveCriticalSection(&_dmon.mutex); _InterlockedExchange(&_dmon.modify_watches, 0); return dmon__make_id(0); @@ -714,7 +727,9 @@ static dmon__state _dmon; /* Implementation of recursive monitoring was removed on Linux for the Lite XL * application. It is never used with recent version of Lite XL starting from 2.0.5 - * and recursive monitoring with inotify was always problematic and half-broken. */ + * and recursive monitoring with inotify was always problematic and half-broken. + * Do not cover the new calling signature with error_code because not used by + * Lite XL. */ #ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask, bool followlinks, dmon__watch_state* watch) @@ -1099,7 +1114,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, const char* dirname, const char* filename, const char* oldname, void* user), - uint32_t flags, void* user_data) + uint32_t flags, void* user_data, dmon_error *error_code) { DMON_ASSERT(watch_cb); DMON_ASSERT(rootdir && rootdir[0]); @@ -1118,7 +1133,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, struct stat root_st; if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || (root_st.st_mode & S_IRUSR) != S_IRUSR) { - _DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir); + *error_code = DMON_ERROR_OPEN_DIR; pthread_mutex_unlock(&_dmon.mutex); return dmon__make_id(0); } @@ -1133,8 +1148,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); } else { - _DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS", - rootdir); + *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; pthread_mutex_unlock(&_dmon.mutex); return dmon__make_id(0); } @@ -1151,7 +1165,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, watch->fd = inotify_init(); if (watch->fd < -1) { - DMON_LOG_ERROR("could not create inotify instance"); + *error_code = DMON_ERROR_MONITOR_FAIL; pthread_mutex_unlock(&_dmon.mutex); return dmon__make_id(0); } @@ -1159,7 +1173,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; int wd = inotify_add_watch(watch->fd, watch->rootdir, inotify_mask); if (wd < 0) { - _DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watch->rootdir, errno); + *error_code = DMON_ERROR_WATCH_DIR; pthread_mutex_unlock(&_dmon.mutex); return dmon__make_id(0); } @@ -1491,7 +1505,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, const char* dirname, const char* filename, const char* oldname, void* user), - uint32_t flags, void* user_data) + uint32_t flags, void* user_data, dmon_error *error_code) { DMON_ASSERT(watch_cb); DMON_ASSERT(rootdir && rootdir[0]); @@ -1511,7 +1525,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, struct stat root_st; if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || (root_st.st_mode & S_IRUSR) != S_IRUSR) { - _DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir); + *error_code = DMON_ERROR_OPEN_DIR; pthread_mutex_unlock(&_dmon.mutex); __sync_lock_test_and_set(&_dmon.modify_watches, 0); return dmon__make_id(0); @@ -1526,7 +1540,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); } else { - _DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS", rootdir); + *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; pthread_mutex_unlock(&_dmon.mutex); __sync_lock_test_and_set(&_dmon.modify_watches, 0); return dmon__make_id(0); diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h index cbacdc93..9b201e17 100644 --- a/lib/dmon/dmon_extra.h +++ b/lib/dmon/dmon_extra.h @@ -52,6 +52,8 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) // else, we assume that watchdir is correct, so save it as it is struct stat st; dmon__watch_subdir subdir; + // FIXME: check if it is a symlink and respect DMON_WATCHFLAGS_FOLLOW_SYMLINKS + // to resolve the link. if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) { dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { diff --git a/src/api/system.c b/src/api/system.c index 955d0ee2..1a862f78 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -724,12 +724,19 @@ static int f_watch_dir(lua_State *L) { * using the function system.watch_dir_add/rm. On other systems we watch recursively * and system.watch_dir_add/rm are dummy functions that always returns true. */ #if __linux__ - const uint32_t dmon_flags = 0; + const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS; +#elif __APPLE__ + const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS | DMON_WATCHFLAGS_RECURSIVE; #else const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE; #endif - dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL); - if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); } + dmon_error error; + dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL, &error); + if (watch_id.id == 0) { + lua_pushnil(L); + lua_pushstring(L, dmon_error_str(error)); + return 2; + } lua_pushnumber(L, watch_id.id); return 1; } From 39366d3a097b1f5d9fe1118da71f9a1c2e8b1dfe Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 8 Jan 2022 17:15:09 +0100 Subject: [PATCH 079/409] Ensure project rescan thread is terminated When changing a project we need to ensure that the old threads are no longer run. --- data/core/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 0d47af14..2f1ced31 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -369,13 +369,14 @@ end local function add_dir_scan_thread(dir) core.add_thread(function() while true do + print("DEBUG: running rescan on", dir.name) local has_changes = rescan_project_subdir(dir, "") if has_changes then core.redraw = true -- we run without an event, from a thread end coroutine.yield(5) end - end) + end, dir) end From f0aea5b1a4a78f9bf61063bd4bd7d35f49f7c6aa Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 8 Jan 2022 17:22:23 +0100 Subject: [PATCH 080/409] Report error codes from dmon_watch_add --- lib/dmon/dmon.h | 4 +++- lib/dmon/dmon_extra.h | 8 ++++++-- src/api/system.c | 9 ++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 496463f1..1a7eed3b 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -107,7 +107,8 @@ typedef enum dmon_error_enum { DMON_ERROR_OPEN_DIR, DMON_ERROR_MONITOR_FAIL, DMON_ERROR_UNSUPPORTED_SYMLINK, - DMON_ERROR_END, + DMON_ERROR_SUBDIR_LOCATION, + DMON_ERROR_END } dmon_error; #ifdef __cplusplus @@ -364,6 +365,7 @@ static const char *dmon__errors[] = { "Error opening directory", "Error enabling monitoring", "Error support for symlink disabled", + "Error not a subdirectory", }; DMON_API_IMPL const char *dmon_error_str(dmon_error err) { diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h index 9b201e17..2252c88e 100644 --- a/lib/dmon/dmon_extra.h +++ b/lib/dmon/dmon_extra.h @@ -27,7 +27,7 @@ extern "C" { #endif -DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir); +DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir, dmon_error *error_code); DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir); #ifdef __cplusplus @@ -36,7 +36,7 @@ DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir); #ifdef DMON_IMPL #if DMON_OS_LINUX -DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) +DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_error *error_code) { DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); @@ -64,6 +64,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); dmon__strcat(fullpath, sizeof(fullpath), watchdir); if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) { + *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -80,6 +81,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) // check that the directory is not already added for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) { if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) { + *error_code = DMON_ERROR_SUBDIR_LOCATION; if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -92,6 +94,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir); int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask); if (wd == -1) { + *error_code = DMON_ERROR_WATCH_DIR; if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; @@ -136,6 +139,7 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) } } if (i >= c) { + *error_code = DMON_ERROR_SUBDIR_LOCATION; if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; diff --git a/src/api/system.c b/src/api/system.c index 1a862f78..45776b51 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -753,7 +753,14 @@ static int f_watch_dir_add(lua_State *L) { dmon_watch_id watch_id; watch_id.id = luaL_checkinteger(L, 1); const char *subdir = luaL_checkstring(L, 2); - lua_pushboolean(L, dmon_watch_add(watch_id, subdir)); + dmon_error error_code; + int success = dmon_watch_add(watch_id, subdir, &error_code) + if (!success) { + lua_pushboolean(L, 0); + lua_pushstring(L, dmon_error_str(error_code)); + return 2; + } + lua_pushboolean(L, 1); return 1; } From 6584bdfd33d7e255a816e07e35e56737734c3cd6 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 8 Jan 2022 18:24:44 +0100 Subject: [PATCH 081/409] On Windows wait indefinitely in dmon thread Avoid waiting with a finite timeout and wait indefinitely in dmon thread. When we need to unwatch we send a signal to a special event meant to wakeup the waiting thread. --- lib/dmon/dmon.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 1a7eed3b..d27d7aea 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -409,6 +409,7 @@ typedef struct dmon__state { volatile LONG modify_watches; dmon__win32_event* events; bool quit; + HANDLE wake_event; } dmon__state; static bool _dmon_init; @@ -494,7 +495,7 @@ _DMON_PRIVATE void dmon__win32_process_events(void) _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) { _DMON_UNUSED(arg); - HANDLE wait_handles[DMON_MAX_WATCHES]; + HANDLE wait_handles[DMON_MAX_WATCHES + 1]; SYSTEMTIME starttm; GetSystemTime(&starttm); @@ -517,9 +518,12 @@ _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) wait_handles[i] = watch->overlapped.hEvent; } - DWORD wait_result = WaitForMultipleObjects(_dmon.num_watches, wait_handles, FALSE, 10); - // FIXME: do not check for WAIT_ABANDONED_, check if that can happen. - if (wait_result != WAIT_TIMEOUT && wait_result != WAIT_FAILED) { + const int n = _dmon.num_watches; + wait_handles[n] = _dmon.wake_event; + DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, INFINITE); + DMON_ASSERT(wait_result != WAIT_TIMEOUT); + // NOTE: maybe we should check for WAIT_ABANDONED_ values if that can happen. + if (wait_result != WAIT_FAILED && wait_result != WAIT_OBJECT_0 + n) { dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; DWORD bytes; @@ -586,6 +590,7 @@ DMON_API_IMPL void dmon_init(void) _dmon.thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL); + _dmon.wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); DMON_ASSERT(_dmon.thread_handle); _dmon_init = true; } @@ -673,6 +678,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) DMON_ASSERT(id.id > 0); _InterlockedExchange(&_dmon.modify_watches, 1); + SetEvent(_dmon.wake_event); EnterCriticalSection(&_dmon.mutex); int index = id.id - 1; From 44a8dc320b0acbe6e929f905e455353527bd3c65 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 8 Jan 2022 18:49:48 +0100 Subject: [PATCH 082/409] Fix some errors with previous commits --- lib/dmon/dmon_extra.h | 1 - src/api/system.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h index 2252c88e..9c6411af 100644 --- a/lib/dmon/dmon_extra.h +++ b/lib/dmon/dmon_extra.h @@ -139,7 +139,6 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) } } if (i >= c) { - *error_code = DMON_ERROR_SUBDIR_LOCATION; if (!skip_lock) pthread_mutex_unlock(&_dmon.mutex); return false; diff --git a/src/api/system.c b/src/api/system.c index 45776b51..4e134e91 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -754,7 +754,7 @@ static int f_watch_dir_add(lua_State *L) { watch_id.id = luaL_checkinteger(L, 1); const char *subdir = luaL_checkstring(L, 2); dmon_error error_code; - int success = dmon_watch_add(watch_id, subdir, &error_code) + int success = dmon_watch_add(watch_id, subdir, &error_code); if (!success) { lua_pushboolean(L, 0); lua_pushstring(L, dmon_error_str(error_code)); From 19ec86d971e17065dbcf980bd3dcf6f481b575f8 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sun, 9 Jan 2022 00:56:11 +0100 Subject: [PATCH 083/409] Do not use timeout in dmon linux select Wait indefinitely in select and wake-up the thread when needed. --- lib/dmon/dmon.h | 43 +++++++++++++++++++++++++++++++++++-------- lib/dmon/dmon_extra.h | 10 ++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index d27d7aea..326459e3 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -727,6 +727,8 @@ typedef struct dmon__state { int num_watches; pthread_t thread_handle; pthread_mutex_t mutex; + int wait_flag; + int wake_event_pipe[2]; bool quit; } dmon__state; @@ -1012,29 +1014,39 @@ static void* dmon__thread(void* arg) static uint8_t buff[_DMON_TEMP_BUFFSIZE]; struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; struct timespec rem = { 0, 0 }; - struct timeval timeout; uint64_t usecs_elapsed = 0; struct timeval starttm; gettimeofday(&starttm, 0); + int debug_count = 0; while (!_dmon.quit) { nanosleep(&req, &rem); - if (_dmon.num_watches == 0 || pthread_mutex_trylock(&_dmon.mutex) != 0) { + if (_dmon.num_watches == 0 || _dmon.wait_flag == 1 || pthread_mutex_trylock(&_dmon.mutex) != 0) { continue; } // Create read FD set fd_set rfds; FD_ZERO(&rfds); - for (int i = 0; i < _dmon.num_watches; i++) { + const int n = _dmon.num_watches; + int nfds = 0; + for (int i = 0; i < n; i++) { dmon__watch_state* watch = &_dmon.watches[i]; FD_SET(watch->fd, &rfds); + if (watch->fd > nfds) + nfds = watch->fd; } + int wake_fd = _dmon.wake_event_pipe[0]; + FD_SET(wake_fd, &rfds); + if (wake_fd > nfds) + nfds = wake_fd; - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) { + if (select(nfds + 1, &rfds, NULL, NULL, NULL)) { + if (FD_ISSET(wake_fd, &rfds)) { + char read_char; + read(wake_fd, &read_char, 1); + } for (int i = 0; i < _dmon.num_watches; i++) { dmon__watch_state* watch = &_dmon.watches[i]; if (FD_ISSET(watch->fd, &rfds)) { @@ -1084,6 +1096,16 @@ static void* dmon__thread(void* arg) return 0x0; } +_DMON_PRIVATE void dmon__mutex_wakeup_lock() { + _dmon.wait_flag = 1; + if (pthread_mutex_trylock(&_dmon.mutex) != 0) { + char send_char = 1; + write(_dmon.wake_event_pipe[1], &send_char, 1); + pthread_mutex_lock(&_dmon.mutex); + } + _dmon.wait_flag = 0; +} + _DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) { close(watch->fd); @@ -1097,6 +1119,9 @@ DMON_API_IMPL void dmon_init(void) DMON_ASSERT(!_dmon_init); pthread_mutex_init(&_dmon.mutex, NULL); + _dmon.wait_flag = 0; + int ret_pipe = pipe(_dmon.wake_event_pipe); + DMON_ASSERT(ret_pipe == 0); int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); _DMON_UNUSED(r); DMON_ASSERT(r == 0 && "pthread_create failed"); @@ -1107,12 +1132,14 @@ DMON_API_IMPL void dmon_deinit(void) { DMON_ASSERT(_dmon_init); _dmon.quit = true; + dmon__mutex_wakeup_lock(); pthread_join(_dmon.thread_handle, NULL); for (int i = 0; i < _dmon.num_watches; i++) { dmon__unwatch(&_dmon.watches[i]); } + pthread_mutex_unlock(&_dmon.mutex); pthread_mutex_destroy(&_dmon.mutex); stb_sb_free(_dmon.events); _dmon_init = false; @@ -1127,7 +1154,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, DMON_ASSERT(watch_cb); DMON_ASSERT(rootdir && rootdir[0]); - pthread_mutex_lock(&_dmon.mutex); + dmon__mutex_wakeup_lock(); DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); @@ -1206,7 +1233,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) { DMON_ASSERT(id.id > 0); - pthread_mutex_lock(&_dmon.mutex); + dmon__mutex_wakeup_lock(); int index = id.id - 1; DMON_ASSERT(index < _dmon.num_watches); diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h index 9c6411af..97631520 100644 --- a/lib/dmon/dmon_extra.h +++ b/lib/dmon/dmon_extra.h @@ -42,8 +42,9 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_e bool skip_lock = pthread_self() == _dmon.thread_handle; - if (!skip_lock) - pthread_mutex_lock(&_dmon.mutex); + if (!skip_lock) { + dmon__mutex_wakeup_lock(); + } dmon__watch_state* watch = &_dmon.watches[id.id - 1]; @@ -115,8 +116,9 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) bool skip_lock = pthread_self() == _dmon.thread_handle; - if (!skip_lock) - pthread_mutex_lock(&_dmon.mutex); + if (!skip_lock) { + dmon__mutex_wakeup_lock(); + } dmon__watch_state* watch = &_dmon.watches[id.id - 1]; From 9be22f0b8d854fedead9be00eb9db21ee8931432 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sun, 9 Jan 2022 01:16:31 +0100 Subject: [PATCH 084/409] Attempt to fix dmon critical section for windows Should fix commit bb12f085f3. When taking the critical section we should always send the event to wakeup the events thread. In addition use TryEnterCriticalSection to send the event only if needed reducing the number of spurious events sent. --- lib/dmon/dmon.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 326459e3..a10be33a 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -595,11 +595,19 @@ DMON_API_IMPL void dmon_init(void) _dmon_init = true; } +static void dmon__enter_critical_wakeup() { + _InterlockedExchange(&_dmon.modify_watches, 1); + if (TryEnterCriticalSection(&_dmon.mutex) == 0) { + SetEvent(_dmon.wake_event); + EnterCriticalSection(&_dmon.mutex); + } +} DMON_API_IMPL void dmon_deinit(void) { DMON_ASSERT(_dmon_init); _dmon.quit = true; + dmon__enter_critical_wakeup(); if (_dmon.thread_handle != INVALID_HANDLE_VALUE) { WaitForSingleObject(_dmon.thread_handle, INFINITE); CloseHandle(_dmon.thread_handle); @@ -609,6 +617,7 @@ DMON_API_IMPL void dmon_deinit(void) dmon__unwatch(&_dmon.watches[i]); } + LeaveCriticalSection(&_dmon.mutex); DeleteCriticalSection(&_dmon.mutex); stb_sb_free(_dmon.events); _dmon_init = false; @@ -623,8 +632,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, DMON_ASSERT(watch_cb); DMON_ASSERT(rootdir && rootdir[0]); - _InterlockedExchange(&_dmon.modify_watches, 1); - EnterCriticalSection(&_dmon.mutex); + dmon__enter_critical_wakeup(); DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); @@ -677,9 +685,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) { DMON_ASSERT(id.id > 0); - _InterlockedExchange(&_dmon.modify_watches, 1); - SetEvent(_dmon.wake_event); - EnterCriticalSection(&_dmon.mutex); + dmon__enter_critical_wakeup(); int index = id.id - 1; DMON_ASSERT(index < _dmon.num_watches); From 648b977c1e5d5a07afd6c2be87748a2570ee2e3b Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sun, 9 Jan 2022 13:01:30 +0100 Subject: [PATCH 085/409] Use a timeout in dmon thread with pending events --- lib/dmon/dmon.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index a10be33a..1ff9dc53 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -520,10 +520,10 @@ _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) const int n = _dmon.num_watches; wait_handles[n] = _dmon.wake_event; - DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, INFINITE); - DMON_ASSERT(wait_result != WAIT_TIMEOUT); + const int n_pending = stb_sb_count(_dmon.events); + DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, n_pending > 0 ? 10 : INFINITE); // NOTE: maybe we should check for WAIT_ABANDONED_ values if that can happen. - if (wait_result != WAIT_FAILED && wait_result != WAIT_OBJECT_0 + n) { + if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + n) { dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; DWORD bytes; @@ -595,7 +595,7 @@ DMON_API_IMPL void dmon_init(void) _dmon_init = true; } -static void dmon__enter_critical_wakeup() { +static void dmon__enter_critical_wakeup(void) { _InterlockedExchange(&_dmon.modify_watches, 1); if (TryEnterCriticalSection(&_dmon.mutex) == 0) { SetEvent(_dmon.wake_event); @@ -1020,6 +1020,7 @@ static void* dmon__thread(void* arg) static uint8_t buff[_DMON_TEMP_BUFFSIZE]; struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; struct timespec rem = { 0, 0 }; + struct timeval timeout; uint64_t usecs_elapsed = 0; struct timeval starttm; @@ -1048,7 +1049,10 @@ static void* dmon__thread(void* arg) if (wake_fd > nfds) nfds = wake_fd; - if (select(nfds + 1, &rfds, NULL, NULL, NULL)) { + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + const int n_pending = stb_sb_count(_dmon.events); + if (select(nfds + 1, &rfds, NULL, NULL, n_pending > 0 ? &timeout : NULL)) { if (FD_ISSET(wake_fd, &rfds)) { char read_char; read(wake_fd, &read_char, 1); @@ -1102,7 +1106,7 @@ static void* dmon__thread(void* arg) return 0x0; } -_DMON_PRIVATE void dmon__mutex_wakeup_lock() { +_DMON_PRIVATE void dmon__mutex_wakeup_lock(void) { _dmon.wait_flag = 1; if (pthread_mutex_trylock(&_dmon.mutex) != 0) { char send_char = 1; From 827f3f876dedab369643795a3c13cbc20eba405b Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sun, 9 Jan 2022 20:01:57 +0100 Subject: [PATCH 086/409] Remove remaining debug code fragment --- lib/dmon/dmon.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 1ff9dc53..3f2bc0c5 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -1026,7 +1026,6 @@ static void* dmon__thread(void* arg) struct timeval starttm; gettimeofday(&starttm, 0); - int debug_count = 0; while (!_dmon.quit) { nanosleep(&req, &rem); if (_dmon.num_watches == 0 || _dmon.wait_flag == 1 || pthread_mutex_trylock(&_dmon.mutex) != 0) { From 656a89c4945aa77698ebd2462575f490e233a242 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sun, 9 Jan 2022 20:22:39 +0100 Subject: [PATCH 087/409] Fix checks when opening new project directory --- data/core/commands/core.lua | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 2ea34b2d..524352fc 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -15,6 +15,15 @@ local function suggest_directory(text) core.recent_projects or common.dir_path_suggest(text)) end +local function check_directory_path(path) + local abs_path = system.absolute_path(path) + local info = abs_path and system.get_file_info(abs_path) + if not info or info.type ~= 'dir' then + return nil + end + return abs_path +end + command.add(nil, { ["core:quit"] = function() core.quit() @@ -156,17 +165,17 @@ command.add(nil, { core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end core.command_view:enter("Change Project Folder", function(text) - text = system.absolute_path(common.home_expand(text)) - if text == core.project_dir then return end - local path_stat = system.get_file_info(text) - if not path_stat or path_stat.type ~= 'dir' then - core.error("Cannot open folder %q", text) + local path = common.home_expand(text) + local abs_path = check_directory_path(path) + if not abs_path then + core.error("Cannot open directory %q", path) return end + if abs_path == core.project_dir then return end core.confirm_close_docs(core.docs, function(dirpath) core.close_current_project() core.open_folder_project(dirpath) - end, text) + end, abs_path) end, suggest_directory) end, @@ -176,13 +185,17 @@ command.add(nil, { core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end core.command_view:enter("Open Project", function(text) - text = common.home_expand(text) - local path_stat = system.get_file_info(text) - if not path_stat or path_stat.type ~= 'dir' then - core.error("Cannot open folder %q", text) + local path = common.home_expand(text) + local abs_path = check_directory_path(path) + if not abs_path then + core.error("Cannot open directory %q", path) return end - system.exec(string.format("%q %q", EXEFILE, text)) + if abs_path == core.project_dir then + core.error("Directory %q is currently opened", abs_path) + return + end + system.exec(string.format("%q %q", EXEFILE, abs_path)) end, suggest_directory) end, From 4cdd42de1a5aaa62d50a9b40c8f7af4142710f9d Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 10 Jan 2022 09:54:47 +0100 Subject: [PATCH 088/409] Ensure config.plugins are restored on new config When a user's or project's module configuration file is changed we make sure that the config.plugins fields are all restored so that all plugins already loaded can continue to work. --- data/core/init.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 2f1ced31..7a44f0ae 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -81,17 +81,24 @@ end local function reload_customizations() - core.reload_module("core.style") + -- The logic is: + -- - the core.style and config modules are reloaded with the purpose of applying + -- the new user's and project's module configs + -- - inside the core.config the existing fields in config.plugins are preserved + -- because they are reserved to plugins configuration and plugins are already + -- loaded. + -- - plugins are not reloaded or unloaded local plugins_save = {} for k, v in pairs(config.plugins) do plugins_save[k] = v end + core.reload_module("core.style") core.reload_module("core.config") + core.load_user_directory() + core.load_project_module() for k, v in pairs(plugins_save) do config.plugins[k] = v end - core.load_user_directory() - core.load_project_module() end From 7eb9908f1afd1f235aef95543fb5d9e852257f0b Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 12 Jan 2022 00:07:14 +0100 Subject: [PATCH 089/409] Improve bit32 polyfill --- data/core/bit.lua | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/data/core/bit.lua b/data/core/bit.lua index f357d5fd..114ea3cb 100644 --- a/data/core/bit.lua +++ b/data/core/bit.lua @@ -12,19 +12,20 @@ local function mask(n) end function bit.extract(n, field, width) - local r = trim(field) - local f = width - r = (r >> f) & mask(width) - return r + local w = width or 1 + assert(w > 0, "width must be positive") + assert(field + w < LUA_NBITS and field + w >= 0, "trying to access non-existent bits") + local m = trim(n) + return m >> field & mask(w) end function bit.replace(n, v, field, width) - local r = trim(v); - local v = trim(field); - local f = width - local m = mask(width); - r = (r & ~(m << f)) | ((v & m) << f); - return r + local w = width or 1 + assert(w > 0, "width must be positive") + assert(field + w < LUA_NBITS and field + w >= 0, "trying to access non-existent bits") + local m = trim(n) + local x = v & mask(width); + return m & ~(mask(w) << field) | (x << field) end -return bit \ No newline at end of file +return bit From 51975472a93a35b9add46aca26bb8eaa44dbb443 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 12 Jan 2022 00:07:53 +0100 Subject: [PATCH 090/409] Add bit32 polyfill globally --- data/core/start.lua | 2 ++ data/core/tokenizer.lua | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/start.lua b/data/core/start.lua index 482f8bd0..b3bcc18e 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -32,3 +32,5 @@ end } table.pack = table.pack or pack or function(...) return {...} end table.unpack = table.unpack or unpack + +bit32 = bit32 or require "core.bit" diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 03769c46..5fd8c69f 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -1,6 +1,5 @@ local syntax = require "core.syntax" local common = require "core.common" -local bit32 = require "core.bit" local tokenizer = {} From ae1890d29a6aeb28be698fcafd3180e63a085de5 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 10 Jan 2022 16:26:39 +0100 Subject: [PATCH 091/409] Fix project files reading with symlink --- data/core/init.lua | 64 +++++++++++++++++++++++++++++++--------------- src/api/system.c | 8 ++++++ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 7a44f0ae..315f8c95 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -278,20 +278,6 @@ local function file_search(files, info) end -local function project_scan_add_entry(dir, fileinfo) - assert(not dir.force_rescan, "should be used on when force_rescan is unset") - local index, match = file_search(dir.files, fileinfo) - if not match then - table.insert(dir.files, index, fileinfo) - if fileinfo.type == "dir" and not dir.files_limit then - -- ASSUMPTION: dir.force_rescan is FALSE - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - end - dir.is_dirty = true - end -end - - local function files_info_equal(a, b) return a.filename == b.filename and a.type == b.type end @@ -308,7 +294,7 @@ local function files_list_match(a, i1, n, b) end -- arguments like for files_list_match -local function files_list_replace(as, i1, n, bs, insert_hook) +local function files_list_replace(as, i1, n, bs, hook) local m = #bs local i, j = 1, 1 while i <= m or i <= n do @@ -318,8 +304,9 @@ local function files_list_replace(as, i1, n, bs, insert_hook) then table.insert(as, i1 + i, b) i, j, n = i + 1, j + 1, n + 1 - if insert_hook then insert_hook(b) end + if hook and hook.insert then hook.insert(b) end elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then + if hook and hook.remove then hook.remove(as[i1 + i]) end table.remove(as, i1 + i) n = n - 1 else @@ -328,6 +315,29 @@ local function files_list_replace(as, i1, n, bs, insert_hook) end end + +local function project_scan_add_entry(dir, fileinfo) + assert(not dir.force_rescan, "should be used only when force_rescan is false") + local index, match = file_search(dir.files, fileinfo) + if not match then + table.insert(dir.files, index, fileinfo) + if fileinfo.type == "dir" and not dir.files_limit then + -- ASSUMPTION: dir.force_rescan is FALSE + system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) + if fileinfo.symlink then + local new_files = get_directory_files(dir, dir.name, PATHSEP .. fileinfo.filename, {}, nil, 0, core.project_subdir_is_shown) + files_list_replace(dir.files, index, 0, new_files, {insert = function(info) + if info.type == "dir" then + system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. info.filename) + end + end}) + end + end + dir.is_dirty = true + end +end + + local function project_subdir_bounds(dir, filename) local index, n = 0, #dir.files for i, file in ipairs(dir.files) do @@ -362,11 +372,11 @@ local function rescan_project_subdir(dir, filename_rooted) -- we missed some directory creation event from the directory monitoring which -- almost never happens. With inotify is at least theoretically possible. local need_subdir_watches = not dir.files_limit and not dir.force_rescan - files_list_replace(dir.files, index, n, new_files, need_subdir_watches and function(fileinfo) + files_list_replace(dir.files, index, n, new_files, need_subdir_watches and {insert = function(fileinfo) if fileinfo.type == "dir" then system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) end - end) + end}) dir.is_dirty = true return true end @@ -624,10 +634,22 @@ local function project_scan_remove_file(dir, filepath) fileinfo.type = filetype local index, match = file_search(dir.files, fileinfo) if match then - table.remove(dir.files, index) - if dir.files_limit and filetype == "dir" then - dir.shown_subdir[filepath] = nil + if filetype == "dir" then + -- If the directory is a symlink it may get deleted and we will + -- never get dirmonitor events for the removal the files it contains. + -- We proceed to remove all the files that belong to the directory. + local _, n_subdir = project_subdir_bounds(dir, filepath) + files_list_replace(dir.files, index, n_subdir, {}, { + remove= function(fileinfo) + if fileinfo.type == "dir" then + system.watch_dir_rm(dir.watch_id, dir.name .. PATHSEP .. filepath) + end + end}) + if dir.files_limit then + dir.shown_subdir[filepath] = nil + end end + table.remove(dir.files, index) dir.is_dirty = true return end diff --git a/src/api/system.c b/src/api/system.c index 4e134e91..10288eba 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -544,6 +544,14 @@ static int f_get_file_info(lua_State *L) { } lua_setfield(L, -2, "type"); +#if __linux__ + if (S_ISDIR(s.st_mode)) { + if (lstat(path, &s) == 0) { + lua_pushboolean(L, S_ISLNK(s.st_mode)); + lua_setfield(L, -2, "symlink"); + } + } +#endif return 1; } From e51c76c72b79e1453b135de8acd12d3c50014b12 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 12 Jan 2022 19:56:09 +0100 Subject: [PATCH 092/409] Assert for negative `field` in bit32 polyfill This more closely matches the behavior of lua5.2. --- data/core/bit.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/data/core/bit.lua b/data/core/bit.lua index 114ea3cb..e55fb9bf 100644 --- a/data/core/bit.lua +++ b/data/core/bit.lua @@ -11,18 +11,23 @@ local function mask(n) return (~((ALLONES << 1) << ((n) - 1))) end +local function check_args(field, width) + assert(field >= 0, "field cannot be negative") + assert(width > 0, "width must be positive") + assert(field + width < LUA_NBITS and field + width >= 0, + "trying to access non-existent bits") +end + function bit.extract(n, field, width) local w = width or 1 - assert(w > 0, "width must be positive") - assert(field + w < LUA_NBITS and field + w >= 0, "trying to access non-existent bits") + check_args(field, w) local m = trim(n) return m >> field & mask(w) end function bit.replace(n, v, field, width) local w = width or 1 - assert(w > 0, "width must be positive") - assert(field + w < LUA_NBITS and field + w >= 0, "trying to access non-existent bits") + check_args(field, w) local m = trim(n) local x = v & mask(width); return m & ~(mask(w) << field) | (x << field) From 2456452f65872a3e0e1ab36dbe8f6eca5faf5fef Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 13 Jan 2022 16:38:20 +0100 Subject: [PATCH 093/409] Fix error to close view when deleting a file --- data/core/commands/doc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index b8ce2cb5..37582474 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -383,7 +383,7 @@ local commands = { end for i,docview in ipairs(core.get_views_referencing_doc(doc())) do local node = core.root_view.root_node:get_node_for_view(docview) - node:close_view(core.root_view, docview) + node:close_view(core.root_view.root_node, docview) end os.remove(filename) core.log("Removed \"%s\"", filename) From 7e9b2f30da444cd65a7c3a5c82d1270142dd3b58 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 13 Jan 2022 16:43:37 +0100 Subject: [PATCH 094/409] Treat final '/' or '/$' in ignore rule as part of the pattern Evolve the rule for directory in ignore_files to be more natural and easy to understand. When a final '/' or '/$' is found we consider the pattern to match a directory and the pattern is not modifed. In turns, is used, before matching a directory's name a final '/' is appended to its name before checking if it matches the pattern. With the previous rule a final '/' in the pattern meant also a directory but the '/' was removed from the pattern. --- data/core/init.lua | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 315f8c95..25bcded9 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -136,11 +136,11 @@ local function compile_ignore_files() -- config.ignore_files could be a simple string... if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end for i, pattern in ipairs(ipatterns) do - local match_dir = pattern:match("(.+)/$") compiled[i] = { - use_path = pattern:match("/[^/]"), -- contains a slash but not at the end - match_dir = match_dir, -- to be used as a boolen value - pattern = match_dir or pattern -- get the actual pattern + use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end + -- An '/' or '/$' at the end means we want to match a directory. + match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value + pattern = pattern -- get the actual pattern } end return compiled @@ -153,9 +153,15 @@ local function fileinfo_pass_filter(info, ignore_compiled) -- replace '\' with '/' for Windows where PATHSEP = '\' local fullname = "/" .. info.filename:gsub("\\", "/") for _, compiled in ipairs(ignore_compiled) do - local pass_dir = (not compiled.match_dir or info.type == "dir") - if (compiled.use_path and fullname or basename):match(compiled.pattern) and pass_dir then - return false + local test = compiled.use_path and fullname or basename + if compiled.match_dir then + if info.type == "dir" and string.match(test .. "/", compiled.pattern) then + return false + end + else + if string.match(test, compiled.pattern) then + return false + end end end return true @@ -765,23 +771,25 @@ local config = require "core.config" -- config.ignore_files = {"^%.", } -- Patterns are normally applied to the file's or directory's name, without --- its path. See below about how to include the path. +-- its path. See below about how to apply filters on a path. -- -- Here some examples: -- -- "^%." match any file of directory whose basename begins with a dot. -- --- When there is an '/' at the end the pattern will only match directories. The final --- '/' is removed from the pattern to match the file's or directory's name. +-- When there is an '/' or a '/$' at the end the pattern it will only match +-- directories. When using such a pattern a final '/' will be added to the name +-- of any directory entry before checking if it matches. -- --- "^%.git$/" match any directory named ".git" anywhere in the project. +-- "^%.git/" matches any directory named ".git" anywhere in the project. -- --- If a "/" appears anywhere in the pattern (except at the end) then the pattern --- will be applied to the full path of the file or directory. An initial "/" will --- be prepended to the file's or directory's path to indicate the project's root. +-- If a "/" appears anywhere in the pattern except if it appears at the end or +-- is immediately followed by a '$' then the pattern will be applied to the full +-- path of the file or directory. An initial "/" will be prepended to the file's +-- or directory's path to indicate the project's root. -- --- "^/node_modules$/" match a directory named "node_modules" at the project's root. --- "^/build/" match any top level directory whose name _begins_ with "build" +-- "^/node_modules/" will match a directory named "node_modules" at the project's root. +-- "^/build.*/" match any top level directory whose name begins with "build" -- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects". -- You may activate some plugins on a pre-project base to override the user's settings. From 192c57796600d5cceb24e21d2be5094a4694e7de Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Sat, 15 Jan 2022 00:53:46 +0100 Subject: [PATCH 095/409] Add fallbacks to all common dependencies --- .gitignore | 3 +-- meson.build | 26 ++++++++++++++++++++------ scripts/install-dependencies.sh | 4 ++-- src/api/system.c | 1 + subprojects/freetype2.wrap | 9 +++++++++ subprojects/lua.wrap | 6 +++--- subprojects/pcre2.wrap | 15 +++++++++++++++ subprojects/sdl2.wrap | 12 ++++++++++++ 8 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 subprojects/freetype2.wrap create mode 100644 subprojects/pcre2.wrap create mode 100644 subprojects/sdl2.wrap diff --git a/.gitignore b/.gitignore index d1bba414..75dec714 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,7 @@ build*/ .build*/ lhelper/ submodules/ -subprojects/lua*/ -subprojects/reproc/ +subprojects/*/ /appimage* .ccls-cache .lite-debug.log diff --git a/meson.build b/meson.build index 867b72b9..ccc84cb3 100644 --- a/meson.build +++ b/meson.build @@ -3,7 +3,10 @@ project('lite-xl', version : '2.0.3', license : 'MIT', meson_version : '>= 0.42', - default_options : ['c_std=gnu11'] + default_options : [ + 'c_std=gnu11', + 'wrap_mode=nofallback' + ] ) #=============================================================================== @@ -24,7 +27,7 @@ endif cc = meson.get_compiler('c') lite_includes = [] -lite_cargs = [] +lite_cargs = ['-DSDL_MAIN_HANDLED', '-DPCRE2_STATIC'] # On macos we need to use the SDL renderer to support retina displays if get_option('renderer') or host_machine.system() == 'darwin' lite_cargs += '-DLITE_USE_SDL_RENDERER' @@ -47,15 +50,26 @@ if not get_option('source-only') libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) threads_dep = dependency('threads') + lua_dep = dependency('lua5.4', required : false) if not lua_dep.found() lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'], - default_options: ['line_editing=false', 'default_library=static'] + default_options: ['default_library=static', 'line_editing=false', 'interpreter=false'] ) endif - pcre2_dep = dependency('libpcre2-8') - freetype_dep = dependency('freetype2') - sdl_dep = dependency('sdl2') + + pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'], + default_options: ['default_library=static', 'grep=false', 'test=false'] + ) + + freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'], + default_options: ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled'] + ) + + sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'], + default_options: ['default_library=static'] + ) + lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep] endif #=============================================================================== diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 2f9519b1..b75e6dca 100644 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -63,10 +63,10 @@ main() { elif [[ "$OSTYPE" == "msys" ]]; then if [[ $lhelper == true ]]; then pacman --noconfirm -S \ - ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config} unzip + ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa} unzip else pacman --noconfirm -S \ - ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,freetype,pcre2,SDL2} unzip + ${MINGW_PACKAGE_PREFIX}-{gcc,meson,ninja,ntldd,pkg-config,mesa,freetype,pcre2,SDL2} unzip fi fi } diff --git a/src/api/system.c b/src/api/system.c index 8abc5595..cf7232e6 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/subprojects/freetype2.wrap b/subprojects/freetype2.wrap new file mode 100644 index 00000000..6d7f449a --- /dev/null +++ b/subprojects/freetype2.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = freetype-2.11.1 +source_url = https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.gz +source_filename = freetype-2.11.1.tar.gz +source_hash = f8db94d307e9c54961b39a1cc799a67d46681480696ed72ecf78d4473770f09b + +[provide] +freetype2 = freetype_dep + diff --git a/subprojects/lua.wrap b/subprojects/lua.wrap index 71a37d39..bd6ee5eb 100644 --- a/subprojects/lua.wrap +++ b/subprojects/lua.wrap @@ -3,9 +3,9 @@ directory = lua-5.4.3 source_url = https://www.lua.org/ftp/lua-5.4.3.tar.gz source_filename = lua-5.4.3.tar.gz source_hash = f8612276169e3bfcbcfb8f226195bfc6e466fe13042f1076cbde92b7ec96bbfb -patch_filename = lua_5.4.3-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-1/get_patch -patch_hash = 66794455e18d373041c8ffa9c23d1629b813c7a716f8905425d347937f5c8dc8 +patch_filename = lua_5.4.3-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/lua_5.4.3-2/get_patch +patch_hash = 3c23ec14a3f000d80fe2e2fdddba63a56e13c758d74195daa4ff0da7bfdb02da [provide] lua-5.4 = lua_dep diff --git a/subprojects/pcre2.wrap b/subprojects/pcre2.wrap new file mode 100644 index 00000000..99e82cf4 --- /dev/null +++ b/subprojects/pcre2.wrap @@ -0,0 +1,15 @@ +[wrap-file] +directory = pcre2-10.39 +source_url = https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.bz2 +source_filename = pcre2-10.39.tar.bz2 +source_hash = 0f03caf57f81d9ff362ac28cd389c055ec2bf0678d277349a1a4bee00ad6d440 +patch_filename = pcre2_10.39-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/pcre2_10.39-2/get_patch +patch_hash = c4cfffff83e7bb239c8c330339b08f4367b019f79bf810f10c415e35fb09cf14 + +[provide] +libpcre2-8 = -libpcre2_8 +libpcre2-16 = -libpcre2_16 +libpcre2-32 = -libpcre2_32 +libpcre2-posix = -libpcre2_posix + diff --git a/subprojects/sdl2.wrap b/subprojects/sdl2.wrap new file mode 100644 index 00000000..9ef9e1d3 --- /dev/null +++ b/subprojects/sdl2.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = SDL2-2.0.18 +source_url = https://www.libsdl.org/release/SDL2-2.0.18.tar.gz +source_filename = SDL2-2.0.18.tar.gz +source_hash = 94d40cd73dbfa10bb6eadfbc28f355992bb2d6ef6761ad9d4074eff95ee5711c +patch_filename = sdl2_2.0.18-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.18-2/get_patch +patch_hash = cd77f33395d3d019bb89217b9da41fc640ed8c78cbbbebc5c662155a25e2820e + +[provide] +sdl2 = sdl2_dep + From 2dd154edeb2fd5d80d412e1b34bf24486b74bed3 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 18 Jan 2022 10:42:20 +0100 Subject: [PATCH 096/409] Remove remaining debug message --- data/core/init.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 25bcded9..6ac6af9c 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -392,7 +392,6 @@ end local function add_dir_scan_thread(dir) core.add_thread(function() while true do - print("DEBUG: running rescan on", dir.name) local has_changes = rescan_project_subdir(dir, "") if has_changes then core.redraw = true -- we run without an event, from a thread From 30cc205cd4cbb9e25936b8829f0bd1edf1751909 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 18 Jan 2022 21:19:16 -0500 Subject: [PATCH 097/409] Fixed issue first mentioned in #771. --- data/core/commands/doc.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 54b84921..eb4c9e38 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -383,11 +383,13 @@ local commands = { block = true end - for _, line1, _, line2 in doc_multiline_selections(true) do + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do if block then + local nline1, ncol1, nline2, ncol2 for line = line1, line2 do - block_comment(comment, line, 1, line, #doc().lines[line]) + nline1, ncol1, nline2, ncol2 = block_comment(comment, line, 1, line, #doc().lines[line]) end + doc():set_selections(idx, line1, col1, nline2, ncol2) else line_comment(comment, line1, line2) end From cdbfecc5ceaaaaee5cc710f78a535b73e10d6b3b Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 18 Jan 2022 21:37:26 -0500 Subject: [PATCH 098/409] Streamlined, and fixed guldo's problem. --- data/core/commands/doc.lua | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index eb4c9e38..a23e17ed 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -97,8 +97,8 @@ local function set_cursor(x, y, snap_type) core.blink_reset() end -local function line_comment(comment, line1, line2) - local comment_text = comment .. " " +local function line_comment(comment, line1, col1, line2, col2) + local comment_text = (type(comment) == 'table' and comment[1] or comment) .. " " local uncomment = true local start_offset = math.huge for line = line1, line2 do @@ -120,8 +120,17 @@ local function line_comment(comment, line1, line2) end elseif s then doc():insert(line, start_offset, comment_text) + if type(comment) == 'table' and #comment > 1 then + doc():insert(line, #doc().lines[line], " " .. comment[2]) + if line == line2 then + col2 = col2 + #comment[1] + #comment[2] + 2 + end + elseif line == line2 then + col2 = col2 + #comment_text + end end end + return line1, col1, line2, col2 end local function block_comment(comment, line1, col1, line2, col2) @@ -375,23 +384,11 @@ local commands = { end, ["doc:toggle-line-comments"] = function() - local comment = doc().syntax.comment - local block = false - if not comment then - comment = doc().syntax.block_comment - if not comment then return end - block = true - end - - for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do - if block then - local nline1, ncol1, nline2, ncol2 - for line = line1, line2 do - nline1, ncol1, nline2, ncol2 = block_comment(comment, line, 1, line, #doc().lines[line]) - end - doc():set_selections(idx, line1, col1, nline2, ncol2) - else - line_comment(comment, line1, line2) + local comment = doc().syntax.comment or doc().syntax.block_comment + if comment then + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + line1, col1, line2, col2 = line_comment(comment, line1, col1, line2, col2) + doc():set_selections(idx, line1, col1, line2, col2) end end end, From cd83df1abfd99a1db1d7bc2cd69ee7f754e051b7 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Wed, 19 Jan 2022 18:18:25 +0100 Subject: [PATCH 099/409] Bump version and changelog to prepare 2.0.5 release --- changelog.md | 39 +++++++++++++++++++++++++++++++++++++++ meson.build | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 06a75b70..fef160b6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,44 @@ This files document the changes done in Lite XL for each release. +### 2.0.5 + +Revamp the project's user module so that modifications are immediately applied. + +Add a mechanism to ignore files or directory based on their project's path. +The new mechanism is backward compatible.* + +Essentially there are two mechanisms: + +- if a '/' or a '/$' appear at the end of the pattern it will match only directories +- if a '/' appears anywhere in the pattern except at the end the pattern will be + applied to the path + +In the first case, when the pattern corresponds to a directory, a '/' will be +appended to the name of each directory before checking the pattern. + +In the second case, when the pattern corresponds to a path, the complete path of +the file or directory will be used with an initial '/' added to the path. + +Fix several problems with the directory monitoring library. +Now the application should no longer assert when some related system call fails +and we fallback to rescan when an error happens. +On linux no longer use the recursive monitoring which was a source of problem. + +Directory monitoring is now aware of symlinks and treat them appropriately. + +Fix problem when encountering special files type on linux. + +Improve directory monitoring so that the related thread actually waits without using +any CPU time when there are no events. + +Improve the suggestion when changing project folder or opening a new one. +Now the previously used directory are suggested but if the path is changed the +actual existing directories that match the pattern are suggested. +In addition always use the text entered in the command view even if a suggested entry +is highlighted. + +The NagView warning window now no longer moves the document content. + ### 2.0.4 Fix some bugs related to newly introduced directory monitoring using the dmon library. diff --git a/meson.build b/meson.build index ccdfdd09..3e6a8a4b 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('lite-xl', ['c', 'cpp'], - version : '2.0.4', + version : '2.0.5', license : 'MIT', meson_version : '>= 0.54', default_options : ['c_std=gnu11', 'cpp_std=c++03'] From b523bd5cee0831ea046f38682e8062538b83e99d Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 20 Jan 2022 22:17:21 -0500 Subject: [PATCH 100/409] Fixed end of block-style line comments. --- data/core/commands/doc.lua | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index a23e17ed..b57c7adc 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -98,38 +98,44 @@ local function set_cursor(x, y, snap_type) end local function line_comment(comment, line1, col1, line2, col2) - local comment_text = (type(comment) == 'table' and comment[1] or comment) .. " " + local start_comment = (type(comment) == 'table' and comment[1] or comment) .. " " + local end_comment = (type(comment) == 'table' and " " .. comment[2]) local uncomment = true local start_offset = math.huge for line = line1, line2 do local text = doc().lines[line] local s = text:find("%S") - local cs, ce = text:find(comment_text, s, true) + local cs, ce = text:find(start_comment, s, true) if s and cs ~= s then uncomment = false - start_offset = math.min(start_offset, s) end + start_offset = math.min(start_offset, s) end + + local end_line = col2 == #doc().lines[line2] for line = line1, line2 do local text = doc().lines[line] local s = text:find("%S") if uncomment then - local cs, ce = text:find(comment_text, s, true) + if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then + doc():remove(line, #text - #end_comment, line, #text) + end + local cs, ce = text:find(start_comment, s, true) if ce then doc():remove(line, cs, line, ce + 1) end elseif s then - doc():insert(line, start_offset, comment_text) - if type(comment) == 'table' and #comment > 1 then + doc():insert(line, start_offset, start_comment) + if end_comment then doc():insert(line, #doc().lines[line], " " .. comment[2]) - if line == line2 then - col2 = col2 + #comment[1] + #comment[2] + 2 - end - elseif line == line2 then - col2 = col2 + #comment_text end end end + col1 = col1 + (col1 > start_offset and #start_comment or 0) * (uncomment and -1 or 1) + col2 = col2 + (col2 > start_offset and #start_comment or 0) * (uncomment and -1 or 1) + if end_comment and end_line then + col2 = col2 + #end_comment * (uncomment and -1 or 1) + end return line1, col1, line2, col2 end @@ -377,9 +383,7 @@ local commands = { col1 = 1 col2 = #doc().lines[line2] end - - line1, col1, line2, col2 = block_comment(comment, line1, col1, line2, col2) - doc():set_selections(idx, line1, col1, line2, col2) + doc():set_selections(idx, block_comment(comment, line1, col1, line2, col2)) end end, @@ -387,8 +391,7 @@ local commands = { local comment = doc().syntax.comment or doc().syntax.block_comment if comment then for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do - line1, col1, line2, col2 = line_comment(comment, line1, col1, line2, col2) - doc():set_selections(idx, line1, col1, line2, col2) + doc():set_selections(idx, line_comment(comment, line1, col1, line2, col2)) end end end, From f24ebf853da02105d53e68d5e25056e328732f5e Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 22 Jan 2022 23:30:48 +0800 Subject: [PATCH 101/409] fix invalid memory access --- src/api/process.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/process.c b/src/api/process.c index ac1b21e6..cabcbf17 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -306,7 +306,7 @@ static int g_read(lua_State* L, int stream, unsigned long read_size) { #else luaL_Buffer b; luaL_buffinit(L, &b); - uint8_t* buffer = (uint8_t*)luaL_prepbuffer(&b); + uint8_t* buffer = (uint8_t*)luaL_prepbuffsize(&b, READ_BUF_SIZE); length = read(self->child_pipes[stream][0], buffer, read_size > READ_BUF_SIZE ? READ_BUF_SIZE : read_size); if (length == 0 && !poll_process(self, WAIT_NONE)) return 0; From 428c757a13addd48f048a302ecdf11d8c29a753f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 22 Jan 2022 12:02:59 -0500 Subject: [PATCH 102/409] Implemented @guldoman's suggestion for how to close file handles. --- src/api/process.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index cabcbf17..3113843d 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -59,9 +59,9 @@ typedef enum { #ifdef _WIN32 static volatile long PipeSerialNumber; - static void close_fd(HANDLE handle) { CloseHandle(handle); } + static void close_fd(HANDLE* handle) { if (*handle) CloseHandle(*handle); *handle = NULL; } #else - static void close_fd(int fd) { close(fd); } + static void close_fd(int* fd) { if (*fd) close(*fd); *fd = 0; } #endif static bool poll_process(process_t* proc, int timeout) { @@ -90,12 +90,8 @@ static bool poll_process(process_t* proc, int timeout) { if (timeout) SDL_Delay(5); } while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout); - if (!proc->running) { - close_fd(proc->child_pipes[STDIN_FD ][1]); - close_fd(proc->child_pipes[STDOUT_FD][0]); - close_fd(proc->child_pipes[STDERR_FD][0]); + if (!proc->running) return false; - } return true; } @@ -273,7 +269,7 @@ static int process_start(lua_State* L) { for (size_t i = 0; i < env_len; ++i) free((char*)env[i]); for (int stream = 0; stream < 3; ++stream) - close_fd(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); + close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); self->running = true; return 1; } @@ -350,7 +346,7 @@ static int f_write(lua_State* L) { static int f_close_stream(lua_State* L) { process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); int stream = luaL_checknumber(L, 2); - close_fd(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); + close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); lua_pushboolean(L, 1); return 1; } @@ -417,7 +413,14 @@ static int self_signal(lua_State* L, signal_e sig) { static int f_terminate(lua_State* L) { return self_signal(L, SIGNAL_TERM); } static int f_kill(lua_State* L) { return self_signal(L, SIGNAL_KILL); } static int f_interrupt(lua_State* L) { return self_signal(L, SIGNAL_INTERRUPT); } -static int f_gc(lua_State* L) { return self_signal(L, SIGNAL_TERM); } +static int f_gc(lua_State* L) { + process_t* self = (process_t*) luaL_checkudata(L, 1, API_TYPE_PROCESS); + signal_process(self, SIGNAL_TERM); + close_fd(&self->child_pipes[STDIN_FD ][1]); + close_fd(&self->child_pipes[STDOUT_FD][0]); + close_fd(&self->child_pipes[STDERR_FD][0]); + return 0; +} static int f_running(lua_State* L) { process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS); From ed4128bc65d24c068b5018077c7755b0bc1352e5 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 22 Jan 2022 12:36:30 -0500 Subject: [PATCH 103/409] Added in support for env on linux. --- src/api/process.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index 3113843d..6b4b1bc2 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -117,7 +117,7 @@ static bool signal_process(process_t* proc, signal_e sig) { static int process_start(lua_State* L) { size_t env_len = 0, key_len, val_len; - const char *cmd[256], *env[256] = { NULL }, *cwd = NULL; + const char *cmd[256], *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL; bool detach = false; int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD }; luaL_checktype(L, 1, LUA_TTABLE); @@ -141,9 +141,12 @@ static int process_start(lua_State* L) { while (lua_next(L, -2) != 0) { const char* key = luaL_checklstring(L, -2, &key_len); const char* val = luaL_checklstring(L, -1, &val_len); - env[env_len] = malloc(key_len+val_len+2); - snprintf((char*)env[env_len++], key_len+val_len+2, "%s=%s", key, val); + env_names[env_len] = malloc(key_len+1); + strcpy((char*)env_names[env_len], key); + env_values[env_len] = malloc(val_len+1); + strcpy((char*)env_values[env_len], val); lua_pop(L, 1); + ++env_len; } } else lua_pop(L, 1); @@ -158,7 +161,6 @@ static int process_start(lua_State* L) { return luaL_error(L, "redirect to handles, FILE* and paths are not supported"); } } - env[env_len] = NULL; process_t* self = lua_newuserdata(L, sizeof(process_t)); memset(self, 0, sizeof(process_t)); @@ -224,11 +226,9 @@ static int process_start(lua_State* L) { strcat(commandLine, cmd[i]); } for (size_t i = 0; i < env_len; ++i) { - size_t len = strlen(env[i]); - if (offset + len >= sizeof(environmentBlock)) + if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock)) break; - memcpy(&environmentBlock[offset], env[i], len); - offset += len; + offset += snprintf(&environmentBlock[offset], sizeof(environmentBlock) - offset, "%s=%s", env_names[i], env_values[i]); environmentBlock[offset++] = 0; } environmentBlock[offset++] = 0; @@ -259,15 +259,19 @@ static int process_start(lua_State* L) { dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream); close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); } - if ((!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1)) - execvp((const char*)cmd[0], (char* const*)cmd); + int set; + for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set); + if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1)) + execvp((const char*)cmd[0], (char* const*)cmd); const char* msg = strerror(errno); int result = write(STDERR_FD, msg, strlen(msg)+1); - exit(result == strlen(msg)+1 ? -1 : -2); + _exit(result == strlen(msg)+1 ? -1 : -2); } #endif - for (size_t i = 0; i < env_len; ++i) - free((char*)env[i]); + for (size_t i = 0; i < env_len; ++i) { + free((char*)env_names[i]); + free((char*)env_values[i]); + } for (int stream = 0; stream < 3; ++stream) close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); self->running = true; From f9ad83e53e9b4ebc15562b11e31e6786ca145f61 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 22 Jan 2022 13:34:47 -0500 Subject: [PATCH 104/409] Fixed windows not converting utf8 environment block to utf16. --- src/api/process.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index 6b4b1bc2..b85db34a 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -215,16 +215,18 @@ static int process_start(lua_State* L) { siStartInfo.hStdInput = self->child_pipes[STDIN_FD][0]; siStartInfo.hStdOutput = self->child_pipes[STDOUT_FD][1]; siStartInfo.hStdError = self->child_pipes[STDERR_FD][1]; - char commandLine[32767] = { 0 }, environmentBlock[32767]; - int offset = 0; + char commandLine[32767] = { 0 }, environmentBlock[32767], wideEnvironmentBlock[32767*2]; strcpy(commandLine, cmd[0]); + int offset = 0; for (size_t i = 1; i < cmd_len; ++i) { size_t len = strlen(cmd[i]); - if (offset + len + 1 >= sizeof(commandLine)) + offset += len + 1; + if (offset >= sizeof(commandLine)) break; strcat(commandLine, " "); strcat(commandLine, cmd[i]); } + offset = 0; for (size_t i = 0; i < env_len; ++i) { if (offset + strlen(env_values[i]) + strlen(env_names[i]) + 1 >= sizeof(environmentBlock)) break; @@ -232,7 +234,9 @@ static int process_start(lua_State* L) { environmentBlock[offset++] = 0; } environmentBlock[offset++] = 0; - if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? environmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) + if (env_len > 0) + MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock)); + if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) return luaL_error(L, "Error creating a process: %d.", GetLastError()); self->pid = (long)self->process_information.dwProcessId; if (detach) From f7193c4fa24d4d600a34d6d8cbff20c1073be380 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 22 Jan 2022 21:46:02 +0100 Subject: [PATCH 105/409] Remove unused whitespace_replacements function --- data/core/init.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 38003179..8810d4b2 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -827,14 +827,6 @@ function core.remove_project_directory(path) end -local function whitespace_replacements() - local r = renderer.replacements.new() - r:add(" ", "·") - r:add("\t", "»") - return r -end - - local function configure_borderless_window() system.set_window_bordered(not config.borderless) core.title_view:configure_hit_test(config.borderless) From 227ca7d0e5618896e38fa620cab6541253cd126f Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Sat, 15 Jan 2022 01:00:51 +0100 Subject: [PATCH 106/409] use lua fallback earlier when fallbacks are forced --- meson.build | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ccc84cb3..46ddcac8 100644 --- a/meson.build +++ b/meson.build @@ -51,7 +51,14 @@ if not get_option('source-only') libdl = cc.find_library('dl', required : false) threads_dep = dependency('threads') - lua_dep = dependency('lua5.4', required : false) + + lua_fallback = ['lua', 'lua_dep'] + lua_quick_fallback = [] + if get_option('wrap_mode') == 'forcefallback' + lua_quick_fallback = lua_fallback + endif + + lua_dep = dependency('lua5.4', fallback: lua_quick_fallback, required : false) if not lua_dep.found() lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'], default_options: ['default_library=static', 'line_editing=false', 'interpreter=false'] From 456126400a7a6b8ec0a60aa84c56a0b169c3509c Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 5 Dec 2021 18:32:20 -0500 Subject: [PATCH 107/409] Added in new merge method, and run it on plugins. Also made it so plugin configs can be set anywhere, even if we don't know the plugin beforehand. --- data/core/common.lua | 8 ++++++++ data/core/config.lua | 10 ++++++++-- data/plugins/autocomplete.lua | 4 ++-- data/plugins/scale.lua | 4 ++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index bae6636a..37d30436 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -17,6 +17,14 @@ function common.clamp(n, lo, hi) end +function common.merge(a, b) + local t = {} + for k, v in pairs(a) do t[k] = v end + if b then for k, v in pairs(b) do t[k] = v end end + return t +end + + function common.round(n) return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5) end diff --git a/data/core/config.lua b/data/core/config.lua index a887d579..1233595b 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -30,10 +30,16 @@ config.borderless = false config.tab_close_button = true config.max_clicks = 3 --- Disable plugin loading setting to false the config entry --- of the same name. config.plugins = {} +-- Allow you to set plugin configs even if we haven't seen the plugin before. +setmetatable(config.plugins, { + __index = function(t, k) + if rawget(t, k) == nil then rawset(t, k, {}) end + return rawget(t, k) + end +}) +-- Disable these plugins by default. config.plugins.trimwhitespace = false config.plugins.lineguide = false config.plugins.drawwhitespace = false diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index fde9487e..4686fdf3 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -10,14 +10,14 @@ local RootView = require "core.rootview" local DocView = require "core.docview" local Doc = require "core.doc" -config.plugins.autocomplete = { +config.plugins.autocomplete = common.merge({ -- Amount of characters that need to be written for autocomplete min_len = 3, -- The max amount of visible items max_height = 6, -- The max amount of scrollable items max_suggestions = 100, -} +}, config.plugins.autocomplete) local autocomplete = {} diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index 616ee40b..f36b1ff3 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -8,10 +8,10 @@ local style = require "core.style" local RootView = require "core.rootview" local CommandView = require "core.commandview" -config.plugins.scale = { +config.plugins.scale = common.merge({ mode = "code", use_mousewheel = true -} +}, config.plugins.scale) local scale_steps = 0.05 From bc9f8a4075373a9d597fe75049c8189699f5bae9 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Fri, 21 Jan 2022 11:08:16 +0100 Subject: [PATCH 108/409] Use new mutex in dmon to avoid possible lock-up We rely on one variable _dmon.modify_watches shared between thread to ensure that we don't lock with the dmon polling thread waiting indefinitely and helding a lock. To ensure that the polling thread sees modifications done to 'modify_watches' we use an additional mutex that act as a memory barrier. --- lib/dmon/dmon.h | 60 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h index 3f2bc0c5..84258bad 100644 --- a/lib/dmon/dmon.h +++ b/lib/dmon/dmon.h @@ -159,10 +159,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id); # define NOMINMAX # endif # include -# include -# ifdef _MSC_VER -# pragma intrinsic(_InterlockedExchange) -# endif #elif DMON_OS_LINUX # ifndef __USE_MISC # define __USE_MISC @@ -406,7 +402,8 @@ typedef struct dmon__state { dmon__watch_state watches[DMON_MAX_WATCHES]; HANDLE thread_handle; CRITICAL_SECTION mutex; - volatile LONG modify_watches; + volatile int modify_watches; + CRITICAL_SECTION modify_watches_mutex; dmon__win32_event* events; bool quit; HANDLE wake_event; @@ -492,6 +489,13 @@ _DMON_PRIVATE void dmon__win32_process_events(void) stb_sb_reset(_dmon.events); } +static int dmon__safe_get_modify_watches() { + EnterCriticalSection(&_dmon.modify_watches_mutex); + const int value = _dmon.modify_watches; + LeaveCriticalSection(&_dmon.modify_watches_mutex); + return value; +} + _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) { _DMON_UNUSED(arg); @@ -502,7 +506,8 @@ _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) uint64_t msecs_elapsed = 0; while (!_dmon.quit) { - if (_dmon.modify_watches || !TryEnterCriticalSection(&_dmon.mutex)) { + if (dmon__safe_get_modify_watches() || + !TryEnterCriticalSection(&_dmon.mutex)) { Sleep(10); continue; } @@ -587,6 +592,7 @@ DMON_API_IMPL void dmon_init(void) { DMON_ASSERT(!_dmon_init); InitializeCriticalSection(&_dmon.mutex); + InitializeCriticalSection(&_dmon.modify_watches_mutex); _dmon.thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL); @@ -596,11 +602,20 @@ DMON_API_IMPL void dmon_init(void) } static void dmon__enter_critical_wakeup(void) { - _InterlockedExchange(&_dmon.modify_watches, 1); + EnterCriticalSection(&_dmon.modify_watches_mutex); + _dmon.modify_watches = 1; if (TryEnterCriticalSection(&_dmon.mutex) == 0) { SetEvent(_dmon.wake_event); EnterCriticalSection(&_dmon.mutex); } + LeaveCriticalSection(&_dmon.modify_watches_mutex); +} + +static void dmon__leave_critical_wakeup(void) { + EnterCriticalSection(&_dmon.modify_watches_mutex); + _dmon.modify_watches = 0; + LeaveCriticalSection(&_dmon.modify_watches_mutex); + LeaveCriticalSection(&_dmon.mutex); } DMON_API_IMPL void dmon_deinit(void) @@ -617,8 +632,9 @@ DMON_API_IMPL void dmon_deinit(void) dmon__unwatch(&_dmon.watches[i]); } - LeaveCriticalSection(&_dmon.mutex); + dmon__leave_critical_wakeup(); DeleteCriticalSection(&_dmon.mutex); + DeleteCriticalSection(&_dmon.modify_watches_mutex); stb_sb_free(_dmon.events); _dmon_init = false; } @@ -665,19 +681,16 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, !dmon__refresh_watch(watch)) { dmon__unwatch(watch); *error_code = DMON_ERROR_WATCH_DIR; - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); + dmon__leave_critical_wakeup(); return dmon__make_id(0); } } else { *error_code = DMON_ERROR_OPEN_DIR; - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); + dmon__leave_critical_wakeup(); return dmon__make_id(0); } - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); + dmon__leave_critical_wakeup(); return dmon__make_id(id); } @@ -696,8 +709,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) } --_dmon.num_watches; - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); + dmon__leave_critical_wakeup(); } #elif DMON_OS_LINUX @@ -733,7 +745,8 @@ typedef struct dmon__state { int num_watches; pthread_t thread_handle; pthread_mutex_t mutex; - int wait_flag; + volatile int wait_flag; + pthread_mutex_t wait_flag_mutex; int wake_event_pipe[2]; bool quit; } dmon__state; @@ -1013,6 +1026,13 @@ _DMON_PRIVATE void dmon__inotify_process_events(void) stb_sb_reset(_dmon.events); } +_DMON_PRIVATE int dmon__safe_get_wait_flag() { + pthread_mutex_lock(&_dmon.wait_flag_mutex); + const int value = _dmon.wait_flag; + pthread_mutex_unlock(&_dmon.wait_flag_mutex); + return value; +} + static void* dmon__thread(void* arg) { _DMON_UNUSED(arg); @@ -1028,7 +1048,9 @@ static void* dmon__thread(void* arg) while (!_dmon.quit) { nanosleep(&req, &rem); - if (_dmon.num_watches == 0 || _dmon.wait_flag == 1 || pthread_mutex_trylock(&_dmon.mutex) != 0) { + if (_dmon.num_watches == 0 || + dmon__safe_get_wait_flag() || + pthread_mutex_trylock(&_dmon.mutex) != 0) { continue; } @@ -1106,6 +1128,7 @@ static void* dmon__thread(void* arg) } _DMON_PRIVATE void dmon__mutex_wakeup_lock(void) { + pthread_mutex_lock(&_dmon.wait_flag_mutex); _dmon.wait_flag = 1; if (pthread_mutex_trylock(&_dmon.mutex) != 0) { char send_char = 1; @@ -1113,6 +1136,7 @@ _DMON_PRIVATE void dmon__mutex_wakeup_lock(void) { pthread_mutex_lock(&_dmon.mutex); } _dmon.wait_flag = 0; + pthread_mutex_unlock(&_dmon.wait_flag_mutex); } _DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) From 3e39da071da130e3539227107d273f6d1b6a79ae Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 24 Jan 2022 09:31:40 +0100 Subject: [PATCH 109/409] Fix problem with project module save hook --- data/core/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 8810d4b2..366d1048 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -839,8 +839,9 @@ local function add_config_files_hooks() local doc_save = Doc.save local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua") function Doc:save(filename, abs_filename) + local module_filename = system.absolute_path(".lite_project.lua") doc_save(self, filename, abs_filename) - if self.abs_filename == user_filename or self.abs_filename == core.project_module_filename then + if self.abs_filename == user_filename or self.abs_filename == module_filename then reload_customizations() rescan_project_directories() configure_borderless_window() @@ -1158,7 +1159,6 @@ end function core.load_project_module() local filename = ".lite_project.lua" - core.project_module_filename = system.absolute_path(filename) if system.get_file_info(filename) then return core.try(function() local fn, err = loadfile(filename) From 3a53b05b29b409d0602ab91e97d45f182106e41e Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Tue, 25 Jan 2022 14:14:56 +0100 Subject: [PATCH 110/409] Do no error out on malformed ignore patterns --- data/core/init.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 366d1048..743d6ca1 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -136,12 +136,15 @@ local function compile_ignore_files() -- config.ignore_files could be a simple string... if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end for i, pattern in ipairs(ipatterns) do - compiled[i] = { - use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end - -- An '/' or '/$' at the end means we want to match a directory. - match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value - pattern = pattern -- get the actual pattern - } + -- we ignore malformed pattern that raise an error + if pcall(string.match, "a", pattern) then + table.insert(compiled, { + use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end + -- An '/' or '/$' at the end means we want to match a directory. + match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value + pattern = pattern -- get the actual pattern + }) + end end return compiled end From 8c0685d440ef7e0afbc383e2292280b61570c65c Mon Sep 17 00:00:00 2001 From: Dheisom Gomes Date: Fri, 28 Jan 2022 11:53:30 -0300 Subject: [PATCH 111/409] Added support to pass extra arguments to functions on core.add_thread --- data/core/init.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 91fc59e8..ae766f57 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -953,9 +953,11 @@ function core.show_title_bar(show) end -function core.add_thread(f, weak_ref) +function core.add_thread(f, weak_ref, ...) local key = weak_ref or #core.threads + 1 - local fn = function() return core.try(f) end + local args = {...} + local unpack = unpack or table.unpack + local fn = function() return core.try(f, unpack(args)) end core.threads[key] = { cr = coroutine.create(fn), wake = 0 } return key end From 6331a23c6b4de4926052b9d8d68e5bcab409a4c9 Mon Sep 17 00:00:00 2001 From: Dheisom Gomes Date: Fri, 28 Jan 2022 12:02:30 -0300 Subject: [PATCH 112/409] Added support to use a array of regex to ignore files --- data/core/common.lua | 13 +++++++++++++ data/core/config.lua | 2 +- data/core/init.lua | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index 37d30436..dc61553b 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -1,3 +1,5 @@ +local config = require 'core.config' + local common = {} @@ -445,4 +447,15 @@ function common.rm(path, recursively) return true end +---@param filename string +---@return boolean +function common.match_ignore_files(filename) + for _, pattern in ipairs(config.ignore_files) do + if common.match_pattern(filename, pattern) then + return true + end + end + return false +end + return common diff --git a/data/core/config.lua b/data/core/config.lua index 1233595b..02c68f5e 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -6,7 +6,7 @@ config.message_timeout = 5 config.mouse_wheel_scroll = 50 * SCALE config.scroll_past_end = true config.file_size_limit = 10 -config.ignore_files = "^%." +config.ignore_files = { "^%.", "node_modules" } config.symbol_pattern = "[%a_][%w_]*" config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-" config.undo_merge_timeout = 0.3 diff --git a/data/core/init.lua b/data/core/init.lua index ae766f57..6a1f306e 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -100,7 +100,7 @@ local function get_project_file_info(root, file) if info then info.filename = strip_leading_path(file) return (info.size < config.file_size_limit * 1e6 and - not common.match_pattern(common.basename(info.filename), config.ignore_files) + not common.match_ignore_files(common.basename(info.filename)) and info) end end @@ -462,7 +462,7 @@ end local function project_scan_add_file(dir, filepath) for fragment in string.gmatch(filepath, "([^/\\]+)") do - if common.match_pattern(fragment, config.ignore_files) then + if common.match_ignore_files(fragment) then return end end From 22d8f69b5c12f59c126f07ee91614900428849f9 Mon Sep 17 00:00:00 2001 From: Dheisom Gomes Date: Fri, 28 Jan 2022 12:13:52 -0300 Subject: [PATCH 113/409] Error correction getting "unpack" function --- data/core/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 6a1f306e..6cd0b2d8 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -956,8 +956,8 @@ end function core.add_thread(f, weak_ref, ...) local key = weak_ref or #core.threads + 1 local args = {...} - local unpack = unpack or table.unpack - local fn = function() return core.try(f, unpack(args)) end + local table_unpack = rawget(_G, 'unpack') or rawget(_G.table, 'unpack') + local fn = function() return core.try(f, table_unpack(args)) end core.threads[key] = { cr = coroutine.create(fn), wake = 0 } return key end From 3773a812bd3c65f30d45ba18ee0734bdc19701f4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 28 Jan 2022 15:39:57 -0500 Subject: [PATCH 114/409] Incorporate realtakase's suggestions. --- src/api/process.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index cabcbf17..8f8a724b 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -330,8 +330,9 @@ static int f_write(lua_State* L) { #if _WIN32 DWORD dwWritten; if (!WriteFile(self->child_pipes[STDIN_FD][1], data, data_size, &dwWritten, NULL)) { + int lastError = GetLastError(); signal_process(self, SIGNAL_TERM); - return luaL_error(L, "error writing to process: %d", GetLastError()); + return luaL_error(L, "error writing to process: %d", lastError); } length = dwWritten; #else @@ -339,8 +340,9 @@ static int f_write(lua_State* L) { if (length < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) length = 0; else if (length < 0) { + const char* lastError = strerror(errno); signal_process(self, SIGNAL_TERM); - return luaL_error(L, "error writing to process: %s", strerror(errno)); + return luaL_error(L, "error writing to process: %s", lastError); } #endif lua_pushinteger(L, length); From 13adedb23a5b98bf4c147a8b7fad4a367222f170 Mon Sep 17 00:00:00 2001 From: Dheisom Gomes Date: Fri, 28 Jan 2022 18:30:19 -0300 Subject: [PATCH 115/409] Go back to `common.match_pattern` and use `table.unpack` directly on function `core.add_thread` --- data/core/common.lua | 14 +------------- data/core/config.lua | 12 ++++++------ data/core/init.lua | 7 +++---- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index dc61553b..6a25cae2 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -1,5 +1,3 @@ -local config = require 'core.config' - local common = {} @@ -24,7 +22,7 @@ function common.merge(a, b) for k, v in pairs(a) do t[k] = v end if b then for k, v in pairs(b) do t[k] = v end end return t -end +end function common.round(n) @@ -447,15 +445,5 @@ function common.rm(path, recursively) return true end ----@param filename string ----@return boolean -function common.match_ignore_files(filename) - for _, pattern in ipairs(config.ignore_files) do - if common.match_pattern(filename, pattern) then - return true - end - end - return false -end return common diff --git a/data/core/config.lua b/data/core/config.lua index 02c68f5e..d8a0bbd1 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -6,7 +6,7 @@ config.message_timeout = 5 config.mouse_wheel_scroll = 50 * SCALE config.scroll_past_end = true config.file_size_limit = 10 -config.ignore_files = { "^%.", "node_modules" } +config.ignore_files = { "^%." } config.symbol_pattern = "[%a_][%w_]*" config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-" config.undo_merge_timeout = 0.3 @@ -32,11 +32,11 @@ config.max_clicks = 3 config.plugins = {} -- Allow you to set plugin configs even if we haven't seen the plugin before. -setmetatable(config.plugins, { - __index = function(t, k) - if rawget(t, k) == nil then rawset(t, k, {}) end - return rawget(t, k) - end +setmetatable(config.plugins, { + __index = function(t, k) + if rawget(t, k) == nil then rawset(t, k, {}) end + return rawget(t, k) + end }) -- Disable these plugins by default. diff --git a/data/core/init.lua b/data/core/init.lua index 6cd0b2d8..85bbaf19 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -100,7 +100,7 @@ local function get_project_file_info(root, file) if info then info.filename = strip_leading_path(file) return (info.size < config.file_size_limit * 1e6 and - not common.match_ignore_files(common.basename(info.filename)) + not common.match_pattern(common.basename(info.filename), config.ignore_files) and info) end end @@ -462,7 +462,7 @@ end local function project_scan_add_file(dir, filepath) for fragment in string.gmatch(filepath, "([^/\\]+)") do - if common.match_ignore_files(fragment) then + if common.match_pattern(fragment, config.ignore_files) then return end end @@ -956,8 +956,7 @@ end function core.add_thread(f, weak_ref, ...) local key = weak_ref or #core.threads + 1 local args = {...} - local table_unpack = rawget(_G, 'unpack') or rawget(_G.table, 'unpack') - local fn = function() return core.try(f, table_unpack(args)) end + local fn = function() return core.try(f, table.unpack(args)) end core.threads[key] = { cr = coroutine.create(fn), wake = 0 } return key end From efedbae6639c9ee7eb06c9c2501b69be1b0986df Mon Sep 17 00:00:00 2001 From: AlexSol Date: Fri, 28 Jan 2022 20:08:54 +0200 Subject: [PATCH 116/409] Travel by contextMenu using keyboard --- data/core/contextmenu.lua | 41 ++++++++++++++++++++++++++++++------ data/plugins/contextmenu.lua | 22 ++++++++++++++++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index 9db35bb3..4cb3e492 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -91,6 +91,7 @@ function ContextMenu:show(x, y) self.position.x, self.position.y = x, y self.show_context_menu = true + core.request_cursor("arrow") return true end return false @@ -101,6 +102,7 @@ function ContextMenu:hide() self.items = nil self.selected = -1 self.height = 0 + core.request_cursor(core.active_view.cursor) end function ContextMenu:each_item() @@ -126,9 +128,6 @@ function ContextMenu:on_mouse_moved(px, py) break end end - if self.selected >= 0 then - core.request_cursor("arrow") - end return true end @@ -140,8 +139,38 @@ function ContextMenu:on_selected(item) end end -function ContextMenu:on_mouse_pressed(button, x, y, clicks) - local selected = (self.items or {})[self.selected] +local function change_value(value, change) + return value + change +end + +function ContextMenu:focus_previous() + self.selected = (self.selected == -1 or self.selected == 1) and #self.items or change_value(self.selected, -1) + if self:get_item_selected() == DIVIDER then + self.selected = change_value(self.selected, -1) + end +end + +function ContextMenu:focus_next() + self.selected = (self.selected == -1 or self.selected == #self.items) and 1 or change_value(self.selected, 1) + if self:get_item_selected() == DIVIDER then + self.selected = change_value(self.selected, 1) + end +end + +function ContextMenu:get_item_selected() + return (self.items or {})[self.selected] +end + +function ContextMenu:call_selected_item() + local selected = self:get_item_selected() + self:hide() + if selected then + self:on_selected(selected) + end +end + +function ContextMenu:on_mouse_pressed(button, px, py, clicks) + local selected = self:get_item_selected() local caught = false self:hide() @@ -153,7 +182,7 @@ function ContextMenu:on_mouse_pressed(button, x, y, clicks) end if button == "right" then - caught = self:show(x, y) + caught = self:show(px, py) end return caught end diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua index 017846ae..ac811b2a 100644 --- a/data/plugins/contextmenu.lua +++ b/data/plugins/contextmenu.lua @@ -3,6 +3,7 @@ local core = require "core" local command = require "core.command" local keymap = require "core.keymap" local ContextMenu = require "core.contextmenu" +local DocView = require "core.docview" local RootView = require "core.rootview" local menu = ContextMenu() @@ -32,7 +33,7 @@ function RootView:draw(...) menu:draw() end -command.add(nil, { +command.add(function() return getmetatable(core.active_view) == DocView end, { ["context:show"] = function() menu:show(core.active_view.position.x, core.active_view.position.y) end @@ -42,6 +43,25 @@ keymap.add { ["menu"] = "context:show" } +command.add(function() return menu.show_context_menu == true end, { + ["context:focus-previous"] = function() + menu:focus_previous() + end, + ["context:focus-next"] = function() + menu:focus_next() + end, + ["context:hide"] = function() + menu:hide() + end, + ["context:on-selected"] = function() + menu:call_selected_item() + end, +}) +keymap.add { ["return"] = "context:on-selected" } +keymap.add { ["up"] = "context:focus-previous" } +keymap.add { ["down"] = "context:focus-next" } +keymap.add { ["escape"] = "context:hide" } + if require("plugins.scale") then menu:register("core.docview", { { text = "Cut", command = "doc:cut" }, From af76f544bef0b65245d0ace610517474a5a33299 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 29 Jan 2022 15:19:22 -0500 Subject: [PATCH 117/409] Fixing performance regression. Due to the way the hashes work, we must 0 out the whole thing. --- src/rencache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rencache.c b/src/rencache.c index 5d464226..5686d984 100644 --- a/src/rencache.c +++ b/src/rencache.c @@ -95,7 +95,7 @@ static Command* push_command(int type, int size) { return NULL; } command_buf_idx = n; - memset(cmd, 0, COMMAND_BARE_SIZE); + memset(cmd, 0, size); cmd->type = type; cmd->size = size; return cmd; From d2d56177747611fae53385663e0fe60d9d5157f4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 30 Jan 2022 20:51:30 -0500 Subject: [PATCH 118/409] Changed signalling so it'll target the whole process group. --- src/api/process.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index 999ccd40..c4ad0673 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -105,9 +105,9 @@ static bool signal_process(process_t* proc, signal_e sig) { } #else switch (sig) { - case SIGNAL_TERM: terminate = kill(proc->pid, SIGTERM) == 1; break; - case SIGNAL_KILL: terminate = kill(proc->pid, SIGKILL) == 1; break; - case SIGNAL_INTERRUPT: kill(proc->pid, SIGINT); break; + case SIGNAL_TERM: terminate = kill(-proc->pid, SIGTERM) == 1; break; + case SIGNAL_KILL: terminate = kill(-proc->pid, SIGKILL) == 1; break; + case SIGNAL_INTERRUPT: kill(-proc->pid, SIGINT); break; } #endif if (terminate) @@ -255,6 +255,7 @@ static int process_start(lua_State* L) { } return luaL_error(L, "Error running fork: %s.", strerror(errno)); } else if (!self->pid) { + setpgrp(); for (int stream = 0; stream < 3; ++stream) { if (new_fds[stream] == REDIRECT_DISCARD) { // Close the stream if we don't want it. close(self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); @@ -429,6 +430,7 @@ static int f_gc(lua_State* L) { close_fd(&self->child_pipes[STDIN_FD ][1]); close_fd(&self->child_pipes[STDOUT_FD][0]); close_fd(&self->child_pipes[STDERR_FD][0]); + poll_process(self, 10); return 0; } From 9a6cd2b453a10350876ee56fcd4f3987e3a9c0da Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 3 Feb 2022 01:50:43 +0100 Subject: [PATCH 119/409] Use SDL to manage color format mapping in `ren_draw_rect` --- src/renderer.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/renderer.c b/src/renderer.c index b0e17f58..5e26a3f5 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -331,18 +332,23 @@ void ren_draw_rect(RenRect rect, RenColor color) { y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2; SDL_Surface *surface = renwin_get_surface(&window_renderer); - RenColor *d = (RenColor*) surface->pixels; + uint32_t *d = surface->pixels; d += x1 + y1 * surface->w; int dr = surface->w - (x2 - x1); - unsigned int translated = SDL_MapRGB(surface->format, color.r, color.g, color.b); if (color.a == 0xff) { + uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b); SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 }; SDL_FillRect(surface, &rect, translated); } else { - RenColor translated_color = (RenColor){ translated & 0xFF, (translated >> 8) & 0xFF, (translated >> 16) & 0xFF, color.a }; + RenColor current_color; + RenColor blended_color; for (int j = y1; j < y2; j++) { for (int i = x1; i < x2; i++, d++) - *d = blend_pixel(*d, translated_color); + { + SDL_GetRGB(*d, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b); + blended_color = blend_pixel(current_color, color); + *d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b); + } d += dr; } } From 6773e85cb83f4d6c9548b99884664cc72a5d2f4f Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 3 Feb 2022 14:55:01 -0400 Subject: [PATCH 120/409] Update autocomplete with changes needed for latest LSP plugin. --- data/core/config.lua | 11 ++- data/plugins/autocomplete.lua | 165 +++++++++++++++++++++++++--------- 2 files changed, 130 insertions(+), 46 deletions(-) diff --git a/data/core/config.lua b/data/core/config.lua index 1233595b..b4f79006 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -19,7 +19,6 @@ config.line_height = 1.2 config.indent_size = 2 config.tab_type = "soft" config.line_limit = 80 -config.max_symbols = 4000 config.max_project_files = 2000 config.transitions = true config.animation_rate = 1.0 @@ -32,11 +31,11 @@ config.max_clicks = 3 config.plugins = {} -- Allow you to set plugin configs even if we haven't seen the plugin before. -setmetatable(config.plugins, { - __index = function(t, k) - if rawget(t, k) == nil then rawset(t, k, {}) end - return rawget(t, k) - end +setmetatable(config.plugins, { + __index = function(t, k) + if rawget(t, k) == nil then rawset(t, k, {}) end + return rawget(t, k) + end }) -- Disable these plugins by default. diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index 4686fdf3..0723ec8d 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -11,12 +11,16 @@ local DocView = require "core.docview" local Doc = require "core.doc" config.plugins.autocomplete = common.merge({ - -- Amount of characters that need to be written for autocomplete - min_len = 3, - -- The max amount of visible items - max_height = 6, - -- The max amount of scrollable items - max_suggestions = 100, + -- Amount of characters that need to be written for autocomplete + min_len = 3, + -- The max amount of visible items + max_height = 6, + -- The max amount of scrollable items + max_suggestions = 100, + -- Maximum amount of symbols to cache per document + max_symbols = 4000, + -- Font size of the description box + desc_font_size = 12 }, config.plugins.autocomplete) local autocomplete = {} @@ -33,7 +37,7 @@ local triggered_manually = false local mt = { __tostring = function(t) return t.text end } -function autocomplete.add(t, triggered_manually) +function autocomplete.add(t, manually_triggered) local items = {} for text, info in pairs(t.items) do if type(info) == "table" then @@ -43,9 +47,10 @@ function autocomplete.add(t, triggered_manually) { text = text, info = info.info, - desc = info.desc, -- Description shown on item selected - cb = info.cb, -- A callback called once when item is selected - data = info.data -- Optional data that can be used on cb + desc = info.desc, -- Description shown on item selected + onhover = info.onhover, -- A callback called once when item is hovered + onselect = info.onselect, -- A callback called when item is selected + data = info.data -- Optional data that can be used on cb }, mt ) @@ -56,7 +61,7 @@ function autocomplete.add(t, triggered_manually) end end - if not triggered_manually then + if not manually_triggered then autocomplete.map[t.name] = { files = t.files or ".*", items = items } else autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items } @@ -66,7 +71,7 @@ end -- -- Thread that scans open document symbols and cache them -- -local max_symbols = config.max_symbols +local max_symbols = config.plugins.autocomplete.max_symbols core.add_thread(function() local cache = setmetatable({}, { __mode = "k" }) @@ -85,7 +90,9 @@ core.add_thread(function() doc.disable_symbols = true core.status_view:show_message("!", style.accent, "Too many symbols in document "..doc.filename.. - ": stopping auto-complete for this document according to config.max_symbols.") + ": stopping auto-complete for this document according to ".. + "config.plugins.autocomplete.max_symbols." + ) collectgarbage('collect') return {} end @@ -159,16 +166,6 @@ local function reset_suggestions() end end -local function in_table(value, table_array) - for i, element in pairs(table_array) do - if element == value then - return true - end - end - - return false -end - local function update_suggestions() local doc = core.active_view.doc local filename = doc and doc.filename or "" @@ -199,6 +196,7 @@ local function update_suggestions() j = j + 1 end end + suggestions_idx = 1 end local function get_partial_symbol() @@ -249,6 +247,11 @@ local function get_suggestions_rect(av) max_width = 150 end + -- if portion not visiable to right, reposition to DocView right margin + if (x - av.position.x) + max_width > av.size.x then + x = (av.size.x + av.position.x) - max_width - (style.padding.x * 2) + end + return x - style.padding.x, y - style.padding.y, @@ -256,20 +259,99 @@ local function get_suggestions_rect(av) max_items * (th + style.padding.y) + style.padding.y end +local function wrap_line(line, max_chars) + if #line > max_chars then + local lines = {} + local line_len = #line + local new_line = "" + local prev_char = "" + local position = 0 + local indent = line:match("^%s+") + for char in line:gmatch(".") do + position = position + 1 + if #new_line < max_chars then + new_line = new_line .. char + prev_char = char + if position >= line_len then + table.insert(lines, new_line) + end + else + if + not prev_char:match("%s") + and + not string.sub(line, position+1, 1):match("%s") + and + position < line_len + then + new_line = new_line .. "-" + end + table.insert(lines, new_line) + if indent then + new_line = indent .. char + else + new_line = char + end + end + end + return lines + end + return line +end + +local previous_scale = SCALE +local desc_font = style.code_font:copy( + config.plugins.autocomplete.desc_font_size * SCALE +) local function draw_description_box(text, av, sx, sy, sw, sh) + if previous_scale ~= SCALE then + desc_font = style.code_font:copy( + config.plugins.autocomplete.desc_font_size * SCALE + ) + previous_scale = SCALE + end + + local font = desc_font + local lh = font:get_height() + local y = sy + style.padding.y + local x = sx + sw + style.padding.x / 4 local width = 0 + local char_width = font:get_width(" ") + local draw_left = false; + + local max_chars = 0 + if sx - av.position.x < av.size.x - (sx - av.position.x) - sw then + max_chars = (((av.size.x+av.position.x) - x) / char_width) - 5 + else + draw_left = true; + max_chars = ( + (sx - av.position.x - (style.padding.x / 4) - style.scrollbar_size) + / char_width + ) - 5 + end local lines = {} for line in string.gmatch(text.."\n", "(.-)\n") do - width = math.max(width, style.font:get_width(line)) - table.insert(lines, line) + local wrapper_lines = wrap_line(line, max_chars) + if type(wrapper_lines) == "table" then + for _, wrapped_line in pairs(wrapper_lines) do + width = math.max(width, font:get_width(wrapped_line)) + table.insert(lines, wrapped_line) + end + else + width = math.max(width, font:get_width(line)) + table.insert(lines, line) + end end - local height = #lines * style.font:get_height() + if draw_left then + x = sx - (style.padding.x / 4) - width - (style.padding.x * 2) + end + + local height = #lines * font:get_height() -- draw background rect renderer.draw_rect( - sx + sw + style.padding.x / 4, + x, sy, width + style.padding.x * 2, height + style.padding.y * 2, @@ -277,13 +359,10 @@ local function draw_description_box(text, av, sx, sy, sw, sh) ) -- draw text - local lh = style.font:get_height() - local y = sy + style.padding.y - local x = sx + sw + style.padding.x / 4 - for _, line in pairs(lines) do common.draw_text( - style.font, style.text, line, "left", x + style.padding.x, y, width, lh + font, style.text, line, "left", + x + style.padding.x, y, width, lh ) y = y + lh end @@ -320,10 +399,9 @@ local function draw_suggestions_box(av) end y = y + lh if suggestions_idx == i then - if s.cb then - s.cb(suggestions_idx, s) - s.cb = nil - s.data = nil + if s.onhover then + s.onhover(suggestions_idx, s) + s.onhover = nil end if s.desc and #s.desc > 0 then draw_description_box(s.desc, av, rx, ry, rw, rh) @@ -487,10 +565,17 @@ command.add(predicate, { ["autocomplete:complete"] = function() local doc = core.active_view.doc local line, col = doc:get_selection() - local text = suggestions[suggestions_idx].text - doc:insert(line, col, text) - doc:remove(line, col, line, col - #partial) - doc:set_selection(line, col + #text - #partial) + local item = suggestions[suggestions_idx] + local text = item.text + local inserted = false + if item.onselect then + inserted = item.onselect(suggestions_idx, item) + end + if not inserted then + doc:insert(line, col, text) + doc:remove(line, col, line, col - #partial) + doc:set_selection(line, col + #text - #partial) + end reset_suggestions() end, From df0635ad354a42928d63c33eed118144e60aade3 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 3 Feb 2022 22:20:42 -0400 Subject: [PATCH 121/409] Implemented system.get_process_id() --- src/api/system.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/api/system.c b/src/api/system.c index 5bd94210..80c0a2ae 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -651,6 +651,16 @@ static int f_set_clipboard(lua_State *L) { } +static int f_get_process_id(lua_State *L) { +#ifdef _WIN32 + lua_pushinteger(L, GetCurrentProcessId()); +#else + lua_pushinteger(L, getpid()); +#endif + return 1; +} + + static int f_get_time(lua_State *L) { double n = SDL_GetPerformanceCounter() / (double) SDL_GetPerformanceFrequency(); lua_pushnumber(L, n); @@ -944,6 +954,7 @@ static const luaL_Reg lib[] = { { "get_file_info", f_get_file_info }, { "get_clipboard", f_get_clipboard }, { "set_clipboard", f_set_clipboard }, + { "get_process_id", f_get_process_id }, { "get_time", f_get_time }, { "sleep", f_sleep }, { "exec", f_exec }, From aec6806d8f9569c4cfa534836e452d030151328d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 4 Feb 2022 15:43:42 -0400 Subject: [PATCH 122/409] Added system.get_process_id() to api docs. --- docs/api/system.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api/system.lua b/docs/api/system.lua index a655099b..e5eff27f 100644 --- a/docs/api/system.lua +++ b/docs/api/system.lua @@ -192,6 +192,12 @@ function system.get_clipboard() end ---@param text string function system.set_clipboard(text) end +--- +---Get the process id of lite-xl it self. +--- +---@return integer +function system.get_process_id() end + --- ---Get amount of iterations since the application was launched ---also known as SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency() From f23cb33f7cbe134db04be292a00cfb47bcce609e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 7 Feb 2022 19:22:43 +0100 Subject: [PATCH 123/409] Ignore empty lines in `line_comment` --- data/core/commands/doc.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index d53e3638..2125e185 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -105,18 +105,20 @@ local function line_comment(comment, line1, col1, line2, col2) for line = line1, line2 do local text = doc().lines[line] local s = text:find("%S") - local cs, ce = text:find(start_comment, s, true) - if s and cs ~= s then - uncomment = false + if s then + local cs, ce = text:find(start_comment, s, true) + if cs ~= s then + uncomment = false + end + start_offset = math.min(start_offset, s) end - start_offset = math.min(start_offset, s) end - + local end_line = col2 == #doc().lines[line2] for line = line1, line2 do local text = doc().lines[line] local s = text:find("%S") - if uncomment then + if s and uncomment then if end_comment and text:sub(#text - #end_comment, #text - 1) == end_comment then doc():remove(line, #text - #end_comment, line, #text) end From d4b8155cbc4aa6b8f8f8c01616763cda45b45f31 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 8 Feb 2022 18:10:54 +0100 Subject: [PATCH 124/409] Update process status when calling `process:running` --- src/api/process.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/process.c b/src/api/process.c index 999ccd40..73e86045 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -67,9 +68,10 @@ typedef enum { static bool poll_process(process_t* proc, int timeout) { if (!proc->running) return false; - unsigned int ticks = SDL_GetTicks(); + uint32_t ticks = SDL_GetTicks(); if (timeout == WAIT_DEADLINE) timeout = proc->deadline; + do { #ifdef _WIN32 DWORD exit_code = -1; @@ -90,9 +92,8 @@ static bool poll_process(process_t* proc, int timeout) { if (timeout) SDL_Delay(5); } while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout); - if (!proc->running) - return false; - return true; + + return proc->running; } static bool signal_process(process_t* proc, signal_e sig) { @@ -433,8 +434,8 @@ static int f_gc(lua_State* L) { } static int f_running(lua_State* L) { - process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS); - lua_pushboolean(L, self->running); + process_t* self = (process_t*)luaL_checkudata(L, 1, API_TYPE_PROCESS); + lua_pushboolean(L, poll_process(self, WAIT_NONE)); return 1; } From bb4569da53af21eb4a3a701320470457e62b1a0c Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 9 Feb 2022 09:58:20 -0400 Subject: [PATCH 125/409] Fix cases of nil node. --- data/core/commands/root.lua | 4 ++-- data/core/rootview.lua | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/core/commands/root.lua b/data/core/commands/root.lua index 5bf18390..c75c3e90 100644 --- a/data/core/commands/root.lua +++ b/data/core/commands/root.lua @@ -30,7 +30,7 @@ local t = { for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true) end, - + ["root:switch-to-previous-tab"] = function() local node = core.root_view:get_active_node() local idx = node:get_view_idx(core.active_view) @@ -64,7 +64,7 @@ local t = { table.insert(node.views, idx + 1, core.active_view) end end, - + ["root:shrink"] = function() local node = core.root_view:get_active_node() local parent = node:get_parent_node(core.root_view.root_node) diff --git a/data/core/rootview.lua b/data/core/rootview.lua index fb735fe3..54de7cfc 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -30,7 +30,9 @@ end function RootView:get_active_node() - return self.root_node:get_node_for_view(core.active_view) + local node = self.root_node:get_node_for_view(core.active_view) + if not node then node = self:get_primary_node() end + return node end @@ -46,6 +48,7 @@ end function RootView:get_active_node_default() local node = self.root_node:get_node_for_view(core.active_view) + if not node then node = self:get_primary_node() end if node.locked then local default_view = self:get_primary_node().views[1] assert(default_view, "internal error: cannot find original document node.") @@ -254,7 +257,7 @@ function RootView:on_mouse_moved(x, y, dx, dy) self.root_node:on_mouse_moved(x, y, dx, dy) self.overlapping_node = self.root_node:get_child_overlapping_point(x, y) - + local div = self.root_node:get_divider_overlapping_point(x, y) local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y) if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then From 59ba75916713c5c984e8351d4e819fecee5bd7d8 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 11 Feb 2022 06:00:38 +0100 Subject: [PATCH 126/409] Don't scroll DocView when executing `doc:select-all` --- data/core/commands/doc.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 2125e185..c2c0e45d 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -278,6 +278,9 @@ local commands = { ["doc:select-all"] = function() doc():set_selection(1, 1, math.huge, math.huge) + -- avoid triggering DocView:scroll_to_make_visible + dv().last_line = 1 + dv().last_col = 1 end, ["doc:select-lines"] = function() From 5526041da32a37a11c9f1400b5eaf6fb82a78254 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 11 Feb 2022 06:04:58 +0100 Subject: [PATCH 127/409] Check entire selection to trigger `DocView:scroll_to_make_visible` This is needed for example when a selection has both `line1` and `col1` at 1, and the left arrow is pressed: `line2` and `col2` change, while `line1` and `col1` don't, but we still want to scroll. --- data/core/commands/doc.lua | 6 ++++-- data/core/docview.lua | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index c2c0e45d..01d85f27 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -279,8 +279,10 @@ local commands = { ["doc:select-all"] = function() doc():set_selection(1, 1, math.huge, math.huge) -- avoid triggering DocView:scroll_to_make_visible - dv().last_line = 1 - dv().last_col = 1 + dv().last_line1 = 1 + dv().last_col1 = 1 + dv().last_line2 = #doc().lines + dv().last_col2 = #doc().lines[#doc().lines] end, ["doc:select-lines"] = function() diff --git a/data/core/docview.lua b/data/core/docview.lua index 5bed6215..61a0cc42 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -284,13 +284,15 @@ end function DocView:update() -- scroll to make caret visible and reset blink timer if it moved - local line, col = self.doc:get_selection() - if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then + local line1, col1, line2, col2 = self.doc:get_selection() + if (line1 ~= self.last_line1 or col1 ~= self.last_col1 or + line2 ~= self.last_line2 or col2 ~= self.last_col2) and self.size.x > 0 then if core.active_view == self then - self:scroll_to_make_visible(line, col) + self:scroll_to_make_visible(line1, col1) end core.blink_reset() - self.last_line, self.last_col = line, col + self.last_line1, self.last_col1 = line1, col1 + self.last_line2, self.last_col2 = line2, col2 end -- update blink timer From 85531b0d3f7529f66be50e4d12d798fcebd21c1e Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 29 Jan 2022 05:16:51 -0800 Subject: [PATCH 128/409] Include addons with build-package for bundles --- scripts/package.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/package.sh b/scripts/package.sh index c90e5b7c..94eab7b8 100644 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -216,11 +216,11 @@ main() { rm -rf "Lite XL.app"; mv "${dest_dir}" "Lite XL.app" dest_dir="Lite XL.app" exe_file="$(pwd)/${dest_dir}/Contents/MacOS/lite-xl" + data_dir="$(pwd)/${dest_dir}/Contents/Resources" fi fi if [[ $bundle == false && $portable == false ]]; then - echo "Creating a compressed archive..." data_dir="$(pwd)/${dest_dir}/$prefix/share/lite-xl" exe_file="$(pwd)/${dest_dir}/$prefix/bin/lite-xl" fi @@ -240,6 +240,7 @@ main() { $stripcmd "${exe_file}" + echo "Creating a compressed archive ${package_name}" if [[ $binary == true ]]; then rm -f "${package_name}".tar.gz rm -f "${package_name}".zip From b02aae939c1c77d307750c3ce9ad3380d8a93452 Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Thu, 27 Jan 2022 08:42:54 +0100 Subject: [PATCH 129/409] Fix again bug with invalid ignore_files patterns The pattern cannot be tested in advance as it seems that Lua inspect the pattern only partially, the part that is actually used. We resort to use pcall to catch any error when using the pattern. --- data/core/init.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 49a7370c..7e2612aa 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -136,20 +136,23 @@ local function compile_ignore_files() -- config.ignore_files could be a simple string... if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end for i, pattern in ipairs(ipatterns) do - -- we ignore malformed pattern that raise an error - if pcall(string.match, "a", pattern) then - table.insert(compiled, { - use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end - -- An '/' or '/$' at the end means we want to match a directory. - match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value - pattern = pattern -- get the actual pattern - }) - end + compiled[i] = { + use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end + -- An '/' or '/$' at the end means we want to match a directory. + match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value + pattern = pattern -- get the actual pattern + } end return compiled end +local function safe_match(s, pattern) + local ok, match = pcall(string.match, s, pattern) + return ok and match +end + + local function fileinfo_pass_filter(info, ignore_compiled) if info.size >= config.file_size_limit * 1e6 then return false end local basename = common.basename(info.filename) @@ -158,11 +161,11 @@ local function fileinfo_pass_filter(info, ignore_compiled) for _, compiled in ipairs(ignore_compiled) do local test = compiled.use_path and fullname or basename if compiled.match_dir then - if info.type == "dir" and string.match(test .. "/", compiled.pattern) then + if info.type == "dir" and safe_match(test .. "/", compiled.pattern) then return false end else - if string.match(test, compiled.pattern) then + if safe_match(test, compiled.pattern) then return false end end From 2079e1f70723c83ff5c554236f201ec73bb23435 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 13 Feb 2022 01:31:58 -0400 Subject: [PATCH 130/409] Plugin projectsearch: set command view text to current document selection. --- data/plugins/projectsearch.lua | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index d0d14014..e06dd6d9 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -229,8 +229,21 @@ local function begin_search(text, fn) end +local function set_command_view_text() + local view = core.active_view + local doc = (view and view.doc) and view.doc or nil + if doc then + core.command_view:set_text( + doc:get_text(table.unpack({ doc:get_selection() })), + true + ) + end +end + + command.add(nil, { ["project-search:find"] = function() + set_command_view_text() core.command_view:enter("Find Text In Project", function(text) text = text:lower() begin_search(text, function(line_text) @@ -243,12 +256,13 @@ command.add(nil, { core.command_view:enter("Find Regex In Project", function(text) local re = regex.compile(text, "i") begin_search(text, function(line_text) - return regex.cmatch(re, line_text) + return regex.cmatch(re, line_text) end) end) end, ["project-search:fuzzy-find"] = function() + set_command_view_text() core.command_view:enter("Fuzzy Find Text In Project", function(text) begin_search(text, function(line_text) return common.fuzzy_match(line_text, text) and 1 @@ -278,22 +292,22 @@ command.add(ResultsView, { ["project-search:refresh"] = function() core.active_view:refresh() end, - + ["project-search:move-to-previous-page"] = function() local view = core.active_view view.scroll.to.y = view.scroll.to.y - view.size.y end, - + ["project-search:move-to-next-page"] = function() local view = core.active_view view.scroll.to.y = view.scroll.to.y + view.size.y end, - + ["project-search:move-to-start-of-doc"] = function() local view = core.active_view view.scroll.to.y = 0 end, - + ["project-search:move-to-end-of-doc"] = function() local view = core.active_view view.scroll.to.y = view:get_scrollable_size() From 539f929e306a416a50bab8782ccad2e77aaec11d Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 15 Feb 2022 00:45:59 +0100 Subject: [PATCH 131/409] Allow intercepting `filedropped` events The event is first sent to the underlying `View`; if not handled, it's managed as before. --- data/core/init.lua | 22 ++++++++++++---------- data/core/rootview.lua | 6 ++++++ data/core/view.lua | 5 +++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 7e2612aa..b9b001e2 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1478,16 +1478,18 @@ function core.on_event(type, ...) elseif type == "minimized" or type == "maximized" or type == "restored" then core.window_mode = type == "restored" and "normal" or type elseif type == "filedropped" then - local filename, mx, my = ... - local info = system.get_file_info(filename) - if info and info.type == "dir" then - system.exec(string.format("%q %q", EXEFILE, filename)) - else - local ok, doc = core.try(core.open_doc, filename) - if ok then - local node = core.root_view.root_node:get_child_overlapping_point(mx, my) - node:set_active_view(node.active_view) - core.root_view:open_doc(doc) + if not core.root_view:on_file_dropped(...) then + local filename, mx, my = ... + local info = system.get_file_info(filename) + if info and info.type == "dir" then + system.exec(string.format("%q %q", EXEFILE, filename)) + else + local ok, doc = core.try(core.open_doc, filename) + if ok then + local node = core.root_view.root_node:get_child_overlapping_point(mx, my) + node:set_active_view(node.active_view) + core.root_view:open_doc(doc) + end end end elseif type == "focuslost" then diff --git a/data/core/rootview.lua b/data/core/rootview.lua index 54de7cfc..863e1fd5 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -272,6 +272,12 @@ function RootView:on_mouse_moved(x, y, dx, dy) end +function RootView:on_file_dropped(filename, x, y) + local node = self.root_node:get_child_overlapping_point(x, y) + return node and node.active_view:on_file_dropped(filename, x, y) +end + + function RootView:on_mouse_wheel(...) local x, y = self.mouse.x, self.mouse.y local node = self.root_node:get_child_overlapping_point(x, y) diff --git a/data/core/view.lua b/data/core/view.lua index 4b787d46..f2c88dc2 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -98,6 +98,11 @@ function View:on_mouse_moved(x, y, dx, dy) end +function View:on_file_dropped(filename, x, y) + return false +end + + function View:on_text_input(text) -- no-op end From 61e712db12b617e03dcfcec973f7745a7853d898 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 15 Feb 2022 15:57:07 -0500 Subject: [PATCH 132/409] Fixed rendering computations for y offset. (#843) * Fixed rendering computations for y offset. * Force monospacing if every ascii character has the same integer advance. * Added in explanatory comment. * Fixed issues. * Made lines less long. --- data/core/docview.lua | 3 +-- src/renderer.c | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/data/core/docview.lua b/data/core/docview.lua index 61a0cc42..70403e68 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -380,9 +380,8 @@ function DocView:draw_line_gutter(idx, x, y, width) break end end - local yoffset = self:get_line_text_y_offset() x = x + style.padding.x - common.draw_text(self:get_font(), color, idx, "right", x, y + yoffset, width, self:get_line_height()) + common.draw_text(self:get_font(), color, idx, "right", x, y, width, self:get_line_height()) end diff --git a/src/renderer.c b/src/renderer.c index 5e26a3f5..e756cbb9 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -43,7 +43,7 @@ typedef struct RenFont { FT_Face face; GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS]; float size, space_advance, tab_advance; - short max_height; + short max_height, baseline, height; ERenFontAntialiasing antialiasing; ERenFontHinting hinting; unsigned char style; @@ -114,8 +114,10 @@ static void font_load_glyphset(RenFont* font, int idx) { font->sets[j][idx] = set; for (int i = 0; i < MAX_GLYPHSET; ++i) { int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET); - if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) + if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option | FT_LOAD_BITMAP_METRICS_ONLY) + || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) { continue; + } FT_GlyphSlot slot = font->face->glyph; int glyph_width = slot->bitmap.width / byte_width; if (font->antialiasing == FONT_ANTIALIASING_NONE) @@ -123,6 +125,13 @@ static void font_load_glyphset(RenFont* font, int idx) { set->metrics[i] = (GlyphMetric){ pen_x, pen_x + glyph_width, 0, slot->bitmap.rows, true, slot->bitmap_left, slot->bitmap_top, (slot->advance.x + slot->lsb_delta - slot->rsb_delta) / 64.0f}; pen_x += glyph_width; font->max_height = slot->bitmap.rows > font->max_height ? slot->bitmap.rows : font->max_height; + // In order to fix issues with monospacing; we need the unhinted xadvance; as FreeType doesn't correctly report the hinted advance for spaces on monospace fonts (like RobotoMono). See #843. + if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, (load_option | FT_LOAD_BITMAP_METRICS_ONLY | FT_LOAD_NO_HINTING) & ~FT_LOAD_FORCE_AUTOHINT) + || font_set_style(&font->face->glyph->outline, j * (64 / SUBPIXEL_BITMAPS_CACHED), font->style) || FT_Render_Glyph(font->face->glyph, render_option)) { + continue; + } + slot = font->face->glyph; + set->metrics[i].xadvance = slot->advance.x / 64.0f; } if (pen_x == 0) continue; @@ -185,10 +194,12 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial strcpy(font->path, path); font->face = face; font->size = size; + font->height = (short)((face->height / (float)face->units_per_EM) * font->size); + font->baseline = (short)((face->bbox.yMax / (float)face->units_per_EM) * font->size); font->antialiasing = antialiasing; font->hinting = hinting; font->style = style; - font->space_advance = (int)font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance; + font->space_advance = font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance; font->tab_advance = font->space_advance * 2; return font; failure: @@ -229,7 +240,7 @@ float ren_font_group_get_size(RenFont **fonts) { return fonts[0]->size; } int ren_font_group_get_height(RenFont **fonts) { - return fonts[0]->size + 3; + return fonts[0]->height; } float ren_font_group_get_width(RenFont **fonts, const char *text) { @@ -239,8 +250,8 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) { while (text < end) { unsigned int codepoint; text = utf8_to_codepoint(text, &codepoint); - font_group_get_glyph(&set, &metric, fonts, codepoint, 0); - width += metric->xadvance ? metric->xadvance : fonts[0]->space_advance; + RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, 0); + width += (!font || metric->xadvance) ? metric->xadvance : fonts[0]->space_advance; } const int surface_scale = renwin_surface_scale(&window_renderer); return width / surface_scale; @@ -271,7 +282,7 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) { unsigned char* source_pixels = set->surface->pixels; for (int line = metric->y0; line < metric->y1; ++line) { - int target_y = line + y - metric->y0 - metric->bitmap_top + font->size * surface_scale; + int target_y = line + y - metric->bitmap_top + font->baseline * surface_scale; if (target_y < clip.y) continue; if (target_y >= clip_end_y) From feaa3b2547d5742b326199d78e78230ea781def1 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 15 Feb 2022 20:49:46 -0400 Subject: [PATCH 133/409] Docs: changes process start from method to function. --- docs/api/process.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/process.lua b/docs/api/process.lua index 4d146bc4..c384a346 100644 --- a/docs/api/process.lua +++ b/docs/api/process.lua @@ -124,7 +124,7 @@ process.options = {} ---@return process | nil ---@return string errmsg ---@return process.errortype | integer errcode -function process:start(command_and_params, options) end +function process.start(command_and_params, options) end --- ---Translates an error code into a useful text message From 7cb4e93d14ce58312aeea39c5f4b1bda0db628d8 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 21 Feb 2022 03:40:25 -0400 Subject: [PATCH 134/409] Improvements to the StatusView * Support for predicates by introducing add_item(). * Support for performing click actions on items. * Support for optional tooltips on item hover. * Deprecate the usage of get_item(). --- data/core/statusview.lua | 398 ++++++++++++++++++++++++++++++++++----- 1 file changed, 347 insertions(+), 51 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 1e5140f2..02b089bb 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -4,36 +4,114 @@ local command = require "core.command" local config = require "core.config" local style = require "core.style" local DocView = require "core.docview" +local CommandView = require "core.commandview" local LogView = require "core.logview" local View = require "core.view" local Object = require "core.object" +---@alias StatusView.styledtext table +---@alias StatusView.itemscb fun():StatusView.styledtext,StatusView.styledtext +---@alias StatusView.clickcb fun(button: string, x: number, y: number) +---@alias StatusView.predicate fun():boolean +---A status bar implementation for lite, check core.status_view. +---@class StatusView : View +---@field private items StatusView.item[] +---@field private hovered_item StatusView.item +---@field private message_timeout number +---@field private message StatusView.styledtext +---@field private tooltip_mode boolean +---@field private tooltip StatusView.styledtext local StatusView = View:extend() +---@class StatusView.item +---@field predicate StatusView.predicate +---@field items StatusView.itemscb +---@field onclick StatusView.clickcb +---@field tooltip string | nil +---@field lx number +---@field lw number +---@field rx number +---@field rw number +---@field active boolean +StatusView.item = {} + +---Space separator +---@type string StatusView.separator = " " + +---Pipe separator +---@type string StatusView.separator2 = " | " +---Constructor function StatusView:new() StatusView.super.new(self) self.message_timeout = 0 self.message = {} self.tooltip_mode = false self.tooltip = {} + self.items = {} + self.hovered_item = {} + self.pointer = {x = 0, y = 0} + + self:add_item( + function() + return core.active_view:is(DocView) and + not core.active_view:is(CommandView) + end, + self.get_doc_items + ) + + self:add_item("core.commandview", self.get_command_items) end -function StatusView:on_mouse_pressed() - core.set_active_view(core.last_active_view) - if system.get_time() < self.message_timeout - and not core.active_view:is(LogView) then - command.perform "core:open-log" +---Adds an item to be rendered in the status bar. +---@param predicate string | table | StatusView.predicate : +---A coindition to evaluate if the item should be displayed. If a string +---is given it is treated as a file that returns a valid object which is +---checked against the current active view, the sames applies if a table is +---given. A function can be used instead to perform a custom evaluation. +---@param itemscb StatusView.itemscb : +---This function should return two tables of StatusView.styledtext elements +---for both left and right, empty tables are allowed. +---@param pos? integer : +---The position in which to insert the given item on the internal table, +---a value of -1 inserts the item at the end which is the default. A value +---of 1 will insert the item at the beggining. +---@param onclick? StatusView.clickcb Executed when user clicks the item +---@param tooltip? string Displayed when mouse hovers the item +function StatusView:add_item(predicate, itemscb, pos, onclick, tooltip) + predicate = predicate or always_true + if type(predicate) == "string" then + predicate = require(predicate) + end + if type(predicate) == "table" then + local class = predicate + predicate = function() return core.active_view:is(class) end + end + ---@type StatusView.item + local item = { + predicate = predicate, + items = itemscb, + onclick = onclick, + tooltip = tooltip + } + pos = type(pos) == "nil" and -1 or math.abs(tonumber(pos)) + if pos == -1 then + table.insert(self.items, item) + else + table.insert(self.items, pos, item) end - return true end +---Shows a message for a predefined amount of time. +---@param icon string +---@param icon_color renderer.color +---@param text string function StatusView:show_message(icon, icon_color, text) self.message = { icon_color, style.icon_font, icon, @@ -43,30 +121,27 @@ function StatusView:show_message(icon, icon_color, text) end +---Activates tooltip mode displaying only the given +---text until StatusView:remove_tooltip() is called. +---@param text string | StatusView.styledtext function StatusView:show_tooltip(text) - self.tooltip = { text } + self.tooltip = type(text) == "table" and text or { text } self.tooltip_mode = true end +---Deactivates tooltip mode. function StatusView:remove_tooltip() self.tooltip_mode = false end -function StatusView:update() - self.size.y = style.font:get_height() + style.padding.y * 2 - - if system.get_time() < self.message_timeout then - self.scroll.to.y = self.size.y - else - self.scroll.to.y = 0 - end - - StatusView.super.update(self) -end - - +---Helper function to draw the styled text. +---@param self StatusView +---@param items StatusView.styledtext +---@param x number +---@param y number +---@param draw_fn fun(font,color,text,align, x,y,w,h):number local function draw_items(self, items, x, y, draw_fn) local font = style.font local color = style.text @@ -85,11 +160,20 @@ local function draw_items(self, items, x, y, draw_fn) end +---Helper function to calculate the width of text by using it as part of +---the helper function draw_items(). +---@param font renderer.font +---@param text string +---@param x number local function text_width(font, _, text, _, x) return x + font:get_width(text) end +---Draws a table of styled text on the status bar starting on the left or right. +---@param items StatusView.styledtext +---@param right_align boolean +---@param yoffset number function StatusView:draw_items(items, right_align, yoffset) local x, y = self:get_content_offset() y = y + (yoffset or 0) @@ -104,38 +188,78 @@ function StatusView:draw_items(items, right_align, yoffset) end -function StatusView:get_items() - if getmetatable(core.active_view) == DocView then - local dv = core.active_view - local line, col = dv.doc:get_selection() - local dirty = dv.doc:is_dirty() - local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() - local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " - local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" +---Draw the tooltip of a given status bar item. +---@param item StatusView.item +function StatusView:draw_item_tooltip(item) + core.root_view:defer_draw(function() + local text = item.tooltip + local w = style.font:get_width(text) + local h = style.font:get_height() + local x = self.pointer.x - (w / 2) - (style.padding.x * 2) - return { - dirty and style.accent or style.text, style.icon_font, "f", - style.dim, style.font, self.separator2, style.text, - dv.doc.filename and style.text or style.dim, dv.doc:get_name(), - style.text, - self.separator, - "line: ", line, - self.separator, - col > config.line_limit and style.accent or style.text, "col: ", col, - style.text, - self.separator, - string.format("%.f%%", line / #dv.doc.lines * 100), - }, { - style.text, indent_label, indent_size, - style.dim, self.separator2, style.text, - style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", - self.separator, - dv.doc.crlf and "CRLF" or "LF" - } - end + if x < 0 then x = 0 end + if x + w + (style.padding.x * 2) > self.size.x then + x = self.size.x - w - (style.padding.x * 2) + end + renderer.draw_rect( + x, + self.position.y - h - (style.padding.y * 2), + w + (style.padding.x * 2), + h + (style.padding.y * 2), + style.background3 + ) + + renderer.draw_text( + style.font, + text, + x + style.padding.x, + self.position.y - h - style.padding.y, + style.text + ) + end) +end + + +---The predefined status bar items displayed when a document view is active. +---@return table left +---@return table right +function StatusView:get_doc_items() + local dv = core.active_view + local line, col = dv.doc:get_selection() + local dirty = dv.doc:is_dirty() + local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() + local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " + local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" + + return { + dirty and style.accent or style.text, style.icon_font, "f", + style.dim, style.font, self.separator2, style.text, + dv.doc.filename and style.text or style.dim, dv.doc:get_name(), + style.text, + self.separator, + "line: ", line, + self.separator, + col > config.line_limit and style.accent or style.text, "col: ", col, + style.text, + self.separator, + string.format("%.f%%", line / #dv.doc.lines * 100), + }, { + style.text, indent_label, indent_size, + style.dim, self.separator2, style.text, + style.icon_font, "g", + style.font, style.dim, self.separator2, style.text, + #dv.doc.lines, " lines", + self.separator, + dv.doc.crlf and "CRLF" or "LF" + } +end + + +---The predefined status bar items displayed when a command view is active. +---@return table left +---@return table right +function StatusView:get_command_items() return {}, { style.icon_font, "g", style.font, style.dim, self.separator2, @@ -145,6 +269,175 @@ function StatusView:get_items() end +---Helper function to copy a styled text table into another. +---@param t1 StatusView.styledtext +---@param t2 StatusView.styledtext +local function table_add(t1, t2) + for i, value in ipairs(t2) do + table.insert(t1, value) + end +end + + +---Get the styled text that will be displayed on the left side or +---right side of the status bar checking their predicates and performing +---positioning calculations for proper functioning of tooltips and clicks. +---@return StatusView.styledtext left +---@return StatusView.styledtext right +function StatusView:get_items_from_list() + local left, right = {}, {} + + local x = self:get_content_offset() + + local rx = x + self.size.x + local lx = x + style.padding.x + local rw, lw = 0, 0 + + ---@class items + ---@field item StatusView.item + local items = {} + + -- calculate left and right width + for _, item in ipairs(self.items) do + if item.predicate(self) then + item.active = true + + local litems, ritems = item.items(self) + + if #litems > 0 then + item.lw = draw_items(self, litems, 0, 0, text_width) + item.lx = lx + lw = lw + item.lw + lx = lx + item.lw + end + + if #ritems > 0 then + item.rw = draw_items(self, ritems, 0, 0, text_width) + item.rx = rx + rw = rw + item.rw + rx = rx + item.rw + end + + if #litems > 0 or #ritems > 0 then + table.insert(items, { + item = item, + left = litems, + right = ritems + }) + end + else + item.active = false + end + end + + -- load deprecated items for compatibility + local dleft, dright = self:get_items(true) + if #dright > 0 then + rw = rw + draw_items(self, dright, 0, 0, text_width) + end + + rw = rw < (self.size.x / 2) and rw or self.size.x / 2 + + for _, item in ipairs(items) do + if #item.left > 0 then + table_add(left, item.left) + end + if #item.right > 0 then + -- re-calculate x position now that we have the total width + item.item.rx = item.item.rx - rw - style.padding.x + table_add(right, item.right) + end + end + + table_add(left, dleft) + table_add(right, dright) + + return left, right +end + + +---Older method of retrieving the status bar items and which is now +---deprecated in favour of core.status_view:add_item(). +---@deprecated +---@param nowarn boolean +---@return table left +---@return table right +function StatusView:get_items(nowarn) + if not nowarn and not self.get_items_warn then + core.error( + "Overriding StatusView:get_items() is deprecated, " + .. "use core.status_view:add_item() instead." + ) + self.get_items_warn = true + end + return {}, {} +end + + +function StatusView:on_mouse_pressed() + core.set_active_view(core.last_active_view) + if system.get_time() < self.message_timeout + and not core.active_view:is(LogView) then + command.perform "core:open-log" + end + return true +end + + +function StatusView:on_mouse_moved(x, y, dx, dy) + StatusView.super.on_mouse_moved(self, x, y, dx, dy) + + if y < self.position.y then self.hovered_item = {} return end + + for _, item in ipairs(self.items) do + if item.onclick and item.active then + if + (item.lx and x > item.lx and (item.lx + item.lw) > x) + or + (item.rx and x > item.rx and (item.rx + item.rw) > x) + then + self.pointer.x = x + self.pointer.y = y + if self.hovered_item ~= item then + self.hovered_item = item + end + return + end + end + end + self.hovered_item = {} +end + + +function StatusView:on_mouse_released(button, x, y) + StatusView.super.on_mouse_released(self, button, x, y) + + if y < self.position.y or not self.hovered_item.onclick then return end + + local item = self.hovered_item + if + (item.lx and x > item.lx and (item.lx + item.lw) > x) + or + (item.rx and x > item.rx and (item.rx + item.rw) > x) + then + self.hovered_item.onclick(button, x, y) + end +end + + +function StatusView:update() + self.size.y = style.font:get_height() + style.padding.y * 2 + + if system.get_time() < self.message_timeout then + self.scroll.to.y = self.size.y + else + self.scroll.to.y = 0 + end + + StatusView.super.update(self) +end + + function StatusView:draw() self:draw_background(style.background2) @@ -155,9 +448,12 @@ function StatusView:draw() if self.tooltip_mode then self:draw_items(self.tooltip) else - local left, right = self:get_items() + local left, right = self:get_items_from_list() self:draw_items(left) self:draw_items(right, true) + if self.hovered_item.tooltip then + self:draw_item_tooltip(self.hovered_item) + end end end From 90983b22a4770fc6bfd5e52e6dc9459d73b41f28 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 05:15:14 -0400 Subject: [PATCH 135/409] StatusView v2.0, some changes include: * Items are now objects that can be retrieved and manipulated. * clip rect is used for left and right panes * initial support for items to do their own custom drawing by also doing a clip rect for them * a custom background color can be specified for the item. * a command or function can be executed on item click. * Introduced functionality to easily hide or show all or specific items. * Better handling of deprecated get_items() * Spacing is automatically added to items and cleaned on deprecated items. * Default items where separated and given the names: doc:file, doc:position, doc:indentation, doc:lines, doc:line-ending, core.commandview. * Some default right or left click actions where given to the default items. * Started adding required bits to support dragging to left and right panes when some items aren't visible. Note: should work well enough already but maybe some repetitive stuff can be cleaned out. --- data/core/statusview.lua | 827 +++++++++++++++++++++++++++++---------- 1 file changed, 614 insertions(+), 213 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 02b089bb..395dbe0a 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -9,33 +9,28 @@ local LogView = require "core.logview" local View = require "core.view" local Object = require "core.object" + ---@alias StatusView.styledtext table ----@alias StatusView.itemscb fun():StatusView.styledtext,StatusView.styledtext ----@alias StatusView.clickcb fun(button: string, x: number, y: number) ----@alias StatusView.predicate fun():boolean ---A status bar implementation for lite, check core.status_view. ---@class StatusView : View ----@field private items StatusView.item[] ----@field private hovered_item StatusView.item +---@field private items StatusView.Item[] +---@field private active_items StatusView.Item[] +---@field private hovered_item StatusView.Item ---@field private message_timeout number ---@field private message StatusView.styledtext ---@field private tooltip_mode boolean ---@field private tooltip StatusView.styledtext +---@field private left_width number +---@field private right_width number +---@field private r_left_width number +---@field private r_right_width number +---@field private left_xoffset number +---@field private right_xoffset number +---@field private dragged_panel '"left"' | '"right"' +---@field private hide_messages boolean local StatusView = View:extend() ----@class StatusView.item ----@field predicate StatusView.predicate ----@field items StatusView.itemscb ----@field onclick StatusView.clickcb ----@field tooltip string | nil ----@field lx number ----@field lw number ----@field rx number ----@field rw number ----@field active boolean -StatusView.item = {} - ---Space separator ---@type string StatusView.separator = " " @@ -44,6 +39,107 @@ StatusView.separator = " " ---@type string StatusView.separator2 = " | " +---@alias StatusView.Item.separator +---|>'StatusView.separator' # Space separator +---| 'StatusView.separator2' # Pipe separator + +---@alias StatusView.Item.predicate fun():boolean +---@alias StatusView.Item.onclick fun(button: string, x: number, y: number) +---@alias StatusView.Item.getitem fun():StatusView.styledtext,StatusView.styledtext +---@alias StatusView.Item.ondraw fun(x, y, h, calc_only: boolean):number + +---@class StatusView.Item : Object +---@field name string +---@field predicate StatusView.Item.predicate +---@field alignment StatusView.Item.alignment +---@field tooltip string | nil +---@field command string | nil @Command to perform when the item is clicked. +---@field on_click StatusView.Item.onclick | nil @Function called when item is clicked and no command is set. +---@field on_draw StatusView.Item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. +---@field background_color renderer.color | nil +---@field visible boolean +---@field separator StatusView.Item.separator +---@field private active boolean +---@field private x number +---@field private w number +---@field private deprecated boolean +---@field private cached_item StatusView.styledtext +StatusView.Item = Object:extend() + +---Flag to tell the item should me aligned on left side of status bar. +---@type number +StatusView.Item.LEFT = 1 + +---Flag to tell the item should me aligned on right side of status bar. +---@type number +StatusView.Item.RIGHT = 2 + +---@alias StatusView.Item.alignment +---|>'StatusView.Item.LEFT' +---| 'StatusView.Item.RIGHT' + +---Constructor +---@param predicate string | table | StatusView.Item.predicate +---@param name string +---@param alignment StatusView.Item.alignment +---@param command string | StatusView.Item.onclick +---@param tooltip? string | nil +function StatusView.Item:new(predicate, name, alignment, command, tooltip) + self:set_predicate(predicate) + self.name = name + self.alignment = alignment or StatusView.Item.LEFT + self.command = type(command) == "string" and command or nil + self.tooltip = tooltip or "" + self.on_click = type(command) == "function" and command or nil + self.on_draw = nil + self.background_color = nil + self.visible = true + self.active = false + self.x = 0 + self.w = 0 + self.separator = StatusView.separator +end + +---Called by the status bar each time that the item needs to be rendered, +---if on_draw() is set this function is obviated. +---@return StatusView.styledtext +function StatusView.Item:get_item() return {} end + +---Do not show the item on the status bar. +function StatusView.Item:hide() self.visible = false end + +---Show the item on the status bar. +function StatusView.Item:show() self.visible = true end + +---Function assiged by default when user provides a nil predicate. +local function predicate_always_true() return true end + +---A condition to evaluate if the item should be displayed. If a string +---is given it is treated as a require import that should return a valid object +---which is checked against the current active view, the sames applies if a +---table is given. A function that returns a boolean can be used instead to +---perform a custom evaluation, setting to nil means always evaluates to true. +---@param predicate string | table | StatusView.Item.predicate +function StatusView.Item:set_predicate(predicate) + predicate = predicate or predicate_always_true + if type(predicate) == "string" then + predicate = require(predicate) + end + if type(predicate) == "table" then + local class = predicate + predicate = function() return core.active_view:is(class) end + end + self.predicate = predicate +end + + +---Predicated used on the default docview widgets. +---@return boolean +local function predicate_docview() + return core.active_view:is(DocView) + and not core.active_view:is(CommandView) +end + ---Constructor function StatusView:new() @@ -53,57 +149,228 @@ function StatusView:new() self.tooltip_mode = false self.tooltip = {} self.items = {} + self.active_items = {} self.hovered_item = {} self.pointer = {x = 0, y = 0} + self.left_width = 0 + self.right_width = 0 + self.r_left_width = 0 + self.r_right_width = 0 + self.left_xoffset = 0 + self.right_xoffset = 0 + self.dragged_panel = "" + self.hide_messages = false + + self:register_docview_items() + self:register_command_items() +end + +---The predefined status bar items displayed when a document view is active. +function StatusView:register_docview_items() + if self:get_item("doc:file") then return end self:add_item( + predicate_docview, + "doc:file", + StatusView.Item.LEFT, function() - return core.active_view:is(DocView) and - not core.active_view:is(CommandView) - end, - self.get_doc_items + local dv = core.active_view + return { + dv.doc:is_dirty() and style.accent or style.text, style.icon_font, "f", + style.dim, style.font, self.separator2, style.text, + dv.doc.filename and style.text or style.dim, dv.doc:get_name() + } + end ) - self:add_item("core.commandview", self.get_command_items) + self:add_item( + predicate_docview, + "doc:position", + StatusView.Item.LEFT, + function() + local dv = core.active_view + local line, col = dv.doc:get_selection() + return { + line, + ":", + col > config.line_limit and style.accent or style.text, col, + style.text, + self.separator, + string.format("%.f%%", line / #dv.doc.lines * 100) + } + end, + "doc:go-to-line" + ).tooltip = "line : column" + + self:add_item( + predicate_docview, + "doc:indentation", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() + local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " + return { + style.text, indent_label, indent_size + } + end, + function(button, x, y) + if button == "left" then + command.perform "indent:set-file-indent-size" + elseif button == "right" then + command.perform "indent:set-file-indent-type" + end + end + ).separator = self.separator2 + + self:add_item( + predicate_docview, + "doc:lines", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + return { + style.text, + style.icon_font, "g", + style.font, style.dim, self.separator2, style.text, + #dv.doc.lines, " lines", + } + end + ).separator = self.separator2 + + self:add_item( + predicate_docview, + "doc:line-ending", + StatusView.Item.RIGHT, + function() + local dv = core.active_view + return { + dv.doc.crlf and "CRLF" or "LF" + } + end, + "doc:toggle-line-ending" + ) +end + + +---The predefined status bar items displayed when a command view is active. +function StatusView:register_command_items() + if self:get_item("command:files") then return end + + self:add_item( + "core.commandview", + "command:files", + StatusView.Item.RIGHT, + function() + return { + style.icon_font, "g", + style.font, style.dim, self.separator2, + #core.docs, style.text, " / ", + #core.project_files, " files" + } + end + ) end ---Adds an item to be rendered in the status bar. ----@param predicate string | table | StatusView.predicate : ----A coindition to evaluate if the item should be displayed. If a string ----is given it is treated as a file that returns a valid object which is ----checked against the current active view, the sames applies if a table is ----given. A function can be used instead to perform a custom evaluation. ----@param itemscb StatusView.itemscb : ----This function should return two tables of StatusView.styledtext elements ----for both left and right, empty tables are allowed. +---@param predicate string | table | StatusView.Item.predicate : +---A condition to evaluate if the item should be displayed. If a string +---is given it is treated as a require import that should return a valid object +---which is checked against the current active view, the sames applies if a +---table is given. A function that returns a boolean can be used instead to +---perform a custom evaluation, setting to nil means always evaluates to true. +---@param name string A unique name to identify the item on the status bar. +---@param alignment StatusView.Item.alignment +---@param getitem StatusView.Item.getitem : +---A function that should return a StatusView.styledtext element, +---returning empty table is allowed. +---@param command? string | StatusView.Item.onclick : +---The name of a valid registered command or a callback function to execute +---when the item is clicked. ---@param pos? integer : ---The position in which to insert the given item on the internal table, ---a value of -1 inserts the item at the end which is the default. A value ---of 1 will insert the item at the beggining. ----@param onclick? StatusView.clickcb Executed when user clicks the item ---@param tooltip? string Displayed when mouse hovers the item -function StatusView:add_item(predicate, itemscb, pos, onclick, tooltip) - predicate = predicate or always_true - if type(predicate) == "string" then - predicate = require(predicate) - end - if type(predicate) == "table" then - local class = predicate - predicate = function() return core.active_view:is(class) end - end - ---@type StatusView.item - local item = { - predicate = predicate, - items = itemscb, - onclick = onclick, - tooltip = tooltip - } - pos = type(pos) == "nil" and -1 or math.abs(tonumber(pos)) +---@return StatusView.Item +function StatusView:add_item(predicate, name, alignment, getitem, command, pos, tooltip) + assert(self:get_item(name) == nil, "status item already exists: " .. name) + ---@type StatusView.Item + local item = StatusView.Item(predicate, name, alignment, command, tooltip) + item.get_item = getitem + pos = type(pos) == "nil" and -1 or tonumber(pos) if pos == -1 then table.insert(self.items, item) else - table.insert(self.items, pos, item) + table.insert(self.items, math.abs(pos), item) + end + return item +end + + +---Get an item associated object or nil if not found. +---@param name string +---@return StatusView.Item | nil +function StatusView:get_item(name) + for _, item in ipairs(self.items) do + if item.name == name then return item end + end + return nil +end + + +---Get a list of items. +---@param alignment? StatusView.Item.alignment +---@return StatusView.Item[] +function StatusView:get_items_list(alignment) + if alignment then + local items = {} + for _, item in ipairs(self.items) do + if item.alignment == alignment then + table.insert(items, item) + end + end + return items + end + return self.items +end + + +---Hides the given items from the status view or all if no names given. +---@param names table | string | nil +function StatusView:hide_items(names) + if type(names) == "string" then + names = {names} + end + if not names then + for _, item in ipairs(self.items) do + item:hide() + end + return + end + for _, name in ipairs(names) do + local item = self:get_item(name) + if item then item:hide() end + end +end + + +---Shows the given items from the status view or all if no names given. +---@param names table | string | nil +function StatusView:show_items(names) + if type(names) == "string" then + names = {names} + end + if not names then + for _, item in ipairs(self.items) do + item:show() + end + return + end + for _, name in ipairs(names) do + local item = self:get_item(name) + if item then item:show() end end end @@ -113,6 +380,7 @@ end ---@param icon_color renderer.color ---@param text string function StatusView:show_message(icon, icon_color, text) + if self.hide_messages then return end self.message = { icon_color, style.icon_font, icon, style.dim, style.font, StatusView.separator2, style.text, text @@ -121,6 +389,13 @@ function StatusView:show_message(icon, icon_color, text) end +---Enable or disable system wide messages on the status bar. +---@param enable boolean +function StatusView:display_messages(enable) + self.hide_messages = not enable +end + + ---Activates tooltip mode displaying only the given ---text until StatusView:remove_tooltip() is called. ---@param text string | StatusView.styledtext @@ -174,8 +449,9 @@ end ---@param items StatusView.styledtext ---@param right_align boolean ---@param yoffset number -function StatusView:draw_items(items, right_align, yoffset) +function StatusView:draw_items(items, right_align, xoffset, yoffset) local x, y = self:get_content_offset() + x = x + (xoffset or 0) y = y + (yoffset or 0) if right_align then local w = draw_items(self, items, 0, 0, text_width) @@ -189,7 +465,7 @@ end ---Draw the tooltip of a given status bar item. ----@param item StatusView.item +---@param item StatusView.Item function StatusView:draw_item_tooltip(item) core.root_view:defer_draw(function() local text = item.tooltip @@ -203,7 +479,7 @@ function StatusView:draw_item_tooltip(item) end renderer.draw_rect( - x, + x + style.padding.x, self.position.y - h - (style.padding.y * 2), w + (style.padding.x * 2), h + (style.padding.y * 2), @@ -213,7 +489,7 @@ function StatusView:draw_item_tooltip(item) renderer.draw_text( style.font, text, - x + style.padding.x, + x + (style.padding.x * 2), self.position.y - h - style.padding.y, style.text ) @@ -221,141 +497,6 @@ function StatusView:draw_item_tooltip(item) end ----The predefined status bar items displayed when a document view is active. ----@return table left ----@return table right -function StatusView:get_doc_items() - local dv = core.active_view - local line, col = dv.doc:get_selection() - local dirty = dv.doc:is_dirty() - local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() - local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " - local indent_size_str = tostring(indent_size) .. (indent_confirmed and "" or "*") or "unknown" - - return { - dirty and style.accent or style.text, style.icon_font, "f", - style.dim, style.font, self.separator2, style.text, - dv.doc.filename and style.text or style.dim, dv.doc:get_name(), - style.text, - self.separator, - "line: ", line, - self.separator, - col > config.line_limit and style.accent or style.text, "col: ", col, - style.text, - self.separator, - string.format("%.f%%", line / #dv.doc.lines * 100), - }, { - style.text, indent_label, indent_size, - style.dim, self.separator2, style.text, - style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", - self.separator, - dv.doc.crlf and "CRLF" or "LF" - } -end - - ----The predefined status bar items displayed when a command view is active. ----@return table left ----@return table right -function StatusView:get_command_items() - return {}, { - style.icon_font, "g", - style.font, style.dim, self.separator2, - #core.docs, style.text, " / ", - #core.project_files, " files" - } -end - - ----Helper function to copy a styled text table into another. ----@param t1 StatusView.styledtext ----@param t2 StatusView.styledtext -local function table_add(t1, t2) - for i, value in ipairs(t2) do - table.insert(t1, value) - end -end - - ----Get the styled text that will be displayed on the left side or ----right side of the status bar checking their predicates and performing ----positioning calculations for proper functioning of tooltips and clicks. ----@return StatusView.styledtext left ----@return StatusView.styledtext right -function StatusView:get_items_from_list() - local left, right = {}, {} - - local x = self:get_content_offset() - - local rx = x + self.size.x - local lx = x + style.padding.x - local rw, lw = 0, 0 - - ---@class items - ---@field item StatusView.item - local items = {} - - -- calculate left and right width - for _, item in ipairs(self.items) do - if item.predicate(self) then - item.active = true - - local litems, ritems = item.items(self) - - if #litems > 0 then - item.lw = draw_items(self, litems, 0, 0, text_width) - item.lx = lx - lw = lw + item.lw - lx = lx + item.lw - end - - if #ritems > 0 then - item.rw = draw_items(self, ritems, 0, 0, text_width) - item.rx = rx - rw = rw + item.rw - rx = rx + item.rw - end - - if #litems > 0 or #ritems > 0 then - table.insert(items, { - item = item, - left = litems, - right = ritems - }) - end - else - item.active = false - end - end - - -- load deprecated items for compatibility - local dleft, dright = self:get_items(true) - if #dright > 0 then - rw = rw + draw_items(self, dright, 0, 0, text_width) - end - - rw = rw < (self.size.x / 2) and rw or self.size.x / 2 - - for _, item in ipairs(items) do - if #item.left > 0 then - table_add(left, item.left) - end - if #item.right > 0 then - -- re-calculate x position now that we have the total width - item.item.rx = item.item.rx - rw - style.padding.x - table_add(right, item.right) - end - end - - table_add(left, dleft) - table_add(right, dright) - - return left, right -end - - ---Older method of retrieving the status bar items and which is now ---deprecated in favour of core.status_view:add_item(). ---@deprecated @@ -370,7 +511,210 @@ function StatusView:get_items(nowarn) ) self.get_items_warn = true end - return {}, {} + return {"{:dummy:}"}, {"{:dummy:}"} +end + + +---Helper function to copy a styled text table into another. +---@param t1 StatusView.styledtext +---@param t2 StatusView.styledtext +local function table_add(t1, t2) + for i, value in ipairs(t2) do + table.insert(t1, value) + end +end + + +---Helper function to merge deprecated items to a temp items table. +---@param destination table +---@param items StatusView.styledtext +---@param alignment StatusView.Item.alignment +local function merge_deprecated_items(destination, items, alignment) + local start = true + local items_start, items_end = {}, {} + for i, value in ipairs(items) do + if value ~= "{:dummy:}" then + if start then + table.insert(items_start, i, value) + else + table.insert(items_end, value) + end + else + start = false + end + end + + local position = alignment == StatusView.Item.LEFT and "left" or "right" + + local item_start = StatusView.Item( + predicate_always_true, + "deprecated:"..position.."-start", + alignment + ) + item_start.get_item = items_start + + local item_end = StatusView.Item( + predicate_always_true, + "deprecated:"..position.."-end", + alignment + ) + item_end.get_item = items_end + + table.insert(destination, 1, item_start) + table.insert(destination, item_end) +end + + +---@param self StatusView +---@param styled_text StatusView.styledtext +---@param separator string +local function add_spacing(self, styled_text, separator) + if + Object.is(styled_text[1], renderer.font) + or + ( + styled_text[2] ~= self.separator + or + styled_text[2] ~= self.separator2 + ) + then + if separator == self.separator2 then + table.insert(styled_text, 1, style.dim) + else + table.insert(styled_text, 1, style.text) + end + table.insert(styled_text, 2, separator) + end +end + + +---@param self StatusView +---@param styled_text StatusView.styledtext +local function remove_spacing(self, styled_text) + if + not Object.is(styled_text[1], renderer.font) + and + type(styled_text[1]) == "table" + and + ( + styled_text[2] == self.separator + or + styled_text[2] == self.separator2 + ) + then + table.remove(styled_text, 1) + table.remove(styled_text, 1) + end + + if + not Object.is(styled_text[#styled_text-1], renderer.font) + and + type(styled_text[#styled_text-1]) == "table" + and + ( + styled_text[#styled_text] == self.separator + or + styled_text[#styled_text] == self.separator2 + ) + then + table.remove(styled_text, #styled_text) + table.remove(styled_text, #styled_text) + end +end + + +---Get the styled text that will be displayed on the left side or +---right side of the status bar checking their predicates and performing +---positioning calculations for proper functioning of tooltips and clicks. +---@return StatusView.styledtext left +---@return StatusView.styledtext right +function StatusView:update_active_items() + local left, right = {}, {} + + local x = self:get_content_offset() + + local rx = x + self.size.x + local lx = x + local rw, lw = 0, 0 + + self.active_items = {} + + ---@type StatusView.Item[] + local combined_items = {} + table_add(combined_items, self.items) + + -- load deprecated items for compatibility + local dleft, dright = self:get_items(true) + merge_deprecated_items(combined_items, dleft, StatusView.Item.LEFT) + merge_deprecated_items(combined_items, dright, StatusView.Item.RIGHT) + + local lfirst, rfirst = true, true + + -- calculate left and right width + for _, item in ipairs(combined_items) do + item.cached_item = {} + if item.visible and item.predicate(self) then + local styled_text = type(item.get_item) == "function" + and item.get_item(self) or item.get_item + + if #styled_text > 0 then + remove_spacing(self, styled_text) + end + + if #styled_text > 0 or item.on_draw then + item.active = true + if item.alignment == StatusView.Item.LEFT then + if not lfirst then + add_spacing(self, styled_text, item.separator, true) + else + lfirst = false + end + item.w = item.on_draw and + item.on_draw(lx, self.position.y, self.size.y, true) + or + draw_items(self, styled_text, 0, 0, text_width) + item.x = lx + lw = lw + item.w + lx = lx + item.w + else + if not rfirst then + add_spacing(self, styled_text, item.separator, true) + else + rfirst = false + end + item.w = item.on_draw and + item.on_draw(rx, self.position.y, self.size.y, true) + or + draw_items(self, styled_text, 0, 0, text_width) + item.x = rx + rw = rw + item.w + rx = rx + item.w + end + item.cached_item = styled_text + table.insert(self.active_items, item) + else + item.active = false + end + else + item.active = false + end + end + + self.r_left_width, self.r_right_width = lw, rw + + if lw + rw + (style.padding.x * 2) > self.size.x then + lw = self.size.x / 2 - (style.padding.x * 2) + rw = self.size.x / 2 - (style.padding.x * 2) + end + + self.left_width, self.right_width = lw, rw + + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.RIGHT then + -- re-calculate x position now that we have the total width + item.x = item.x - rw - (style.padding.x * 2) + end + end end @@ -380,6 +724,7 @@ function StatusView:on_mouse_pressed() and not core.active_view:is(LogView) then command.perform "core:open-log" end + self.mouse_pressed = true return true end @@ -387,15 +732,18 @@ end function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) - if y < self.position.y then self.hovered_item = {} return end + if y < self.position.y or system.get_time() <= self.message_timeout then + self.hovered_item = {} return + end for _, item in ipairs(self.items) do - if item.onclick and item.active then - if - (item.lx and x > item.lx and (item.lx + item.lw) > x) - or - (item.rx and x > item.rx and (item.rx + item.rw) > x) - then + if + item.visible and item.active + and + (item.command or item.on_click or item.tooltip ~= "") + then + local item_x = item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then self.pointer.x = x self.pointer.y = y if self.hovered_item ~= item then @@ -412,15 +760,18 @@ end function StatusView:on_mouse_released(button, x, y) StatusView.super.on_mouse_released(self, button, x, y) - if y < self.position.y or not self.hovered_item.onclick then return end + self.mouse_pressed = false + + if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - if - (item.lx and x > item.lx and (item.lx + item.lw) > x) - or - (item.rx and x > item.rx and (item.rx + item.rw) > x) - then - self.hovered_item.onclick(button, x, y) + local item_x = item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then + if item.command then + command.perform(item.command) + elseif item.on_click then + self.hovered_item.on_click(button, x, y) + end end end @@ -435,24 +786,74 @@ function StatusView:update() end StatusView.super.update(self) + + self:update_active_items() end function StatusView:draw() self:draw_background(style.background2) - if self.message then - self:draw_items(self.message, false, self.size.y) - end - - if self.tooltip_mode then + if self.message and system.get_time() <= self.message_timeout then + self:draw_items(self.message, false, 0, self.size.y) + elseif self.tooltip_mode then self:draw_items(self.tooltip) else - local left, right = self:get_items_from_list() - self:draw_items(left) - self:draw_items(right, true) - if self.hovered_item.tooltip then - self:draw_item_tooltip(self.hovered_item) + if #self.active_items > 0 then + --- draw left pane + core.push_clip_rect( + 0, self.position.y, + self.left_width + style.padding.x, self.size.y + ) + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.LEFT then + if type(item.background_color) == "table" then + renderer.draw_rect( + item.x + style.padding.x, self.position.y, + item.w, self.size.y, item.background_color + ) + end + if item.on_draw then + core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) + item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.pop_clip_rect() + else + self:draw_items(item.cached_item, false, item.x) + end + end + end + core.pop_clip_rect() + + --- draw right pane + core.push_clip_rect( + self.size.x - (self.right_width + style.padding.x), self.position.y, + self.right_width + style.padding.x, self.size.y + ) + local rx = 0 + for _, item in ipairs(self.active_items) do + if item.alignment == StatusView.Item.RIGHT then + if type(item.background_color) == "table" then + renderer.draw_rect( + item.x + style.padding.x, self.position.y, + item.w, self.size.y, item.background_color + ) + end + if item.on_draw then + core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) + item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.pop_clip_rect() + else + self:draw_items(item.cached_item, false, item.x) + end + rx = rx + item.w + end + end + core.pop_clip_rect() + + -- draw tooltip + if self.hovered_item.tooltip ~= "" and self.hovered_item.active then + self:draw_item_tooltip(self.hovered_item) + end end end end From 3452d499e50c95fad2db1257fb46782d2f953b1e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 05:56:59 -0400 Subject: [PATCH 136/409] Show hand cursor on clickable elements as suggested by @Guldoman --- data/core/statusview.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 395dbe0a..b51b0ba8 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -733,7 +733,9 @@ function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) if y < self.position.y or system.get_time() <= self.message_timeout then - self.hovered_item = {} return + self.cursor = "arrow" + self.hovered_item = {} + return end for _, item in ipairs(self.items) do @@ -749,10 +751,14 @@ function StatusView:on_mouse_moved(x, y, dx, dy) if self.hovered_item ~= item then self.hovered_item = item end + if item.command or item.on_click then + self.cursor = "hand" + end return end end end + self.cursor = "arrow" self.hovered_item = {} end From c5648e1f02dd6aa97ed719a9940017c539704b94 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 23 Feb 2022 19:25:25 -0400 Subject: [PATCH 137/409] StatusView: implemented dragging and scrolling. --- data/core/statusview.lua | 113 ++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b51b0ba8..b1295ec8 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -28,6 +28,7 @@ local Object = require "core.object" ---@field private left_xoffset number ---@field private right_xoffset number ---@field private dragged_panel '"left"' | '"right"' +---@field private hovered_panel '"left"' | '"right"' ---@field private hide_messages boolean local StatusView = View:extend() @@ -159,6 +160,7 @@ function StatusView:new() self.left_xoffset = 0 self.right_xoffset = 0 self.dragged_panel = "" + self.hovered_panel = "" self.hide_messages = false self:register_docview_items() @@ -705,6 +707,9 @@ function StatusView:update_active_items() if lw + rw + (style.padding.x * 2) > self.size.x then lw = self.size.x / 2 - (style.padding.x * 2) rw = self.size.x / 2 - (style.padding.x * 2) + else + self.left_xoffset = 0 + self.right_xoffset = 0 end self.left_width, self.right_width = lw, rw @@ -718,13 +723,57 @@ function StatusView:update_active_items() end -function StatusView:on_mouse_pressed() - core.set_active_view(core.last_active_view) - if system.get_time() < self.message_timeout - and not core.active_view:is(LogView) then - command.perform "core:open-log" +---Drag the given panel if possible. +---@param panel '"left"' | '"right"' +---@param distance number +function StatusView:drag_panel(panel, dx) + if panel == "left" and self.r_left_width > self.left_width then + local nonvisible_w = self.r_left_width - self.left_width + local new_offset = self.left_xoffset + dx + if new_offset >= 0 - nonvisible_w and new_offset <= 0 then + self.left_xoffset = new_offset + end + elseif panel == "right" and self.r_right_width > self.right_width then + local nonvisible_w = self.r_right_width - self.right_width + local new_offset = self.right_xoffset + dx + if new_offset >= 0 - nonvisible_w and new_offset <= 0 then + self.right_xoffset = new_offset + end + end +end + + +---Return the currently hovered panel or empty string if none. +---@param x number +---@param y number +---@return string +function StatusView:get_hovered_panel(x, y) + if y >= self.position.y and x <= self.left_width + style.padding.x then + return "left" + else + return "right" + end + return "" +end + + +function StatusView:on_mouse_pressed(button, x, y, clicks) + core.set_active_view(core.last_active_view) + if + system.get_time() < self.message_timeout + and + not core.active_view:is(LogView) + then + command.perform "core:open-log" + else + if y >= self.position.y and button == "left" and clicks == 1 then + self.position.dx = x + if self.r_left_width > self.left_width then + self.dragged_panel = self:get_hovered_panel(x, y) + self.cursor = "hand" + end + end end - self.mouse_pressed = true return true end @@ -732,6 +781,13 @@ end function StatusView:on_mouse_moved(x, y, dx, dy) StatusView.super.on_mouse_moved(self, x, y, dx, dy) + self.hovered_panel = self:get_hovered_panel(x, y) + + if self.dragged_panel ~= "" then + self:drag_panel(self.dragged_panel, dx) + return + end + if y < self.position.y or system.get_time() <= self.message_timeout then self.cursor = "arrow" self.hovered_item = {} @@ -744,7 +800,11 @@ function StatusView:on_mouse_moved(x, y, dx, dy) and (item.command or item.on_click or item.tooltip ~= "") then - local item_x = item.x + style.padding.x + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + + local item_x = item_ox + item.x + style.padding.x + if x > item_x and (item_x + item.w) > x then self.pointer.x = x self.pointer.y = y @@ -766,22 +826,35 @@ end function StatusView:on_mouse_released(button, x, y) StatusView.super.on_mouse_released(self, button, x, y) - self.mouse_pressed = false + if self.dragged_panel ~= "" then + self.dragged_panel = "" + self.cursor = "arrow" + if self.position.dx ~= x then + return + end + end if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - local item_x = item.x + style.padding.x + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + local item_x = item_ox + item.x + style.padding.x if x > item_x and (item_x + item.w) > x then if item.command then command.perform(item.command) elseif item.on_click then - self.hovered_item.on_click(button, x, y) + item.on_click(button, x, y) end end end +function StatusView:on_mouse_wheel(y) + self:drag_panel(self.hovered_panel, y * self.left_width / 10) +end + + function StatusView:update() self.size.y = style.font:get_height() + style.padding.y * 2 @@ -812,19 +885,20 @@ function StatusView:draw() self.left_width + style.padding.x, self.size.y ) for _, item in ipairs(self.active_items) do + local item_x = self.left_xoffset + item.x + style.padding.x if item.alignment == StatusView.Item.LEFT then if type(item.background_color) == "table" then renderer.draw_rect( - item.x + style.padding.x, self.position.y, + item_x, self.position.y, item.w, self.size.y, item.background_color ) end if item.on_draw then - core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) - item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y) core.pop_clip_rect() else - self:draw_items(item.cached_item, false, item.x) + self:draw_items(item.cached_item, false, item_x - style.padding.x) end end end @@ -835,23 +909,22 @@ function StatusView:draw() self.size.x - (self.right_width + style.padding.x), self.position.y, self.right_width + style.padding.x, self.size.y ) - local rx = 0 for _, item in ipairs(self.active_items) do + local item_x = self.right_xoffset + item.x + style.padding.x if item.alignment == StatusView.Item.RIGHT then if type(item.background_color) == "table" then renderer.draw_rect( - item.x + style.padding.x, self.position.y, + item_x, self.position.y, item.w, self.size.y, item.background_color ) end if item.on_draw then - core.push_clip_rect(item.x + style.padding.x, self.position.y, item.w, self.size.y) - item.on_draw(item.x + style.padding.x, self.position.y, self.size.y) + core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y) core.pop_clip_rect() else - self:draw_items(item.cached_item, false, item.x) + self:draw_items(item.cached_item, false, item_x - style.padding.x) end - rx = rx + item.w end end core.pop_clip_rect() From b1ce68548923ed24dd76f7f307f386d9b99a178b Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 00:21:21 -0400 Subject: [PATCH 138/409] StatusView: better calculate panel sizes and handle out of bounds drags. --- data/core/statusview.lua | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b1295ec8..629fe35b 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -704,9 +704,20 @@ function StatusView:update_active_items() self.r_left_width, self.r_right_width = lw, rw - if lw + rw + (style.padding.x * 2) > self.size.x then - lw = self.size.x / 2 - (style.padding.x * 2) - rw = self.size.x / 2 - (style.padding.x * 2) + -- try to calc best size for left and right + if lw + rw + (style.padding.x * 4) > self.size.x then + if lw + (style.padding.x * 2) < self.size.x / 2 then + rw = self.size.x - lw - (style.padding.x * 3) + if rw > self.r_right_width then + lw = lw + (rw - self.r_right_width) + rw = self.r_right_width + end + elseif rw + (style.padding.x * 2) < self.size.x / 2 then + lw = self.size.x - rw - (style.padding.x * 3) + else + lw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) + rw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) + end else self.left_xoffset = 0 self.right_xoffset = 0 @@ -725,19 +736,27 @@ end ---Drag the given panel if possible. ---@param panel '"left"' | '"right"' ----@param distance number +---@param dx number function StatusView:drag_panel(panel, dx) if panel == "left" and self.r_left_width > self.left_width then local nonvisible_w = self.r_left_width - self.left_width local new_offset = self.left_xoffset + dx if new_offset >= 0 - nonvisible_w and new_offset <= 0 then self.left_xoffset = new_offset + elseif dx < 0 then + self.left_xoffset = 0 - nonvisible_w + else + self.left_xoffset = 0 end elseif panel == "right" and self.r_right_width > self.right_width then local nonvisible_w = self.r_right_width - self.right_width local new_offset = self.right_xoffset + dx if new_offset >= 0 - nonvisible_w and new_offset <= 0 then self.right_xoffset = new_offset + elseif dx < 0 then + self.right_xoffset = 0 - nonvisible_w + else + self.right_xoffset = 0 end end end From 131bdf9fb2bf8aec92b95fd44dbcb96b401ebc03 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 02:23:29 -0400 Subject: [PATCH 139/409] StatusView: implemented and applied get_item_visible_area. --- data/core/statusview.lua | 49 ++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 629fe35b..cb9673a1 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -776,6 +776,41 @@ function StatusView:get_hovered_panel(x, y) end +---@param item StatusView.Item +---@return number x +---@return number w +function StatusView:get_item_visible_area(item) + local item_ox = item.alignment == StatusView.Item.LEFT and + self.left_xoffset or self.right_xoffset + + local item_x = item_ox + item.x + style.padding.x + local item_w = item.w + + if item.alignment == StatusView.Item.LEFT then + if self.left_width - item_x > 0 and self.left_width - item_x < item.w then + item_w = (self.left_width + style.padding.x) - item_x + elseif self.left_width - item_x < 0 then + item_x = 0 + item_w = 0 + end + else + local rx = self.size.x - self.right_width - style.padding.x + if item_x < rx then + if item_x + item.w > rx then + item_x = rx + item_w = (item_x + item.w) - rx + else + item_x = 0 + item_w = 0 + end + end + end + + return item_x, item_w +end + + + function StatusView:on_mouse_pressed(button, x, y, clicks) core.set_active_view(core.last_active_view) if @@ -819,12 +854,9 @@ function StatusView:on_mouse_moved(x, y, dx, dy) and (item.command or item.on_click or item.tooltip ~= "") then - local item_ox = item.alignment == StatusView.Item.LEFT and - self.left_xoffset or self.right_xoffset + local item_x, item_w = self:get_item_visible_area(item) - local item_x = item_ox + item.x + style.padding.x - - if x > item_x and (item_x + item.w) > x then + if x > item_x and (item_x + item_w) > x then self.pointer.x = x self.pointer.y = y if self.hovered_item ~= item then @@ -856,10 +888,9 @@ function StatusView:on_mouse_released(button, x, y) if y < self.position.y or not self.hovered_item.active then return end local item = self.hovered_item - local item_ox = item.alignment == StatusView.Item.LEFT and - self.left_xoffset or self.right_xoffset - local item_x = item_ox + item.x + style.padding.x - if x > item_x and (item_x + item.w) > x then + local item_x, item_w = self:get_item_visible_area(item) + + if x > item_x and (item_x + item_w) > x then if item.command then command.perform(item.command) elseif item.on_click then From 128e10ba4733fa1affc15dd5428dbe04eef4c3e4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 02:57:39 -0400 Subject: [PATCH 140/409] StatusView: add spaces as separate items instead of pre-pending them to items. --- data/core/statusview.lua | 74 ++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index cb9673a1..0f793be6 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -63,7 +63,6 @@ StatusView.separator2 = " | " ---@field private active boolean ---@field private x number ---@field private w number ----@field private deprecated boolean ---@field private cached_item StatusView.styledtext StatusView.Item = Object:extend() @@ -193,8 +192,7 @@ function StatusView:register_docview_items() local dv = core.active_view local line, col = dv.doc:get_selection() return { - line, - ":", + style.text, line, ":", col > config.line_limit and style.accent or style.text, col, style.text, self.separator, @@ -234,8 +232,8 @@ function StatusView:register_docview_items() return { style.text, style.icon_font, "g", - style.font, style.dim, self.separator2, style.text, - #dv.doc.lines, " lines", + style.font, style.dim, self.separator2, + style.text, #dv.doc.lines, " lines", } end ).separator = self.separator2 @@ -247,7 +245,7 @@ function StatusView:register_docview_items() function() local dv = core.active_view return { - dv.doc.crlf and "CRLF" or "LF" + style.text, dv.doc.crlf and "CRLF" or "LF" } end, "doc:toggle-line-ending" @@ -267,7 +265,7 @@ function StatusView:register_command_items() return { style.icon_font, "g", style.font, style.dim, self.separator2, - #core.docs, style.text, " / ", + style.text, #core.docs, style.text, " / ", #core.project_files, " files" } end @@ -311,7 +309,7 @@ function StatusView:add_item(predicate, name, alignment, getitem, command, pos, end ----Get an item associated object or nil if not found. +---Get an item object associated to a name or nil if not found. ---@param name string ---@return StatusView.Item | nil function StatusView:get_item(name) @@ -450,7 +448,8 @@ end ---Draws a table of styled text on the status bar starting on the left or right. ---@param items StatusView.styledtext ---@param right_align boolean ----@param yoffset number +---@param xoffset? number +---@param yoffset? number function StatusView:draw_items(items, right_align, xoffset, yoffset) local x, y = self:get_content_offset() x = x + (xoffset or 0) @@ -567,29 +566,30 @@ local function merge_deprecated_items(destination, items, alignment) end +---Append a space item into the given items list. ---@param self StatusView ----@param styled_text StatusView.styledtext +---@param destination StatusView.Item[] ---@param separator string -local function add_spacing(self, styled_text, separator) - if - Object.is(styled_text[1], renderer.font) - or - ( - styled_text[2] ~= self.separator - or - styled_text[2] ~= self.separator2 - ) - then - if separator == self.separator2 then - table.insert(styled_text, 1, style.dim) - else - table.insert(styled_text, 1, style.text) - end - table.insert(styled_text, 2, separator) - end +---@param alignment StatusView.Item.alignment +---@return StatusView.Item +local function add_spacing(self, destination, separator, alignment, x) + ---@type StatusView.Item + local space = StatusView.Item(nil, "space", alignment) + space.cached_item = separator == self.separator and { + style.text, separator + } or { + style.dim, separator + } + space.x = x + space.w = draw_items(self, space.cached_item, 0, 0, text_width) + + table.insert(destination, space) + + return space end +---Remove starting and ending separators. ---@param self StatusView ---@param styled_text StatusView.styledtext local function remove_spacing(self, styled_text) @@ -625,11 +625,9 @@ local function remove_spacing(self, styled_text) end ----Get the styled text that will be displayed on the left side or ----right side of the status bar checking their predicates and performing ----positioning calculations for proper functioning of tooltips and clicks. ----@return StatusView.styledtext left ----@return StatusView.styledtext right +---Set the active items that will be displayed on the left or right side +---of the status bar checking their predicates and performing positioning +---calculations for proper functioning of tooltips and clicks. function StatusView:update_active_items() local left, right = {}, {} @@ -667,7 +665,11 @@ function StatusView:update_active_items() item.active = true if item.alignment == StatusView.Item.LEFT then if not lfirst then - add_spacing(self, styled_text, item.separator, true) + local space = add_spacing( + self, self.active_items, item.separator, item.alignment, lx + ) + lw = lw + space.w + lx = lx + space.w else lfirst = false end @@ -680,7 +682,11 @@ function StatusView:update_active_items() lx = lx + item.w else if not rfirst then - add_spacing(self, styled_text, item.separator, true) + local space = add_spacing( + self, self.active_items, item.separator, item.alignment, rx + ) + rw = rw + space.w + rx = rx + space.w else rfirst = false end From 15a31418cad88503754de65e4257420604aae9d3 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Feb 2022 14:22:52 -0400 Subject: [PATCH 141/409] StatusView: restored indent confirmed --- data/core/statusview.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 0f793be6..c40c8924 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -211,7 +211,8 @@ function StatusView:register_docview_items() local indent_type, indent_size, indent_confirmed = dv.doc:get_indent_info() local indent_label = (indent_type == "hard") and "tabs: " or "spaces: " return { - style.text, indent_label, indent_size + style.text, indent_label, indent_size, + indent_confirmed and "" or "*" } end, function(button, x, y) From 4c80aa7a40c04ae4f6ed726e713adf9ca78c2a7e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Feb 2022 17:04:37 -0400 Subject: [PATCH 142/409] StatusView: reposition left and right panel offsets on window resize. --- data/core/statusview.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index c40c8924..fa7352f6 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -725,6 +725,17 @@ function StatusView:update_active_items() lw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) rw = self.size.x / 2 - (style.padding.x + style.padding.x / 2) end + -- reposition left and right offsets when window is resized + if rw >= self.r_right_width then + self.right_xoffset = 0 + elseif rw > self.right_xoffset + self.r_right_width then + self.right_xoffset = rw - self.r_right_width + end + if lw >= self.r_left_width then + self.left_xoffset = 0 + elseif lw > self.left_xoffset + self.r_left_width then + self.left_xoffset = lw - self.r_left_width + end else self.left_xoffset = 0 self.right_xoffset = 0 From e9775ced78d534e1af7ec0f8401da6fbbcadde8d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Feb 2022 17:56:17 -0400 Subject: [PATCH 143/409] StatusView: added items hovered option for bg color and param to on_draw as suggested by @Guldoman --- data/core/statusview.lua | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index fa7352f6..85ea6a2b 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -47,7 +47,7 @@ StatusView.separator2 = " | " ---@alias StatusView.Item.predicate fun():boolean ---@alias StatusView.Item.onclick fun(button: string, x: number, y: number) ---@alias StatusView.Item.getitem fun():StatusView.styledtext,StatusView.styledtext ----@alias StatusView.Item.ondraw fun(x, y, h, calc_only: boolean):number +---@alias StatusView.Item.ondraw fun(x, y, h, hovered: boolean, calc_only: boolean):number ---@class StatusView.Item : Object ---@field name string @@ -58,6 +58,7 @@ StatusView.separator2 = " | " ---@field on_click StatusView.Item.onclick | nil @Function called when item is clicked and no command is set. ---@field on_draw StatusView.Item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. ---@field background_color renderer.color | nil +---@field background_color_hover renderer.color | nil ---@field visible boolean ---@field separator StatusView.Item.separator ---@field private active boolean @@ -93,6 +94,7 @@ function StatusView.Item:new(predicate, name, alignment, command, tooltip) self.on_click = type(command) == "function" and command or nil self.on_draw = nil self.background_color = nil + self.background_color_hover = nil self.visible = true self.active = false self.x = 0 @@ -664,6 +666,7 @@ function StatusView:update_active_items() if #styled_text > 0 or item.on_draw then item.active = true + local hovered = self.hovered_item == item if item.alignment == StatusView.Item.LEFT then if not lfirst then local space = add_spacing( @@ -675,7 +678,7 @@ function StatusView:update_active_items() lfirst = false end item.w = item.on_draw and - item.on_draw(lx, self.position.y, self.size.y, true) + item.on_draw(lx, self.position.y, self.size.y, hovered, true) or draw_items(self, styled_text, 0, 0, text_width) item.x = lx @@ -692,7 +695,7 @@ function StatusView:update_active_items() rfirst = false end item.w = item.on_draw and - item.on_draw(rx, self.position.y, self.size.y, true) + item.on_draw(rx, self.position.y, self.size.y, hovered, true) or draw_items(self, styled_text, 0, 0, text_width) item.x = rx @@ -938,6 +941,21 @@ function StatusView:update() end +---Retrieve the hover status and proper background color if any. +---@param self StatusView +---@param item StatusView.Item +---@return boolean is_hovered +---@return renderer.color | nil color +local function get_item_bg_color(self, item) + local hovered = self.hovered_item == item + + local item_bg = hovered + and item.background_color_hover or item.background_color + + return hovered, item_bg +end + + function StatusView:draw() self:draw_background(style.background2) @@ -954,16 +972,17 @@ function StatusView:draw() ) for _, item in ipairs(self.active_items) do local item_x = self.left_xoffset + item.x + style.padding.x + local hovered, item_bg = get_item_bg_color(self, item) if item.alignment == StatusView.Item.LEFT then - if type(item.background_color) == "table" then + if type(item_bg) == "table" then renderer.draw_rect( item_x, self.position.y, - item.w, self.size.y, item.background_color + item.w, self.size.y, item_bg ) end if item.on_draw then core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) - item.on_draw(item_x, self.position.y, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y, hovered) core.pop_clip_rect() else self:draw_items(item.cached_item, false, item_x - style.padding.x) @@ -979,16 +998,17 @@ function StatusView:draw() ) for _, item in ipairs(self.active_items) do local item_x = self.right_xoffset + item.x + style.padding.x + local hovered, item_bg = get_item_bg_color(self, item) if item.alignment == StatusView.Item.RIGHT then - if type(item.background_color) == "table" then + if type(item_bg) == "table" then renderer.draw_rect( item_x, self.position.y, - item.w, self.size.y, item.background_color + item.w, self.size.y, item_bg ) end if item.on_draw then core.push_clip_rect(item_x, self.position.y, item.w, self.size.y) - item.on_draw(item_x, self.position.y, self.size.y) + item.on_draw(item_x, self.position.y, self.size.y, hovered) core.pop_clip_rect() else self:draw_items(item.cached_item, false, item_x - style.padding.x) From 3d879286a401e1fc123e8a5c20e7c87b87bc00e1 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 1 Mar 2022 22:41:54 +0100 Subject: [PATCH 144/409] Use epsilon to compare values in `move_towards` --- data/core/view.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/view.lua b/data/core/view.lua index f2c88dc2..a3664a55 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -25,7 +25,8 @@ function View:move_towards(t, k, dest, rate) return self:move_towards(self, t, k, dest, rate) end local val = t[k] - if not config.transitions or math.abs(val - dest) < 0.5 then + local diff = math.abs(val - dest) + if not config.transitions or diff < 0.5 then t[k] = dest else rate = rate or 0.5 @@ -35,7 +36,7 @@ function View:move_towards(t, k, dest, rate) end t[k] = common.lerp(val, dest, rate) end - if val ~= dest then + if diff > 1e-8 then core.redraw = true end end From 05e355d383f1cf01e0e7f8d7ea9c53f6d6796394 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 2 Mar 2022 03:53:04 -0400 Subject: [PATCH 145/409] meson: append git commit if possible and to non release builds --- meson.build | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index aec0cdb1..2f26305d 100644 --- a/meson.build +++ b/meson.build @@ -9,13 +9,35 @@ project('lite-xl', ] ) +#=============================================================================== +# Project version including git commit if possible +#=============================================================================== +version = meson.project_version() + +if get_option('buildtype') != 'release' + git_command = find_program('git', required : false) + + if git_command.found() + git_branch = run_command( + [git_command, 'branch', '--show-current'] + ).stdout().strip() + git_commit = run_command( + [git_command, 'rev-parse', git_branch] + ).stdout().strip() + + if git_commit != '' + version += ' (git-' + git_commit.substring(0, 8) + ')' + endif + endif +endif + #=============================================================================== # Configuration #=============================================================================== conf_data = configuration_data() conf_data.set('PROJECT_BUILD_DIR', meson.current_build_dir()) conf_data.set('PROJECT_SOURCE_DIR', meson.current_source_dir()) -conf_data.set('PROJECT_VERSION', meson.project_version()) +conf_data.set('PROJECT_VERSION', version) #=============================================================================== # Compiler Settings From 1e765b5c28b5b7c3bcd47855ac22e094514dddd5 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 2 Mar 2022 03:55:03 -0400 Subject: [PATCH 146/409] EmptyView: handle lite-xl version strings that overlap. --- data/core/emptyview.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/data/core/emptyview.lua b/data/core/emptyview.lua index 9d375c20..36ba8fb7 100644 --- a/data/core/emptyview.lua +++ b/data/core/emptyview.lua @@ -8,8 +8,18 @@ local function draw_text(x, y, color) local th = style.big_font:get_height() local dh = 2 * th + style.padding.y * 2 local x1, y1 = x, y + (dh - th) / 2 - x = renderer.draw_text(style.big_font, "Lite XL", x1, y1, color) - renderer.draw_text(style.font, "version " .. VERSION, x1, y1 + th, color) + local xv = x1 + local title = "Lite XL" + local version = "version " .. VERSION + local title_width = style.big_font:get_width(title) + local version_width = style.font:get_width(version) + if version_width > title_width then + version = VERSION + version_width = style.font:get_width(version) + xv = x1 - (version_width - title_width) + end + x = renderer.draw_text(style.big_font, title, x1, y1, color) + renderer.draw_text(style.font, version, xv, y1 + th, color) x = x + style.padding.x renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color) local lines = { From a587182982702c58efba6c796c0387e2ebd10fb4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 2 Mar 2022 18:55:46 -0400 Subject: [PATCH 147/409] meson: when running git rev-parse use HEAD instead of checking current branch name --- meson.build | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 2f26305d..3821e24c 100644 --- a/meson.build +++ b/meson.build @@ -18,11 +18,8 @@ if get_option('buildtype') != 'release' git_command = find_program('git', required : false) if git_command.found() - git_branch = run_command( - [git_command, 'branch', '--show-current'] - ).stdout().strip() git_commit = run_command( - [git_command, 'rev-parse', git_branch] + [git_command, 'rev-parse', 'HEAD'] ).stdout().strip() if git_commit != '' From fbb893c6b1b1c17f0ad2d755e2a5f128ecbc17ce Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 3 Mar 2022 22:09:48 +0100 Subject: [PATCH 148/409] Fix `^` regex matching when using an offset Before, if `offset > 1` was used, the match would have failed because the beginning of the string was never met. Now we force the beginning of the string to be the one specified by the offset. --- src/api/regex.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/api/regex.c b/src/api/regex.c index 08f0f142..6a0aac7a 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -60,12 +60,14 @@ static int f_pcre_match(lua_State *L) { const char* str = luaL_checklstring(L, 2, &len); if (lua_gettop(L) > 2) offset = luaL_checknumber(L, 3); + offset -= 1; + len -= offset; if (lua_gettop(L) > 3) opts = luaL_checknumber(L, 4); lua_rawgeti(L, 1, 1); pcre2_code* re = (pcre2_code*)lua_touserdata(L, -1); pcre2_match_data* md = pcre2_match_data_create_from_pattern(re, NULL); - int rc = pcre2_match(re, (PCRE2_SPTR)str, len, offset - 1, opts, md, NULL); + int rc = pcre2_match(re, (PCRE2_SPTR)&str[offset], len, 0, opts, md, NULL); if (rc < 0) { pcre2_match_data_free(md); if (rc != PCRE2_ERROR_NOMATCH) { @@ -86,7 +88,7 @@ static int f_pcre_match(lua_State *L) { return 0; } for (int i = 0; i < rc*2; i++) - lua_pushnumber(L, ovector[i]+1); + lua_pushnumber(L, ovector[i]+offset+1); pcre2_match_data_free(md); return rc*2; } From caefc9112ae033751f05383080e120d59409efaa Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 4 Mar 2022 11:27:01 +0100 Subject: [PATCH 149/409] Force syntax patterns starting with `^` to match with the whole line Before, syntax patterns/regexes that started with `^` didn't have the desired effect of matching with the start of the line. Now those patterns are used only when matching the whole line. --- data/core/tokenizer.lua | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 5fd8c69f..bb3faa03 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -136,20 +136,42 @@ function tokenizer.tokenize(incoming_syntax, text, state) end local function find_text(text, p, offset, at_start, close) - local target, res = p.pattern or p.regex, { 1, offset - 1 }, p.regex - local code = type(target) == "table" and target[close and 2 or 1] or target + local target, res = p.pattern or p.regex, { 1, offset - 1 } + local p_idx = close and 2 or 1 + local code = type(target) == "table" and target[p_idx] or target + + if p.whole_line == nil then p.whole_line = { } end + if p.whole_line[p_idx] == nil then + -- Match patterns that start with '^' + p.whole_line[p_idx] = code:match("^%^") and true or false + if p.whole_line[p_idx] then + -- Remove '^' from the beginning of the pattern + if type(target) == "table" then + target[p_idx] = code:sub(2) + else + p.pattern = p.pattern and code:sub(2) + p.regex = p.regex and code:sub(2) + end + end + end + if p.regex and type(p.regex) ~= "table" then p._regex = p._regex or regex.compile(p.regex) code = p._regex - end + end + repeat local next = res[2] + 1 + -- If the pattern contained '^', allow matching only the whole line + if p.whole_line[p_idx] and next > 1 then + return + end -- go to the start of the next utf-8 character while text:byte(next) and common.is_utf8_cont(text, next) do next = next + 1 end - res = p.pattern and { text:find(at_start and "^" .. code or code, next) } - or { regex.match(code, text, next, at_start and regex.ANCHORED or 0) } + res = p.pattern and { text:find((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } + or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } if res[1] and close and target[3] then local count = 0 for i = res[1] - 1, 1, -1 do From 2d3d9a16719b2881d7f5690e53a4962d1d9ffcef Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 4 Mar 2022 14:29:29 -0400 Subject: [PATCH 150/409] language_md: improvements consolidating #814 and #840 --- data/plugins/language_md.lua | 224 +++++++++++++++++++++++++++++++---- 1 file changed, 198 insertions(+), 26 deletions(-) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index e7c870ec..f7df1c79 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -1,56 +1,228 @@ -- mod-version:2 -- lite-xl 2.0 local syntax = require "core.syntax" +local style = require "core.style" +local core = require "core" +local initial_scale = SCALE +local initial_color = style.syntax["keyword2"] +-- Add 3 type of font styles for use on markdown files +for _, attr in pairs({"bold", "italic", "bold_italic"}) do + local attributes = {} + if attr ~= "bold_italic" then + attributes[attr] = true + else + attributes["bold"] = true + attributes["italic"] = true + end + -- no way to copy user custom font with additional attributes :( + style.syntax_fonts["markdown_"..attr] = renderer.font.load( + DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", + style.code_font:get_size(), + attributes + ) + -- also add a color for it + style.syntax["markdown_"..attr] = style.syntax["keyword2"] +end + +local in_squares_match = "^%[%]" +local in_parenthesis_match = "^%(%)" syntax.add { name = "Markdown", files = { "%.md$", "%.markdown$" }, + block_comment = { "" }, patterns = { - { pattern = "\\.", type = "normal" }, - { pattern = { "" }, type = "comment" }, + ---- HTML rules imported and adapted from language_html + ---- to not conflict with markdown rules + -- Inline JS and CSS + { + pattern = { + "<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" .. + "['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>", + "<%s*/[sS][cC][rR][iI][pP][tT]>" + }, + syntax = ".js", + type = "function" + }, + { + pattern = { + "<%s*[sS][cC][rR][iI][pP][tT]%s*>", + "<%s*/%s*[sS][cC][rR][iI][pP][tT]>" + }, + syntax = ".js", + type = "function" + }, + { + pattern = { + "<%s*[sS][tT][yY][lL][eE][^>]*>", + "<%s*/%s*[sS][tT][yY][lL][eE]%s*>" + }, + syntax = ".css", + type = "function" + }, + -- Comments + { pattern = { "" }, type = "comment" }, + -- Tags + { pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" }, + { pattern = "%f[^<][%a_][%w_]*", type = "function" }, + { pattern = "%f[^<]/[%a_][%w_]*", type = "function" }, + -- Attributes + { + pattern = "[a-z%-]+%s*()=%s*()\".-\"", + type = { "keyword", "operator", "string" } + }, + { + pattern = "[a-z%-]+%s*()=%s*()'.-'", + type = { "keyword", "operator", "string" } + }, + { + pattern = "[a-z%-]+%s*()=%s*()%-?%d[%d%.]*", + type = { "keyword", "operator", "number" } + }, + -- Entities + { pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" }, + + ---- Markdown rules + -- code blocks { pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" }, + { pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```python", "```" }, type = "string", syntax = ".py" }, { pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" }, { pattern = { "```perl", "```" }, type = "string", syntax = ".pl" }, { pattern = { "```php", "```" }, type = "string", syntax = ".php" }, { pattern = { "```javascript", "```" }, type = "string", syntax = ".js" }, + { pattern = { "```json", "```" }, type = "string", syntax = ".js" }, { pattern = { "```html", "```" }, type = "string", syntax = ".html" }, + { pattern = { "```ini", "```" }, type = "string", syntax = ".ini" }, { pattern = { "```xml", "```" }, type = "string", syntax = ".xml" }, { pattern = { "```css", "```" }, type = "string", syntax = ".css" }, { pattern = { "```lua", "```" }, type = "string", syntax = ".lua" }, { pattern = { "```bash", "```" }, type = "string", syntax = ".sh" }, + { pattern = { "```sh", "```" }, type = "string", syntax = ".sh" }, { pattern = { "```java", "```" }, type = "string", syntax = ".java" }, { pattern = { "```c#", "```" }, type = "string", syntax = ".cs" }, { pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" }, { pattern = { "```d", "```" }, type = "string", syntax = ".d" }, { pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" }, { pattern = { "```c", "```" }, type = "string", syntax = ".c" }, - { pattern = { "```julia", "```" }, type = "string", syntax = ".jl" }, - { pattern = { "```rust", "```" }, type = "string", syntax = ".rs" }, - { pattern = { "```dart", "```" }, type = "string", syntax = ".dart" }, + { pattern = { "```julia", "```" }, type = "string", syntax = ".jl" }, + { pattern = { "```rust", "```" }, type = "string", syntax = ".rs" }, + { pattern = { "```dart", "```" }, type = "string", syntax = ".dart" }, { pattern = { "```v", "```" }, type = "string", syntax = ".v" }, - { pattern = { "```toml", "```" }, type = "string", syntax = ".toml" }, - { pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" }, - { pattern = { "```php", "```" }, type = "string", syntax = ".php" }, - { pattern = { "```nim", "```" }, type = "string", syntax = ".nim" }, - { pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" }, - { pattern = { "```rescript", "```" }, type = "string", syntax = ".res" }, - { pattern = { "```moon", "```" }, type = "string", syntax = ".moon" }, - { pattern = { "```go", "```" }, type = "string", syntax = ".go" }, - { pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" }, - { pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" }, - { pattern = { "```", "```" }, type = "string" }, - { pattern = { "``", "``", "\\" }, type = "string" }, - { pattern = { "`", "`", "\\" }, type = "string" }, - { pattern = { "~~", "~~", "\\" }, type = "keyword2" }, - { pattern = "%-%-%-+", type = "comment" }, - { pattern = "%*%s+", type = "operator" }, - { pattern = { "%*", "[%*\n]", "\\" }, type = "operator" }, - { pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" }, - { pattern = "#.-\n", type = "keyword" }, - { pattern = "!?%[.-%]%(.-%)", type = "function" }, - { pattern = "https?://%S+", type = "function" }, + { pattern = { "```toml", "```" }, type = "string", syntax = ".toml" }, + { pattern = { "```yaml", "```" }, type = "string", syntax = ".yaml" }, + { pattern = { "```nim", "```" }, type = "string", syntax = ".nim" }, + { pattern = { "```typescript", "```" }, type = "string", syntax = ".ts" }, + { pattern = { "```rescript", "```" }, type = "string", syntax = ".res" }, + { pattern = { "```moon", "```" }, type = "string", syntax = ".moon" }, + { pattern = { "```go", "```" }, type = "string", syntax = ".go" }, + { pattern = { "```lobster", "```" }, type = "string", syntax = ".lobster" }, + { pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" }, + { pattern = { "```", "```" }, type = "string" }, + { pattern = { "``", "``" }, type = "string" }, + { pattern = { "`", "`" }, type = "string" }, + -- strike + { pattern = { "~~", "~~" }, type = "keyword2" }, + -- highlight + { pattern = { "==", "==" }, type = "literal" }, + -- lines + { regex = "^\\-{2,}\\-$", type = "comment" }, + { regex = "^\\*{2,}\\*$", type = "comment" }, + { regex = "^\\_{2,}_$", type = "comment" }, + -- bullets + { regex = "^\\s*\\*\\s", type = "number" }, + { regex = "^\\s*-\\s", type = "number" }, + { regex = "^\\s*\\+\\s", type = "number" }, + -- numbered bullet + { regex = "^\\s*[0-9]+\\.\\s", type = "number" }, + -- blockquote + { regex = "^\\s*>{1,}\\s", type = "string" }, + -- bold and italic + { pattern = { "%*%*%*", "%*%*%*" }, type = "markdown_bold_italic" }, + { pattern = { "%*%*", "%*%*" }, type = "markdown_bold" }, + -- handle edge case where asterisk can be at end of line and not close + { pattern = { "%*[%S]", "%*" }, type = "markdown_italic" }, + -- alternative bold italic formats + { + regex = "^_{3}[\\s[:punct:]A-Za-z0-9]+_{3}\\s" , + type = "markdown_bold_italic" + }, + { + regex = "^_{2}[\\s[:punct:]A-Za-z0-9]+_{2}\\s" , + type = "markdown_bold" + }, + { + regex = "^_{1}[\\s[:punct:]A-Za-z0-9]+_{1}\\s" , + type = "markdown_italic" + }, + { pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" }, + { pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" }, + { pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" }, + -- headings + { regex = "^#{1,6}.+", type = "keyword" }, + -- superscript and subscript + { + pattern = "%^()%d+()%^", + type = { "function", "number", "function" } + }, + { + pattern = "%~()%d+()%~", + type = { "function", "number", "function" } + }, + -- definitions + { regex = "^:\\s.+", type = "function" }, + -- emoji + { pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" }, + -- images and links + { + pattern = "!?%[()["..in_squares_match.."]+()%]()%(()["..in_parenthesis_match.."]+()%)", + type = { "function", "string", "function", "function", "number", "function" } + }, + -- reference links + { + pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]", + type = { "function", "string", "function", "function", "number", "function" } + }, + { + pattern = "%[%^?()["..in_squares_match.."]+()%]: ", + type = { "function", "number", "function" } + }, + { + pattern = "%[%^?()["..in_squares_match.."]+()%]", + type = { "function", "number", "function" } + }, + -- url's and email + { + pattern = "<[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+%.[a-zA-Z0-9-.]+>", + type = "function" + }, + { pattern = "", type = "function" }, + { pattern = "https?://%S+", type = "function" } }, symbols = { }, } + +-- Adjust markdown fonts on scale changes. +-- Note: this should be performed by lite-xl it self to all style.syntax_fonts +core.add_thread(function() + while true do + if initial_scale ~= SCALE then + for _, attr in pairs({"bold", "italic", "bold_italic"}) do + style.syntax_fonts["markdown_"..attr] = + style.syntax_fonts["markdown_"..attr]:copy( + style.code_font:get_size() + ) + end + initial_scale = SCALE + end + -- we also adjust the color on theme changes + if initial_color ~= style.syntax["keyword2"] then + for _, attr in pairs({"bold", "italic", "bold_italic"}) do + style.syntax["markdown_"..attr] = style.syntax["keyword2"] + end + initial_color = style.syntax["keyword2"] + end + coroutine.yield(1) + end +end) From e5ca08e13fb6424f4aea0ae4413c01dd48c9d68e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sat, 5 Mar 2022 19:03:33 -0400 Subject: [PATCH 151/409] plugin scale: replace non existing font.set_size with font.copy --- data/plugins/scale.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index f36b1ff3..698b7b3a 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -50,12 +50,8 @@ local function set_scale(scale) style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size()) end - for _, font in pairs(style.syntax_fonts) do - renderer.font.set_size(font, s * font:get_size()) - end - - for _, font in pairs(style.syntax_fonts) do - renderer.font.set_size(font, s * font:get_size()) + for name, font in pairs(style.syntax_fonts) do + style.syntax_fonts[name] = renderer.font.copy(font, s * font:get_size()) end -- restore scroll positions From c4f7380a95aa78e8f3a41cb7f179ad083e476a39 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sat, 5 Mar 2022 19:13:24 -0400 Subject: [PATCH 152/409] language_md: removed scale adjustment code that was needed because of a bug on scale plugin. --- data/plugins/language_md.lua | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index f7df1c79..5013279e 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -3,7 +3,6 @@ local syntax = require "core.syntax" local style = require "core.style" local core = require "core" -local initial_scale = SCALE local initial_color = style.syntax["keyword2"] -- Add 3 type of font styles for use on markdown files @@ -203,20 +202,9 @@ syntax.add { symbols = { }, } --- Adjust markdown fonts on scale changes. --- Note: this should be performed by lite-xl it self to all style.syntax_fonts +-- Adjust the color on theme changes core.add_thread(function() while true do - if initial_scale ~= SCALE then - for _, attr in pairs({"bold", "italic", "bold_italic"}) do - style.syntax_fonts["markdown_"..attr] = - style.syntax_fonts["markdown_"..attr]:copy( - style.code_font:get_size() - ) - end - initial_scale = SCALE - end - -- we also adjust the color on theme changes if initial_color ~= style.syntax["keyword2"] then for _, attr in pairs({"bold", "italic", "bold_italic"}) do style.syntax["markdown_"..attr] = style.syntax["keyword2"] From 6cb403b4505af343840c5aa16e876d9b27cdc75f Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sat, 5 Mar 2022 20:15:48 -0400 Subject: [PATCH 153/409] meson: increased min version to 0.47 and added check flag on run_command. --- meson.build | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 3821e24c..5b4662c3 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('lite-xl', ['c'], version : '2.0.5', license : 'MIT', - meson_version : '>= 0.42', + meson_version : '>= 0.47', default_options : [ 'c_std=gnu11', 'wrap_mode=nofallback' @@ -19,7 +19,8 @@ if get_option('buildtype') != 'release' if git_command.found() git_commit = run_command( - [git_command, 'rev-parse', 'HEAD'] + [git_command, 'rev-parse', 'HEAD'], + check : false ).stdout().strip() if git_commit != '' From f85612e0f025e32a9e6aa264b713c13a5585985c Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 6 Mar 2022 00:59:22 -0500 Subject: [PATCH 154/409] Fix Project Scanning (#746) Removed dmon, and replaced with logic that works across Linux, Mac, FreeBSD and Windows. Have tested on all platforms, and seems to work. Co-authored-by: Jan200101 --- README.md | 1 + data/core/commands/core.lua | 1 - data/core/dirwatch.lua | 220 ++++ data/core/init.lua | 536 +++------ data/plugins/treeview.lua | 5 +- lib/dmon/dmon.h | 1685 --------------------------- lib/dmon/dmon_extra.h | 165 --- lib/dmon/meson.build | 1 - licenses/licenses.md | 27 - meson.build | 6 +- resources/notes-dmon-integration.md | 54 - src/api/api.c | 13 +- src/api/api.h | 1 + src/api/dirmonitor.c | 203 ++++ src/api/system.c | 131 +-- src/dirmonitor.c | 59 - src/dirmonitor.h | 15 - src/main.c | 7 +- src/meson.build | 2 +- 19 files changed, 588 insertions(+), 2544 deletions(-) create mode 100644 data/core/dirwatch.lua delete mode 100644 lib/dmon/dmon.h delete mode 100644 lib/dmon/dmon_extra.h delete mode 100644 lib/dmon/meson.build delete mode 100644 resources/notes-dmon-integration.md create mode 100644 src/api/dirmonitor.c delete mode 100644 src/dirmonitor.c delete mode 100644 src/dirmonitor.h diff --git a/README.md b/README.md index b54c17e4..b5c31a05 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ See the [licenses] file for details on licenses used by the required dependencie [Get color themes]: https://github.com/lite-xl/lite-xl-colors [changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md [Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins +[plugins repository]: https://github.com/rxi/lite-plugins [colors repository]: https://github.com/lite-xl/lite-xl-colors [LICENSE]: LICENSE [licenses]: licenses/licenses.md diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 524352fc..84ec9b91 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -173,7 +173,6 @@ command.add(nil, { end if abs_path == core.project_dir then return end core.confirm_close_docs(core.docs, function(dirpath) - core.close_current_project() core.open_folder_project(dirpath) end, abs_path) end, suggest_directory) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua new file mode 100644 index 00000000..3848e5a2 --- /dev/null +++ b/data/core/dirwatch.lua @@ -0,0 +1,220 @@ +local common = require "core.common" +local config = require "core.config" +local dirwatch = {} + +function dirwatch:__index(idx) + local value = rawget(self, idx) + if value ~= nil then return value end + return dirwatch[idx] +end + +function dirwatch.new() + local t = { + scanned = {}, + watched = {}, + reverse_watched = {}, + monitor = dirmonitor.new(), + windows_watch_top = nil, + windows_watch_count = 0 + } + setmetatable(t, dirwatch) + return t +end + + +function dirwatch:scan(directory, bool) + if bool == false then return self:unwatch(directory) end + self.scanned[directory] = system.get_file_info(directory).modified +end + +-- Should be called on every directory in a subdirectory. +-- In windows, this is a no-op for anything underneath a top-level directory, +-- but code should be called anyway, so we can ensure that we have a proper +-- experience across all platforms. +function dirwatch:watch(directory, bool) + if bool == false then return self:unwatch(directory) end + if not self.watched[directory] and not self.scanned[directory] then + if PLATFORM == "Windows" then + if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then + -- Get the highest level of directory that is common to this directory, and the original. + local target = directory + while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do + target = common.dirname(target) + end + if target ~= self.windows_watch_top then + local value = self.monitor:watch(target) + if value and value < 0 then + return self:scan(directory) + end + self.windows_watch_top = target + self.windows_watch_count = self.windows_watch_count + 1 + end + end + self.watched[directory] = true + else + local value = self.monitor:watch(directory) + -- If for whatever reason, we can't watch this directory, revert back to scanning. + -- Don't bother trying to find out why, for now. + if value and value < 0 then + return self:scan(directory) + end + self.watched[directory] = value + self.reverse_watched[value] = directory + end + end +end + +function dirwatch:unwatch(directory) + if self.watched[directory] then + if PLATFORM ~= "Windows" then + self.monitor.unwatch(directory) + self.reverse_watched[self.watched[directory]] = nil + else + self.windows_watch_count = self.windows_watch_count - 1 + if self.windows_watch_count == 0 then + self.windows_watch_top = nil + self.monitor.unwatch(directory) + end + end + self.watched[directory] = nil + elseif self.scanned[directory] then + self.scanned[directory] = nil + end +end + +-- designed to be run inside a coroutine. +function dirwatch:check(change_callback, scan_time, wait_time) + self.monitor:check(function(id) + if PLATFORM == "Windows" then + change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) + elseif self.reverse_watched[id] then + change_callback(self.reverse_watched[id]) + end + end) + local start_time = system.get_time() + for directory, old_modified in pairs(self.scanned) do + if old_modified then + local new_modified = system.get_file_info(directory).modified + if old_modified < new_modified then + change_callback(directory) + self.scanned[directory] = new_modified + end + end + if system.get_time() - start_time > scan_time then + coroutine.yield(wait_time) + start_time = system.get_time() + end + end +end + + +local function strip_leading_path(filename) + return filename:sub(2) +end + +-- inspect config.ignore_files patterns and prepare ready to use entries. +local function compile_ignore_files() + local ipatterns = config.ignore_files + local compiled = {} + -- config.ignore_files could be a simple string... + if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end + for i, pattern in ipairs(ipatterns) do + -- we ignore malformed pattern that raise an error + if pcall(string.match, "a", pattern) then + table.insert(compiled, { + use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end + -- An '/' or '/$' at the end means we want to match a directory. + match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value + pattern = pattern -- get the actual pattern + }) + end + end + return compiled +end + + +local function fileinfo_pass_filter(info, ignore_compiled) + if info.size >= config.file_size_limit * 1e6 then return false end + local basename = common.basename(info.filename) + -- replace '\' with '/' for Windows where PATHSEP = '\' + local fullname = "/" .. info.filename:gsub("\\", "/") + for _, compiled in ipairs(ignore_compiled) do + local test = compiled.use_path and fullname or basename + if compiled.match_dir then + if info.type == "dir" and string.match(test .. "/", compiled.pattern) then + return false + end + else + if string.match(test, compiled.pattern) then + return false + end + end + end + return true +end + + +local function compare_file(a, b) + return a.filename < b.filename +end + + +-- compute a file's info entry completed with "filename" to be used +-- in project scan or falsy if it shouldn't appear in the list. +local function get_project_file_info(root, file, ignore_compiled) + local info = system.get_file_info(root .. file) + -- info can be not nil but info.type may be nil if is neither a file neither + -- a directory, for example for /dev/* entries on linux. + if info and info.type then + info.filename = strip_leading_path(file) + return fileinfo_pass_filter(info, ignore_compiled) and info + end +end + + +-- "root" will by an absolute path without trailing '/' +-- "path" will be a path starting with '/' and without trailing '/' +-- or the empty string. +-- It will identifies a sub-path within "root. +-- The current path location will therefore always be: root .. path. +-- When recursing "root" will always be the same, only "path" will change. +-- Returns a list of file "items". In eash item the "filename" will be the +-- complete file path relative to "root" *without* the trailing '/'. +function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred) + local t0 = system.get_time() + local all = system.list_dir(root .. path) or {} + local t_elapsed = system.get_time() - t0 + local dirs, files = {}, {} + local ignore_compiled = compile_ignore_files() + + for _, file in ipairs(all) do + local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) + if info then + table.insert(info.type == "dir" and dirs or files, info) + entries_count = entries_count + 1 + end + end + + local recurse_complete = true + table.sort(dirs, compare_file) + for _, f in ipairs(dirs) do + table.insert(t, f) + if recurse_pred(dir, f.filename, entries_count, t_elapsed) then + local _, complete, n = dirwatch.get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred) + recurse_complete = recurse_complete and complete + entries_count = n + else + recurse_complete = false + end + end + + table.sort(files, compare_file) + for _, f in ipairs(files) do + table.insert(t, f) + end + + return t, recurse_complete, entries_count +end + + +return dirwatch diff --git a/data/core/init.lua b/data/core/init.lua index b9b001e2..4b2b0ea0 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -5,6 +5,7 @@ local config = require "core.config" local style = require "core.style" local command local keymap +local dirwatch local RootView local StatusView local TitleView @@ -62,23 +63,6 @@ function core.set_project_dir(new_dir, change_project_fn) return chdir_ok end -function core.close_current_project() - -- When using system.unwatch_dir we need to pass the watch_id provided by dmon. - -- In reality when unwatching a directory the dmon library shifts the other watch_id - -- values so the actual watch_id changes. To workaround this problem we assume the - -- first watch_id is always 1 and the watch_id are continguous and we unwatch the - -- first watch_id repeateadly up to the number of watch_ids. - local watch_id_max = 0 - for _, project_dir in ipairs(core.project_directories) do - if project_dir.watch_id and project_dir.watch_id > watch_id_max then - watch_id_max = project_dir.watch_id - end - end - for i = 1, watch_id_max do - system.unwatch_dir(1) - end -end - local function reload_customizations() -- The logic is: @@ -124,134 +108,6 @@ local function strip_trailing_slash(filename) return filename end -local function compare_file(a, b) - return a.filename < b.filename -end - - --- inspect config.ignore_files patterns and prepare ready to use entries. -local function compile_ignore_files() - local ipatterns = config.ignore_files - local compiled = {} - -- config.ignore_files could be a simple string... - if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end - for i, pattern in ipairs(ipatterns) do - compiled[i] = { - use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end - -- An '/' or '/$' at the end means we want to match a directory. - match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value - pattern = pattern -- get the actual pattern - } - end - return compiled -end - - -local function safe_match(s, pattern) - local ok, match = pcall(string.match, s, pattern) - return ok and match -end - - -local function fileinfo_pass_filter(info, ignore_compiled) - if info.size >= config.file_size_limit * 1e6 then return false end - local basename = common.basename(info.filename) - -- replace '\' with '/' for Windows where PATHSEP = '\' - local fullname = "/" .. info.filename:gsub("\\", "/") - for _, compiled in ipairs(ignore_compiled) do - local test = compiled.use_path and fullname or basename - if compiled.match_dir then - if info.type == "dir" and safe_match(test .. "/", compiled.pattern) then - return false - end - else - if safe_match(test, compiled.pattern) then - return false - end - end - end - return true -end - - --- compute a file's info entry completed with "filename" to be used --- in project scan or falsy if it shouldn't appear in the list. -local function get_project_file_info(root, file, ignore_compiled) - local info = system.get_file_info(root .. file) - -- info can be not nil but info.type may be nil if is neither a file neither - -- a directory, for example for /dev/* entries on linux. - if info and info.type then - info.filename = strip_leading_path(file) - return fileinfo_pass_filter(info, ignore_compiled) and info - end -end - - --- Predicate function to inhibit directory recursion in get_directory_files --- based on a time limit and the number of files. -local function timed_max_files_pred(dir, filename, entries_count, t_elapsed) - local n_limit = entries_count <= config.max_project_files - local t_limit = t_elapsed < 20 / config.fps - return n_limit and t_limit and core.project_subdir_is_shown(dir, filename) -end - - --- "root" will by an absolute path without trailing '/' --- "path" will be a path starting with '/' and without trailing '/' --- or the empty string. --- It will identifies a sub-path within "root. --- The current path location will therefore always be: root .. path. --- When recursing "root" will always be the same, only "path" will change. --- Returns a list of file "items". In eash item the "filename" will be the --- complete file path relative to "root" *without* the trailing '/'. -local function get_directory_files(dir, root, path, t, ignore_compiled, entries_count, recurse_pred, begin_hook) - if begin_hook then begin_hook() end - ignore_compiled = ignore_compiled or compile_ignore_files() - local t0 = system.get_time() - local all = system.list_dir(root .. path) or {} - local t_elapsed = system.get_time() - t0 - local dirs, files = {}, {} - - for _, file in ipairs(all) do - local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) - if info then - table.insert(info.type == "dir" and dirs or files, info) - entries_count = entries_count + 1 - end - end - - local recurse_complete = true - table.sort(dirs, compare_file) - for _, f in ipairs(dirs) do - table.insert(t, f) - if recurse_pred(dir, f.filename, entries_count, t_elapsed) then - local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, ignore_compiled, entries_count, recurse_pred, begin_hook) - recurse_complete = recurse_complete and complete - entries_count = n - else - recurse_complete = false - end - end - - table.sort(files, compare_file) - for _, f in ipairs(files) do - table.insert(t, f) - end - - return t, recurse_complete, entries_count -end - - -function core.project_subdir_set_show(dir, filename, show) - if dir.files_limit and not dir.force_rescan then - local fullpath = dir.name .. PATHSEP .. filename - if not (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) then - return false - end - end - dir.shown_subdir[filename] = show - return true -end function core.project_subdir_is_shown(dir, filename) @@ -264,8 +120,10 @@ local function show_max_files_warning(dir) "Filesystem is too slow: project files will not be indexed." or "Too many files in project directory: stopped reading at ".. config.max_project_files.." files. For more information see ".. - "usage.md at github.com/lite-xl/lite-xl." - core.status_view:show_message("!", style.accent, message) + "usage.md at https://github.com/lite-xl/lite-xl." + if core.status_view then + core.status_view:show_message("!", style.accent, message) + end end @@ -328,28 +186,6 @@ local function files_list_replace(as, i1, n, bs, hook) end -local function project_scan_add_entry(dir, fileinfo) - assert(not dir.force_rescan, "should be used only when force_rescan is false") - local index, match = file_search(dir.files, fileinfo) - if not match then - table.insert(dir.files, index, fileinfo) - if fileinfo.type == "dir" and not dir.files_limit then - -- ASSUMPTION: dir.force_rescan is FALSE - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - if fileinfo.symlink then - local new_files = get_directory_files(dir, dir.name, PATHSEP .. fileinfo.filename, {}, nil, 0, core.project_subdir_is_shown) - files_list_replace(dir.files, index, 0, new_files, {insert = function(info) - if info.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. info.filename) - end - end}) - end - end - dir.is_dirty = true - end -end - - local function project_subdir_bounds(dir, filename) local index, n = 0, #dir.files for i, file in ipairs(dir.files) do @@ -367,123 +203,148 @@ local function project_subdir_bounds(dir, filename) end end -local function rescan_project_subdir(dir, filename_rooted) - local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, nil, 0, core.project_subdir_is_shown, coroutine.yield) - local index, n = 0, #dir.files - if filename_rooted ~= "" then - local filename = strip_leading_path(filename_rooted) - index, n = project_subdir_bounds(dir, filename) - end - if not files_list_match(dir.files, index, n, new_files) then - -- Since we are modifying the list of files we may add new directories and - -- when dir.files_limit is false we need to add a watch for each subdirectory. - -- We are therefore passing a insert hook function to the purpose of adding - -- a watch. - -- Note that the hook shold almost never be called, it happens only if - -- we missed some directory creation event from the directory monitoring which - -- almost never happens. With inotify is at least theoretically possible. - local need_subdir_watches = not dir.files_limit and not dir.force_rescan - files_list_replace(dir.files, index, n, new_files, need_subdir_watches and {insert = function(fileinfo) - if fileinfo.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - end - end}) - dir.is_dirty = true - return true - end +-- Predicate function to inhibit directory recursion in get_directory_files +-- based on a time limit and the number of files. +local function timed_max_files_pred(dir, filename, entries_count, t_elapsed) + local n_limit = entries_count <= config.max_project_files + local t_limit = t_elapsed < 20 / config.fps + return n_limit and t_limit and core.project_subdir_is_shown(dir, filename) end -local function add_dir_scan_thread(dir) - core.add_thread(function() - while true do - local has_changes = rescan_project_subdir(dir, "") - if has_changes then - core.redraw = true -- we run without an event, from a thread - end - coroutine.yield(5) - end - end, dir) -end - -local function folder_add_subdirs_watch(dir) - for _, fileinfo in ipairs(dir.files) do - if fileinfo.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - end - end -end - - --- Populate a project folder top directory by scanning the filesystem. -local function scan_project_folder(index) - local dir = core.project_directories[index] - local fstype = system.get_fs_type(dir.name) - dir.force_rescan = (fstype == "nfs" or fstype == "fuse") - if not dir.force_rescan then - local watch_err - dir.watch_id, watch_err = system.watch_dir(dir.name) - if not dir.watch_id then - core.log("Watch directory %s: %s", dir.name, watch_err) - dir.force_rescan = true - end - end - local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0, timed_max_files_pred) - -- If dir.files_limit is set to TRUE it means that: - -- * we will not read recursively all the project files and we don't index them - -- * we read only the files for the subdirectories that are opened/expanded in the - -- TreeView - -- * we add a subdirectory watch only to the directories that are opened/expanded - -- * we set the values in the shown_subdir table - -- - -- If dir.files_limit is set to FALSE it means that: - -- * we will read recursively all the project files and we index them - -- * all the list of project files is always complete and kept updated when - -- changes happen on the disk - -- * all the subdirectories at any depth must have a watch using system.watch_dir_add - -- * we DO NOT set the values in the shown_subdir table - -- - -- * If force_rescan is set to TRUE no watch are used in any case. - if not complete then - dir.slow_filesystem = not complete and (entries_count <= config.max_project_files) - dir.files_limit = true - if core.status_view then -- May be not yet initialized. - show_max_files_warning(dir) - end - end - dir.files = t - if dir.force_rescan then - add_dir_scan_thread(dir) +-- Should be called on any directory that registers a change. +-- Uses relative paths at the project root. +local function refresh_directory(topdir, target, expanded) + local index, n, directory + if target == "" then + index, n = 1, #topdir.files + directory = "" else - if not dir.files_limit then - folder_add_subdirs_watch(dir) + index, n = project_subdir_bounds(topdir, target) + index = index + 1 + n = index + n - 1 + directory = (PATHSEP .. target) + end + if index then + local files + local change = false + if topdir.files_limit then + -- If we have the folders literally open on the side panel. + files = expanded and dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, core.project_subdir_is_shown) or {} + change = true + else + -- If we're expecting to keep track of everything, go through the list and iteratively deal with directories. + files = dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, function() return false end) end - core.dir_rescan_add_job(dir, ".") + + local new_idx, old_idx = 1, index + local new_directories = {} + local last_dir = nil + while old_idx <= n or new_idx <= #files do + local old_info, new_info = topdir.files[old_idx], files[new_idx] + if not new_info or not old_info or not last_dir or old_info.filename:sub(1, #last_dir + 1) ~= last_dir .. "/" then + if not new_info or not old_info or not files_info_equal(new_info, old_info) then + change = true + if not old_info or (new_info and system.path_compare(new_info.filename, new_info.type, old_info.filename, old_info.type)) then + table.insert(topdir.files, old_idx, new_info) + new_idx = new_idx + 1 + old_idx = old_idx + 1 + if new_info.type == "dir" then + table.insert(new_directories, new_info) + end + n = n + 1 + else + table.remove(topdir.files, old_idx) + if old_info.type == "dir" then + topdir.watch:unwatch(target .. PATHSEP .. old_info.filename) + end + n = n - 1 + end + else + new_idx = new_idx + 1 + old_idx = old_idx + 1 + end + if old_info and old_info.type == "dir" then + last_dir = old_info.filename + end + else + old_idx = old_idx + 1 + end + end + for i, v in ipairs(new_directories) do + topdir.watch:watch(target) + if refresh_directory(topdir, target .. PATHSEP .. v.filename) then + change = true + end + end + if change then + core.redraw = true + topdir.is_dirty = true + end + return change end end - function core.add_project_directory(path) -- top directories has a file-like "item" but the item.filename -- will be simply the name of the directory, without its path. -- The field item.topdir will identify it as a top level directory. path = common.normalize_volume(path) - local dir = { + local topdir = { name = path, item = {filename = common.basename(path), type = "dir", topdir = true}, files_limit = false, is_dirty = true, shown_subdir = {}, + watch_thread = nil, + watch = dirwatch.new() } - table.insert(core.project_directories, dir) - scan_project_folder(#core.project_directories) + table.insert(core.project_directories, topdir) + + local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown" + topdir.force_scans = (fstype == "nfs" or fstype == "fuse") + local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred) + topdir.files = t + if not complete then + topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files) + topdir.files_limit = true + show_max_files_warning(topdir) + refresh_directory(topdir, "", true) + else + for i,v in ipairs(t) do + if v.type == "dir" then topdir.watch:watch(path .. PATHSEP .. v.filename) end + end + end + topdir.watch:watch(topdir.name) + -- each top level directory gets a watch thread. if the project is small, or + -- if the ablity to use directory watches hasn't been compromised in some way + -- either through error, or amount of files, then this should be incredibly + -- quick; essentially one syscall per check. Otherwise, this may take a bit of + -- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds. + topdir.watch_thread = core.add_thread(function() + while true do + topdir.watch:check(function(target) + if target == topdir.name then return refresh_directory(topdir, "", true) end + local dirpath = target:sub(#topdir.name + 2) + local abs_dirpath = topdir.name .. PATHSEP .. dirpath + if dirpath then + -- check if the directory is in the project files list, if not exit. + local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"}) + if not dir_match or not core.project_subdir_is_shown(topdir, topdir.files[dir_index].filename) then return end + end + return refresh_directory(topdir, dirpath, true) + end, 0.01, 0.01) + coroutine.yield(0.05) + end + end) + if path == core.project_dir then - core.project_files = dir.files + core.project_files = topdir.files end core.redraw = true - return dir + return topdir end @@ -496,7 +357,6 @@ local function rescan_project_directories() local dir = core.project_directories[i] save_project_dirs[i] = {name = dir.name, shown_subdir = dir.shown_subdir} end - core.close_current_project() -- ensure we unwatch directories core.project_directories = {} for i = 1, n do -- add again the directories in the project local dir = core.add_project_directory(save_project_dirs[i].name) @@ -520,7 +380,6 @@ local function rescan_project_directories() -- In theory set_show below may fail and return false but is it is listed -- there it means it succeeded before so we are optimistically assume it -- will not fail for the sake of simplicity. - core.project_subdir_set_show(dir, subdir, show) core.update_project_subdir(dir, subdir, show) break end @@ -542,14 +401,10 @@ end function core.update_project_subdir(dir, filename, expanded) assert(dir.files_limit, "function should be called only when directory is in files limit mode") + dir.shown_subdir[filename] = expanded local index, n, file = project_subdir_bounds(dir, filename) if index then - local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, nil, 0, core.project_subdir_is_shown) or {} - -- ASSUMPTION: core.update_project_subdir is called only when dir.files_limit is true - -- NOTE: we may add new directories below but we do not need to call - -- system.watch_dir_add because the current function is called only - -- in dir.files_limit mode and in this latter case we don't need to - -- add watch to new, unfolded, subdirectories. + local new_files = expanded and dirwatch.get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} files_list_replace(dir.files, index, n, new_files) dir.is_dirty = true return true @@ -569,7 +424,7 @@ local function find_files_rec(root, path) info.filename = strip_leading_path(file) if info.type == "file" then coroutine.yield(root, info) - else + elseif not common.match_pattern(common.basename(info.filename), config.ignore_files) then find_files_rec(root, PATHSEP .. info.filename) end end @@ -630,62 +485,6 @@ function core.project_files_number() end -local function project_dir_by_watch_id(watch_id) - for i = 1, #core.project_directories do - if core.project_directories[i].watch_id == watch_id then - return core.project_directories[i] - end - end -end - - -local function project_scan_remove_file(dir, filepath) - local fileinfo = { filename = filepath } - for _, filetype in ipairs {"dir", "file"} do - fileinfo.type = filetype - local index, match = file_search(dir.files, fileinfo) - if match then - if filetype == "dir" then - -- If the directory is a symlink it may get deleted and we will - -- never get dirmonitor events for the removal the files it contains. - -- We proceed to remove all the files that belong to the directory. - local _, n_subdir = project_subdir_bounds(dir, filepath) - files_list_replace(dir.files, index, n_subdir, {}, { - remove= function(fileinfo) - if fileinfo.type == "dir" then - system.watch_dir_rm(dir.watch_id, dir.name .. PATHSEP .. filepath) - end - end}) - if dir.files_limit then - dir.shown_subdir[filepath] = nil - end - end - table.remove(dir.files, index) - dir.is_dirty = true - return - end - end -end - - -local function project_scan_add_file(dir, filepath) - local ignore = compile_ignore_files() - local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath, ignore) - if fileinfo then - -- on Windows and MacOS we can get events from directories we are not following: - -- check if each parent directories pass the ignore_files rules. - repeat - filepath = common.dirname(filepath) - local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath, ignore) - if filepath and not parent_info then - return -- parent directory does match ignore_files rules: stop there - end - until not parent_info - project_scan_add_entry(dir, fileinfo) - end -end - - -- create a directory using mkdir but may need to create the parent -- directories as well. local function create_user_directory() @@ -859,6 +658,7 @@ end function core.init() command = require "core.command" keymap = require "core.keymap" + dirwatch = require "core.dirwatch" RootView = require "core.rootview" StatusView = require "core.statusview" TitleView = require "core.titleview" @@ -919,6 +719,8 @@ function core.init() core.threads = setmetatable({}, { __mode = "k" }) core.blink_start = system.get_time() core.blink_timer = core.blink_start + + local got_user_error, got_project_error = not core.load_user_directory() local project_dir_abs = system.absolute_path(project_dir) -- We prevent set_project_dir below to effectively add and scan the directory becaese tha @@ -933,7 +735,9 @@ function core.init() update_recents_project("remove", project_dir) end project_dir_abs = system.absolute_path(".") - if not core.set_project_dir(project_dir_abs) then + if not core.set_project_dir(project_dir_abs, function() + got_project_error = not core.load_project_module() + end) then system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd") os.exit(1) end @@ -960,14 +764,12 @@ function core.init() cur_node = cur_node:split("down", core.status_view, {y = true}) command.add_defaults() - local got_user_error = not core.load_user_directory() local plugins_success, plugins_refuse_list = core.load_plugins() do local pdir, pname = project_dir_abs:match("(.*)[/\\\\](.*)") core.log("Opening project %q from directory %s", pname, pdir) end - local got_project_error = not core.load_project_module() -- We add the project directory now because the project's module is loaded. core.add_project_directory(project_dir_abs) @@ -1381,78 +1183,6 @@ function core.has_pending_rescan() end end - -function core.dir_rescan_add_job(dir, filepath) - local dirpath = filepath:match("^(.+)[/\\].+$") - local dirpath_rooted = dirpath and PATHSEP .. dirpath or "" - local abs_dirpath = dir.name .. dirpath_rooted - if dirpath then - -- check if the directory is in the project files list, if not exit - local dir_index, dir_match = file_search(dir.files, {filename = dirpath, type = "dir"}) - -- Note that is dir_match is false dir_index greaten than the last valid index. - -- We use dir_index to index dir.files below only if dir_match is true. - if not dir_match or not core.project_subdir_is_shown(dir, dir.files[dir_index].filename) then return end - end - local new_time = system.get_time() + 1 - - -- evaluate new rescan request versus existing rescan - local remove_list = {} - for _, rescan in pairs(scheduled_rescan) do - if abs_dirpath == rescan.abs_path or common.path_belongs_to(abs_dirpath, rescan.abs_path) then - -- abs_dirpath is a subpath of a scan already ongoing: skip - rescan.time_limit = new_time - return - elseif common.path_belongs_to(rescan.abs_path, abs_dirpath) then - -- abs_dirpath already cover this rescan: add to the list of rescan to be removed - table.insert(remove_list, rescan.abs_path) - end - end - for _, key_path in ipairs(remove_list) do - scheduled_rescan[key_path] = nil - end - - scheduled_rescan[abs_dirpath] = {dir = dir, path = dirpath_rooted, abs_path = abs_dirpath, time_limit = new_time} - core.add_thread(function() - while true do - local rescan = scheduled_rescan[abs_dirpath] - if not rescan then return end - if system.get_time() > rescan.time_limit then - local has_changes = rescan_project_subdir(rescan.dir, rescan.path) - if has_changes then - core.redraw = true -- we run without an event, from a thread - rescan.time_limit = new_time - else - scheduled_rescan[rescan.abs_path] = nil - return - end - end - coroutine.yield(0.2) - end - end) -end - - --- no-op but can be overrided by plugins -function core.on_dirmonitor_modify(dir, filepath) end -function core.on_dirmonitor_delete(dir, filepath) end - - -function core.on_dir_change(watch_id, action, filepath) - local dir = project_dir_by_watch_id(watch_id) - if not dir then return end - core.dir_rescan_add_job(dir, filepath) - if action == "delete" then - project_scan_remove_file(dir, filepath) - core.on_dirmonitor_delete(dir, filepath) - elseif action == "create" then - project_scan_add_file(dir, filepath) - core.on_dirmonitor_modify(dir, filepath); - elseif action == "modify" then - core.on_dirmonitor_modify(dir, filepath); - end -end - - function core.on_event(type, ...) local did_keymap = false if type == "textinput" then @@ -1494,8 +1224,6 @@ function core.on_event(type, ...) end elseif type == "focuslost" then core.root_view:on_focus_lost(...) - elseif type == "dirchange" then - core.on_dir_change(...) elseif type == "quit" then core.quit() end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 6a48dfe2..6841586d 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -236,9 +236,6 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) else local hovered_dir = core.project_dir_by_name(hovered_item.dir_name) if hovered_dir and hovered_dir.files_limit then - if not core.project_subdir_set_show(hovered_dir, hovered_item.filename, not hovered_item.expanded) then - return - end core.update_project_subdir(hovered_dir, hovered_item.filename, not hovered_item.expanded) end hovered_item.expanded = not hovered_item.expanded @@ -592,7 +589,7 @@ command.add(function() return view.hovered_item ~= nil end, { system.exec(string.format("start \"\" %q", hovered_item.abs_filename)) elseif string.find(PLATFORM, "Mac") then system.exec(string.format("open %q", hovered_item.abs_filename)) - elseif PLATFORM == "Linux" then + elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then system.exec(string.format("xdg-open %q", hovered_item.abs_filename)) end end, diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h deleted file mode 100644 index 84258bad..00000000 --- a/lib/dmon/dmon.h +++ /dev/null @@ -1,1685 +0,0 @@ -#ifndef __DMON_H__ -#define __DMON_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Portable directory monitoring library -// watches directories for file or directory changes. -// -// Usage: -// define DMON_IMPL and include this file to use it: -// #define DMON_IMPL -// #include "dmon.h" -// -// dmon_init(): -// Call this once at the start of your program. -// This will start a low-priority monitoring thread -// dmon_deinit(): -// Call this when your work with dmon is finished, usually on program terminate -// This will free resources and stop the monitoring thread -// dmon_watch: -// Watch for directories -// You can watch multiple directories by calling this function multiple times -// rootdir: root directory to monitor -// watch_cb: callback function to receive events. -// NOTE that this function is called from another thread, so you should -// beware of data races in your application when accessing data within this -// callback -// flags: watch flags, see dmon_watch_flags_t -// user_data: user pointer that is passed to callback function -// Returns the Id of the watched directory after successful call, or returns Id=0 if error -// dmon_unwatch: -// Remove the directory from watch list -// -// see test.c for the basic example -// -// Configuration: -// You can customize some low-level functionality like malloc and logging by overriding macros: -// -// DMON_MALLOC, DMON_FREE, DMON_REALLOC: -// define these macros to override memory allocations -// default is 'malloc', 'free' and 'realloc' -// DMON_ASSERT: -// define this to provide your own assert -// default is 'assert' -// DMON_LOG_DEBUG -// define this to provide your own extra debug logging mechanism -// default implementation logs to stdout in DEBUG and does nothing in other builds -// DMON_API_DECL, DMON_API_IMPL -// define these to provide your own API declerations. (for example: static) -// default is nothing (which is extern in C language ) -// DMON_MAX_PATH -// Maximum size of path characters -// default is 260 characters -// DMON_MAX_WATCHES -// Maximum number of watch directories -// default is 64 -// -// TODO: -// - DMON_WATCHFLAGS_FOLLOW_SYMLINKS does not resolve files -// - implement DMON_WATCHFLAGS_OUTOFSCOPE_LINKS -// - implement DMON_WATCHFLAGS_IGNORE_DIRECTORIES -// -// History: -// 1.0.0 First version. working Win32/Linux backends -// 1.1.0 MacOS backend -// 1.1.1 Minor fixes, eliminate gcc/clang warnings with -Wall -// 1.1.2 Eliminate some win32 dead code -// 1.1.3 Fixed select not resetting causing high cpu usage on linux -// 1.2.1 inotify (linux) fixes and improvements, added extra functionality header for linux -// to manually add/remove directories manually to the watch handle, in case of large file sets -// - -#include -#include - -#ifndef DMON_API_DECL -# define DMON_API_DECL -#endif - -#ifndef DMON_API_IMPL -# define DMON_API_IMPL -#endif - -typedef struct { uint32_t id; } dmon_watch_id; - -// Pass these flags to `dmon_watch` -typedef enum dmon_watch_flags_t { - DMON_WATCHFLAGS_RECURSIVE = 0x1, // monitor all child directories - DMON_WATCHFLAGS_FOLLOW_SYMLINKS = 0x2, // resolve symlinks (linux only) - DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4, // TODO: not implemented yet - DMON_WATCHFLAGS_IGNORE_DIRECTORIES = 0x8 // TODO: not implemented yet -} dmon_watch_flags; - -// Action is what operation performed on the file. this value is provided by watch callback -typedef enum dmon_action_t { - DMON_ACTION_CREATE = 1, - DMON_ACTION_DELETE, - DMON_ACTION_MODIFY, - DMON_ACTION_MOVE -} dmon_action; - -typedef enum dmon_error_enum { - DMON_SUCCESS = 0, - DMON_ERROR_WATCH_DIR, - DMON_ERROR_OPEN_DIR, - DMON_ERROR_MONITOR_FAIL, - DMON_ERROR_UNSUPPORTED_SYMLINK, - DMON_ERROR_SUBDIR_LOCATION, - DMON_ERROR_END -} dmon_error; - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL const char *dmon_error_str(dmon_error err); - -DMON_API_DECL void dmon_init(void); -DMON_API_DECL void dmon_deinit(void); - -DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* rootdir, const char* filepath, - const char* oldfilepath, void* user), - uint32_t flags, void* user_data, dmon_error *error_code); -DMON_API_DECL void dmon_unwatch(dmon_watch_id id); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL - -#define DMON_OS_WINDOWS 0 -#define DMON_OS_MACOS 0 -#define DMON_OS_LINUX 0 - -#if defined(_WIN32) || defined(_WIN64) -# undef DMON_OS_WINDOWS -# define DMON_OS_WINDOWS 1 -#elif defined(__linux__) -# undef DMON_OS_LINUX -# define DMON_OS_LINUX 1 -#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -# undef DMON_OS_MACOS -# define DMON_OS_MACOS __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#else -# define DMON_OS 0 -# error "unsupported platform" -#endif - -#if DMON_OS_WINDOWS -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include -#elif DMON_OS_LINUX -# ifndef __USE_MISC -# define __USE_MISC -# endif -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -/* Recursive removed for Lite XL when using inotify. */ -# define LITE_XL_DISABLE_INOTIFY_RECURSIVE -# define DMON_LOG_DEBUG(s) -#elif DMON_OS_MACOS -# include -# include -# include -# include -# include -#endif - -#ifndef DMON_MALLOC -# include -# define DMON_MALLOC(size) malloc(size) -# define DMON_FREE(ptr) free(ptr) -# define DMON_REALLOC(ptr, size) realloc(ptr, size) -#endif - -#ifndef DMON_ASSERT -# include -# define DMON_ASSERT(e) assert(e) -#endif - -#ifndef DMON_LOG_DEBUG -# ifndef NDEBUG -# include -# define DMON_LOG_DEBUG(s) do { puts(s); } while(0) -# else -# define DMON_LOG_DEBUG(s) -# endif -#endif - -#ifndef DMON_MAX_WATCHES -# define DMON_MAX_WATCHES 64 -#endif - -#ifndef DMON_MAX_PATH -# define DMON_MAX_PATH 260 -#endif - -#define _DMON_UNUSED(x) (void)(x) - -#ifndef _DMON_PRIVATE -# if defined(__GNUC__) || defined(__clang__) -# define _DMON_PRIVATE __attribute__((unused)) static -# else -# define _DMON_PRIVATE static -# endif -#endif - -#include - -#ifndef _DMON_LOG_DEBUGF -# define _DMON_LOG_DEBUGF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_DEBUG(msg); } while(0); -#endif - -#ifndef dmon__min -# define dmon__min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef dmon__max -# define dmon__max(a, b) ((a) > (b) ? (a) : (b)) -#endif - -#ifndef dmon__swap -# define dmon__swap(a, b, _type) \ - do { \ - _type tmp = a; \ - a = b; \ - b = tmp; \ - } while (0) -#endif - -#ifndef dmon__make_id -# ifdef __cplusplus -# define dmon__make_id(id) {id} -# else -# define dmon__make_id(id) (dmon_watch_id) {id} -# endif -#endif // dmon__make_id - -_DMON_PRIVATE bool dmon__isrange(char ch, char from, char to) -{ - return (uint8_t)(ch - from) <= (uint8_t)(to - from); -} - -_DMON_PRIVATE bool dmon__isupperchar(char ch) -{ - return dmon__isrange(ch, 'A', 'Z'); -} - -_DMON_PRIVATE char dmon__tolowerchar(char ch) -{ - return ch + (dmon__isupperchar(ch) ? 0x20 : 0); -} - -_DMON_PRIVATE char* dmon__tolower(char* dst, int dst_sz, const char* str) -{ - int offset = 0; - int dst_max = dst_sz - 1; - while (*str && offset < dst_max) { - dst[offset++] = dmon__tolowerchar(*str); - ++str; - } - dst[offset] = '\0'; - return dst; -} - -_DMON_PRIVATE char* dmon__strcpy(char* dst, int dst_sz, const char* src) -{ - DMON_ASSERT(dst); - DMON_ASSERT(src); - - const int32_t len = (int32_t)strlen(src); - const int32_t _max = dst_sz - 1; - const int32_t num = (len < _max ? len : _max); - memcpy(dst, src, num); - dst[num] = '\0'; - - return dst; -} - -_DMON_PRIVATE char* dmon__unixpath(char* dst, int size, const char* path) -{ - size_t len = strlen(path); - len = dmon__min(len, (size_t)size - 1); - - for (size_t i = 0; i < len; i++) { - if (path[i] != '\\') - dst[i] = path[i]; - else - dst[i] = '/'; - } - dst[len] = '\0'; - return dst; -} - -#if DMON_OS_LINUX || DMON_OS_MACOS -_DMON_PRIVATE char* dmon__strcat(char* dst, int dst_sz, const char* src) -{ - int len = (int)strlen(dst); - return dmon__strcpy(dst + len, dst_sz - len, src); -} -#endif // DMON_OS_LINUX || DMON_OS_MACOS - -// stretchy buffer: https://github.com/nothings/stb/blob/master/stretchy_buffer.h -#define stb_sb_free(a) ((a) ? DMON_FREE(stb__sbraw(a)),0 : 0) -#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) -#define stb_sb_pop(a) (stb__sbn(a)--) -#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) -#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) -#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) -#define stb_sb_reset(a) ((a) ? (stb__sbn(a) = 0) : 0) - -#define stb__sbraw(a) ((int *) (a) - 2) -#define stb__sbm(a) stb__sbraw(a)[0] -#define stb__sbn(a) stb__sbraw(a)[1] - -#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) -#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) -#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) - -static void * stb__sbgrowf(void *arr, int increment, int itemsize) -{ - int dbl_cur = arr ? 2*stb__sbm(arr) : 0; - int min_needed = stb_sb_count(arr) + increment; - int m = dbl_cur > min_needed ? dbl_cur : min_needed; - int *p = (int *) DMON_REALLOC(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); - if (p) { - if (!arr) - p[1] = 0; - p[0] = m; - return p+2; - } else { - return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later - } -} - -// watcher callback (same as dmon.h's decleration) -typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const char*, const char*, void*); - -static const char *dmon__errors[] = { - "Success", - "Error watching directory", - "Error opening directory", - "Error enabling monitoring", - "Error support for symlink disabled", - "Error not a subdirectory", -}; - -DMON_API_IMPL const char *dmon_error_str(dmon_error err) { - DMON_ASSERT(err >= 0 && err < DMON_ERROR_END); - return dmon__errors[(int) err]; -} - -#if DMON_OS_WINDOWS -// IOCP (windows) -#ifdef UNICODE -# define _DMON_WINAPI_STR(name, size) wchar_t _##name[size]; MultiByteToWideChar(CP_UTF8, 0, name, -1, _##name, size) -#else -# define _DMON_WINAPI_STR(name, size) const char* _##name = name -#endif - -typedef struct dmon__win32_event { - char filepath[DMON_MAX_PATH]; - DWORD action; - dmon_watch_id watch_id; - bool skip; -} dmon__win32_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - OVERLAPPED overlapped; - HANDLE dir_handle; - uint8_t buffer[64512]; // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx - DWORD notify_filter; - dmon__watch_cb* watch_cb; - uint32_t watch_flags; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char old_filepath[DMON_MAX_PATH]; -} dmon__watch_state; - -typedef struct dmon__state { - int num_watches; - dmon__watch_state watches[DMON_MAX_WATCHES]; - HANDLE thread_handle; - CRITICAL_SECTION mutex; - volatile int modify_watches; - CRITICAL_SECTION modify_watches_mutex; - dmon__win32_event* events; - bool quit; - HANDLE wake_event; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE bool dmon__refresh_watch(dmon__watch_state* watch) -{ - return ReadDirectoryChangesW(watch->dir_handle, watch->buffer, sizeof(watch->buffer), - (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) ? TRUE : FALSE, - watch->notify_filter, NULL, &watch->overlapped, NULL) != 0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - CancelIo(watch->dir_handle); - CloseHandle(watch->overlapped.hEvent); - CloseHandle(watch->dir_handle); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -_DMON_PRIVATE void dmon__win32_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - if (ev->action == FILE_ACTION_MODIFIED || ev->action == FILE_ACTION_ADDED) { - // remove duplicate modifies on a single file - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_MODIFIED && - strcmp(ev->filepath, check_ev->filepath) == 0) { - check_ev->skip = true; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - switch (ev->action) { - case FILE_ACTION_ADDED: - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_MODIFIED: - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_RENAMED_OLD_NAME: { - // find the first occurance of the NEW_NAME - // this is somewhat API flaw that we have no reference for relating old and new files - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_RENAMED_NEW_NAME) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } break; - case FILE_ACTION_REMOVED: - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - } - } - stb_sb_reset(_dmon.events); -} - -static int dmon__safe_get_modify_watches() { - EnterCriticalSection(&_dmon.modify_watches_mutex); - const int value = _dmon.modify_watches; - LeaveCriticalSection(&_dmon.modify_watches_mutex); - return value; -} - -_DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) -{ - _DMON_UNUSED(arg); - HANDLE wait_handles[DMON_MAX_WATCHES + 1]; - - SYSTEMTIME starttm; - GetSystemTime(&starttm); - uint64_t msecs_elapsed = 0; - - while (!_dmon.quit) { - if (dmon__safe_get_modify_watches() || - !TryEnterCriticalSection(&_dmon.mutex)) { - Sleep(10); - continue; - } - - if (_dmon.num_watches == 0) { - Sleep(10); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - wait_handles[i] = watch->overlapped.hEvent; - } - - const int n = _dmon.num_watches; - wait_handles[n] = _dmon.wake_event; - const int n_pending = stb_sb_count(_dmon.events); - DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, n_pending > 0 ? 10 : INFINITE); - // NOTE: maybe we should check for WAIT_ABANDONED_ values if that can happen. - if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + n) { - dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; - - DWORD bytes; - if (HasOverlappedIoCompleted(&watch->overlapped) && - GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) { - char filepath[DMON_MAX_PATH]; - PFILE_NOTIFY_INFORMATION notify; - size_t offset = 0; - - if (bytes == 0) { - dmon__refresh_watch(watch); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - do { - notify = (PFILE_NOTIFY_INFORMATION)&watch->buffer[offset]; - - int count = WideCharToMultiByte(CP_UTF8, 0, notify->FileName, - notify->FileNameLength / sizeof(WCHAR), - filepath, DMON_MAX_PATH - 1, NULL, NULL); - filepath[count] = TEXT('\0'); - dmon__unixpath(filepath, sizeof(filepath), filepath); - - // TODO: ignore directories if flag is set - - if (stb_sb_count(_dmon.events) == 0) { - msecs_elapsed = 0; - } - dmon__win32_event wev = { { 0 }, notify->Action, watch->id, false }; - dmon__strcpy(wev.filepath, sizeof(wev.filepath), filepath); - stb_sb_push(_dmon.events, wev); - - offset += notify->NextEntryOffset; - } while (notify->NextEntryOffset > 0); - - if (!_dmon.quit) { - dmon__refresh_watch(watch); - } - } - } // if (WaitForMultipleObjects) - - SYSTEMTIME tm; - GetSystemTime(&tm); - LONG dt = - (tm.wSecond - starttm.wSecond) * 1000 + (tm.wMilliseconds - starttm.wMilliseconds); - starttm = tm; - msecs_elapsed += dt; - if (msecs_elapsed > 100 && stb_sb_count(_dmon.events) > 0) { - dmon__win32_process_events(); - msecs_elapsed = 0; - } - - LeaveCriticalSection(&_dmon.mutex); - } - return 0; -} - - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - InitializeCriticalSection(&_dmon.mutex); - InitializeCriticalSection(&_dmon.modify_watches_mutex); - - _dmon.thread_handle = - CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL); - _dmon.wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); - DMON_ASSERT(_dmon.thread_handle); - _dmon_init = true; -} - -static void dmon__enter_critical_wakeup(void) { - EnterCriticalSection(&_dmon.modify_watches_mutex); - _dmon.modify_watches = 1; - if (TryEnterCriticalSection(&_dmon.mutex) == 0) { - SetEvent(_dmon.wake_event); - EnterCriticalSection(&_dmon.mutex); - } - LeaveCriticalSection(&_dmon.modify_watches_mutex); -} - -static void dmon__leave_critical_wakeup(void) { - EnterCriticalSection(&_dmon.modify_watches_mutex); - _dmon.modify_watches = 0; - LeaveCriticalSection(&_dmon.modify_watches_mutex); - LeaveCriticalSection(&_dmon.mutex); -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - dmon__enter_critical_wakeup(); - if (_dmon.thread_handle != INVALID_HANDLE_VALUE) { - WaitForSingleObject(_dmon.thread_handle, INFINITE); - CloseHandle(_dmon.thread_handle); - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - dmon__leave_critical_wakeup(); - DeleteCriticalSection(&_dmon.mutex); - DeleteCriticalSection(&_dmon.modify_watches_mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - dmon__enter_critical_wakeup(); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), rootdir); - size_t rootdir_len = strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - _DMON_WINAPI_STR(rootdir, DMON_MAX_PATH); - watch->dir_handle = - CreateFile(_rootdir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (watch->dir_handle != INVALID_HANDLE_VALUE) { - watch->notify_filter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE | - FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_SIZE; - watch->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - if (watch->overlapped.hEvent == INVALID_HANDLE_VALUE || - !dmon__refresh_watch(watch)) { - dmon__unwatch(watch); - *error_code = DMON_ERROR_WATCH_DIR; - dmon__leave_critical_wakeup(); - return dmon__make_id(0); - } - } else { - *error_code = DMON_ERROR_OPEN_DIR; - dmon__leave_critical_wakeup(); - return dmon__make_id(0); - } - - dmon__leave_critical_wakeup(); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - dmon__enter_critical_wakeup(); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - dmon__leave_critical_wakeup(); -} - -#elif DMON_OS_LINUX -// inotify linux backend -#define _DMON_TEMP_BUFFSIZE ((sizeof(struct inotify_event) + PATH_MAX) * 1024) - -typedef struct dmon__watch_subdir { - char rootdir[DMON_MAX_PATH]; -} dmon__watch_subdir; - -typedef struct dmon__inotify_event { - char filepath[DMON_MAX_PATH]; - uint32_t mask; - uint32_t cookie; - dmon_watch_id watch_id; - bool skip; -} dmon__inotify_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - int fd; - uint32_t watch_flags; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - dmon__watch_subdir* subdirs; - int* wds; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[DMON_MAX_WATCHES]; - dmon__inotify_event* events; - int num_watches; - pthread_t thread_handle; - pthread_mutex_t mutex; - volatile int wait_flag; - pthread_mutex_t wait_flag_mutex; - int wake_event_pipe[2]; - bool quit; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -/* Implementation of recursive monitoring was removed on Linux for the Lite XL - * application. It is never used with recent version of Lite XL starting from 2.0.5 - * and recursive monitoring with inotify was always problematic and half-broken. - * Do not cover the new calling signature with error_code because not used by - * Lite XL. */ -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE -_DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask, - bool followlinks, dmon__watch_state* watch) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char watchdir[DMON_MAX_PATH]; - - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - if (entry->d_type == DT_DIR) { - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - entry_valid = true; - } - } else if (followlinks && entry->d_type == DT_LNK) { - char linkpath[PATH_MAX]; - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - char* r = realpath(watchdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - dmon__strcpy(watchdir, sizeof(watchdir), linkpath); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - int watchdir_len = (int)strlen(watchdir); - if (watchdir[watchdir_len - 1] != '/') { - watchdir[watchdir_len] = '/'; - watchdir[watchdir_len + 1] = '\0'; - } - int wd = inotify_add_watch(fd, watchdir, mask); - _DMON_UNUSED(wd); - DMON_ASSERT(wd != -1); - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // recurse - dmon__watch_recursive(watchdir, fd, mask, followlinks, watch); - } - } - closedir(dir); -} -#endif - -_DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd) -{ - const int* wds = watch->wds; - for (int i = 0, c = stb_sb_count(wds); i < c; i++) { - if (wd == wds[i]) { - return watch->subdirs[i].rootdir; - } - } - - return NULL; -} - -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE -_DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* dirname) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char newdir[DMON_MAX_PATH]; - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - bool is_dir = false; - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(newdir, sizeof(newdir), dirname); - dmon__strcat(newdir, sizeof(newdir), entry->d_name); - is_dir = (entry->d_type == DT_DIR); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir + strlen(watch->rootdir)); - } - - dmon__inotify_event dev = { { 0 }, IN_CREATE|(is_dir ? IN_ISDIR : 0), 0, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), subdir.rootdir); - stb_sb_push(_dmon.events, dev); - } - } - closedir(dir); -} -#endif - -_DMON_PRIVATE void dmon__inotify_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->mask & IN_MODIFY) { - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } else if ((ev->mask & IN_ISDIR) && (check_ev->mask & (IN_ISDIR|IN_MODIFY))) { - // in some cases, particularly when created files under sub directories - // there can be two modify events for a single subdir one with trailing slash and one without - // remove traling slash from both cases and test - int l1 = (int)strlen(ev->filepath); - int l2 = (int)strlen(check_ev->filepath); - if (ev->filepath[l1-1] == '/') ev->filepath[l1-1] = '\0'; - if (check_ev->filepath[l2-1] == '/') check_ev->filepath[l2-1] = '\0'; - if (strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } - } else if (ev->mask & IN_CREATE) { - bool loop_break = false; - for (int j = i + 1; j < c && !loop_break; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if ((check_ev->mask & IN_MOVED_FROM) && strcmp(ev->filepath, check_ev->filepath) == 0) { - // there is a case where some programs (like gedit): - // when we save, it creates a temp file, and moves it to the file being modified - // search for these cases and remove all of them - for (int k = j + 1; k < c; k++) { - dmon__inotify_event* third_ev = &_dmon.events[k]; - if (third_ev->mask & IN_MOVED_TO && check_ev->cookie == third_ev->cookie) { - third_ev->mask = IN_MODIFY; // change to modified - ev->skip = check_ev->skip = true; - loop_break = true; - break; - } - } - } else if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - // Another case is that file is copied. CREATE and MODIFY happens sequentially - // so we ignore MODIFY event - check_ev->skip = true; - } - } - } else if (ev->mask & IN_MOVED_FROM) { - bool move_valid = false; - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // in some environments like nautilus file explorer: - // when a file is deleted, it is moved to recycle bin - // so if the destination of the move is not valid, it's probably DELETE - if (!move_valid) { - ev->mask = IN_DELETE; - } - } else if (ev->mask & IN_MOVED_TO) { - bool move_valid = false; - for (int j = 0; j < i; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_FROM && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // in some environments like nautilus file explorer: - // when a file is deleted, it is moved to recycle bin, on undo it is moved back it - // so if the destination of the move is not valid, it's probably CREATE - if (!move_valid) { - ev->mask = IN_CREATE; - } - } else if (ev->mask & IN_DELETE) { - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - // if the file is DELETED and then MODIFIED after, just ignore the modify event - if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - check_ev->skip = true; - break; - } - } - } - } - - // trigger user callbacks - for (int i = 0; i < stb_sb_count(_dmon.events); i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->mask & IN_CREATE) { -# ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE - if (ev->mask & IN_ISDIR) { - if (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) { - char watchdir[DMON_MAX_PATH]; - dmon__strcpy(watchdir, sizeof(watchdir), watch->rootdir); - dmon__strcat(watchdir, sizeof(watchdir), ev->filepath); - dmon__strcat(watchdir, sizeof(watchdir), "/"); - uint32_t mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watchdir, mask); - // Removing the assertion below because it was giving errors for some reason - // when building a new package. - // _DMON_UNUSED(wd); - // DMON_ASSERT(wd != -1); - if (wd == -1) continue; - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // some directories may be already created, for instance, with the command: mkdir -p - // so we will enumerate them manually and add them to the events - dmon__gather_recursive(watch, watchdir); - ev = &_dmon.events[i]; // gotta refresh the pointer because it may be relocated - } - } -# endif - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - else if (ev->mask & IN_MODIFY) { - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - else if (ev->mask & IN_MOVED_FROM) { - for (int j = i + 1; j < stb_sb_count(_dmon.events); j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } - else if (ev->mask & IN_DELETE) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -_DMON_PRIVATE int dmon__safe_get_wait_flag() { - pthread_mutex_lock(&_dmon.wait_flag_mutex); - const int value = _dmon.wait_flag; - pthread_mutex_unlock(&_dmon.wait_flag_mutex); - return value; -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - static uint8_t buff[_DMON_TEMP_BUFFSIZE]; - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - struct timeval timeout; - uint64_t usecs_elapsed = 0; - - struct timeval starttm; - gettimeofday(&starttm, 0); - - while (!_dmon.quit) { - nanosleep(&req, &rem); - if (_dmon.num_watches == 0 || - dmon__safe_get_wait_flag() || - pthread_mutex_trylock(&_dmon.mutex) != 0) { - continue; - } - - // Create read FD set - fd_set rfds; - FD_ZERO(&rfds); - const int n = _dmon.num_watches; - int nfds = 0; - for (int i = 0; i < n; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - FD_SET(watch->fd, &rfds); - if (watch->fd > nfds) - nfds = watch->fd; - } - int wake_fd = _dmon.wake_event_pipe[0]; - FD_SET(wake_fd, &rfds); - if (wake_fd > nfds) - nfds = wake_fd; - - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - const int n_pending = stb_sb_count(_dmon.events); - if (select(nfds + 1, &rfds, NULL, NULL, n_pending > 0 ? &timeout : NULL)) { - if (FD_ISSET(wake_fd, &rfds)) { - char read_char; - read(wake_fd, &read_char, 1); - } - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (FD_ISSET(watch->fd, &rfds)) { - ssize_t offset = 0; - ssize_t len = read(watch->fd, buff, _DMON_TEMP_BUFFSIZE); - if (len <= 0) { - continue; - } - - while (offset < len) { - struct inotify_event* iev = (struct inotify_event*)&buff[offset]; - - const char *subdir = dmon__find_subdir(watch, iev->wd); - if (subdir) { - char filepath[DMON_MAX_PATH]; - dmon__strcpy(filepath, sizeof(filepath), subdir); - dmon__strcat(filepath, sizeof(filepath), iev->name); - - // TODO: ignore directories if flag is set - - if (stb_sb_count(_dmon.events) == 0) { - usecs_elapsed = 0; - } - dmon__inotify_event dev = { { 0 }, iev->mask, iev->cookie, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), filepath); - stb_sb_push(_dmon.events, dev); - } - - offset += sizeof(struct inotify_event) + iev->len; - } - } - } - } - - struct timeval tm; - gettimeofday(&tm, 0); - long dt = (tm.tv_sec - starttm.tv_sec) * 1000000 + tm.tv_usec - starttm.tv_usec; - starttm = tm; - usecs_elapsed += dt; - if (usecs_elapsed > 100000 && stb_sb_count(_dmon.events) > 0) { - dmon__inotify_process_events(); - usecs_elapsed = 0; - } - - pthread_mutex_unlock(&_dmon.mutex); - } - return 0x0; -} - -_DMON_PRIVATE void dmon__mutex_wakeup_lock(void) { - pthread_mutex_lock(&_dmon.wait_flag_mutex); - _dmon.wait_flag = 1; - if (pthread_mutex_trylock(&_dmon.mutex) != 0) { - char send_char = 1; - write(_dmon.wake_event_pipe[1], &send_char, 1); - pthread_mutex_lock(&_dmon.mutex); - } - _dmon.wait_flag = 0; - pthread_mutex_unlock(&_dmon.wait_flag_mutex); -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - close(watch->fd); - stb_sb_free(watch->subdirs); - stb_sb_free(watch->wds); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - _dmon.wait_flag = 0; - int ret_pipe = pipe(_dmon.wake_event_pipe); - DMON_ASSERT(ret_pipe == 0); - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - dmon__mutex_wakeup_lock(); - pthread_join(_dmon.thread_handle, NULL); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_unlock(&_dmon.mutex); - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - dmon__mutex_wakeup_lock(); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (root_st.st_mode & S_IRUSR) != S_IRUSR) { - *error_code = DMON_ERROR_OPEN_DIR; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } else { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - watch->fd = inotify_init(); - if (watch->fd < -1) { - *error_code = DMON_ERROR_MONITOR_FAIL; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watch->rootdir, inotify_mask); - if (wd < 0) { - *error_code = DMON_ERROR_WATCH_DIR; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), ""); // root dir is just a dummy entry - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // recursive mode: enumarate all child directories and add them to watch -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE - if (flags & DMON_WATCHFLAGS_RECURSIVE) { - dmon__watch_recursive(watch->rootdir, watch->fd, inotify_mask, - (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) ? true : false, watch); - } -#endif - - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - dmon__mutex_wakeup_lock(); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); -} -#elif DMON_OS_MACOS -// FSEvents MacOS backend -typedef struct dmon__fsevent_event { - char filepath[DMON_MAX_PATH]; - uint64_t event_id; - long event_flags; - dmon_watch_id watch_id; - bool skip; - bool move_valid; -} dmon__fsevent_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - uint32_t watch_flags; - FSEventStreamRef fsev_stream_ref; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char rootdir_unmod[DMON_MAX_PATH]; - bool init; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[DMON_MAX_WATCHES]; - dmon__fsevent_event* events; - int num_watches; - volatile int modify_watches; - pthread_t thread_handle; - dispatch_semaphore_t thread_sem; - pthread_mutex_t mutex; - CFRunLoopRef cf_loop_ref; - CFAllocatorRef cf_alloc_ref; - bool quit; -} dmon__state; - -union dmon__cast_userdata { - void* ptr; - uint32_t id; -}; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE void* dmon__cf_malloc(CFIndex size, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_MALLOC(size); -} - -_DMON_PRIVATE void dmon__cf_free(void* ptr, void* info) -{ - _DMON_UNUSED(info); - DMON_FREE(ptr); -} - -_DMON_PRIVATE void* dmon__cf_realloc(void* ptr, CFIndex newsize, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_REALLOC(ptr, (size_t)newsize); -} - -_DMON_PRIVATE void dmon__fsevent_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->event_flags & kFSEventStreamEventFlagItemModified) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemModified) && - strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } else if ((ev->event_flags & kFSEventStreamEventFlagItemRenamed) && !ev->move_valid) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) && - check_ev->event_id == (ev->event_id + 1)) { - ev->move_valid = check_ev->move_valid = true; - break; - } - } - - // in some environments like finder file explorer: - // when a file is deleted, it is moved to recycle bin - // so if the destination of the move is not valid, it's probably DELETE or CREATE - // decide CREATE if file exists - if (!ev->move_valid) { - ev->event_flags &= ~kFSEventStreamEventFlagItemRenamed; - - char abs_filepath[DMON_MAX_PATH]; - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id-1]; - dmon__strcpy(abs_filepath, sizeof(abs_filepath), watch->rootdir); - dmon__strcat(abs_filepath, sizeof(abs_filepath), ev->filepath); - - struct stat root_st; - if (stat(abs_filepath, &root_st) != 0) { - ev->event_flags |= kFSEventStreamEventFlagItemRemoved; - } else { - ev->event_flags |= kFSEventStreamEventFlagItemCreated; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->event_flags & kFSEventStreamEventFlagItemCreated) { - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } else if (ev->event_flags & kFSEventStreamEventFlagItemModified) { - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } else if (ev->event_flags & kFSEventStreamEventFlagItemRenamed) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if (check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir_unmod, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } else if (ev->event_flags & kFSEventStreamEventFlagItemRemoved) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - - _dmon.cf_loop_ref = CFRunLoopGetCurrent(); - dispatch_semaphore_signal(_dmon.thread_sem); - - while (!_dmon.quit) { - if (_dmon.modify_watches || pthread_mutex_trylock(&_dmon.mutex) != 0) { - nanosleep(&req, &rem); - continue; - } - - if (_dmon.num_watches == 0) { - nanosleep(&req, &rem); - pthread_mutex_unlock(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (!watch->init) { - DMON_ASSERT(watch->fsev_stream_ref); - FSEventStreamScheduleWithRunLoop(watch->fsev_stream_ref, _dmon.cf_loop_ref, - kCFRunLoopDefaultMode); - FSEventStreamStart(watch->fsev_stream_ref); - - watch->init = true; - } - } - - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, kCFRunLoopRunTimedOut); - dmon__fsevent_process_events(); - - pthread_mutex_unlock(&_dmon.mutex); - } - - CFRunLoopStop(_dmon.cf_loop_ref); - _dmon.cf_loop_ref = NULL; - return 0x0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - if (watch->fsev_stream_ref) { - FSEventStreamStop(watch->fsev_stream_ref); - FSEventStreamInvalidate(watch->fsev_stream_ref); - FSEventStreamRelease(watch->fsev_stream_ref); - watch->fsev_stream_ref = NULL; - } - - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - CFAllocatorContext cf_alloc_ctx = { 0 }; - cf_alloc_ctx.allocate = dmon__cf_malloc; - cf_alloc_ctx.deallocate = dmon__cf_free; - cf_alloc_ctx.reallocate = dmon__cf_realloc; - _dmon.cf_alloc_ref = CFAllocatorCreate(NULL, &cf_alloc_ctx); - - _dmon.thread_sem = dispatch_semaphore_create(0); - DMON_ASSERT(_dmon.thread_sem); - - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - - // wait for thread to initialize loop object - dispatch_semaphore_wait(_dmon.thread_sem, DISPATCH_TIME_FOREVER); - - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - pthread_join(_dmon.thread_handle, NULL); - - dispatch_release(_dmon.thread_sem); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - if (_dmon.cf_alloc_ref) { - CFRelease(_dmon.cf_alloc_ref); - } - - _dmon_init = false; -} - -_DMON_PRIVATE void dmon__fsevent_callback(ConstFSEventStreamRef stream_ref, void* user_data, - size_t num_events, void* event_paths, - const FSEventStreamEventFlags event_flags[], - const FSEventStreamEventId event_ids[]) -{ - _DMON_UNUSED(stream_ref); - - union dmon__cast_userdata _userdata; - _userdata.ptr = user_data; - dmon_watch_id watch_id = dmon__make_id(_userdata.id); - DMON_ASSERT(watch_id.id > 0); - dmon__watch_state* watch = &_dmon.watches[watch_id.id - 1]; - char abs_filepath[DMON_MAX_PATH]; - char abs_filepath_lower[DMON_MAX_PATH]; - - for (size_t i = 0; i < num_events; i++) { - const char* filepath = ((const char**)event_paths)[i]; - long flags = (long)event_flags[i]; - uint64_t event_id = (uint64_t)event_ids[i]; - dmon__fsevent_event ev; - memset(&ev, 0x0, sizeof(ev)); - - dmon__strcpy(abs_filepath, sizeof(abs_filepath), filepath); - dmon__unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath); - - // normalize path, so it would be the same on both MacOS file-system types (case/nocase) - dmon__tolower(abs_filepath_lower, sizeof(abs_filepath), abs_filepath); - DMON_ASSERT(strstr(abs_filepath_lower, watch->rootdir) == abs_filepath_lower); - - // strip the root dir from the begining - dmon__strcpy(ev.filepath, sizeof(ev.filepath), abs_filepath + strlen(watch->rootdir)); - - ev.event_flags = flags; - ev.event_id = event_id; - ev.watch_id = watch_id; - stb_sb_push(_dmon.events, ev); - } -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - __sync_lock_test_and_set(&_dmon.modify_watches, 1); - pthread_mutex_lock(&_dmon.mutex); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (root_st.st_mode & S_IRUSR) != S_IRUSR) { - *error_code = DMON_ERROR_OPEN_DIR; - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(0); - } - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } else { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(0); - } - } else { - char rootdir_abspath[DMON_MAX_PATH]; - if (realpath(rootdir, rootdir_abspath) != NULL) { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir_abspath); - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - } - - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - dmon__strcpy(watch->rootdir_unmod, sizeof(watch->rootdir_unmod), watch->rootdir); - dmon__tolower(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // create FS objects - CFStringRef cf_dir = CFStringCreateWithCString(NULL, watch->rootdir_unmod, kCFStringEncodingUTF8); - CFArrayRef cf_dirarr = CFArrayCreate(NULL, (const void**)&cf_dir, 1, NULL); - - FSEventStreamContext ctx; - union dmon__cast_userdata userdata; - userdata.id = id; - ctx.version = 0; - ctx.info = userdata.ptr; - ctx.retain = NULL; - ctx.release = NULL; - ctx.copyDescription = NULL; - watch->fsev_stream_ref = FSEventStreamCreate(_dmon.cf_alloc_ref, dmon__fsevent_callback, &ctx, - cf_dirarr, kFSEventStreamEventIdSinceNow, 0.25, - kFSEventStreamCreateFlagFileEvents); - - - CFRelease(cf_dirarr); - CFRelease(cf_dir); - - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - __sync_lock_test_and_set(&_dmon.modify_watches, 1); - pthread_mutex_lock(&_dmon.mutex); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); -} - -#endif - -#endif // DMON_IMPL -#endif // __DMON_H__ diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h deleted file mode 100644 index 97631520..00000000 --- a/lib/dmon/dmon_extra.h +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef __DMON_EXTRA_H__ -#define __DMON_EXTRA_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Extra header functionality for dmon.h for the backend based on inotify -// -// Add/Remove directory functions: -// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take -// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one -// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user -// will be reached. The default maximum is 8192. -// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the -// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched. -// The function dmon_watch_add and dmon_watch_rm are used to this purpose. -// - -#ifndef __DMON_H__ -#error "Include 'dmon.h' before including this file" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir, dmon_error *error_code); -DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL -#if DMON_OS_LINUX -DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_error *error_code) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) { - dmon__mutex_wakeup_lock(); - } - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - // check if the directory exists - // if watchdir contains absolute/root-included path, try to strip the rootdir from it - // else, we assume that watchdir is correct, so save it as it is - struct stat st; - dmon__watch_subdir subdir; - // FIXME: check if it is a symlink and respect DMON_WATCHFLAGS_FOLLOW_SYMLINKS - // to resolve the link. - if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - } else { - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), watchdir); - if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - } - - int dirlen = (int)strlen(subdir.rootdir); - if (subdir.rootdir[dirlen - 1] != '/') { - subdir.rootdir[dirlen] = '/'; - subdir.rootdir[dirlen + 1] = '\0'; - } - - // check that the directory is not already added - for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) { - if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) { - *error_code = DMON_ERROR_SUBDIR_LOCATION; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - } - - const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir); - int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask); - if (wd == -1) { - *error_code = DMON_ERROR_WATCH_DIR; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - - return true; -} - -DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) { - dmon__mutex_wakeup_lock(); - } - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - char subdir[DMON_MAX_PATH]; - dmon__strcpy(subdir, sizeof(subdir), watchdir); - if (strstr(subdir, watch->rootdir) == subdir) { - dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir)); - } - - int dirlen = (int)strlen(subdir); - if (subdir[dirlen - 1] != '/') { - subdir[dirlen] = '/'; - subdir[dirlen + 1] = '\0'; - } - - int i, c = stb_sb_count(watch->subdirs); - for (i = 0; i < c; i++) { - if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) { - break; - } - } - if (i >= c) { - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - inotify_rm_watch(watch->fd, watch->wds[i]); - - /* Remove entry from subdirs and wds by swapping position with the last entry */ - watch->subdirs[i] = stb_sb_last(watch->subdirs); - stb_sb_pop(watch->subdirs); - - watch->wds[i] = stb_sb_last(watch->wds); - stb_sb_pop(watch->wds); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return true; -} -#endif // DMON_OS_LINUX -#endif // DMON_IMPL - -#endif // __DMON_EXTRA_H__ - diff --git a/lib/dmon/meson.build b/lib/dmon/meson.build deleted file mode 100644 index 83edd1c9..00000000 --- a/lib/dmon/meson.build +++ /dev/null @@ -1 +0,0 @@ -lite_includes += include_directories('.') diff --git a/licenses/licenses.md b/licenses/licenses.md index 928d88d9..8005c4a7 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -22,33 +22,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## septag/dmon - -Copyright 2019 Sepehr Taghdisian. All rights reserved. - -https://github.com/septag/dmon - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ## Fira Sans Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. diff --git a/meson.build b/meson.build index 5b4662c3..b3b03257 100644 --- a/meson.build +++ b/meson.build @@ -69,9 +69,6 @@ endif if not get_option('source-only') libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) - threads_dep = dependency('threads') - - lua_fallback = ['lua', 'lua_dep'] lua_quick_fallback = [] if get_option('wrap_mode') == 'forcefallback' @@ -97,7 +94,7 @@ if not get_option('source-only') default_options: ['default_library=static'] ) - lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep] + lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl] endif #=============================================================================== # Install Configuration @@ -151,7 +148,6 @@ configure_file( ) if not get_option('source-only') - subdir('lib/dmon') subdir('src') subdir('scripts') endif diff --git a/resources/notes-dmon-integration.md b/resources/notes-dmon-integration.md deleted file mode 100644 index 5179df40..00000000 --- a/resources/notes-dmon-integration.md +++ /dev/null @@ -1,54 +0,0 @@ - -`core.set_project_dir`: - Reset project directories and set its directory. - It chdir into the directory, empty the `core.project_directories` and add - the given directory. - `core.add_project_directory`: - Add a new top-level directory to the project. - Also called from modules and commands outside core.init. - local function `scan_project_folder`: - Scan all files for a given top-level project directory. - Can emit a warning about file limit. - Called only from within core.init module. - -`core.scan_project_subdir`: (before was named `core.scan_project_folder`) - scan a single folder, without recursion. Used when too many files. - -Local function `scan_project_folder`: - Populate the project folder top directory. Done only once when the directory - is added to the project. - -`core.add_project_directory`: - Add a new top-level folder to the project. - -`core.set_project_dir`: - Set the initial project directory. - -`core.dir_rescan_add_job`: - Add a job to rescan after an elapsed time a project's subdirectory to fix for any - changes. - -Local function `rescan_project_subdir`: - Rescan a project's subdirectory, compare to the current version and patch the list if - a difference is found. - - -`core.project_scan_thread`: - Should disappear now that we use dmon. - - -`core.project_scan_topdir`: - New function to scan a top level project folder. - - -`config.project_scan_rate`: -`core.project_scan_thread_id`: -`core.reschedule_project_scan`: -`core.project_files_limit`: - A eliminer. - -`core.get_project_files`: - To be fixed. Use `find_project_files_co` for a single directory - -In TreeView remove usage of self.last to detect new scan that changed the files list. - diff --git a/src/api/api.c b/src/api/api.c index 9f74f6b5..1a6e516d 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -1,20 +1,23 @@ #include "api.h" - int luaopen_system(lua_State *L); int luaopen_renderer(lua_State *L); int luaopen_regex(lua_State *L); int luaopen_process(lua_State *L); +int luaopen_dirmonitor(lua_State* L); static const luaL_Reg libs[] = { - { "system", luaopen_system }, - { "renderer", luaopen_renderer }, - { "regex", luaopen_regex }, - { "process", luaopen_process }, + { "system", luaopen_system }, + { "renderer", luaopen_renderer }, + { "regex", luaopen_regex }, + { "process", luaopen_process }, + { "dirmonitor", luaopen_dirmonitor }, { NULL, NULL } }; + void api_load_libs(lua_State *L) { for (int i = 0; libs[i].name; i++) luaL_requiref(L, libs[i].name, libs[i].func, 1); } + diff --git a/src/api/api.h b/src/api/api.h index e7bc57ea..c11fbdb3 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -7,6 +7,7 @@ #define API_TYPE_FONT "Font" #define API_TYPE_PROCESS "Process" +#define API_TYPE_DIRMONITOR "Dirmonitor" #define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key)) diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c new file mode 100644 index 00000000..7f298724 --- /dev/null +++ b/src/api/dirmonitor.c @@ -0,0 +1,203 @@ +#include "api.h" +#include +#ifdef _WIN32 + #include +#elif __linux__ + #include + #include +#else + #include +#endif +#include +#include +#include +#include +#include +#include + +/* +This is *slightly* a clusterfuck. Normally, we'd +have windows wait on a list of handles like inotify, +however, MAXIMUM_WAIT_OBJECTS is 64. Yes, seriously. + +So, for windows, we are recursive. +*/ +struct dirmonitor { + int fd; + #if _WIN32 + HANDLE handle; + char buffer[8192]; + OVERLAPPED overlapped; + bool running; + #endif +}; + +struct dirmonitor* init_dirmonitor() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + #ifndef _WIN32 + #if __linux__ + monitor->fd = inotify_init1(IN_NONBLOCK); + #else + monitor->fd = kqueue(); + #endif + #endif + return monitor; +} + +#if _WIN32 +static void close_monitor_handle(struct dirmonitor* monitor) { + if (monitor->handle) { + if (monitor->running) { + BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); + DWORD error = GetLastError(); + if (result == TRUE || error != ERROR_NOT_FOUND) { + DWORD bytes_transferred; + GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); + } + monitor->running = false; + } + CloseHandle(monitor->handle); + } + monitor->handle = NULL; +} +#endif + +void deinit_dirmonitor(struct dirmonitor* monitor) { + #if _WIN32 + close_monitor_handle(monitor); + #else + close(monitor->fd); + #endif + free(monitor); +} + +int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + #if _WIN32 + if (!monitor->running) { + if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) + return GetLastError(); + monitor->running = true; + } + DWORD bytes_transferred; + if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { + int error = GetLastError(); + return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; + } + monitor->running = false; + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { + change_callback(info->FileNameLength, (char*)info->FileName, data); + if (!info->NextEntryOffset) + break; + } + monitor->running = false; + return 0; + #elif __linux__ + char buf[PATH_MAX + sizeof(struct inotify_event)]; + while (1) { + ssize_t len = read(monitor->fd, buf, sizeof(buf)); + if (len == -1 && errno != EAGAIN) + return errno; + if (len <= 0) + return 0; + for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ((struct inotify_event*)ptr)->len) + change_callback(((const struct inotify_event *) ptr)->wd, NULL, data); + } + #else + struct kevent event; + while (1) { + struct timespec tm = {0}; + int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); + if (nev == -1) + return errno; + if (nev <= 0) + return 0; + change_callback(event.ident, NULL, data); + } + #endif +} + +int add_dirmonitor(struct dirmonitor* monitor, const char* path) { + #if _WIN32 + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) + return 1; + monitor->handle = NULL; + return -1; + #elif __linux__ + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + #else + int fd = open(path, O_RDONLY); + struct kevent change; + EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); + kevent(monitor->fd, &change, 1, NULL, 0, NULL); + return fd; + #endif +} + +void remove_dirmonitor(struct dirmonitor* monitor, int fd) { + #if _WIN32 + close_monitor_handle(monitor); + #elif __linux__ + inotify_rm_watch(monitor->fd, fd); + #else + close(fd); + #endif +} + +static int f_check_dir_callback(int watch_id, const char* path, void* L) { + #if _WIN32 + char buffer[PATH_MAX*4]; + int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); + lua_pushlstring(L, buffer, count); + #else + lua_pushnumber(L, watch_id); + #endif + lua_pcall(L, 1, 1, 0); + int result = lua_toboolean(L, -1); + lua_pop(L, 1); + return !result; +} + +static int f_dirmonitor_new(lua_State* L) { + struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**)); + *monitor = init_dirmonitor(); + luaL_setmetatable(L, API_TYPE_DIRMONITOR); + return 1; +} + +static int f_dirmonitor_gc(lua_State* L) { + deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))); + return 0; +} + +static int f_dirmonitor_watch(lua_State *L) { + lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2))); + return 1; +} + +static int f_dirmonitor_unwatch(lua_State *L) { + remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2)); + return 0; +} + +static int f_dirmonitor_check(lua_State* L) { + lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L)); + return 1; +} +static const luaL_Reg dirmonitor_lib[] = { + { "new", f_dirmonitor_new }, + { "__gc", f_dirmonitor_gc }, + { "watch", f_dirmonitor_watch }, + { "unwatch", f_dirmonitor_unwatch }, + { "check", f_dirmonitor_check }, + {NULL, NULL} +}; + +int luaopen_dirmonitor(lua_State* L) { + luaL_newmetatable(L, API_TYPE_DIRMONITOR); + luaL_setfuncs(L, dirmonitor_lib, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + return 1; +} diff --git a/src/api/system.c b/src/api/system.c index 80c0a2ae..f1f9c3c7 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -7,7 +7,6 @@ #include #include #include "api.h" -#include "../dirmonitor.h" #include "../rencache.h" #ifdef _WIN32 #include @@ -255,26 +254,6 @@ top: lua_pushinteger(L, e.wheel.y); return 2; - case SDL_USEREVENT: - lua_pushstring(L, "dirchange"); - lua_pushinteger(L, e.user.code >> 16); - switch (e.user.code & 0xffff) { - case DMON_ACTION_DELETE: - lua_pushstring(L, "delete"); - break; - case DMON_ACTION_CREATE: - lua_pushstring(L, "create"); - break; - case DMON_ACTION_MODIFY: - lua_pushstring(L, "modify"); - break; - default: - return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff); - } - lua_pushstring(L, e.user.data1); - free(e.user.data1); - return 4; - default: goto top; } @@ -592,29 +571,27 @@ static struct f_type_names fs_names[] = { { 0x0, NULL }, }; -static int f_get_fs_type(lua_State *L) { - const char *path = luaL_checkstring(L, 1); - struct statfs buf; - int status = statfs(path, &buf); - if (status != 0) { - return luaL_error(L, "error calling statfs on %s", path); - } - for (int i = 0; fs_names[i].magic; i++) { - if (fs_names[i].magic == buf.f_type) { - lua_pushstring(L, fs_names[i].name); - return 1; - } - } - lua_pushstring(L, "unknown"); - return 1; -} -#else -static int f_return_unknown(lua_State *L) { - lua_pushstring(L, "unknown"); - return 1; -} #endif +static int f_get_fs_type(lua_State *L) { + #if __linux__ + const char *path = luaL_checkstring(L, 1); + struct statfs buf; + int status = statfs(path, &buf); + if (status != 0) { + return luaL_error(L, "error calling statfs on %s", path); + } + for (int i = 0; fs_names[i].magic; i++) { + if (fs_names[i].magic == buf.f_type) { + lua_pushstring(L, fs_names[i].name); + return 1; + } + } + #endif + lua_pushstring(L, "unknown"); + return 1; +} + static int f_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -815,66 +792,6 @@ static int f_load_native_plugin(lua_State *L) { return result; } -static int f_watch_dir(lua_State *L) { - const char *path = luaL_checkstring(L, 1); - /* On linux we watch non-recursively and we add/remove each sub-directory explicitly - * using the function system.watch_dir_add/rm. On other systems we watch recursively - * and system.watch_dir_add/rm are dummy functions that always returns true. */ -#if __linux__ - const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS; -#elif __APPLE__ - const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS | DMON_WATCHFLAGS_RECURSIVE; -#else - const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE; -#endif - dmon_error error; - dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL, &error); - if (watch_id.id == 0) { - lua_pushnil(L); - lua_pushstring(L, dmon_error_str(error)); - return 2; - } - lua_pushinteger(L, watch_id.id); - return 1; -} - -static int f_unwatch_dir(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - dmon_unwatch(watch_id); - return 0; -} - -#if __linux__ -static int f_watch_dir_add(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - const char *subdir = luaL_checkstring(L, 2); - dmon_error error_code; - int success = dmon_watch_add(watch_id, subdir, &error_code); - if (!success) { - lua_pushboolean(L, 0); - lua_pushstring(L, dmon_error_str(error_code)); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -static int f_watch_dir_rm(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - const char *subdir = luaL_checkstring(L, 2); - lua_pushboolean(L, dmon_watch_rm(watch_id, subdir)); - return 1; -} -#else -static int f_return_true(lua_State *L) { - lua_pushboolean(L, 1); - return 1; -} -#endif - #ifdef _WIN32 #define PATHSEP '\\' #else @@ -961,18 +878,8 @@ static const luaL_Reg lib[] = { { "fuzzy_match", f_fuzzy_match }, { "set_window_opacity", f_set_window_opacity }, { "load_native_plugin", f_load_native_plugin }, - { "watch_dir", f_watch_dir }, - { "unwatch_dir", f_unwatch_dir }, { "path_compare", f_path_compare }, -#if __linux__ - { "watch_dir_add", f_watch_dir_add }, - { "watch_dir_rm", f_watch_dir_rm }, { "get_fs_type", f_get_fs_type }, -#else - { "watch_dir_add", f_return_true }, - { "watch_dir_rm", f_return_true }, - { "get_fs_type", f_return_unknown }, -#endif { NULL, NULL } }; diff --git a/src/dirmonitor.c b/src/dirmonitor.c deleted file mode 100644 index 0063e400..00000000 --- a/src/dirmonitor.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include - -#include - -#define DMON_IMPL -#include "dmon.h" -#include "dmon_extra.h" - -#include "dirmonitor.h" - -static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const char *filepath) { - SDL_Event ev; - const int size = strlen(filepath) + 1; - /* The string allocated below should be deallocated as soon as the event is - treated in the SDL main loop. */ - char *new_filepath = malloc(size); - if (!new_filepath) return; - memcpy(new_filepath, filepath, size); -#ifdef _WIN32 - for (int i = 0; i < size; i++) { - if (new_filepath[i] == '/') { - new_filepath[i] = '\\'; - } - } -#endif - SDL_zero(ev); - ev.type = SDL_USEREVENT; - ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff); - ev.user.data1 = new_filepath; - SDL_PushEvent(&ev); -} - -void dirmonitor_init() { - dmon_init(); - /* In theory we should register our user event but since we - have just one type of user event this is not really needed. */ - /* sdl_dmon_event_type = SDL_RegisterEvents(1); */ -} - -void dirmonitor_deinit() { - dmon_deinit(); -} - -void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir, - const char *filepath, const char *oldfilepath, void *user) -{ - (void) rootdir; - (void) user; - switch (action) { - case DMON_ACTION_MOVE: - send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath); - send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath); - break; - default: - send_sdl_event(watch_id, action, filepath); - } -} - diff --git a/src/dirmonitor.h b/src/dirmonitor.h deleted file mode 100644 index 074a9ae8..00000000 --- a/src/dirmonitor.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef DIRMONITOR_H -#define DIRMONITOR_H - -#include - -#include "dmon.h" -#include "dmon_extra.h" - -void dirmonitor_init(); -void dirmonitor_deinit(); -void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir, - const char *filepath, const char *oldfilepath, void *user); - -#endif - diff --git a/src/main.c b/src/main.c index 9fcbeab6..94d2674b 100644 --- a/src/main.c +++ b/src/main.c @@ -7,15 +7,13 @@ #ifdef _WIN32 #include -#elif __linux__ +#elif __linux__ || __FreeBSD__ #include #include #elif __APPLE__ #include #endif -#include "dirmonitor.h" - SDL_Window *window; @@ -108,8 +106,6 @@ int main(int argc, char **argv) { SDL_DisplayMode dm; SDL_GetCurrentDisplayMode(0, &dm); - dirmonitor_init(); - window = SDL_CreateWindow( "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); @@ -192,7 +188,6 @@ init_lua: lua_close(L); ren_free_window_resources(); - dirmonitor_deinit(); return EXIT_SUCCESS; } diff --git a/src/meson.build b/src/meson.build index 1eaf87fd..8a4f1272 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,10 +1,10 @@ lite_sources = [ 'api/api.c', + 'api/dirmonitor.c', 'api/renderer.c', 'api/regex.c', 'api/system.c', 'api/process.c', - 'dirmonitor.c', 'renderer.c', 'renwindow.c', 'rencache.c', From 240afa7abd42c10710284f8fb6b403f0e608a584 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 6 Mar 2022 21:03:54 +0100 Subject: [PATCH 155/409] Load project module on startup --- data/core/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 4b2b0ea0..0d8c8e51 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -723,10 +723,11 @@ function core.init() local got_user_error, got_project_error = not core.load_user_directory() local project_dir_abs = system.absolute_path(project_dir) - -- We prevent set_project_dir below to effectively add and scan the directory becaese tha + -- We prevent set_project_dir below to effectively add and scan the directory because the -- project module and its ignore files is not yet loaded. local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs) if set_project_ok then + got_project_error = not core.load_project_module() if project_dir_explicit then update_recents_project("add", project_dir_abs) end From d7d88a2037b51c7dc6c9280152649dc96add5bc8 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 6 Mar 2022 19:34:12 -0400 Subject: [PATCH 156/409] init: load core views before user plugins --- data/core/init.lua | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 0d8c8e51..27257b20 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -238,11 +238,11 @@ local function refresh_directory(topdir, target, expanded) -- If we're expecting to keep track of everything, go through the list and iteratively deal with directories. files = dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, function() return false end) end - + local new_idx, old_idx = 1, index local new_directories = {} local last_dir = nil - while old_idx <= n or new_idx <= #files do + while old_idx <= n or new_idx <= #files do local old_info, new_info = topdir.files[old_idx], files[new_idx] if not new_info or not old_info or not last_dir or old_info.filename:sub(1, #last_dir + 1) ~= last_dir .. "/" then if not new_info or not old_info or not files_info_equal(new_info, old_info) then @@ -282,7 +282,7 @@ local function refresh_directory(topdir, target, expanded) if change then core.redraw = true topdir.is_dirty = true - end + end return change end end @@ -301,8 +301,8 @@ function core.add_project_directory(path) watch_thread = nil, watch = dirwatch.new() } - table.insert(core.project_directories, topdir) - + table.insert(core.project_directories, topdir) + local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown" topdir.force_scans = (fstype == "nfs" or fstype == "fuse") local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred) @@ -320,13 +320,13 @@ function core.add_project_directory(path) topdir.watch:watch(topdir.name) -- each top level directory gets a watch thread. if the project is small, or -- if the ablity to use directory watches hasn't been compromised in some way - -- either through error, or amount of files, then this should be incredibly + -- either through error, or amount of files, then this should be incredibly -- quick; essentially one syscall per check. Otherwise, this may take a bit of -- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds. topdir.watch_thread = core.add_thread(function() while true do topdir.watch:check(function(target) - if target == topdir.name then return refresh_directory(topdir, "", true) end + if target == topdir.name then return refresh_directory(topdir, "", true) end local dirpath = target:sub(#topdir.name + 2) local abs_dirpath = topdir.name .. PATHSEP .. dirpath if dirpath then @@ -339,7 +339,7 @@ function core.add_project_directory(path) coroutine.yield(0.05) end end) - + if path == core.project_dir then core.project_files = topdir.files end @@ -719,7 +719,18 @@ function core.init() core.threads = setmetatable({}, { __mode = "k" }) core.blink_start = system.get_time() core.blink_timer = core.blink_start - + core.redraw = true + core.visited_files = {} + core.restart_request = false + core.quit_request = false + + -- We load core views before plugins who may need them. + core.root_view = RootView() + core.command_view = CommandView() + core.status_view = StatusView() + core.nag_view = NagView() + core.title_view = TitleView() + local got_user_error, got_project_error = not core.load_user_directory() local project_dir_abs = system.absolute_path(project_dir) @@ -736,7 +747,7 @@ function core.init() update_recents_project("remove", project_dir) end project_dir_abs = system.absolute_path(".") - if not core.set_project_dir(project_dir_abs, function() + if not core.set_project_dir(project_dir_abs, function() got_project_error = not core.load_project_module() end) then system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd") @@ -744,17 +755,6 @@ function core.init() end end - core.redraw = true - core.visited_files = {} - core.restart_request = false - core.quit_request = false - - core.root_view = RootView() - core.command_view = CommandView() - core.status_view = StatusView() - core.nag_view = NagView() - core.title_view = TitleView() - local cur_node = core.root_view.root_node cur_node.is_primary_node = true cur_node:split("up", core.title_view, {y = true}) From 9d4e475a2cbedace1064a06a7f51bd58e19e8e2a Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 6 Mar 2022 21:21:00 -0400 Subject: [PATCH 157/409] init: also load default nodes and commands before user plugins and project module. --- data/core/init.lua | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 27257b20..1a2cf61f 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -724,13 +724,27 @@ function core.init() core.restart_request = false core.quit_request = false - -- We load core views before plugins who may need them. + -- We load core views before plugins that may need them. core.root_view = RootView() core.command_view = CommandView() core.status_view = StatusView() core.nag_view = NagView() core.title_view = TitleView() + -- Some plugins (eg: console) require the nodes to be initialized to defaults + local cur_node = core.root_view.root_node + cur_node.is_primary_node = true + cur_node:split("up", core.title_view, {y = true}) + cur_node = cur_node.b + cur_node:split("up", core.nag_view, {y = true}) + cur_node = cur_node.b + cur_node = cur_node:split("down", core.command_view, {y = true}) + cur_node = cur_node:split("down", core.status_view, {y = true}) + + -- Load defaiult commands first so plugins can override them + command.add_defaults() + + -- Load user module, plugins and project module local got_user_error, got_project_error = not core.load_user_directory() local project_dir_abs = system.absolute_path(project_dir) @@ -755,16 +769,7 @@ function core.init() end end - local cur_node = core.root_view.root_node - cur_node.is_primary_node = true - cur_node:split("up", core.title_view, {y = true}) - cur_node = cur_node.b - cur_node:split("up", core.nag_view, {y = true}) - cur_node = cur_node.b - cur_node = cur_node:split("down", core.command_view, {y = true}) - cur_node = cur_node:split("down", core.status_view, {y = true}) - - command.add_defaults() + -- Load core plugins after user ones to let the user override them local plugins_success, plugins_refuse_list = core.load_plugins() do From 48e86bb11754f6a8e67ce855092a83aef0b9a644 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 7 Mar 2022 16:52:32 -0400 Subject: [PATCH 158/409] plugin treeview: skip rootview events if not visible. --- data/plugins/treeview.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 6841586d..e12782b6 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -186,6 +186,7 @@ end function TreeView:on_mouse_moved(px, py, ...) + if not self.visible then return end TreeView.super.on_mouse_moved(self, px, py, ...) if self.dragging_scrollbar then return end @@ -223,6 +224,7 @@ end function TreeView:on_mouse_pressed(button, x, y, clicks) + if not self.visible then return end local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks) if caught or button ~= "left" then return true @@ -263,6 +265,8 @@ function TreeView:update() self:move_towards(self.size, "x", dest) end + if not self.visible then return end + local duration = system.get_time() - self.tooltip.begin if self.hovered_item and self.tooltip.x and duration > tooltip_delay then self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate) @@ -372,6 +376,7 @@ end function TreeView:draw() + if not self.visible then return end self:draw_background(style.background2) local _y, _h = self.position.y, self.size.y From 5714da81f83ca1b14c37b9560d916cb0f8f5c916 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 7 Mar 2022 23:35:56 +0100 Subject: [PATCH 159/409] Always check if the beginning of the text needs to be clipped --- src/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer.c b/src/renderer.c index e756cbb9..c2fa233e 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -289,7 +289,7 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor break; if (start_x + (glyph_end - glyph_start) >= clip_end_x) glyph_end = glyph_start + (clip_end_x - start_x); - else if (start_x < clip.x) { + if (start_x < clip.x) { int offset = clip.x - start_x; start_x += offset; glyph_start += offset; From 960b4820619081b81d82e69292287a4b7a6ed2ba Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 8 Mar 2022 19:30:25 -0500 Subject: [PATCH 160/409] Fixed some issues with inotify and multiple events at the same time. (#872) * Fixed some issues with inotify and multiple events at the same time. Seems to be working now. * Cleaned up and simplified function, and commented, and fixed a number of bugs. * Simplifying and fixing further. * Improved performance for skipping large amounts of files. * Added in extra checks, and changed paths. We should probably unify these path styles. * Fixed stutter. * Removed extraneous functions. * Cleaned up more, added more testing; dealt with multiple sequential events correctly. --- data/core/dirwatch.lua | 34 +++---- data/core/init.lua | 224 ++++++++++++++++------------------------- src/api/dirmonitor.c | 14 ++- 3 files changed, 116 insertions(+), 156 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 3848e5a2..2bcf5508 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -30,7 +30,7 @@ end -- Should be called on every directory in a subdirectory. -- In windows, this is a no-op for anything underneath a top-level directory, -- but code should be called anyway, so we can ensure that we have a proper --- experience across all platforms. +-- experience across all platforms. Should be an absolute path. function dirwatch:watch(directory, bool) if bool == false then return self:unwatch(directory) end if not self.watched[directory] and not self.scanned[directory] then @@ -64,16 +64,17 @@ function dirwatch:watch(directory, bool) end end +-- this should be an absolute path function dirwatch:unwatch(directory) if self.watched[directory] then if PLATFORM ~= "Windows" then - self.monitor.unwatch(directory) - self.reverse_watched[self.watched[directory]] = nil + self.monitor:unwatch(self.watched[directory]) + self.reverse_watched[directory] = nil else self.windows_watch_count = self.windows_watch_count - 1 if self.windows_watch_count == 0 then self.windows_watch_top = nil - self.monitor.unwatch(directory) + self.monitor:unwatch(directory) end end self.watched[directory] = nil @@ -108,10 +109,6 @@ function dirwatch:check(change_callback, scan_time, wait_time) end -local function strip_leading_path(filename) - return filename:sub(2) -end - -- inspect config.ignore_files patterns and prepare ready to use entries. local function compile_ignore_files() local ipatterns = config.ignore_files @@ -162,33 +159,36 @@ end -- compute a file's info entry completed with "filename" to be used -- in project scan or falsy if it shouldn't appear in the list. local function get_project_file_info(root, file, ignore_compiled) - local info = system.get_file_info(root .. file) + local info = system.get_file_info(root .. PATHSEP .. file) -- info can be not nil but info.type may be nil if is neither a file neither -- a directory, for example for /dev/* entries on linux. if info and info.type then - info.filename = strip_leading_path(file) + info.filename = file return fileinfo_pass_filter(info, ignore_compiled) and info end end -- "root" will by an absolute path without trailing '/' --- "path" will be a path starting with '/' and without trailing '/' +-- "path" will be a path starting without '/' and without trailing '/' -- or the empty string. -- It will identifies a sub-path within "root. -- The current path location will therefore always be: root .. path. -- When recursing "root" will always be the same, only "path" will change. --- Returns a list of file "items". In eash item the "filename" will be the --- complete file path relative to "root" *without* the trailing '/'. +-- Returns a list of file "items". In each item the "filename" will be the +-- complete file path relative to "root" *without* the trailing '/', and without the starting '/'. function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred) local t0 = system.get_time() - local all = system.list_dir(root .. path) or {} local t_elapsed = system.get_time() - t0 local dirs, files = {}, {} local ignore_compiled = compile_ignore_files() - for _, file in ipairs(all) do - local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) + + local all = system.list_dir(root .. PATHSEP .. path) + if not all then return nil end + + for _, file in ipairs(all or {}) do + local info = get_project_file_info(root, (path ~= "" and (path .. PATHSEP) or "") .. file, ignore_compiled) if info then table.insert(info.type == "dir" and dirs or files, info) entries_count = entries_count + 1 @@ -200,7 +200,7 @@ function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse for _, f in ipairs(dirs) do table.insert(t, f) if recurse_pred(dir, f.filename, entries_count, t_elapsed) then - local _, complete, n = dirwatch.get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred) + local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred) recurse_complete = recurse_complete and complete entries_count = n else diff --git a/data/core/init.lua b/data/core/init.lua index 1a2cf61f..671dc6ad 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -109,7 +109,6 @@ local function strip_trailing_slash(filename) end - function core.project_subdir_is_shown(dir, filename) return not dir.files_limit or dir.shown_subdir[filename] end @@ -127,80 +126,115 @@ local function show_max_files_warning(dir) end -local function file_search(files, info) - local filename, type = info.filename, info.type - local inf, sup = 1, #files +-- bisects the sorted file list to get to things in ln(n) +local function file_bisect(files, is_superior, start_idx, end_idx) + local inf, sup = start_idx or 1, end_idx or #files while sup - inf > 8 do local curr = math.floor((inf + sup) / 2) - if system.path_compare(filename, type, files[curr].filename, files[curr].type) then + if is_superior(files[curr]) then sup = curr - 1 else inf = curr end end - while inf <= sup and not system.path_compare(filename, type, files[inf].filename, files[inf].type) do - if files[inf].filename == filename then - return inf, true - end + while inf <= sup and not is_superior(files[inf]) do inf = inf + 1 end - return inf, false + return inf +end + + +local function file_search(files, info) + local idx = file_bisect(files, function(file) + return system.path_compare(info.filename, info.type, file.filename, file.type) + end) + if idx > 1 and files[idx-1].filename == info.filename then + return idx - 1, true + end + return idx, false end local function files_info_equal(a, b) - return a.filename == b.filename and a.type == b.type + return (a == nil and b == nil) or (a and b and a.filename == b.filename and a.type == b.type) end --- for "a" inclusive from i1 + 1 and i1 + n -local function files_list_match(a, i1, n, b) - if n ~= #b then return false end - for i = 1, n do - if not files_info_equal(a[i1 + i], b[i]) then - return false - end + +local function project_subdir_bounds(dir, filename, start_index) + local found = true + if not start_index then + start_index, found = file_search(dir.files, { type = "dir", filename = filename }) end - return true -end - --- arguments like for files_list_match -local function files_list_replace(as, i1, n, bs, hook) - local m = #bs - local i, j = 1, 1 - while i <= m or i <= n do - local a, b = as[i1 + i], bs[j] - if i > n or (j <= m and not files_info_equal(a, b) and - not system.path_compare(a.filename, a.type, b.filename, b.type)) - then - table.insert(as, i1 + i, b) - i, j, n = i + 1, j + 1, n + 1 - if hook and hook.insert then hook.insert(b) end - elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then - if hook and hook.remove then hook.remove(as[i1 + i]) end - table.remove(as, i1 + i) - n = n - 1 - else - i, j = i + 1, j + 1 - end + if found then + local end_index = file_bisect(dir.files, function(file) + return not common.path_belongs_to(file.filename, filename) + end, start_index + 1) + return start_index, end_index - start_index, dir.files[start_index] end end -local function project_subdir_bounds(dir, filename) - local index, n = 0, #dir.files - for i, file in ipairs(dir.files) do - local file = dir.files[i] - if file.filename == filename then - index, n = i, #dir.files - i - for j = 1, #dir.files - i do - if not common.path_belongs_to(dir.files[i + j].filename, filename) then - n = j - 1 - break +-- Should be called on any directory that registers a change, or on a directory we open if we're over the file limit. +-- Uses relative paths at the project root (i.e. target = "", target = "first-level-directory", target = "first-level-directory/second-level-directory") +local function refresh_directory(topdir, target) + local directory_start_idx, directory_end_idx = 1, #topdir.files + if target and target ~= "" then + directory_start_idx, directory_end_idx = project_subdir_bounds(topdir, target) + directory_end_idx = directory_start_idx + directory_end_idx - 1 + directory_start_idx = directory_start_idx + 1 + end + + local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), {}, 0, function() return false end) + local change = false + + -- If this file doesn't exist, we should be calling this on our parent directory, assume we'll do that. + -- Unwatch just in case. + if files == nil then + topdir.watch:unwatch(topdir.name .. PATHSEP .. (target or "")) + return true + end + + local new_idx, old_idx = 1, directory_start_idx + local new_directories = {} + -- Run through each sorted list and compare them. If we find a new entry, insert it and flag as new. If we're missing an entry + -- remove it and delete the entry from the list. + while old_idx <= directory_end_idx or new_idx <= #files do + local old_info, new_info = topdir.files[old_idx], files[new_idx] + if not files_info_equal(new_info, old_info) then + change = true + -- If we're a new file, and we exist *before* the other file in the list, then add to the list. + if not old_info or (new_info and system.path_compare(new_info.filename, new_info.type, old_info.filename, old_info.type)) then + table.insert(topdir.files, old_idx, new_info) + old_idx, new_idx = old_idx + 1, new_idx + 1 + if new_info.type == "dir" then + table.insert(new_directories, new_info) end + directory_end_idx = directory_end_idx + 1 + else + -- If it's not there, remove the entry from the list as being out of order. + table.remove(topdir.files, old_idx) + if old_info.type == "dir" then + topdir.watch:unwatch(topdir.name .. PATHSEP .. old_info.filename) + end + directory_end_idx = directory_end_idx - 1 end - return index, n, file + else + -- If this file is a directory, determine in ln(n) the size of the directory, and skip every file in it. + local size = old_info and old_info.type == "dir" and select(2, project_subdir_bounds(topdir, old_info.filename, old_idx)) or 1 + old_idx, new_idx = old_idx + size, new_idx + 1 end end + for i, v in ipairs(new_directories) do + topdir.watch:watch(topdir.name .. PATHSEP .. v.filename) + if not topdir.files_limit or core.project_subdir_is_shown(topdir, v.filename) then + refresh_directory(topdir, v.filename) + end + end + if change then + core.redraw = true + topdir.is_dirty = true + end + return change end @@ -213,80 +247,6 @@ local function timed_max_files_pred(dir, filename, entries_count, t_elapsed) end - --- Should be called on any directory that registers a change. --- Uses relative paths at the project root. -local function refresh_directory(topdir, target, expanded) - local index, n, directory - if target == "" then - index, n = 1, #topdir.files - directory = "" - else - index, n = project_subdir_bounds(topdir, target) - index = index + 1 - n = index + n - 1 - directory = (PATHSEP .. target) - end - if index then - local files - local change = false - if topdir.files_limit then - -- If we have the folders literally open on the side panel. - files = expanded and dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, core.project_subdir_is_shown) or {} - change = true - else - -- If we're expecting to keep track of everything, go through the list and iteratively deal with directories. - files = dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, function() return false end) - end - - local new_idx, old_idx = 1, index - local new_directories = {} - local last_dir = nil - while old_idx <= n or new_idx <= #files do - local old_info, new_info = topdir.files[old_idx], files[new_idx] - if not new_info or not old_info or not last_dir or old_info.filename:sub(1, #last_dir + 1) ~= last_dir .. "/" then - if not new_info or not old_info or not files_info_equal(new_info, old_info) then - change = true - if not old_info or (new_info and system.path_compare(new_info.filename, new_info.type, old_info.filename, old_info.type)) then - table.insert(topdir.files, old_idx, new_info) - new_idx = new_idx + 1 - old_idx = old_idx + 1 - if new_info.type == "dir" then - table.insert(new_directories, new_info) - end - n = n + 1 - else - table.remove(topdir.files, old_idx) - if old_info.type == "dir" then - topdir.watch:unwatch(target .. PATHSEP .. old_info.filename) - end - n = n - 1 - end - else - new_idx = new_idx + 1 - old_idx = old_idx + 1 - end - if old_info and old_info.type == "dir" then - last_dir = old_info.filename - end - else - old_idx = old_idx + 1 - end - end - for i, v in ipairs(new_directories) do - topdir.watch:watch(target) - if refresh_directory(topdir, target .. PATHSEP .. v.filename) then - change = true - end - end - if change then - core.redraw = true - topdir.is_dirty = true - end - return change - end -end - function core.add_project_directory(path) -- top directories has a file-like "item" but the item.filename -- will be simply the name of the directory, without its path. @@ -311,7 +271,7 @@ function core.add_project_directory(path) topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files) topdir.files_limit = true show_max_files_warning(topdir) - refresh_directory(topdir, "", true) + refresh_directory(topdir) else for i,v in ipairs(t) do if v.type == "dir" then topdir.watch:watch(path .. PATHSEP .. v.filename) end @@ -326,7 +286,7 @@ function core.add_project_directory(path) topdir.watch_thread = core.add_thread(function() while true do topdir.watch:check(function(target) - if target == topdir.name then return refresh_directory(topdir, "", true) end + if target == topdir.name then return refresh_directory(topdir) end local dirpath = target:sub(#topdir.name + 2) local abs_dirpath = topdir.name .. PATHSEP .. dirpath if dirpath then @@ -334,7 +294,7 @@ function core.add_project_directory(path) local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"}) if not dir_match or not core.project_subdir_is_shown(topdir, topdir.files[dir_index].filename) then return end end - return refresh_directory(topdir, dirpath, true) + return refresh_directory(topdir, dirpath) end, 0.01, 0.01) coroutine.yield(0.05) end @@ -402,13 +362,7 @@ end function core.update_project_subdir(dir, filename, expanded) assert(dir.files_limit, "function should be called only when directory is in files limit mode") dir.shown_subdir[filename] = expanded - local index, n, file = project_subdir_bounds(dir, filename) - if index then - local new_files = expanded and dirwatch.get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} - files_list_replace(dir.files, index, n, new_files) - dir.is_dirty = true - return true - end + return refresh_directory(dir, filename) end diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 7f298724..15b63a97 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -93,14 +93,19 @@ int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, con return 0; #elif __linux__ char buf[PATH_MAX + sizeof(struct inotify_event)]; + ssize_t offset = 0; while (1) { - ssize_t len = read(monitor->fd, buf, sizeof(buf)); + ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); if (len == -1 && errno != EAGAIN) return errno; if (len <= 0) return 0; - for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ((struct inotify_event*)ptr)->len) - change_callback(((const struct inotify_event *) ptr)->wd, NULL, data); + while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { + change_callback(((const struct inotify_event *)buf)->wd, NULL, data); + len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; + memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); + offset = len; + } } #else struct kevent event; @@ -146,6 +151,7 @@ void remove_dirmonitor(struct dirmonitor* monitor, int fd) { } static int f_check_dir_callback(int watch_id, const char* path, void* L) { + lua_pushvalue(L, -1); #if _WIN32 char buffer[PATH_MAX*4]; int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); @@ -153,7 +159,7 @@ static int f_check_dir_callback(int watch_id, const char* path, void* L) { #else lua_pushnumber(L, watch_id); #endif - lua_pcall(L, 1, 1, 0); + lua_call(L, 1, 1); int result = lua_toboolean(L, -1); lua_pop(L, 1); return !result; From eeb2b28a03b51846b46ff835a2356be77ac0667a Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 9 Mar 2022 02:15:01 -0400 Subject: [PATCH 161/409] config overwritten on user/project modules save When a user modifies and saves the init.lua or a project module file the reload_customizations() function was performing unnecessary reloading of core.config and core.style. This resulted on the replacement of config tables with new tables, breaking all active references been used by the consumers of this config options. Been redundant this means that every consumer was using its own copy of a configuration table different from the one referenced on core.config and user changes not taking place. --- data/core/init.lua | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 671dc6ad..86c26dc7 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -65,24 +65,8 @@ end local function reload_customizations() - -- The logic is: - -- - the core.style and config modules are reloaded with the purpose of applying - -- the new user's and project's module configs - -- - inside the core.config the existing fields in config.plugins are preserved - -- because they are reserved to plugins configuration and plugins are already - -- loaded. - -- - plugins are not reloaded or unloaded - local plugins_save = {} - for k, v in pairs(config.plugins) do - plugins_save[k] = v - end - core.reload_module("core.style") - core.reload_module("core.config") core.load_user_directory() core.load_project_module() - for k, v in pairs(plugins_save) do - config.plugins[k] = v - end end @@ -146,7 +130,7 @@ end local function file_search(files, info) local idx = file_bisect(files, function(file) - return system.path_compare(info.filename, info.type, file.filename, file.type) + return system.path_compare(info.filename, info.type, file.filename, file.type) end) if idx > 1 and files[idx-1].filename == info.filename then return idx - 1, true @@ -183,22 +167,22 @@ local function refresh_directory(topdir, target) directory_end_idx = directory_start_idx + directory_end_idx - 1 directory_start_idx = directory_start_idx + 1 end - + local files = dirwatch.get_directory_files(topdir, topdir.name, (target or ""), {}, 0, function() return false end) local change = false - + -- If this file doesn't exist, we should be calling this on our parent directory, assume we'll do that. -- Unwatch just in case. - if files == nil then + if files == nil then topdir.watch:unwatch(topdir.name .. PATHSEP .. (target or "")) - return true + return true end - + local new_idx, old_idx = 1, directory_start_idx local new_directories = {} -- Run through each sorted list and compare them. If we find a new entry, insert it and flag as new. If we're missing an entry -- remove it and delete the entry from the list. - while old_idx <= directory_end_idx or new_idx <= #files do + while old_idx <= directory_end_idx or new_idx <= #files do local old_info, new_info = topdir.files[old_idx], files[new_idx] if not files_info_equal(new_info, old_info) then change = true @@ -211,7 +195,7 @@ local function refresh_directory(topdir, target) end directory_end_idx = directory_end_idx + 1 else - -- If it's not there, remove the entry from the list as being out of order. + -- If it's not there, remove the entry from the list as being out of order. table.remove(topdir.files, old_idx) if old_info.type == "dir" then topdir.watch:unwatch(topdir.name .. PATHSEP .. old_info.filename) @@ -224,7 +208,7 @@ local function refresh_directory(topdir, target) old_idx, new_idx = old_idx + size, new_idx + 1 end end - for i, v in ipairs(new_directories) do + for i, v in ipairs(new_directories) do topdir.watch:watch(topdir.name .. PATHSEP .. v.filename) if not topdir.files_limit or core.project_subdir_is_shown(topdir, v.filename) then refresh_directory(topdir, v.filename) @@ -286,7 +270,7 @@ function core.add_project_directory(path) topdir.watch_thread = core.add_thread(function() while true do topdir.watch:check(function(target) - if target == topdir.name then return refresh_directory(topdir) end + if target == topdir.name then return refresh_directory(topdir) end local dirpath = target:sub(#topdir.name + 2) local abs_dirpath = topdir.name .. PATHSEP .. dirpath if dirpath then From 0aa53a0e7f24add1d8a0ae54ce576d2314a198c9 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 10 Mar 2022 04:27:04 -0400 Subject: [PATCH 162/409] nagview: support vscroll when message is too long Also some other minor changes: * fix transition when nagview is closed * do not draw or update when not visible * do not process events when not visible * cleaned a bit the logic on next and show * fixes #848 --- data/core/nagview.lua | 121 ++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 39 deletions(-) diff --git a/data/core/nagview.lua b/data/core/nagview.lua index fca6c306..0c9e575e 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -19,6 +19,9 @@ function NagView:new() self.show_height = 0 self.force_focus = false self.queue = {} + self.scrollable = true + self.target_height = 0 + self.on_mouse_pressed_root = nil end function NagView:get_title() @@ -47,15 +50,15 @@ function NagView:get_target_height() return self.target_height + 2 * style.padding.y end -function NagView:update() - NagView.super.update(self) - - if core.active_view == self and self.title then - self:move_towards(self, "show_height", self:get_target_height()) - self:move_towards(self, "underline_progress", 1) +function NagView:get_scrollable_size() + local w, h = system.get_window_size() + if self.visible and self:get_target_height() > h then + self.size.y = h + return self:get_target_height() else - self:move_towards(self, "show_height", 0) + self.size.y = 0 end + return 0 end function NagView:dim_window_content() @@ -95,6 +98,8 @@ function NagView:each_option() end function NagView:on_mouse_moved(mx, my, ...) + if not self.visible then return end + core.set_active_view(self) NagView.super.on_mouse_moved(self, mx, my, ...) for i, _, x,y,w,h in self:each_option() do if mx >= x and my >= y and mx < x + w and my < y + h then @@ -104,43 +109,55 @@ function NagView:on_mouse_moved(mx, my, ...) end end --- Used to store saved value for RootView.on_view_mouse_pressed -local on_view_mouse_pressed - - -local function capture_mouse_pressed(nag_view) +local function register_mouse_pressed(self) + if self.on_mouse_pressed_root then return end -- RootView is loaded locally to avoid NagView and RootView being -- mutually recursive local RootView = require "core.rootview" - on_view_mouse_pressed = RootView.on_view_mouse_pressed - RootView.on_view_mouse_pressed = function(button, x, y, clicks) - local handled = NagView.on_mouse_pressed(nag_view, button, x, y, clicks) - return handled or on_view_mouse_pressed(button, x, y, clicks) + self.on_mouse_pressed_root = RootView.on_mouse_pressed + local this = self + function RootView:on_mouse_pressed(button, x, y, clicks) + if + not this:on_mouse_pressed(button, x, y, clicks) + then + return this.on_mouse_pressed_root(self, button, x, y, clicks) + else + return true + end end + self.new_on_mouse_pressed_root = RootView.on_mouse_pressed end - -local function release_mouse_pressed() +local function unregister_mouse_pressed(self) local RootView = require "core.rootview" - if on_view_mouse_pressed then - RootView.on_view_mouse_pressed = on_view_mouse_pressed - on_view_mouse_pressed = nil + if + self.on_mouse_pressed_root + and + -- just in case prevent overwriting what something else may + -- have overwrote after us, but after testing with various + -- plugins this doesn't seems to happen, but just in case + self.new_on_mouse_pressed_root == RootView.on_mouse_pressed + then + RootView.on_mouse_pressed = self.on_mouse_pressed_root + self.on_mouse_pressed_root = nil + self.new_on_mouse_pressed_root = nil end end - function NagView:on_mouse_pressed(button, mx, my, clicks) + if not self.visible then return false end if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end for i, _, x,y,w,h in self:each_option() do if mx >= x and my >= y and mx < x + w and my < y + h then self:change_hovered(i) command.perform "dialog:select" - return true end end + return true end function NagView:on_text_input(text) + if not self.visible then return end if text:lower() == "y" then command.perform "dialog:select-yes" elseif text:lower() == "n" then @@ -148,10 +165,25 @@ function NagView:on_text_input(text) end end +function NagView:update() + if not self.visible and self.show_height <= 0 then return end + NagView.super.update(self) + + if self.visible and core.active_view == self and self.title then + self:move_towards(self, "show_height", self:get_target_height()) + self:move_towards(self, "underline_progress", 1) + else + self:move_towards(self, "show_height", 0) + if self.show_height <= 0 then + self.title = nil + self.message = nil + self.options = nil + self.on_selected = nil + end + end +end local function draw_nagview_message(self) - if self.show_height <= 0 or not self.title then return end - self:dim_window_content() -- draw message's background @@ -160,6 +192,8 @@ local function draw_nagview_message(self) ox = ox + style.padding.x + core.push_clip_rect(ox, oy, self.size.x, self.show_height) + -- if there are other items, show it if #self.queue > 0 then local str = string.format("[%d]", #self.queue) @@ -196,9 +230,16 @@ local function draw_nagview_message(self) common.draw_text(opt.font, style.nagbar_text, opt.text, "center", fx,fy,fw,fh) end + + self:draw_scrollbar() + + core.pop_clip_rect() end function NagView:draw() + if (not self.visible and self.show_height <= 0) or not self.title then + return + end core.root_view:defer_draw(draw_nagview_message, self) end @@ -210,28 +251,30 @@ function NagView:get_message_height() return h end - function NagView:next() local opts = table.remove(self.queue, 1) or {} - self.title = opts.title - self.message = opts.message and opts.message .. "\n" - self.options = opts.options - self.on_selected = opts.on_selected - if self.message and self.options then + if opts.title and opts.message and opts.options then + self.visible = true + self.title = opts.title + self.message = opts.message and opts.message .. "\n" + self.options = opts.options + self.on_selected = opts.on_selected + local message_height = self:get_message_height() -- self.target_height is the nagview height needed to display the message and -- the buttons, excluding the top and bottom padding space. self.target_height = math.max(message_height, self:get_buttons_height()) self:change_hovered(common.find_index(self.options, "default_yes")) - end - self.force_focus = self.message ~= nil - core.set_active_view(self.message ~= nil and self or - core.next_active_view or core.last_active_view) - if self.message ~= nil and self then + + self.force_focus = true + core.set_active_view(self) -- We add a hook to manage all the mouse_pressed events. - capture_mouse_pressed(self) + register_mouse_pressed(self) else - release_mouse_pressed() + self.force_focus = false + core.set_active_view(core.next_active_view or core.last_active_view) + self.visible = false + unregister_mouse_pressed(self) end end @@ -242,7 +285,7 @@ function NagView:show(title, message, options, on_select) opts.options = assert(options, "No options") opts.on_selected = on_select or noop table.insert(self.queue, opts) - if #self.queue > 0 and not self.title then self:next() end + self:next() end return NagView From d9909cf4ea6154fb0d79f8de82107cebacb2a975 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 10 Mar 2022 22:29:33 -0400 Subject: [PATCH 163/409] config: added skip_plugins_version This new config flag ignores the plugins version check at startup which helps a lot when working on new or old plugins that doesn't match the mod or lite-xl version and you still desire to load them to fix them by checking with lite-xl it self which errors need to be corrected. --- data/core/config.lua | 3 +++ data/core/init.lua | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/core/config.lua b/data/core/config.lua index 73986068..0f3b8ad3 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -29,6 +29,9 @@ config.borderless = false config.tab_close_button = true config.max_clicks = 3 +-- set as true to be able to test non supported plugins +config.skip_plugins_version = false + config.plugins = {} -- Allow you to set plugin configs even if we haven't seen the plugin before. setmetatable(config.plugins, { diff --git a/data/core/init.lua b/data/core/init.lua index 86c26dc7..6b70d589 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -891,12 +891,11 @@ function core.load_plugins() local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename) if is_lua_file then - if not version_match then + if not config.skip_plugins_version and not version_match then core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir) local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins table.insert(list, filename) - end - if version_match and config.plugins[basename] ~= false then + elseif config.plugins[basename] ~= false then local ok = core.try(require, "plugins." .. basename) if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end if not ok then From 4b0531cdfc8485dd34d4c8992609378fa203844b Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 11 Mar 2022 17:02:42 -0400 Subject: [PATCH 164/409] language_cpp: improvements and fixes * Removed pcall(require, "plugins.language_c") since it doesn't works as it seems to have been intended. * Removed duplicate keywords * Added support for magic and uppercase constants. * Basically merged most changes from the lite-xl-plugins repo. --- data/plugins/language_cpp.lua | 78 ++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index 2b3a104f..e350653a 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -1,6 +1,4 @@ -- mod-version:2 -- lite-xl 2.0 -pcall(require, "plugins.language_c") - local syntax = require "core.syntax" syntax.add { @@ -12,27 +10,52 @@ syntax.add { comment = "//", block_comment = { "/*", "*/" }, patterns = { - { pattern = "//.-\n", type = "comment" }, - { pattern = { "/%*", "%*/" }, type = "comment" }, - { pattern = { '"', '"', '\\' }, type = "string" }, - { pattern = { "'", "'", '\\' }, type = "string" }, - { pattern = "0x%x+", type = "number" }, - { pattern = "%d+[%d%.eE]*f?", type = "number" }, - { pattern = "%.?%d+f?", type = "number" }, - { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, + { pattern = "//.-\n", type = "comment" }, + { pattern = { "/%*", "%*/" }, type = "comment" }, + { pattern = { '"', '"', '\\' }, type = "string" }, + { pattern = { "'", "'", '\\' }, type = "string" }, + { pattern = "0x%x+", type = "number" }, + { pattern = "%d+[%d%.eE]*f?", type = "number" }, + { pattern = "%.?%d+f?", type = "number" }, + { + pattern = "^%s*#define%s+()[%a_][%a%d_]*", + type = { "keyword", "symbol" } + }, + { + pattern = "#include%s+()<.->", + type = { "keyword", "string" } + }, + { pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, - { pattern = "[%a_][%w_]*::", type = "symbol" }, - { pattern = "::", type = "symbol" }, - { pattern = "[%a_][%w_]*", type = "symbol" }, - { pattern = "#include%s()<.->", type = {"keyword", "string"} }, - { pattern = "#[%a_][%w_]*", type = "keyword" }, + { pattern = "[%a_][%w_]*%f[(]", type = "function" }, + -- Match scope operator element access + { pattern = "[%a_][%w_]*[%s]*%f[:]", type = "literal" }, + -- Uppercase constants of at least 2 chars in len + { + pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]", + type = "number" + }, + -- Magic constants + { pattern = "__[%u%l]+__", type = "number" }, + -- Somehow makes macros properly work + { pattern = "#[%a_][%w_]*", type = "normal" }, + -- Everything else to make the tokenizer work properly + { pattern = "[%a_][%w_]*", type = "symbol" }, }, symbols = { ["alignof"] = "keyword", ["alignas"] = "keyword", + ["and"] = "keyword", + ["and_eq"] = "keyword", + ["not"] = "keyword", + ["not_eq"] = "keyword", + ["or"] = "keyword", + ["or_eq"] = "keyword", + ["xor"] = "keyword", + ["xor_eq"] = "keyword", ["private"] = "keyword", ["protected"] = "keyword", ["public"] = "keyword", @@ -40,9 +63,12 @@ syntax.add { ["nullptr"] = "keyword", ["operator"] = "keyword", ["asm"] = "keyword", + ["bitand"] = "keyword", + ["bitor"] = "keyword", ["catch"] = "keyword", ["throw"] = "keyword", ["try"] = "keyword", + ["class"] = "keyword", ["compl"] = "keyword", ["explicit"] = "keyword", ["export"] = "keyword", @@ -52,8 +78,8 @@ syntax.add { ["constinit"] = "keyword", ["const_cast"] = "keyword", ["dynamic_cast"] = "keyword", - ["reinterpret_cast"] = "keyword", - ["static_cast"] = "keyword", + ["reinterpret_cast"] = "keyword", + ["static_cast"] = "keyword", ["static_assert"] = "keyword", ["template"] = "keyword", ["this"] = "keyword", @@ -64,7 +90,6 @@ syntax.add { ["co_yield"] = "keyword", ["decltype"] = "keyword", ["delete"] = "keyword", - ["export"] = "keyword", ["friend"] = "keyword", ["typeid"] = "keyword", ["typename"] = "keyword", @@ -72,6 +97,7 @@ syntax.add { ["override"] = "keyword", ["virtual"] = "keyword", ["using"] = "keyword", + ["namespace"] = "keyword", ["new"] = "keyword", ["noexcept"] = "keyword", ["if"] = "keyword", @@ -85,6 +111,8 @@ syntax.add { ["continue"] = "keyword", ["return"] = "keyword", ["goto"] = "keyword", + ["struct"] = "keyword", + ["union"] = "keyword", ["typedef"] = "keyword", ["enum"] = "keyword", ["extern"] = "keyword", @@ -96,7 +124,6 @@ syntax.add { ["case"] = "keyword", ["default"] = "keyword", ["auto"] = "keyword", - ["const"] = "keyword", ["void"] = "keyword2", ["int"] = "keyword2", ["short"] = "keyword2", @@ -106,12 +133,18 @@ syntax.add { ["char"] = "keyword2", ["unsigned"] = "keyword2", ["bool"] = "keyword2", - ["true"] = "keyword2", - ["false"] = "keyword2", + ["true"] = "literal", + ["false"] = "literal", + ["NULL"] = "literal", + ["wchar_t"] = "keyword2", + ["char8_t"] = "keyword2", + ["char16_t"] = "keyword2", + ["char32_t"] = "keyword2", ["#include"] = "keyword", ["#if"] = "keyword", ["#ifdef"] = "keyword", ["#ifndef"] = "keyword", + ["#elif"] = "keyword", ["#else"] = "keyword", ["#elseif"] = "keyword", ["#endif"] = "keyword", @@ -119,6 +152,5 @@ syntax.add { ["#warning"] = "keyword", ["#error"] = "keyword", ["#pragma"] = "keyword", - }, + }, } - From b880aa42f9c60d8d9d9adba9f4e28df5ab3ef3ca Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 11 Mar 2022 17:03:49 -0400 Subject: [PATCH 165/409] language_c: fixes and improvements * Do not compete with language_cpp.lua over the .h and .inl files, these files can contain both cpp and c so we choose the former which supports both syntaxes. * Added support for magic and uppercase constants. --- data/plugins/language_c.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index c4d3b07b..9ba639a0 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -3,7 +3,7 @@ local syntax = require "core.syntax" syntax.add { name = "C", - files = { "%.c$", "%.h$", "%.inl$" }, + files = { "%.c$" }, comment = "//", block_comment = { "/*", "*/" }, patterns = { @@ -17,10 +17,21 @@ syntax.add { { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, + { + pattern = "^%s*#define%s+()[%a_][%a%d_]*", + type = { "keyword", "symbol" } + }, + -- Uppercase constants of at least 2 chars in len + { + pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]", + type = "number" + }, + -- Magic constants + { pattern = "__[%u%l]+__", type = "number" }, { pattern = "[%a_][%w_]*%f[(]", type = "function" }, - { pattern = "[%a_][%w_]*", type = "symbol" }, { pattern = "#include%s()<.->", type = {"keyword", "string"} }, { pattern = "#[%a_][%w_]*", type = "keyword" }, + { pattern = "[%a_][%w_]*", type = "symbol" }, }, symbols = { ["if"] = "keyword", @@ -45,6 +56,8 @@ syntax.add { ["case"] = "keyword", ["default"] = "keyword", ["auto"] = "keyword", + ["struct"] = "keyword", + ["union"] = "keyword", ["void"] = "keyword2", ["int"] = "keyword2", ["short"] = "keyword2", @@ -61,6 +74,7 @@ syntax.add { ["#if"] = "keyword", ["#ifdef"] = "keyword", ["#ifndef"] = "keyword", + ["#elif"] = "keyword", ["#else"] = "keyword", ["#elseif"] = "keyword", ["#endif"] = "keyword", From 620b669517bea43cf7c309e32e808ff5a7e2f008 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 10 Mar 2022 09:34:46 -0400 Subject: [PATCH 166/409] statusview: added ability to hide and commands Also fixed the right panel not been draggable. --- data/core/command.lua | 2 +- data/core/commands/statusbar.lua | 71 ++++++++++++++++++++++++++++++++ data/core/statusview.lua | 48 +++++++++++++++++++-- 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 data/core/commands/statusbar.lua diff --git a/data/core/command.lua b/data/core/command.lua index 07b30fb8..bdc1ed34 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -64,7 +64,7 @@ end function command.add_defaults() local reg = { "core", "root", "command", "doc", "findreplace", - "files", "drawwhitespace", "dialog", "log" + "files", "drawwhitespace", "dialog", "log", "statusbar" } for _, name in ipairs(reg) do require("core.commands." .. name) diff --git a/data/core/commands/statusbar.lua b/data/core/commands/statusbar.lua new file mode 100644 index 00000000..ac8837ed --- /dev/null +++ b/data/core/commands/statusbar.lua @@ -0,0 +1,71 @@ +local core = require "core" +local command = require "core.command" +local common = require "core.common" +local style = require "core.style" +local StatusView = require "core.statusview" + +local function status_view_item_names() + local items = core.status_view:get_items_list() + local names = {} + for _, item in ipairs(items) do + table.insert(names, item.name) + end + return names +end + +local function status_view_items_data(names) + local data = {} + for _, name in ipairs(names) do + local item = core.status_view:get_item(name) + table.insert(data, { + text = command.prettify_name(item.name), + info = item.alignment == StatusView.Item.LEFT and "Left" or "Right", + name = item.name + }) + end + return data +end + +local function status_view_get_items(text) + local names = status_view_item_names() + local results = common.fuzzy_match(names, text) + results = status_view_items_data(results) + return results +end + +command.add(nil, { + ["status-bar:toggle"] = function() + core.status_view:toggle() + end, + ["status-bar:show"] = function() + core.status_view:show() + end, + ["status-bar:hide"] = function() + core.status_view:hide() + end, + ["status-bar:disable-messages"] = function() + core.status_view:display_messages(false) + end, + ["status-bar:enable-messages"] = function() + core.status_view:display_messages(true) + end, + ["status-bar:hide-item"] = function() + core.command_view:enter("Status bar item to hide", + function(text, item) + core.status_view:hide_items(item.name) + end, + status_view_get_items + ) + end, + ["status-bar:show-item"] = function() + core.command_view:enter("Status bar item to show", + function(text, item) + core.status_view:show_items(item.name) + end, + status_view_get_items + ) + end, + ["status-bar:reset-items"] = function() + core.status_view:show_items() + end, +}) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 85ea6a2b..65bac1ae 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -163,6 +163,7 @@ function StatusView:new() self.dragged_panel = "" self.hovered_panel = "" self.hide_messages = false + self.visible = true self:register_docview_items() self:register_command_items() @@ -340,6 +341,24 @@ function StatusView:get_items_list(alignment) end +---Hide the status bar +function StatusView:hide() + self.visible = false +end + + +---Show the status bar +function StatusView:show() + self.visible = true +end + + +---Toggle the visibility of the status bar +function StatusView:toggle() + self.visible = not self.visible +end + + ---Hides the given items from the status view or all if no names given. ---@param names table | string | nil function StatusView:hide_items(names) @@ -383,7 +402,7 @@ end ---@param icon_color renderer.color ---@param text string function StatusView:show_message(icon, icon_color, text) - if self.hide_messages then return end + if not self.visible or self.hide_messages then return end self.message = { icon_color, style.icon_font, icon, style.dim, style.font, StatusView.separator2, style.text, text @@ -833,6 +852,7 @@ end function StatusView:on_mouse_pressed(button, x, y, clicks) + if not self.visible then return end core.set_active_view(core.last_active_view) if system.get_time() < self.message_timeout @@ -843,7 +863,11 @@ function StatusView:on_mouse_pressed(button, x, y, clicks) else if y >= self.position.y and button == "left" and clicks == 1 then self.position.dx = x - if self.r_left_width > self.left_width then + if + self.r_left_width > self.left_width + or + self.r_right_width > self.right_width + then self.dragged_panel = self:get_hovered_panel(x, y) self.cursor = "hand" end @@ -854,6 +878,7 @@ end function StatusView:on_mouse_moved(x, y, dx, dy) + if not self.visible then return end StatusView.super.on_mouse_moved(self, x, y, dx, dy) self.hovered_panel = self:get_hovered_panel(x, y) @@ -896,6 +921,7 @@ end function StatusView:on_mouse_released(button, x, y) + if not self.visible then return end StatusView.super.on_mouse_released(self, button, x, y) if self.dragged_panel ~= "" then @@ -922,12 +948,26 @@ end function StatusView:on_mouse_wheel(y) + if not self.visible then return end self:drag_panel(self.hovered_panel, y * self.left_width / 10) end function StatusView:update() - self.size.y = style.font:get_height() + style.padding.y * 2 + if not self.visible and self.size.y <= 0 then + return + elseif not self.visible and self.size.y > 0 then + self:move_towards(self.size, "y", 0) + return + end + + local height = style.font:get_height() + style.padding.y * 2; + + if self.size.y + 1 < height then + self:move_towards(self.size, "y", height) + else + self.size.y = height + end if system.get_time() < self.message_timeout then self.scroll.to.y = self.size.y @@ -957,6 +997,8 @@ end function StatusView:draw() + if not self.visible and self.size.y <= 0 then return end + self:draw_background(style.background2) if self.message and system.get_time() <= self.message_timeout then From 2ce4dbc8ef7ffeff6fa950a927d4007712479d44 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 13 Mar 2022 04:55:41 -0400 Subject: [PATCH 167/409] language_md: some more improvements * handle images with links * handle escaping of * and ` * support coloring a heading custom id * reverted from regex to lua patterns --- data/plugins/language_md.lua | 65 ++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index 5013279e..591dba47 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -120,46 +120,45 @@ syntax.add { { pattern = { "```liquid", "```" }, type = "string", syntax = ".liquid" }, { pattern = { "```", "```" }, type = "string" }, { pattern = { "``", "``" }, type = "string" }, - { pattern = { "`", "`" }, type = "string" }, + { pattern = { "%f[\\`]%`[%S]", "`" }, type = "string" }, -- strike { pattern = { "~~", "~~" }, type = "keyword2" }, -- highlight { pattern = { "==", "==" }, type = "literal" }, -- lines - { regex = "^\\-{2,}\\-$", type = "comment" }, - { regex = "^\\*{2,}\\*$", type = "comment" }, - { regex = "^\\_{2,}_$", type = "comment" }, + { pattern = "^%-%-%-+\n", type = "comment" }, + { pattern = "^%*%*%*+\n", type = "comment" }, + { pattern = "^___+\n", type = "comment" }, -- bullets - { regex = "^\\s*\\*\\s", type = "number" }, - { regex = "^\\s*-\\s", type = "number" }, - { regex = "^\\s*\\+\\s", type = "number" }, + { pattern = "^%s*%*%s", type = "number" }, + { pattern = "^%s*%-%s", type = "number" }, + { pattern = "^%s*%+%s", type = "number" }, -- numbered bullet - { regex = "^\\s*[0-9]+\\.\\s", type = "number" }, + { pattern = "^%s*[0-9]+%.%s", type = "number" }, -- blockquote - { regex = "^\\s*>{1,}\\s", type = "string" }, + { pattern = "^%s*>+%s", type = "string" }, -- bold and italic - { pattern = { "%*%*%*", "%*%*%*" }, type = "markdown_bold_italic" }, - { pattern = { "%*%*", "%*%*" }, type = "markdown_bold" }, + { pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" }, + { pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" }, -- handle edge case where asterisk can be at end of line and not close - { pattern = { "%*[%S]", "%*" }, type = "markdown_italic" }, - -- alternative bold italic formats { - regex = "^_{3}[\\s[:punct:]A-Za-z0-9]+_{3}\\s" , - type = "markdown_bold_italic" - }, - { - regex = "^_{2}[\\s[:punct:]A-Za-z0-9]+_{2}\\s" , - type = "markdown_bold" - }, - { - regex = "^_{1}[\\s[:punct:]A-Za-z0-9]+_{1}\\s" , + pattern = { "%f[\\%*]%*[%S]", "%*%f[^%*]" }, type = "markdown_italic" }, + -- alternative bold italic formats + { pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" }, + { pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" }, + { pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" }, { pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" }, { pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" }, { pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" }, + -- heading with custom id + { + pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}", + type = { "keyword", "function", "string", "function" } + }, -- headings - { regex = "^#{1,6}.+", type = "keyword" }, + { pattern = "^#+%s.+\n", type = "keyword" }, -- superscript and subscript { pattern = "%^()%d+()%^", @@ -170,13 +169,17 @@ syntax.add { type = { "function", "number", "function" } }, -- definitions - { regex = "^:\\s.+", type = "function" }, + { pattern = "^:%s.+", type = "function" }, -- emoji { pattern = ":[a-zA-Z0-9_%-]+:", type = "literal" }, - -- images and links + -- images and link { - pattern = "!?%[()["..in_squares_match.."]+()%]()%(()["..in_parenthesis_match.."]+()%)", - type = { "function", "string", "function", "function", "number", "function" } + pattern = "!?%[!?%[()["..in_squares_match.."]+()%]%(()["..in_parenthesis_match.."]+()%)%]%(()["..in_parenthesis_match.."]+()%)", + type = { "function", "string", "function", "number", "function", "number", "function" } + }, + { + pattern = "!?%[!?%[?()["..in_squares_match.."]+()%]?%]%(()["..in_parenthesis_match.."]+()%)", + type = { "function", "string", "function", "number", "function" } }, -- reference links { @@ -184,11 +187,15 @@ syntax.add { type = { "function", "string", "function", "function", "number", "function" } }, { - pattern = "%[%^?()["..in_squares_match.."]+()%]: ", + pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ", type = { "function", "number", "function" } }, { - pattern = "%[%^?()["..in_squares_match.."]+()%]", + pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.+\n", + type = { "function", "number", "function" } + }, + { + pattern = "!?%[%^?()["..in_squares_match.."]+()%]", type = { "function", "number", "function" } }, -- url's and email From dcbebef2aba11484d80fd6a7da22ffd0744c61c4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 15 Mar 2022 18:14:27 -0400 Subject: [PATCH 168/409] plugin detectident: fixes and improvements * Improved performance 67x by not using the tokenizer, this means that now opening files or saving them where indentation is re-detected is much more faster. * Improved the algorithm to detect the space size. --- data/plugins/detectindent.lua | 310 ++++++++++++++++++++++++++-------- 1 file changed, 239 insertions(+), 71 deletions(-) diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 9ac29882..119601f0 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -3,93 +3,253 @@ local core = require "core" local command = require "core.command" local common = require "core.common" local config = require "core.config" +local core_syntax = require "core.syntax" local DocView = require "core.docview" local Doc = require "core.doc" -local tokenizer = require "core.tokenizer" local cache = setmetatable({}, { __mode = "k" }) +local comments_cache = {} +local auto_detect_max_lines = 150 -local function add_to_stat(stat, val) - for i = 1, #stat do - if val == stat[i][1] then - stat[i][2] = stat[i][2] + 1 - return - end +local function indent_occurrences_more_than_once(stat, idx) + if stat[idx-1] and stat[idx-1] == stat[idx] then + return true + elseif stat[idx+1] and stat[idx+1] == stat[idx] then + return true end - stat[#stat + 1] = {val, 1} + return false end local function optimal_indent_from_stat(stat) if #stat == 0 then return nil, 0 end - local bins = {} - for k = 1, #stat do - local indent = stat[k][1] + table.sort(stat, function(a, b) return a > b end) + local best_indent = 0 + local best_score = 0 + local count = #stat + for x=1, count do + local indent = stat[x] local score = 0 - local mult_prev, lines_prev - for i = k, #stat do - if stat[i][1] % indent == 0 then - local mult = stat[i][1] / indent - if not mult_prev or (mult_prev + 1 == mult and lines_prev / stat[i][2] > 0.1) then - -- we add the number of lines to the score only if the previous - -- multiple of "indent" was populated with enough lines. - score = score + stat[i][2] - end - mult_prev, lines_prev = mult, stat[i][2] + for y=1, count do + if y ~= x and stat[y] % indent == 0 then + score = score + 1 + elseif + indent > stat[y] + and + indent_occurrences_more_than_once(stat, y) + then + score = 0 + break end end - bins[#bins + 1] = {indent, score} - end - table.sort(bins, function(a, b) return a[2] > b[2] end) - return bins[1][1], bins[1][2] -end - - --- return nil if it is a comment or blank line or the initial part of the --- line otherwise. --- we don't need to have the whole line to detect indentation. -local function get_first_line_part(tokens) - local i, n = 1, #tokens - while i + 1 <= n do - local ttype, ttext = tokens[i], tokens[i + 1] - if ttype ~= "comment" and ttext:gsub("%s+", "") ~= "" then - return ttext + if score > best_score then + best_indent = indent + best_score = score + end + if score > 0 then + break end - i = i + 2 end + return best_score > 0 and best_indent or nil, best_score end + +local function escape_comment_tokens(token) + local special_chars = "*-%[].()+?^$" + local escaped = "" + for x=1, token:len() do + local found = false + for y=1, special_chars:len() do + if token:sub(x, x) == special_chars:sub(y, y) then + escaped = escaped .. "%" .. token:sub(x, x) + found = true + break + end + end + if not found then + escaped = escaped .. token:sub(x, x) + end + end + return escaped +end + + +local function get_comment_patterns(syntax) + if comments_cache[syntax.name] then + if #comments_cache[syntax.name] > 0 then + return comments_cache[syntax.name] + else + return nil + end + end + local comments = {} + for idx=1, #syntax.patterns do + local pattern = syntax.patterns[idx] + local startp = "" + if + type(pattern.type) == "string" + and + (pattern.type == "comment" or pattern.type == "string") + then + local not_is_string = pattern.type ~= "string" + if pattern.pattern then + startp = type(pattern.pattern) == "table" + and pattern.pattern[1] or pattern.pattern + if not_is_string and startp:sub(1, 1) ~= "^" then + startp = "^%s*" .. startp + elseif not_is_string then + startp = "^%s*" .. startp:sub(2, startp:len()) + end + if type(pattern.pattern) == "table" then + table.insert(comments, {"p", startp, pattern.pattern[2]}) + elseif not_is_string then + table.insert(comments, {"p", startp}) + end + elseif pattern.regex then + startp = type(pattern.regex) == "table" + and pattern.regex[1] or pattern.regex + if not_is_string and startp:sub(1, 1) ~= "^" then + startp = "^\\s*" .. startp + elseif not_is_string then + startp = "^\\s*" .. startp:sub(2, startp:len()) + end + if type(pattern.regex) == "table" then + table.insert(comments, { + "r", startp, pattern.regex[2] + }) + elseif not_is_string then + table.insert(comments, {"r", startp}) + end + end + elseif pattern.syntax then + local subsyntax = core_syntax.get("file"..pattern.syntax, "") + local sub_comments = get_comment_patterns(subsyntax) + if sub_comments then + for s=1, #sub_comments do + table.insert(comments, sub_comments[s]) + end + end + end + end + if #comments == 0 then + local single_line_comment = syntax.comment + and escape_comment_tokens(syntax.comment) or nil + local block_comment = nil + if syntax.block_comment then + block_comment = { + escape_comment_tokens(syntax.block_comment[1]), + escape_comment_tokens(syntax.block_comment[2]) + } + end + if single_line_comment then + table.insert(comments, {"p", "^%s*" .. single_line_comment}) + end + if block_comment then + table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]}) + end + end + comments_cache[syntax.name] = comments + if #comments > 0 then + return comments + end + return nil +end + + local function get_non_empty_lines(syntax, lines) return coroutine.wrap(function() - local tokens, state + local comments = get_comment_patterns(syntax) + local i = 0 + local end_regex = nil + local end_pattern = nil + local inside_comment = false for _, line in ipairs(lines) do - tokens, state = tokenizer.tokenize(syntax, line, state) - local line_start = get_first_line_part(tokens) - if line_start then - i = i + 1 - coroutine.yield(i, line_start) + if line:gsub("^%s+", "") ~= "" then + local is_comment = false + if comments then + if not inside_comment then + for c=1, #comments do + local comment = comments[c] + if comment[1] == "p" then + if comment[3] then + local start, ending = line:find(comment[2]) + if start then + if not line:find(comment[3], ending+1) then + is_comment = true + inside_comment = true + end_pattern = comment[3] + end + break + end + elseif line:find(comment[2]) then + is_comment = true + break + end + else + if comment[3] then + local start, ending = regex.match( + comment[2], line, 1, regex.ANCHORED + ) + if start then + if not regex.match( + comment[3], line, ending+1, regex.ANCHORED + ) + then + is_comment = true + inside_comment = true + end_regex = comment[3] + end + break + end + elseif regex.match(comment[2], line, 1, regex.ANCHORED) then + is_comment = true + break + end + end + end + elseif end_pattern and line:find(end_pattern) then + is_comment = true + inside_comment = false + end_pattern = nil + elseif end_regex and regex.match(end_regex, line) then + is_comment = true + inside_comment = false + end_regex = nil + end + end + if + not is_comment + and + not inside_comment + then + i = i + 1 + coroutine.yield(i, line) + end end end end) end -local auto_detect_max_lines = 100 - local function detect_indent_stat(doc) local stat = {} local tab_count = 0 + local runs = 1 + local max_lines = auto_detect_max_lines for i, text in get_non_empty_lines(doc.syntax, doc.lines) do - local str = text:match("^ %s+%S") - if str then add_to_stat(stat, #str - 1) end - local str = text:match("^\t+") - if str then tab_count = tab_count + 1 end + local spaces = text:match("^ +") + if spaces then table.insert(stat, spaces:len()) end + local tabs = text:match("^\t+") + if tabs then tab_count = tab_count + 1 end + -- if nothing found for first lines try at least 4 more times + if i == max_lines and runs < 5 and #stat == 0 and tab_count == 0 then + max_lines = max_lines + auto_detect_max_lines + runs = runs + 1 -- Stop parsing when files is very long. Not needed for euristic determination. - if i > auto_detect_max_lines then break end + elseif i > max_lines then break end end - table.sort(stat, function(a, b) return a[1] < b[1] end) local indent, score = optimal_indent_from_stat(stat) if tab_count > score then return "hard", config.indent_size, tab_count @@ -101,7 +261,7 @@ end local function update_cache(doc) local type, size, score = detect_indent_stat(doc) - local score_threshold = 4 + local score_threshold = 2 if score < score_threshold then -- use default values type = config.tab_type @@ -130,9 +290,11 @@ end local function set_indent_type(doc, type) local _, indent_size = doc:get_indent_info() - cache[doc] = {type = type, - size = indent_size, - confirmed = true} + cache[doc] = { + type = type, + size = indent_size, + confirmed = true + } doc.indent_info = cache[doc] end @@ -158,9 +320,11 @@ end local function set_indent_size(doc, size) local indent_type = doc:get_indent_info() - cache[doc] = {type = indent_type, - size = size, - confirmed = true} + cache[doc] = { + type = indent_type, + size = size, + confirmed = true + } doc.indent_info = cache[doc] end @@ -168,14 +332,14 @@ local function set_indent_size_command() core.command_view:enter( "Specify indent size for current file", function(value) -- submit - local value = math.floor(tonumber(value)) + value = math.floor(tonumber(value)) local doc = core.active_view.doc set_indent_size(doc, value) end, nil, -- suggest nil, -- cancel function(value) -- validate - local value = tonumber(value) + value = tonumber(value) return value ~= nil and value >= 1 end ) @@ -187,20 +351,24 @@ command.add("core.docview", { ["indent:set-file-indent-size"] = set_indent_size_command }) - -command.add(function() +command.add( + function() return core.active_view:is(DocView) - and cache[core.active_view.doc] - and cache[core.active_view.doc].type == "soft" + and cache[core.active_view.doc] + and cache[core.active_view.doc].type == "soft" end, { - ["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end + ["indent:switch-file-to-tabs-indentation"] = function() + set_indent_type(core.active_view.doc, "hard") + end }) - -command.add(function() +command.add( + function() return core.active_view:is(DocView) - and cache[core.active_view.doc] - and cache[core.active_view.doc].type == "hard" + and cache[core.active_view.doc] + and cache[core.active_view.doc].type == "hard" end, { - ["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end + ["indent:switch-file-to-spaces-indentation"] = function() + set_indent_type(core.active_view.doc, "soft") + end }) From 5830b7d9f0aeb5bfc3eed8011ebcf0d4ca9caf08 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 17 Mar 2022 00:14:36 -0400 Subject: [PATCH 169/409] plugin detectindent: pre-compile regexes --- data/plugins/detectindent.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 119601f0..ea9c8b28 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -116,10 +116,10 @@ local function get_comment_patterns(syntax) end if type(pattern.regex) == "table" then table.insert(comments, { - "r", startp, pattern.regex[2] + "r", regex.compile(startp), regex.compile(pattern.regex[2]) }) elseif not_is_string then - table.insert(comments, {"r", startp}) + table.insert(comments, {"r", regex.compile(startp)}) end end elseif pattern.syntax then From 120c769e7e184a2c8ba772969ceb2f3a7e709843 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 17 Mar 2022 18:43:01 +0100 Subject: [PATCH 170/409] seperate dirmonitor logic, add build time detection of features (#866) this also adds libkqueue support --- meson_options.txt | 1 + src/api/dirmonitor.c | 147 ++++------------------------------- src/api/dirmonitor/dummy.c | 22 ++++++ src/api/dirmonitor/inotify.c | 58 ++++++++++++++ src/api/dirmonitor/kqueue.c | 53 +++++++++++++ src/api/dirmonitor/win32.c | 77 ++++++++++++++++++ src/meson.build | 36 ++++++++- 7 files changed, 263 insertions(+), 131 deletions(-) create mode 100644 src/api/dirmonitor/dummy.c create mode 100644 src/api/dirmonitor/inotify.c create mode 100644 src/api/dirmonitor/kqueue.c create mode 100644 src/api/dirmonitor/win32.c diff --git a/meson_options.txt b/meson_options.txt index 1cf3e22f..7850416e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,3 +2,4 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies') option('portable', type : 'boolean', value : false, description: 'Portable install') option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') +option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') \ No newline at end of file diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 15b63a97..3c79c0d9 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -15,144 +15,31 @@ #include #include -/* -This is *slightly* a clusterfuck. Normally, we'd -have windows wait on a list of handles like inotify, -however, MAXIMUM_WAIT_OBJECTS is 64. Yes, seriously. - -So, for windows, we are recursive. -*/ -struct dirmonitor { - int fd; - #if _WIN32 - HANDLE handle; - char buffer[8192]; - OVERLAPPED overlapped; - bool running; - #endif -}; - -struct dirmonitor* init_dirmonitor() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); - #ifndef _WIN32 - #if __linux__ - monitor->fd = inotify_init1(IN_NONBLOCK); - #else - monitor->fd = kqueue(); - #endif - #endif - return monitor; -} - -#if _WIN32 -static void close_monitor_handle(struct dirmonitor* monitor) { - if (monitor->handle) { - if (monitor->running) { - BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); - DWORD error = GetLastError(); - if (result == TRUE || error != ERROR_NOT_FOUND) { - DWORD bytes_transferred; - GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); - } - monitor->running = false; - } - CloseHandle(monitor->handle); - } - monitor->handle = NULL; -} +#ifndef DIRMONITOR_BACKEND +#error No dirmonitor backend defined #endif -void deinit_dirmonitor(struct dirmonitor* monitor) { - #if _WIN32 - close_monitor_handle(monitor); - #else - close(monitor->fd); - #endif - free(monitor); -} +#define GLUE_HELPER(x, y) x##y +#define GLUE(x, y) GLUE_HELPER(x, y) -int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - #if _WIN32 - if (!monitor->running) { - if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) - return GetLastError(); - monitor->running = true; - } - DWORD bytes_transferred; - if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { - int error = GetLastError(); - return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; - } - monitor->running = false; - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { - change_callback(info->FileNameLength, (char*)info->FileName, data); - if (!info->NextEntryOffset) - break; - } - monitor->running = false; - return 0; - #elif __linux__ - char buf[PATH_MAX + sizeof(struct inotify_event)]; - ssize_t offset = 0; - while (1) { - ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); - if (len == -1 && errno != EAGAIN) - return errno; - if (len <= 0) - return 0; - while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { - change_callback(((const struct inotify_event *)buf)->wd, NULL, data); - len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; - memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); - offset = len; - } - } - #else - struct kevent event; - while (1) { - struct timespec tm = {0}; - int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); - if (nev == -1) - return errno; - if (nev <= 0) - return 0; - change_callback(event.ident, NULL, data); - } - #endif -} +#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND) +#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND) +#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND) +#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND) +#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND) -int add_dirmonitor(struct dirmonitor* monitor, const char* path) { - #if _WIN32 - close_monitor_handle(monitor); - monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) - return 1; - monitor->handle = NULL; - return -1; - #elif __linux__ - return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); - #else - int fd = open(path, O_RDONLY); - struct kevent change; - EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); - kevent(monitor->fd, &change, 1, NULL, 0, NULL); - return fd; - #endif -} +struct dirmonitor {}; // dirmonitor struct is defined in each backend -void remove_dirmonitor(struct dirmonitor* monitor, int fd) { - #if _WIN32 - close_monitor_handle(monitor); - #elif __linux__ - inotify_rm_watch(monitor->fd, fd); - #else - close(fd); - #endif -} +// define functions so we know their signature +struct dirmonitor* init_dirmonitor(); +void deinit_dirmonitor(struct dirmonitor*); +int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*); +int add_dirmonitor(struct dirmonitor*, const char*); +void remove_dirmonitor(struct dirmonitor*, int); static int f_check_dir_callback(int watch_id, const char* path, void* L) { lua_pushvalue(L, -1); - #if _WIN32 + #ifdef DIRMONITOR_WIN32 char buffer[PATH_MAX*4]; int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); lua_pushlstring(L, buffer, count); diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c new file mode 100644 index 00000000..cc0b41ee --- /dev/null +++ b/src/api/dirmonitor/dummy.c @@ -0,0 +1,22 @@ +#include + +struct dirmonitor { +}; + +struct dirmonitor* init_dirmonitor_dummy() { + return NULL; +} + +void deinit_dirmonitor_dummy(struct dirmonitor* monitor) { +} + +int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + return -1; +} + +int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) { + return -1; +} + +void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) { +} \ No newline at end of file diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c new file mode 100644 index 00000000..c284e501 --- /dev/null +++ b/src/api/dirmonitor/inotify.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include + +struct dirmonitor { + int fd; +}; + +struct dirmonitor* init_dirmonitor_inotify() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + + monitor->fd = inotify_init(); + fcntl(monitor->fd, F_SETFL, O_NONBLOCK); + + + return monitor; +} + +void deinit_dirmonitor_inotify(struct dirmonitor* monitor) { + close(monitor->fd); + free(monitor); +} + +int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + char buf[PATH_MAX + sizeof(struct inotify_event)]; + ssize_t offset = 0; + + while (1) { + ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); + + if (len == -1 && errno != EAGAIN) { + return errno; + } + + if (len <= 0) { + return 0; + } + + while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { + change_callback(((const struct inotify_event *)buf)->wd, NULL, data); + len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; + memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); + offset = len; + } + } +} + +int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) { + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); +} + +void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) { + inotify_rm_watch(monitor->fd, fd); +} \ No newline at end of file diff --git a/src/api/dirmonitor/kqueue.c b/src/api/dirmonitor/kqueue.c new file mode 100644 index 00000000..e7f041bd --- /dev/null +++ b/src/api/dirmonitor/kqueue.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include + +struct dirmonitor { + int fd; +}; + +struct dirmonitor* init_dirmonitor_kqueue() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + monitor->fd = kqueue(); + return monitor; +} + +void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) { + close(monitor->fd); + free(monitor); +} + +int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + struct kevent event; + while (1) { + struct timespec tm = {0}; + int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); + + if (nev == -1) { + return errno; + } + + if (nev <= 0) { + return 0; + } + + change_callback(event.ident, NULL, data); + } +} + +int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { + int fd = open(path, O_RDONLY); + struct kevent change; + + EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); + kevent(monitor->fd, &change, 1, NULL, 0, NULL); + + return fd; +} + +void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) { + close(fd); +} \ No newline at end of file diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c new file mode 100644 index 00000000..9dd2a254 --- /dev/null +++ b/src/api/dirmonitor/win32.c @@ -0,0 +1,77 @@ +#include +#include + +struct dirmonitor { + HANDLE handle; + char buffer[8192]; + OVERLAPPED overlapped; + bool running; +}; + +struct dirmonitor* init_dirmonitor_win32() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + + return monitor; +} + +static void close_monitor_handle(struct dirmonitor* monitor) { + if (monitor->handle) { + if (monitor->running) { + BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); + DWORD error = GetLastError(); + if (result == TRUE || error != ERROR_NOT_FOUND) { + DWORD bytes_transferred; + GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); + } + monitor->running = false; + } + CloseHandle(monitor->handle); + } + monitor->handle = NULL; +} + +void deinit_dirmonitor_win32(struct dirmonitor* monitor) { + close_monitor_handle(monitor); + free(monitor); +} + +int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + if (!monitor->running) { + if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) { + return GetLastError(); + } + monitor->running = true; + } + + DWORD bytes_transferred; + + if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { + int error = GetLastError(); + return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; + } + + monitor->running = false; + + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { + change_callback(info->FileNameLength, (char*)info->FileName, data); + if (!info->NextEntryOffset) + break; + } + + monitor->running = false; + return 0; +} + +int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { + return 1; + } + monitor->handle = NULL; + return -1; +} + +void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { + close_monitor_handle(monitor); +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 8a4f1272..5ad95bdd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,5 @@ lite_sources = [ 'api/api.c', - 'api/dirmonitor.c', 'api/renderer.c', 'api/regex.c', 'api/system.c', @@ -11,6 +10,41 @@ lite_sources = [ 'main.c', ] +# dirmonitor backend +if get_option('dirmonitor_backend') == '' + if cc.has_function('inotify_init', prefix : '#include') + dirmonitor_backend = 'inotify' + elif cc.has_function('kqueue', prefix : '#include') + dirmonitor_backend = 'kqueue' + elif dependency('libkqueue', required : false).found() + dirmonitor_backend = 'kqueue' + elif host_machine.system() == 'windows' + dirmonitor_backend = 'win32' + else + dirmonitor_backend = 'dummy' + warning('no suitable backend found, defaulting to dummy backend') + endif +else + dirmonitor_backend = get_option('dirmonitor_backend') +endif + +message('dirmonitor_backend: @0@'.format(dirmonitor_backend)) + +if dirmonitor_backend == 'kqueue' + libkqueue_dep = dependency('libkqueue', required : false) + if libkqueue_dep.found() + lite_deps += libkqueue_dep + endif +endif + +lite_sources += [ + 'api/dirmonitor.c', + 'api/dirmonitor/' + dirmonitor_backend + '.c', +] +lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend +lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper() + + lite_rc = [] if host_machine.system() == 'windows' windows = import('windows') From 82325b6a08bfcf3243308a5ca3df1d397d5fbe33 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 17 Mar 2022 16:55:52 -0400 Subject: [PATCH 171/409] Fixed a bunch of problems. Fixed left+click not allowing for square selections, fixed esc not exiting multicursor mode, and allowed cntrl+click to remove a cursor. --- data/core/commands/doc.lua | 25 ++++++++++++++++--------- data/core/doc/init.lua | 6 ++++++ data/core/docview.lua | 1 - 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 01d85f27..81592b66 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -175,14 +175,12 @@ local function block_comment(comment, line1, col1, line2, col2) end end -local selection_commands = { +local commands = { ["doc:select-none"] = function() local line, col = doc():get_selection() doc():set_selection(line, col) - end -} - -local commands = { + end, + ["doc:cut"] = function() cut_or_copy(true) end, @@ -523,7 +521,19 @@ local commands = { ["doc:split-cursor"] = function(x, y, clicks) local line, col = dv():resolve_screen_position(x, y) - doc():add_selection(line, col, line, col) + local removal_target = nil + for idx, line1, col1 in doc():get_selections(true) do + if line1 == line and col1 == col and #doc().selections > 4 then + removal_target = idx + end + end + end + if removal_target then + doc():remove_selection(removal_target) + else + doc():add_selection(line, col, line, col) + end + dv().mouse_selecting = { line, col, "set" } end, ["doc:create-cursor-previous-line"] = function() @@ -584,6 +594,3 @@ commands["doc:move-to-next-char"] = function() end command.add("core.docview", commands) -command.add(function() - return core.active_view:is(DocView) and core.active_view.doc:has_any_selection() -end ,selection_commands) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index ab4b18a0..0a0756a8 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -198,6 +198,12 @@ function Doc:add_selection(line1, col1, line2, col2, swap) self:set_selections(target, line1, col1, line2, col2, swap, 0) end + +function Doc:remove_selection(idx) + common.splice(self.selections, (idx - 1) * 4 + 1, 4) +end + + function Doc:set_selection(line1, col1, line2, col2, swap) self.selections = {} self:set_selections(1, line1, col1, line2, col2, swap) diff --git a/data/core/docview.lua b/data/core/docview.lua index 70403e68..397b455e 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -224,7 +224,6 @@ function DocView:scroll_to_make_visible(line, col) end end - function DocView:on_mouse_moved(x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...) From ba5289dc75b6ffa0f99b08a08af32cfade0b0462 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 17 Mar 2022 16:57:18 -0400 Subject: [PATCH 172/409] Typo. --- data/core/commands/doc.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 81592b66..d80c4641 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -522,10 +522,9 @@ local commands = { ["doc:split-cursor"] = function(x, y, clicks) local line, col = dv():resolve_screen_position(x, y) local removal_target = nil - for idx, line1, col1 in doc():get_selections(true) do - if line1 == line and col1 == col and #doc().selections > 4 then - removal_target = idx - end + for idx, line1, col1 in doc():get_selections(true) do + if line1 == line and col1 == col and #doc().selections > 4 then + removal_target = idx end end if removal_target then From fb4a5f38284ce046a4b0b18133ef78af96de7702 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 10 Oct 2021 03:21:49 +0200 Subject: [PATCH 173/409] Add command to create a new named Doc --- data/core/commands/core.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 84ec9b91..927d01f6 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -103,6 +103,15 @@ command.add(nil, { core.root_view:open_doc(core.open_doc()) end, + ["core:new-named-doc"] = function() + core.command_view:enter( + "File name", + function(text) + core.root_view:open_doc(core.open_doc(text)) + end + ) + end, + ["core:open-file"] = function() local view = core.active_view if view.doc and view.doc.abs_filename then From 9763701cbf5195edf3e0a6dd9e65b46e2b37abbb Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 10 Oct 2021 03:28:06 +0200 Subject: [PATCH 174/409] Reset syntax when a filename is provided --- data/core/doc/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 0a0756a8..f9369a46 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -54,6 +54,7 @@ end function Doc:set_filename(filename, abs_filename) self.filename = filename self.abs_filename = abs_filename + self:reset_syntax() end @@ -95,7 +96,6 @@ function Doc:save(filename, abs_filename) fp:close() self:set_filename(filename, abs_filename) self.new_file = false - self:reset_syntax() self:clean() end From 000caf2e43bcee1f371f6bea167c43be43a570c0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 10 Oct 2021 03:43:23 +0200 Subject: [PATCH 175/409] Allow opening non existing files from arguments --- data/core/init.lua | 54 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 6b70d589..31462e39 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -593,6 +593,25 @@ local function add_config_files_hooks() end +-- The function below works like system.absolute_path except it +-- doesn't fail if the file does not exist. We consider that the +-- current dir is core.project_dir so relative filename are considered +-- to be in core.project_dir. +-- Please note that .. or . in the filename are not taken into account. +-- This function should get only filenames normalized using +-- common.normalize_path function. +function core.project_absolute_path(filename) + if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then + return common.normalize_path(filename) + elseif not core.project_dir then + local cwd = system.absolute_path(".") + return cwd .. PATHSEP .. common.normalize_path(filename) + else + return core.project_dir .. PATHSEP .. filename + end +end + + function core.init() command = require "core.command" keymap = require "core.keymap" @@ -626,23 +645,20 @@ function core.init() local project_dir = core.recent_projects[1] or "." local project_dir_explicit = false local files = {} - local delayed_error for i = 2, #ARGS do local arg_filename = strip_trailing_slash(ARGS[i]) local info = system.get_file_info(arg_filename) or {} - if info.type == "file" then - local file_abs = system.absolute_path(arg_filename) - if file_abs then - table.insert(files, file_abs) - project_dir = file_abs:match("^(.+)[/\\].+$") - end - elseif info.type == "dir" then + if info.type == "dir" then project_dir = arg_filename project_dir_explicit = true else -- on macOS we can get an argument like "-psn_0_52353" that we just ignore. if not ARGS[i]:match("^-psn") then - delayed_error = string.format("error: invalid file or directory %q", ARGS[i]) + local file_abs = core.project_absolute_path(arg_filename) + if file_abs then + table.insert(files, file_abs) + project_dir = file_abs:match("^(.+)[/\\].+$") + end end end end @@ -728,10 +744,6 @@ function core.init() core.root_view:open_doc(core.open_doc(filename)) end - if delayed_error then - core.error(delayed_error) - end - if not plugins_success or got_user_error or got_project_error then command.perform("core:open-log") end @@ -1002,22 +1014,6 @@ function core.normalize_to_project_dir(filename) end --- The function below works like system.absolute_path except it --- doesn't fail if the file does not exist. We consider that the --- current dir is core.project_dir so relative filename are considered --- to be in core.project_dir. --- Please note that .. or . in the filename are not taken into account. --- This function should get only filenames normalized using --- common.normalize_path function. -function core.project_absolute_path(filename) - if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then - return filename - else - return core.project_dir .. PATHSEP .. filename - end -end - - function core.open_doc(filename) local new_file = not filename or not system.get_file_info(filename) local abs_filename From d5da711b6f3989d6cc2b654f1342b4465d24aa28 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 12 Dec 2021 16:07:51 +0800 Subject: [PATCH 176/409] add selections in treeview --- data/plugins/treeview.lua | 173 +++++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 31 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index e12782b6..066fdf92 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -9,6 +9,9 @@ local View = require "core.view" local ContextMenu = require "core.contextmenu" local RootView = require "core.rootview" +config.plugins.treeview = { + clicks_to_open = 2 +} local default_treeview_size = 200 * SCALE local tooltip_offset = style.font:get_height() @@ -42,6 +45,7 @@ function TreeView:new() self.target_size = default_treeview_size self.cache = {} self.tooltip = { x = 0, y = 0, begin = 0, alpha = 0 } + self.cursor_pos = { x = 0, y = 0 } self.item_icon_width = 0 self.item_text_spacing = 0 @@ -176,6 +180,21 @@ function TreeView:each_item() end +function TreeView:set_selection(selection, selection_y) + self.selected_item = selection + if selection and selection_y + and (selection_y <= 0 or selection_y >= self.size.y) then + + local lh = self:get_item_height() + if selection_y >= self.size.y - lh then + selection_y = selection_y - self.size.y + lh + end + local _, y = self:get_content_offset() + self.scroll.to.y = selection and (selection_y - y) + end +end + + function TreeView:get_text_bounding_box(item, x, y, w, h) local icon_width = style.icon_font:get_width("D") local xoffset = item.depth * style.padding.x + style.padding.x + icon_width @@ -190,6 +209,8 @@ function TreeView:on_mouse_moved(px, py, ...) TreeView.super.on_mouse_moved(self, px, py, ...) if self.dragging_scrollbar then return end + self.cursor_pos.x = px + self.cursor_pos.y = py local item_changed, tooltip_changed for item, x,y,w,h in self:each_item() do if px > x and py > y and px <= x + w and py <= y + h then @@ -229,28 +250,23 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) if caught or button ~= "left" then return true end - local hovered_item = self.hovered_item - if not hovered_item then - return false - elseif hovered_item.type == "dir" then + + if self.hovered_item then + self:set_selection(self.hovered_item) + if keymap.modkeys["ctrl"] and button == "left" then - create_directory_in(hovered_item) - else - local hovered_dir = core.project_dir_by_name(hovered_item.dir_name) - if hovered_dir and hovered_dir.files_limit then - core.update_project_subdir(hovered_dir, hovered_item.filename, not hovered_item.expanded) - end - hovered_item.expanded = not hovered_item.expanded + create_directory_in(self.selected_item) + elseif self.selected_item.type == "dir" + or (self.selected_item.type == "file" + and clicks == config.plugins.treeview.clicks_to_open + ) + then + command.perform "treeview:open" end else - core.try(function() - if core.last_active_view and core.active_view == self then - core.set_active_view(core.last_active_view) - end - local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename) - core.root_view:open_doc(core.open_doc(doc_filename)) - end) + return false end + return true end @@ -277,6 +293,13 @@ function TreeView:update() self.item_icon_width = style.icon_font:get_width("D") self.item_text_spacing = style.icon_font:get_width("f") / 2 + -- this will make sure hovered_item is updated + -- we don't want events when the thing is scrolling fast + local dy = math.abs(self.scroll.to.y - self.scroll.y) + if self.scroll.to.y ~= 0 and dy < self:get_item_height() then + self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, self.scroll.to.y - self.scroll.y) + end + TreeView.super.update(self) end @@ -360,6 +383,10 @@ end function TreeView:draw_item_background(item, active, hovered, x, y, w, h) if hovered then + local hover_color = { table.unpack(style.line_highlight) } + hover_color[4] = 160 + renderer.draw_rect(x, y, w, h, hover_color) + elseif active then renderer.draw_rect(x, y, w, h, style.line_highlight) end end @@ -386,7 +413,7 @@ function TreeView:draw() for item, x,y,w,h in self:each_item() do if y + h >= _y and y < _y + _h then self:draw_item(item, - item.abs_filename == active_filename, + item == self.selected_item, item == self.hovered_item, x, y, w, h) end @@ -497,13 +524,89 @@ menu:register( command.add(nil, { ["treeview:toggle"] = function() view.visible = not view.visible - end}) + end +}) + +command.add(TreeView, { + ["treeview:next"] = function() + local item = view.selected_item + local item_y + local stop = false + for it, _, y in view:each_item() do + if item == it then + stop = true + elseif stop then + item = it + item_y = y + break + end + end + + view:set_selection(item, item_y) + end, + + ["treeview:previous"] = function() + local last_item + local last_item_y + for it, _, y in view:each_item() do + if it == view.selected_item then + if not last_item then + last_item = it + last_item_y = y + end + break + end + last_item = it + last_item_y = y + end + + view:set_selection(last_item, last_item_y) + end, + + ["treeview:open"] = function() + local item = view.selected_item + + if not item then return end + if item.type == "dir" then + item.expanded = not item.expanded + + if view.selected_item + and view.selected_item.abs_filename ~= item.abs_filename + and view.selected_item.abs_filename:find(item.abs_filename, 1, true) == 1 + and not item.expanded + then + -- deselect the item if it is hidden when its parent is collapsed + view.selected_item = nil + end + + local hovered_dir = core.project_dir_by_name(item.dir_name) + if hovered_dir and hovered_dir.files_limit then + core.update_project_subdir(hovered_dir, item.filename, not item.expanded) + end + else + core.try(function() + if core.last_active_view and core.active_view == view then + core.set_active_view(core.last_active_view) + end + local doc_filename = core.normalize_to_project_dir(item.abs_filename) + core.root_view:open_doc(core.open_doc(doc_filename)) + end) + end + end, + + ["treeview:deselect"] = function() + view.selected_item = nil + end +}) -command.add(function() return view.hovered_item ~= nil end, { +local function treeitem() return view.hovered_item or view.selected_item end + + +command.add(function() return treeitem() ~= nil end, { ["treeview:rename"] = function() - local old_filename = view.hovered_item.filename - local old_abs_filename = view.hovered_item.abs_filename + local old_filename = treeitem().filename + local old_abs_filename = treeitem().abs_filename core.command_view:set_text(old_filename) core.command_view:enter("Rename", function(filename) filename = core.normalize_to_project_dir(filename) @@ -525,8 +628,8 @@ command.add(function() return view.hovered_item ~= nil end, { end, ["treeview:new-file"] = function() - if not is_project_folder(view.hovered_item.abs_filename) then - core.command_view:set_text(view.hovered_item.filename .. "/") + if not is_project_folder(treeitem().abs_filename) then + core.command_view:set_text(treeitem().filename .. "/") end core.command_view:enter("Filename", function(filename) local doc_filename = core.project_dir .. PATHSEP .. filename @@ -539,8 +642,8 @@ command.add(function() return view.hovered_item ~= nil end, { end, ["treeview:new-folder"] = function() - if not is_project_folder(view.hovered_item.abs_filename) then - core.command_view:set_text(view.hovered_item.filename .. "/") + if not is_project_folder(treeitem().abs_filename) then + core.command_view:set_text(treeitem().filename .. "/") end core.command_view:enter("Folder Name", function(filename) local dir_path = core.project_dir .. PATHSEP .. filename @@ -550,8 +653,8 @@ command.add(function() return view.hovered_item ~= nil end, { end, ["treeview:delete"] = function() - local filename = view.hovered_item.abs_filename - local relfilename = view.hovered_item.filename + local filename = treeitem().abs_filename + local relfilename = treeitem().filename local file_info = system.get_file_info(filename) local file_type = file_info.type == "dir" and "Directory" or "File" -- Ask before deleting @@ -588,7 +691,7 @@ command.add(function() return view.hovered_item ~= nil end, { end, ["treeview:open-in-system"] = function() - local hovered_item = view.hovered_item + local hovered_item = treeitem() if PLATFORM == "Windows" then system.exec(string.format("start \"\" %q", hovered_item.abs_filename)) @@ -600,7 +703,15 @@ command.add(function() return view.hovered_item ~= nil end, { end, }) -keymap.add { ["ctrl+\\"] = "treeview:toggle" } +keymap.add { + ["ctrl+\\"] = "treeview:toggle", + ["up"] = "treeview:previous", + ["down"] = "treeview:next", + ["return"] = "treeview:open", + ["escape"] = "treeview:deselect", + ["delete"] = "treeview:delete", + ["ctrl+return"] = "treeview:new-folder" +} -- Return the treeview with toolbar and contextmenu to allow -- user or plugin modifications From 02f6dcc07d460c6429d900eea29b346cef749877 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 18 Mar 2022 03:57:14 -0400 Subject: [PATCH 177/409] treeview: added @AlexSol suggestions * suggestions included collapse, expand and focus * also added missing common.merge * some other minor fixes --- data/plugins/treeview.lua | 67 ++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 066fdf92..b70b0700 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -9,9 +9,10 @@ local View = require "core.view" local ContextMenu = require "core.contextmenu" local RootView = require "core.rootview" -config.plugins.treeview = { +config.plugins.treeview = common.merge({ + -- Amount of clicks to open a file clicks_to_open = 2 -} +}, config.plugins.treeview) local default_treeview_size = 200 * SCALE local tooltip_offset = style.font:get_height() @@ -425,6 +426,24 @@ function TreeView:draw() end end +function TreeView:toggle_expand(toggle) + local item = self.selected_item + + if not item then return end + + if item.type == "dir" then + if type(toggle) == "boolean" then + item.expanded = toggle + else + item.expanded = not item.expanded + end + local hovered_dir = core.project_dir_by_name(item.dir_name) + if hovered_dir and hovered_dir.files_limit then + core.update_project_subdir(hovered_dir, item.filename, item.expanded) + end + end +end + -- init local view = TreeView() @@ -524,6 +543,21 @@ menu:register( command.add(nil, { ["treeview:toggle"] = function() view.visible = not view.visible + end, + + ["treeview:toggle-focus"] = function() + if not core.active_view:is(TreeView) then + core.set_active_view(view) + if not view.selected_item then + for it, _, y in view:each_item() do + view:set_selection(it, y) + break + end + end + + else + core.set_active_view(core.last_active_view) + end end }) @@ -565,24 +599,9 @@ command.add(TreeView, { ["treeview:open"] = function() local item = view.selected_item - if not item then return end if item.type == "dir" then - item.expanded = not item.expanded - - if view.selected_item - and view.selected_item.abs_filename ~= item.abs_filename - and view.selected_item.abs_filename:find(item.abs_filename, 1, true) == 1 - and not item.expanded - then - -- deselect the item if it is hidden when its parent is collapsed - view.selected_item = nil - end - - local hovered_dir = core.project_dir_by_name(item.dir_name) - if hovered_dir and hovered_dir.files_limit then - core.update_project_subdir(hovered_dir, item.filename, not item.expanded) - end + view:toggle_expand() else core.try(function() if core.last_active_view and core.active_view == view then @@ -596,7 +615,15 @@ command.add(TreeView, { ["treeview:deselect"] = function() view.selected_item = nil - end + end, + + ["treeview:collapse"] = function() + view:toggle_expand(false) + end, + + ["treeview:expand"] = function() + view:toggle_expand(true) + end, }) @@ -707,6 +734,8 @@ keymap.add { ["ctrl+\\"] = "treeview:toggle", ["up"] = "treeview:previous", ["down"] = "treeview:next", + ["left"] = "treeview:collapse", + ["right"] = "treeview:expand", ["return"] = "treeview:open", ["escape"] = "treeview:deselect", ["delete"] = "treeview:delete", From e8427ae1687720748fcfb2809ce5a5feffd870ac Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 18 Mar 2022 04:23:32 -0400 Subject: [PATCH 178/409] treeview: fixed github merging error --- data/plugins/treeview.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 696dc5de..4b81d55a 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -11,14 +11,11 @@ local RootView = require "core.rootview" config.plugins.treeview = common.merge({ -- Amount of clicks to open a file - clicks_to_open = 2 -}, config.plugins.treeview) - -config.plugins.treeview = common.merge({ + clicks_to_open = 2, + -- Default treeview width size = 200 * SCALE }, config.plugins.treeview) - local tooltip_offset = style.font:get_height() local tooltip_border = 1 local tooltip_delay = 0.5 From a7fc7b4408da729b034cb2ad1e0927a7bbc69072 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 18 Mar 2022 06:02:48 -0400 Subject: [PATCH 179/409] treeview: fix crash on tooltip.x been nil --- data/plugins/treeview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 4b81d55a..e94b5e3e 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -422,7 +422,7 @@ function TreeView:draw() end self:draw_scrollbar() - if self.hovered_item and self.tooltip.alpha > 0 then + if self.hovered_item and self.tooltip.alpha > 0 and self.draw_tooltip.x then core.root_view:defer_draw(self.draw_tooltip, self) end end From 2d5af22dc90003f6619f0df808240827ae6bfd87 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 18 Mar 2022 16:10:24 +0100 Subject: [PATCH 180/409] Don't draw `treeview` tooltip if its position is not defined --- data/plugins/treeview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index e94b5e3e..a168ac4f 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -422,7 +422,7 @@ function TreeView:draw() end self:draw_scrollbar() - if self.hovered_item and self.tooltip.alpha > 0 and self.draw_tooltip.x then + if self.hovered_item and self.tooltip.x and self.tooltip.alpha > 0 then core.root_view:defer_draw(self.draw_tooltip, self) end end From 3ec0f384466425714df4a1feed7f8d0e55fc7ca0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 18 Mar 2022 16:15:53 +0100 Subject: [PATCH 181/409] Make `treeview:collapse` select parent if current item can't collapse --- data/plugins/treeview.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index a168ac4f..39c67907 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -427,6 +427,16 @@ function TreeView:draw() end end +function TreeView:get_parent(item) + local parent_path = common.dirname(item.abs_filename) + if not parent_path then return end + for it, _, y in self:each_item() do + if it.abs_filename == parent_path then + return it, y + end + end +end + function TreeView:toggle_expand(toggle) local item = self.selected_item @@ -619,7 +629,16 @@ command.add(TreeView, { end, ["treeview:collapse"] = function() - view:toggle_expand(false) + if view.selected_item then + if view.selected_item.type == "dir" and view.selected_item.expanded then + view:toggle_expand(false) + else + local parent_item, y = view:get_parent(view.selected_item) + if parent_item then + view:set_selection(parent_item, y) + end + end + end end, ["treeview:expand"] = function() From 3ffabced6247c11245ed333c2506a5c8a02d2999 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sat, 19 Mar 2022 22:33:41 -0400 Subject: [PATCH 182/409] treeview: move delete command to proper predicate --- data/plugins/treeview.lua | 76 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 39c67907..9a470a8d 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -644,6 +644,44 @@ command.add(TreeView, { ["treeview:expand"] = function() view:toggle_expand(true) end, + + ["treeview:delete"] = function() + local filename = treeitem().abs_filename + local relfilename = treeitem().filename + local file_info = system.get_file_info(filename) + local file_type = file_info.type == "dir" and "Directory" or "File" + -- Ask before deleting + local opt = { + { font = style.font, text = "Yes", default_yes = true }, + { font = style.font, text = "No" , default_no = true } + } + core.nag_view:show( + string.format("Delete %s", file_type), + string.format( + "Are you sure you want to delete the %s?\n%s: %s", + file_type:lower(), file_type, relfilename + ), + opt, + function(item) + if item.text == "Yes" then + if file_info.type == "dir" then + local deleted, error, path = common.rm(filename, true) + if not deleted then + core.error("Error: %s - \"%s\" ", error, path) + return + end + else + local removed, error = os.remove(filename) + if not removed then + core.error("Error: %s - \"%s\"", error, filename) + return + end + end + core.log("Deleted \"%s\"", filename) + end + end + ) + end, }) @@ -699,44 +737,6 @@ command.add(function() return treeitem() ~= nil end, { end, common.path_suggest) end, - ["treeview:delete"] = function() - local filename = treeitem().abs_filename - local relfilename = treeitem().filename - local file_info = system.get_file_info(filename) - local file_type = file_info.type == "dir" and "Directory" or "File" - -- Ask before deleting - local opt = { - { font = style.font, text = "Yes", default_yes = true }, - { font = style.font, text = "No" , default_no = true } - } - core.nag_view:show( - string.format("Delete %s", file_type), - string.format( - "Are you sure you want to delete the %s?\n%s: %s", - file_type:lower(), file_type, relfilename - ), - opt, - function(item) - if item.text == "Yes" then - if file_info.type == "dir" then - local deleted, error, path = common.rm(filename, true) - if not deleted then - core.error("Error: %s - \"%s\" ", error, path) - return - end - else - local removed, error = os.remove(filename) - if not removed then - core.error("Error: %s - \"%s\"", error, filename) - return - end - end - core.log("Deleted \"%s\"", filename) - end - end - ) - end, - ["treeview:open-in-system"] = function() local hovered_item = treeitem() From b741c204db21ae540dd19fc97fca9532a13cbed8 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sat, 19 Mar 2022 23:10:26 -0400 Subject: [PATCH 183/409] treeview: better handle previous view when focus/unfocus from CommandView --- data/plugins/treeview.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 9a470a8d..5b4d0ff4 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -8,6 +8,7 @@ local style = require "core.style" local View = require "core.view" local ContextMenu = require "core.contextmenu" local RootView = require "core.rootview" +local CommandView = require "core.commandview" config.plugins.treeview = common.merge({ -- Amount of clicks to open a file @@ -550,6 +551,8 @@ menu:register( } ) +local previous_view = nil + -- Register the TreeView commands and keymap command.add(nil, { ["treeview:toggle"] = function() @@ -558,6 +561,14 @@ command.add(nil, { ["treeview:toggle-focus"] = function() if not core.active_view:is(TreeView) then + if core.active_view:is(CommandView) then + previous_view = core.last_active_view + else + previous_view = core.active_view + end + if not previous_view then + previous_view = core.root_view:get_primary_node().views[1] + end core.set_active_view(view) if not view.selected_item then for it, _, y in view:each_item() do @@ -567,7 +578,7 @@ command.add(nil, { end else - core.set_active_view(core.last_active_view) + core.set_active_view(previous_view) end end }) From 3765ef1d7afe2caaf77ad8c4be6d5e7d659f34ab Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 20 Mar 2022 04:28:26 +0100 Subject: [PATCH 184/409] Move cursor to the beginning or the end of its selection When using `doc:move-to-{previous,next}-char` in a selection, we were moving the cursor to the character before the initial/after the last character of the selection. Now we follow what other editors do and move it to just before the initial/just after the final character. --- data/core/commands/doc.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index d80c4641..ed35913a 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -578,18 +578,20 @@ commands["doc:move-to-previous-char"] = function() for idx, line1, col1, line2, col2 in doc():get_selections(true) do if line1 ~= line2 or col1 ~= col2 then doc():set_selections(idx, line1, col1) + else + doc():move_to_cursor(idx, translate.previous_char) end end - doc():move_to(translate.previous_char) end commands["doc:move-to-next-char"] = function() for idx, line1, col1, line2, col2 in doc():get_selections(true) do if line1 ~= line2 or col1 ~= col2 then doc():set_selections(idx, line2, col2) + else + doc():move_to_cursor(idx, translate.next_char) end end - doc():move_to(translate.next_char) end command.add("core.docview", commands) From 699655bebfed23a1ab18d1382084a6374885f439 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 20 Mar 2022 04:45:14 +0100 Subject: [PATCH 185/409] Don't specify delta movement when simulating `TreeView:on_mouse_moved` --- data/plugins/treeview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 39c67907..bb7255b4 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -299,7 +299,7 @@ function TreeView:update() -- we don't want events when the thing is scrolling fast local dy = math.abs(self.scroll.to.y - self.scroll.y) if self.scroll.to.y ~= 0 and dy < self:get_item_height() then - self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, self.scroll.to.y - self.scroll.y) + self:on_mouse_moved(self.cursor_pos.x, self.cursor_pos.y, 0, 0) end TreeView.super.update(self) From 46f9be2960eabf9df23a76900bdeafd238fcb50e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 20 Mar 2022 04:46:57 +0100 Subject: [PATCH 186/409] Hide hovered `TreeView` item when dragging the scrollbar --- data/plugins/treeview.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index bb7255b4..2f208e95 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -209,10 +209,13 @@ end function TreeView:on_mouse_moved(px, py, ...) if not self.visible then return end TreeView.super.on_mouse_moved(self, px, py, ...) - if self.dragging_scrollbar then return end - self.cursor_pos.x = px self.cursor_pos.y = py + if self.dragging_scrollbar then + self.hovered_item = nil + return + end + local item_changed, tooltip_changed for item, x,y,w,h in self:each_item() do if px > x and py > y and px <= x + w and py <= y + h then From f0cc973e382acd1714ef5855923dd036a7090aa4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 20 Mar 2022 00:53:13 -0400 Subject: [PATCH 187/409] treeview: also handle focus change from mouse and then commandview --- data/plugins/treeview.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 4a8fc0d3..b8f5dda3 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -581,7 +581,9 @@ command.add(nil, { end else - core.set_active_view(previous_view) + local last_active = not core.last_active_view:is(CommandView) + and core.last_active_view or core.root_view:get_primary_node().views[1] + core.set_active_view(previous_view or last_active) end end }) From c3bcf68851cf67fa2c5288c54bc6786d045c0c39 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 20 Mar 2022 01:05:07 -0400 Subject: [PATCH 188/409] treeview: use root_view:get_primary_node().active_view for focus. --- data/plugins/treeview.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index b8f5dda3..6e45aad2 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -570,7 +570,7 @@ command.add(nil, { previous_view = core.active_view end if not previous_view then - previous_view = core.root_view:get_primary_node().views[1] + previous_view = core.root_view:get_primary_node().active_view end core.set_active_view(view) if not view.selected_item then @@ -581,9 +581,9 @@ command.add(nil, { end else - local last_active = not core.last_active_view:is(CommandView) - and core.last_active_view or core.root_view:get_primary_node().views[1] - core.set_active_view(previous_view or last_active) + core.set_active_view( + previous_view or core.root_view:get_primary_node().active_view + ) end end }) From bbac4d156057074b49a7076371ccbdac28cc79c4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 20 Mar 2022 01:58:39 -0400 Subject: [PATCH 189/409] treeview: add proper predicate for delete command --- data/plugins/treeview.lua | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 6e45aad2..36235e5c 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -660,7 +660,22 @@ command.add(TreeView, { ["treeview:expand"] = function() view:toggle_expand(true) end, +}) + +local function treeitem() return view.hovered_item or view.selected_item end + + +command.add( + function() + return treeitem() ~= nil + and ( + core.active_view == view or core.active_view == menu + or (view.toolbar and core.active_view == view.toolbar) + -- sometimes the context menu is shown on top of statusbar + or core.active_view == core.status_view + ) + end, { ["treeview:delete"] = function() local filename = treeitem().abs_filename local relfilename = treeitem().filename @@ -697,13 +712,10 @@ command.add(TreeView, { end end ) - end, + end }) -local function treeitem() return view.hovered_item or view.selected_item end - - command.add(function() return treeitem() ~= nil end, { ["treeview:rename"] = function() local old_filename = treeitem().filename From c82d6b08d9d2921fda901e89632fa0ed61764d6d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 21 Mar 2022 18:40:14 -0400 Subject: [PATCH 190/409] statusview: added functions for easy custom item ordering --- data/core/statusview.lua | 95 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 65bac1ae..b322a688 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -277,6 +277,38 @@ function StatusView:register_command_items() end +---Set a position to the best match according to total available items. +---@param self StatusView +---@param position integer +---@param alignment StatusView.Item.alignment +---@return integer position +local function normalize_position(self, position, alignment) + local offset = 0 + local items_count = 0 + local left = self:get_items_list(1) + local right = self:get_items_list(2) + if alignment == 2 then + items_count = #right + offset = #left + else + items_count = #left + end + if position == 0 then + position = offset + 1 + elseif position < 0 then + position = offset + items_count + (position + 2) + else + position = offset + position + end + if position < 1 then + position = offset + 1 + elseif position > #left + #right then + position = offset + items_count + 1 + end + return position +end + + ---Adds an item to be rendered in the status bar. ---@param predicate string | table | StatusView.Item.predicate : ---A condition to evaluate if the item should be displayed. If a string @@ -304,11 +336,7 @@ function StatusView:add_item(predicate, name, alignment, getitem, command, pos, local item = StatusView.Item(predicate, name, alignment, command, tooltip) item.get_item = getitem pos = type(pos) == "nil" and -1 or tonumber(pos) - if pos == -1 then - table.insert(self.items, item) - else - table.insert(self.items, math.abs(pos), item) - end + table.insert(self.items, normalize_position(self, pos, alignment), item) return item end @@ -341,6 +369,63 @@ function StatusView:get_items_list(alignment) end +---Move an item to a different position. +---@param name string +---@param position integer Can be negative value to position in reverse order +---@param alignment? StatusView.Item.alignment +---@return boolean moved +function StatusView:move_item(name, position, alignment) + assert(name, "no name provided") + assert(position, "no position provided") + local item = nil + for pos, it in ipairs(self.items) do + if it.name == name then + item = table.remove(self.items, pos) + break + end + end + if item then + if alignment then + item.alignment = alignment + end + position = normalize_position(self, position, item.alignment) + table.insert(self.items, position, item) + return true + end + return false +end + + +---Remove an item from the status view. +---@param name string +---@return StatusView.Item removed_item +function StatusView:remove_item(name) + local item = nil + for pos, it in ipairs(self.items) do + if it.name == name then + item = table.remove(self.items, pos) + break + end + end + return item +end + + +---Order the items by name +---@param names table +function StatusView:order_items(names) + local removed_items = {} + for _, name in ipairs(names) do + local item = self:remove_item(name) + if item then table.insert(removed_items, item) end + end + + for i, item in ipairs(removed_items) do + table.insert(self.items, i, item) + end +end + + ---Hide the status bar function StatusView:hide() self.visible = false From 17645ba4ecef15ca45ebf8b6b47a79493481f6b8 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 22 Mar 2022 10:17:42 -0400 Subject: [PATCH 191/409] Fixed anonyous syntaxes. --- data/plugins/detectindent.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index ea9c8b28..405a5243 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -76,7 +76,7 @@ end local function get_comment_patterns(syntax) - if comments_cache[syntax.name] then + if syntax.name and comments_cache[syntax.name] then if #comments_cache[syntax.name] > 0 then return comments_cache[syntax.name] else @@ -123,7 +123,8 @@ local function get_comment_patterns(syntax) end end elseif pattern.syntax then - local subsyntax = core_syntax.get("file"..pattern.syntax, "") + local subsyntax = type(pattern.syntax) == 'table' and pattern.syntax + or core_syntax.get("file"..pattern.syntax, "") local sub_comments = get_comment_patterns(subsyntax) if sub_comments then for s=1, #sub_comments do @@ -149,7 +150,9 @@ local function get_comment_patterns(syntax) table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]}) end end - comments_cache[syntax.name] = comments + if syntax.name then + comments_cache[syntax.name] = comments + end if #comments > 0 then return comments end From 3e7a97737e273d69ff1bcceb99fb68a3fb1f9132 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 22 Mar 2022 10:35:44 -0400 Subject: [PATCH 192/409] Re-enabled comment cache. --- data/plugins/detectindent.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 405a5243..ee03f034 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -76,9 +76,9 @@ end local function get_comment_patterns(syntax) - if syntax.name and comments_cache[syntax.name] then - if #comments_cache[syntax.name] > 0 then - return comments_cache[syntax.name] + if comments_cache[syntax] then + if #comments_cache[syntax] > 0 then + return comments_cache[syntax] else return nil end @@ -150,9 +150,7 @@ local function get_comment_patterns(syntax) table.insert(comments, {"p", "^%s*" .. block_comment[1], block_comment[2]}) end end - if syntax.name then - comments_cache[syntax.name] = comments - end + comments_cache[syntax] = comments if #comments > 0 then return comments end From 951f0913da8ce19a650218fe0a96c88784903a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jefferson=20Gonz=C3=A1lez?= Date: Fri, 25 Mar 2022 11:25:32 -0400 Subject: [PATCH 193/409] syntax: add pattern to boost tokenizer performance (#896) --- data/core/syntax.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/core/syntax.lua b/data/core/syntax.lua index adecd0cd..8ff5062d 100644 --- a/data/core/syntax.lua +++ b/data/core/syntax.lua @@ -7,6 +7,11 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} } function syntax.add(t) + -- this rule gives us a performance gain for the tokenizer in lines with + -- long amounts of consecutive spaces without affecting other patterns + if t.patterns then + table.insert(t.patterns, 1, { pattern = "%s+", type = "normal" }) + end table.insert(syntax.items, t) end From 9808378511936f3474a795e248bd2a4b5f80ec80 Mon Sep 17 00:00:00 2001 From: buffet Date: Fri, 25 Mar 2022 20:17:11 +0000 Subject: [PATCH 194/409] Add minimum version to lua fallback dependency to avoid confusion --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index b3b03257..75cd8cf3 100644 --- a/meson.build +++ b/meson.build @@ -78,6 +78,7 @@ if not get_option('source-only') lua_dep = dependency('lua5.4', fallback: lua_quick_fallback, required : false) if not lua_dep.found() lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'], + version: '>= 5.4', default_options: ['default_library=static', 'line_editing=false', 'interpreter=false'] ) endif From 9c2abb38dbc16a38df2a0c596269b3bcb6f2b686 Mon Sep 17 00:00:00 2001 From: George Sokianos Date: Tue, 21 Dec 2021 17:48:27 +0000 Subject: [PATCH 195/409] innosetup changes --- scripts/innosetup/innosetup.iss.in | 176 ++++++++++++++--------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/scripts/innosetup/innosetup.iss.in b/scripts/innosetup/innosetup.iss.in index 31ca8008..628733ff 100644 --- a/scripts/innosetup/innosetup.iss.in +++ b/scripts/innosetup/innosetup.iss.in @@ -1,88 +1,88 @@ -#define MyAppName "Lite XL" -#define MyAppVersion "@PROJECT_VERSION@" -#define MyAppPublisher "Lite XL Team" -#define MyAppURL "https://lite-xl.com" -#define MyAppExeName "lite-xl.exe" -#define BuildDir "@PROJECT_BUILD_DIR@" -#define SourceDir "@PROJECT_SOURCE_DIR@" - -; Use /dArch option to create a setup for a different architecture, e.g.: -; iscc /dArch=x86 innosetup.iss -#ifndef Arch - #define Arch "x64" -#endif - -[Setup] -; NOTE: The value of AppId uniquely identifies this application. -; Do not use the same AppId value in installers for other applications. -; To generate a new GUID, click Tools | Generate GUID inside the InnoSetup IDE. -AppId={{06761240-D97C-43DE-B9ED-C15F765A2D65} - -AppName={#MyAppName} -AppVersion={#MyAppVersion} -;AppVerName={#MyAppName} {#MyAppVersion} -AppPublisher={#MyAppPublisher} -AppPublisherURL={#MyAppURL} -AppSupportURL={#MyAppURL} -AppUpdatesURL={#MyAppURL} - -#if Arch=="x64" - ArchitecturesAllowed=x64 - ArchitecturesInstallIn64BitMode=x64 - #define ArchInternal "x86_64" -#else - #define ArchInternal "i686" -#endif - -AllowNoIcons=yes -Compression=lzma -SolidCompression=yes -DefaultDirName={autopf}/{#MyAppName} -DefaultGroupName={#MyAppPublisher} -UninstallFilesDir={app} - -; Uncomment the following line to run in non administrative install mode -; (install for current user only.) -;PrivilegesRequired=lowest -PrivilegesRequiredOverridesAllowed=dialog - -; The [Icons] "quicklaunchicon" entry uses {userappdata} -; but its [Tasks] entry has a proper IsAdminInstallMode Check. -UsedUserAreasWarning=no - -OutputDir=. -OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup -;DisableDirPage=yes -;DisableProgramGroupPage=yes - -LicenseFile={#SourceDir}/LICENSE -SetupIconFile={#SourceDir}/resources/icons/icon.ico -WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp" -WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp" - -[Languages] -Name: "english"; MessagesFile: "compiler:Default.isl" - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked -Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode -Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked - -[Files] -Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}')) -Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs -; NOTE: Don't use "Flags: ignoreversion" on any shared system files - -[Icons] -Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon -Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not WizardIsTaskSelected('portablemode') -Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not WizardIsTaskSelected('portablemode') -Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode') -; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" - -[Run] -Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent - -[Setup] -Uninstallable=not WizardIsTaskSelected('portablemode') +#define MyAppName "Lite XL" +#define MyAppVersion "@PROJECT_VERSION@" +#define MyAppPublisher "Lite XL Team" +#define MyAppURL "https://lite-xl.github.io" +#define MyAppExeName "lite-xl.exe" +#define BuildDir "@PROJECT_BUILD_DIR@" +#define SourceDir "@PROJECT_SOURCE_DIR@" + +; Use /dArch option to create a setup for a different architecture, e.g.: +; iscc /dArch=x86 innosetup.iss +#ifndef Arch + #define Arch "x64" +#endif + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; To generate a new GUID, click Tools | Generate GUID inside the InnoSetup IDE. +AppId={{06761240-D97C-43DE-B9ED-C15F765A2D65} + +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} + +#if Arch=="x64" + ArchitecturesAllowed=x64 + ArchitecturesInstallIn64BitMode=x64 + #define ArchInternal "x86_64" +#else + #define ArchInternal "i686" +#endif + +AllowNoIcons=yes +Compression=lzma +SolidCompression=yes +DefaultDirName={autopf}/{#MyAppName} +DefaultGroupName={#MyAppPublisher} +UninstallFilesDir={app} + +; Uncomment the following line to run in non administrative install mode +; (install for current user only.) +;PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog + +; The [Icons] "quicklaunchicon" entry uses {userappdata} +; but its [Tasks] entry has a proper IsAdminInstallMode Check. +UsedUserAreasWarning=no + +OutputDir=. +OutputBaseFilename=LiteXL-{#MyAppVersion}-{#ArchInternal}-setup +;DisableDirPage=yes +;DisableProgramGroupPage=yes + +LicenseFile={#SourceDir}/LICENSE +SetupIconFile={#SourceDir}/resources/icons/icon.ico +WizardImageFile="{#SourceDir}/scripts/innosetup/wizard-modern-image.bmp" +WizardSmallImageFile="{#SourceDir}/scripts/innosetup/litexl-55px.bmp" + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode +Name: "portablemode"; Description: "Portable Mode"; Flags: unchecked + +[Files] +Source: "{#BuildDir}/src/lite-xl.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#BuildDir}/mingwLibs{#Arch}/*"; DestDir: "{app}"; Flags: ignoreversion ; Check: DirExists(ExpandConstant('{#BuildDir}/mingwLibs{#Arch}')) +Source: "{#SourceDir}/data/*"; DestDir: "{app}/data"; Flags: ignoreversion recursesubdirs +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: not WizardIsTaskSelected('portablemode') +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"; Check: not WizardIsTaskSelected('portablemode') +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon; Check: not WizardIsTaskSelected('portablemode') +; Name: "{usersendto}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +Filename: "{app}/{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Setup] +Uninstallable=not WizardIsTaskSelected('portablemode') From e862fe90526e2982b6a797ca98e35c4aa958a2c0 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Mar 2022 20:51:09 -0400 Subject: [PATCH 196/409] syntax: fix conflicts introduced with #896 * mainly the language_md got affected which has some exotic rules * some other languages are also using spaces at start of pattern and even if not affected this change tackles that --- data/core/syntax.lua | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/data/core/syntax.lua b/data/core/syntax.lua index 8ff5062d..6d59d2ea 100644 --- a/data/core/syntax.lua +++ b/data/core/syntax.lua @@ -7,11 +7,49 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} } function syntax.add(t) - -- this rule gives us a performance gain for the tokenizer in lines with - -- long amounts of consecutive spaces without affecting other patterns + -- the rule %s+ gives us a performance gain for the tokenizer in lines with + -- long amounts of consecutive spaces, to not affect other patterns we + -- insert it after any rule that starts with spaces to prevent conflicts if t.patterns then - table.insert(t.patterns, 1, { pattern = "%s+", type = "normal" }) + local temp_patterns = {} + ::pattern_remove_loop:: + for pos, pattern in ipairs(t.patterns) do + local pattern_str = "" + local ptype = pattern.pattern + and "pattern" or (pattern.regex and "regex" or nil) + if ptype then + if type(pattern[ptype]) == "table" then + pattern_str = pattern[ptype][1] + else + pattern_str = pattern[ptype] + end + if (ptype == "pattern" and( + pattern_str:find("^%^?%%s") + or + pattern_str:find("^%^?%s") + )) + or + (ptype == "regex" and ( + pattern_str:find("^%^?\\s") + or + pattern_str:find("^%^?%s") + )) + then + table.insert(temp_patterns, table.remove(t.patterns, pos)) + -- since we are removing from iterated table we need to start + -- from the beginning again to prevent any issues + goto pattern_remove_loop + end + end + end + for pos, pattern in ipairs(temp_patterns) do + table.insert(t.patterns, pos, pattern) + end + local pos = 1 + if #temp_patterns > 0 then pos = #temp_patterns+1 end + table.insert(t.patterns, pos, { pattern = "%s+", type = "normal" }) end + table.insert(syntax.items, t) end From 5f9d45895deb55d6be3778d6123bf53b398d2a6d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Mar 2022 21:03:59 -0400 Subject: [PATCH 197/409] language_md: parenthesis support to numbered bullets --- data/plugins/language_md.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index 591dba47..2a622d8c 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -134,7 +134,7 @@ syntax.add { { pattern = "^%s*%-%s", type = "number" }, { pattern = "^%s*%+%s", type = "number" }, -- numbered bullet - { pattern = "^%s*[0-9]+%.%s", type = "number" }, + { pattern = "^%s*[0-9]+[%.%)]%s", type = "number" }, -- blockquote { pattern = "^%s*>+%s", type = "string" }, -- bold and italic From ca37644aa915650fccd0c94d4ec3093100ad34c9 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 28 Mar 2022 22:36:49 -0400 Subject: [PATCH 198/409] core: fixes and changes to temp files * fix delete_temp_files() deleting in EXEDIR but temp_filename() was creating temp files in USERDIR * make delete_temp_files() public so it can be used by plugins * add optional `dir` parameter to both delete_temp_files() and temp_filename() to allow specifying a different directory, this is for example useful when generting markdown previews, the temp file should be generated in the project dir in case the readme references images that are relative to it, so the web browser can find them. --- data/core/init.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 31462e39..75534073 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -809,17 +809,19 @@ local temp_uid = math.floor(system.get_time() * 1000) % 0xffffffff local temp_file_prefix = string.format(".lite_temp_%08x", tonumber(temp_uid)) local temp_file_counter = 0 -local function delete_temp_files() - for _, filename in ipairs(system.list_dir(EXEDIR)) do +function core.delete_temp_files(dir) + dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR + for _, filename in ipairs(system.list_dir(dir)) do if filename:find(temp_file_prefix, 1, true) == 1 then - os.remove(EXEDIR .. PATHSEP .. filename) + os.remove(dir .. PATHSEP .. filename) end end end -function core.temp_filename(ext) +function core.temp_filename(ext, dir) + dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR temp_file_counter = temp_file_counter + 1 - return USERDIR .. PATHSEP .. temp_file_prefix + return dir .. PATHSEP .. temp_file_prefix .. string.format("%06x", temp_file_counter) .. (ext or "") end @@ -834,7 +836,7 @@ end local function quit_with_function(quit_fn, force) if force then - delete_temp_files() + core.delete_temp_files() core.on_quit_project() save_session() quit_fn() From e74761da95d7425d673fc1116880a6c0d36d567d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 24 Mar 2022 00:20:14 -0400 Subject: [PATCH 199/409] language_c/cpp: fixes and improvements * support colorization of function and variables type declarations * support the macro concatenation operator ## * support what seems to be new cpp number notation format #'### * improved uppercase constants matching --- data/plugins/language_c.lua | 57 ++++++++++++++++++++---- data/plugins/language_cpp.lua | 81 +++++++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 25 deletions(-) diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index 9ba639a0..af8f7895 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -15,22 +15,63 @@ syntax.add { { pattern = "%d+[%d%.eE]*f?", type = "number" }, { pattern = "%.?%d+f?", type = "number" }, { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" }, + { pattern = "##", type = "operator" }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, - { - pattern = "^%s*#define%s+()[%a_][%a%d_]*", - type = { "keyword", "symbol" } + -- static declarations + { pattern = "static()%s+()inline", + type = { "keyword", "normal", "keyword" } + }, + { pattern = "static()%s+()const", + type = { "keyword", "normal", "keyword" } + }, + { pattern = "static()%s+()[%a_][%w_]*", + type = { "keyword", "normal", "literal" } + }, + -- match function type declarations + { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]", + type = { "literal", "operator", "normal", "function" } + }, + { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]", + type = { "literal", "normal", "operator", "function" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]", + type = { "literal", "normal", "function" } + }, + -- match variable type declarations + { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*", + type = { "literal", "operator", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*", + type = { "literal", "normal", "operator", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]", + type = { "literal", "normal", "normal", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=", + type = { "literal", "normal", "normal", "normal", "operator" } + }, + { pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*", + type = { "literal", "operator", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*", + type = { "literal", "normal", "operator", "normal" } }, -- Uppercase constants of at least 2 chars in len - { - pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]", - type = "number" + { pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]", + type = "number" }, -- Magic constants { pattern = "__[%u%l]+__", type = "number" }, + -- all other functions { pattern = "[%a_][%w_]*%f[(]", type = "function" }, - { pattern = "#include%s()<.->", type = {"keyword", "string"} }, - { pattern = "#[%a_][%w_]*", type = "keyword" }, + -- Macros + { pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*", + type = { "keyword", "symbol" } + }, + { pattern = "#%s*include%s()<.->", type = {"keyword", "string"} }, + { pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" }, + -- Everything else to make the tokenizer work properly { pattern = "[%a_][%w_]*", type = "symbol" }, }, symbols = { diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index e350653a..b1afa0f0 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -15,33 +15,80 @@ syntax.add { { pattern = { '"', '"', '\\' }, type = "string" }, { pattern = { "'", "'", '\\' }, type = "string" }, { pattern = "0x%x+", type = "number" }, - { pattern = "%d+[%d%.eE]*f?", type = "number" }, + { pattern = "%d+[%d%.'eE]*f?", type = "number" }, { pattern = "%.?%d+f?", type = "number" }, - { - pattern = "^%s*#define%s+()[%a_][%a%d_]*", - type = { "keyword", "symbol" } - }, - { - pattern = "#include%s+()<.->", - type = { "keyword", "string" } - }, - { pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" }, + { pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" }, + { pattern = "##", type = "operator" }, { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, { pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} }, - { pattern = "[%a_][%w_]*%f[(]", type = "function" }, + -- static declarations + { pattern = "static()%s+()inline", + type = { "keyword", "normal", "keyword" } + }, + { pattern = "static()%s+()const", + type = { "keyword", "normal", "keyword" } + }, + { pattern = "static()%s+()[%a_][%w_]*", + type = { "keyword", "normal", "literal" } + }, + -- match method type declarations + { pattern = "[%a_][%w_]*()%s*()%**()%s*()[%a_][%w_]*()%s*()::", + type = { + "literal", "normal", "operator", "normal", + "literal", "normal", "operator" + } + }, + -- match function type declarations + { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]", + type = { "literal", "operator", "normal", "function" } + }, + { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]", + type = { "literal", "normal", "operator", "function" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]", + type = { "literal", "normal", "function" } + }, + -- match variable type declarations + { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*", + type = { "literal", "operator", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*", + type = { "literal", "normal", "operator", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]", + type = { "literal", "normal", "normal", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=", + type = { "literal", "normal", "normal", "normal", "operator" } + }, + { pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*", + type = { "literal", "operator", "normal", "normal" } + }, + { pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*", + type = { "literal", "normal", "operator", "normal" } + }, -- Match scope operator element access - { pattern = "[%a_][%w_]*[%s]*%f[:]", type = "literal" }, + { pattern = "[%a_][%w_]*()%s*()::", + type = { "literal", "normal", "operator" } + }, -- Uppercase constants of at least 2 chars in len - { - pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%(%)%?%^%%=/<>~|&;:,!]", - type = "number" + { pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]", + type = "number" }, -- Magic constants { pattern = "__[%u%l]+__", type = "number" }, - -- Somehow makes macros properly work - { pattern = "#[%a_][%w_]*", type = "normal" }, + -- all other functions + { pattern = "[%a_][%w_]*%f[(]", type = "function" }, + -- Macros + { pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*", + type = { "keyword", "symbol" } + }, + { pattern = "#%s*include%s+()<.->", + type = { "keyword", "string" } + }, + { pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" }, -- Everything else to make the tokenizer work properly { pattern = "[%a_][%w_]*", type = "symbol" }, }, From b0c005a5acaf0cd419fa84ce2e0f0065056348a7 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 29 Mar 2022 21:48:34 -0400 Subject: [PATCH 200/409] syntax: remove pattern re-ordering on optimization * Introduces a flag that syntax writers can turn off named space_handling, turning it off means that your syntax will take care of handling the excessive amount of spaces that can slow down the tokenizer. * Adds another pattern at the end of every single table that also improves tokenizer performance by matching words that weren't match by any of the synxtax patterns. * Modifies language_md to turn off the provided space_handling and do its own since it has rules that require a space at the beginning, also handles long consecutives amount of dashes used in tables that degrade performance. * This changes where discussed in collaboration with @Guldoman and @takase1121 thanks to all! --- data/core/syntax.lua | 53 ++++++++++-------------------------- data/plugins/language_md.lua | 51 ++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 59 deletions(-) diff --git a/data/core/syntax.lua b/data/core/syntax.lua index 6d59d2ea..89208bce 100644 --- a/data/core/syntax.lua +++ b/data/core/syntax.lua @@ -7,47 +7,22 @@ local plain_text_syntax = { name = "Plain Text", patterns = {}, symbols = {} } function syntax.add(t) - -- the rule %s+ gives us a performance gain for the tokenizer in lines with - -- long amounts of consecutive spaces, to not affect other patterns we - -- insert it after any rule that starts with spaces to prevent conflicts + if type(t.space_handling) ~= "boolean" then t.space_handling = true end + if t.patterns then - local temp_patterns = {} - ::pattern_remove_loop:: - for pos, pattern in ipairs(t.patterns) do - local pattern_str = "" - local ptype = pattern.pattern - and "pattern" or (pattern.regex and "regex" or nil) - if ptype then - if type(pattern[ptype]) == "table" then - pattern_str = pattern[ptype][1] - else - pattern_str = pattern[ptype] - end - if (ptype == "pattern" and( - pattern_str:find("^%^?%%s") - or - pattern_str:find("^%^?%s") - )) - or - (ptype == "regex" and ( - pattern_str:find("^%^?\\s") - or - pattern_str:find("^%^?%s") - )) - then - table.insert(temp_patterns, table.remove(t.patterns, pos)) - -- since we are removing from iterated table we need to start - -- from the beginning again to prevent any issues - goto pattern_remove_loop - end - end + -- the rule %s+ gives us a performance gain for the tokenizer in lines with + -- long amounts of consecutive spaces, can be disabled by plugins where it + -- causes conflicts by declaring the table property: space_handling = false + if t.space_handling then + table.insert(t.patterns, { pattern = "%s+", type = "normal" }) end - for pos, pattern in ipairs(temp_patterns) do - table.insert(t.patterns, pos, pattern) - end - local pos = 1 - if #temp_patterns > 0 then pos = #temp_patterns+1 end - table.insert(t.patterns, pos, { pattern = "%s+", type = "normal" }) + + -- this rule gives us additional performance gain by matching every word + -- that was not matched by the syntax patterns as a single token, preventing + -- the tokenizer from iterating over each character individually which is a + -- lot slower since iteration occurs in lua instead of C and adding to that + -- it will also try to match every pattern to a single char (same as spaces) + table.insert(t.patterns, { pattern = "%w+%f[%s]", type = "normal" }) end table.insert(syntax.items, t) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index 2a622d8c..e7fa9f49 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -31,7 +31,35 @@ syntax.add { name = "Markdown", files = { "%.md$", "%.markdown$" }, block_comment = { "" }, + space_handling = false, -- turn off this feature to handle it our selfs patterns = { + ---- Place patterns that require spaces at start to optimize matching speed + ---- and apply the %s+ optimization immediately afterwards + -- bullets + { pattern = "^%s*%*%s", type = "number" }, + { pattern = "^%s*%-%s", type = "number" }, + { pattern = "^%s*%+%s", type = "number" }, + -- numbered bullet + { pattern = "^%s*[0-9]+[%.%)]%s", type = "number" }, + -- blockquote + { pattern = "^%s*>+%s", type = "string" }, + -- alternative bold italic formats + { pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" }, + { pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" }, + { pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" }, + -- reference links + { + pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ", + type = { "function", "number", "function" } + }, + { + pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.+\n", + type = { "function", "number", "function" } + }, + -- optimization + { pattern = "%s+", type = "normal" }, + + ---- HTML rules imported and adapted from language_html ---- to not conflict with markdown rules -- Inline JS and CSS @@ -129,14 +157,6 @@ syntax.add { { pattern = "^%-%-%-+\n", type = "comment" }, { pattern = "^%*%*%*+\n", type = "comment" }, { pattern = "^___+\n", type = "comment" }, - -- bullets - { pattern = "^%s*%*%s", type = "number" }, - { pattern = "^%s*%-%s", type = "number" }, - { pattern = "^%s*%+%s", type = "number" }, - -- numbered bullet - { pattern = "^%s*[0-9]+[%.%)]%s", type = "number" }, - -- blockquote - { pattern = "^%s*>+%s", type = "string" }, -- bold and italic { pattern = { "%*%*%*%S", "%*%*%*" }, type = "markdown_bold_italic" }, { pattern = { "%*%*%S", "%*%*" }, type = "markdown_bold" }, @@ -149,9 +169,6 @@ syntax.add { { pattern = "^___[%s%p%w]+___%s" , type = "markdown_bold_italic" }, { pattern = "^__[%s%p%w]+__%s" , type = "markdown_bold" }, { pattern = "^_[%s%p%w]+_%s" , type = "markdown_italic" }, - { pattern = { "%s___", "___%f[%s]" }, type = "markdown_bold_italic" }, - { pattern = { "%s__", "__%f[%s]" }, type = "markdown_bold" }, - { pattern = { "%s_[%S]", "_%f[%s]" }, type = "markdown_italic" }, -- heading with custom id { pattern = "^#+%s[%w%s%p]+(){()#[%w%-]+()}", @@ -186,14 +203,6 @@ syntax.add { pattern = "%[()["..in_squares_match.."]+()%] *()%[()["..in_squares_match.."]+()%]", type = { "function", "string", "function", "function", "number", "function" } }, - { - pattern = "^%s*%[%^()["..in_squares_match.."]+()%]: ", - type = { "function", "number", "function" } - }, - { - pattern = "^%s*%[%^?()["..in_squares_match.."]+()%]:%s+.+\n", - type = { "function", "number", "function" } - }, { pattern = "!?%[%^?()["..in_squares_match.."]+()%]", type = { "function", "number", "function" } @@ -204,7 +213,9 @@ syntax.add { type = "function" }, { pattern = "", type = "function" }, - { pattern = "https?://%S+", type = "function" } + { pattern = "https?://%S+", type = "function" }, + -- optimize consecutive dashes used in tables + { pattern = "%-+", type = "normal" }, }, symbols = { }, } From 23bd21a19105cc77a617d7b8c22d87c92fe6c9fa Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 30 Mar 2022 09:30:55 -0400 Subject: [PATCH 201/409] language_md: remove extra empty line --- data/plugins/language_md.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index e7fa9f49..f4e537ef 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -59,7 +59,6 @@ syntax.add { -- optimization { pattern = "%s+", type = "normal" }, - ---- HTML rules imported and adapted from language_html ---- to not conflict with markdown rules -- Inline JS and CSS From c70b5130a974d0dce3bf5d52f8ca70a717f8a680 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 1 Apr 2022 18:10:22 +0200 Subject: [PATCH 202/409] Improve `drawwhitespace` plugin Add configuration options to specify: - characters to substitute and their substitution; - whether to substitute at the beginning, middle or end of the line; - the color of the substitution; - the color for the beginning, middle or end; - the minimum number of white space to show in the middle. --- data/plugins/drawwhitespace.lua | 112 ++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 0004c7ea..9c28faa6 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -3,34 +3,108 @@ local style = require "core.style" local DocView = require "core.docview" local common = require "core.common" +local config = require "core.config" + +config.plugins.drawwhitespace = common.merge({ + show_leading = true, + show_trailing = true, + show_middle = true, + + show_middle_min = 1, + + color = style.syntax.whitespace or style.syntax.comment, + leading_color = nil, + middle_color = nil, + trailing_color = nil, + + substitutions = { + { + char = " ", + sub = "·", + -- You can put any of the previous options here too. + -- For example: + -- show_middle_min = 2, + -- show_leading = false, + }, + { + char = "\t", + sub = "»", + }, + }, +}, config.plugins.drawwhitespace) + +local function get_option(substitution, option) + if substitution[option] == nil then + return config.plugins.drawwhitespace[option] + end + return substitution[option] +end local draw_line_text = DocView.draw_line_text - function DocView:draw_line_text(idx, x, y) local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) - local color = style.syntax.whitespace or style.syntax.comment local ty = y + self:get_line_text_y_offset() local tx local text, offset, s, e = self.doc.lines[idx], 1 local x1, _, x2, _ = self:get_content_bounds() local _offset = self:get_x_offset_col(idx, x1) - offset = _offset - while true do - s, e = text:find(" +", offset) - if not s then break end - tx = self:get_col_x_offset(idx, s) + x - renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color) - if tx > x + x2 then break end - offset = e + 1 - end - offset = _offset - while true do - s, e = text:find("\t", offset) - if not s then break end - tx = self:get_col_x_offset(idx, s) + x - renderer.draw_text(font, "»", tx, ty, color) - if tx > x + x2 then break end - offset = e + 1 + + for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do + local char = substitution.char + local sub = substitution.sub + offset = _offset + + local show_leading = get_option(substitution, "show_leading") + local show_middle = get_option(substitution, "show_middle") + local show_trailing = get_option(substitution, "show_trailing") + + local show_middle_min = get_option(substitution, "show_middle_min") + + local base_color = get_option(substitution, "color") + local leading_color = get_option(substitution, "leading_color") or base_color + local middle_color = get_option(substitution, "middle_color") or base_color + local trailing_color = get_option(substitution, "trailing_color") or base_color + + local pattern = char.."+" + while true do + s, e = text:find(pattern, offset) + if not s then break end + + tx = self:get_col_x_offset(idx, s) + x + + local color = base_color + local draw = false + + if e == #text - 1 then + draw = show_trailing + color = trailing_color + elseif s == 1 then + draw = show_leading + color = leading_color + else + draw = show_middle and (e - s + 1 >= show_middle_min) + color = middle_color + end + + if draw then + -- We need to draw tabs one at a time because they might have a + -- different size than the substituting character. + -- This also applies to any other char if we use non-monospace fonts + -- but we ignore this case for now. + if char == "\t" then + for i = s,e do + tx = self:get_col_x_offset(idx, i) + x + tx = renderer.draw_text(font, sub, tx, ty, color) + end + else + tx = renderer.draw_text(font, string.rep(sub, e - s + 1), tx, ty, color) + end + end + + if tx > x + x2 then break end + offset = e + 1 + end end + draw_line_text(self, idx, x, y) end From 50acf2e7e6b13061bcd25f48059c18eab165035c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 3 Apr 2022 22:24:39 +0200 Subject: [PATCH 203/409] Fix keeping scroll position when restoring a `DocView` (#910) Since 5526041da32a37a11c9f1400b5eaf6fb82a78254 we check a pair of line&column to decide if we should scroll to the cursor. Previously we only considered a single line&column. --- data/plugins/workspace.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/workspace.lua b/data/plugins/workspace.lua index 1edfbe1e..f2e678eb 100644 --- a/data/plugins/workspace.lua +++ b/data/plugins/workspace.lua @@ -117,7 +117,7 @@ local function load_view(t) -- cannot be read. if dv and dv.doc then dv.doc:set_selection(table.unpack(t.selection)) - dv.last_line, dv.last_col = dv.doc:get_selection() + dv.last_line1, dv.last_col1, dv.last_line2, dv.last_col2 = dv.doc:get_selection() dv.scroll.x, dv.scroll.to.x = t.scroll.x, t.scroll.x dv.scroll.y, dv.scroll.to.y = t.scroll.y, t.scroll.y end From 45a0382d50616789d822e97879c5c532fe793bb2 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 24 Mar 2022 11:57:26 -0400 Subject: [PATCH 204/409] Bumping version numbers. --- data/core/start.lua | 2 +- data/plugins/autocomplete.lua | 2 +- data/plugins/autoreload.lua | 2 +- data/plugins/contextmenu.lua | 2 +- data/plugins/detectindent.lua | 2 +- data/plugins/drawwhitespace.lua | 2 +- data/plugins/language_c.lua | 2 +- data/plugins/language_cpp.lua | 2 +- data/plugins/language_css.lua | 2 +- data/plugins/language_html.lua | 2 +- data/plugins/language_js.lua | 2 +- data/plugins/language_lua.lua | 2 +- data/plugins/language_md.lua | 2 +- data/plugins/language_python.lua | 2 +- data/plugins/language_xml.lua | 2 +- data/plugins/lineguide.lua | 2 +- data/plugins/macro.lua | 2 +- data/plugins/projectsearch.lua | 2 +- data/plugins/quote.lua | 2 +- data/plugins/reflow.lua | 2 +- data/plugins/scale.lua | 2 +- data/plugins/tabularize.lua | 2 +- data/plugins/toolbarview.lua | 2 +- data/plugins/treeview.lua | 2 +- data/plugins/trimwhitespace.lua | 2 +- data/plugins/workspace.lua | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/data/core/start.lua b/data/core/start.lua index b3bcc18e..dfddcbda 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -1,6 +1,6 @@ -- this file is used by lite-xl to setup the Lua environment when starting VERSION = "@PROJECT_VERSION@" -MOD_VERSION = "2" +MOD_VERSION = "3" SCALE = tonumber(os.getenv("LITE_SCALE") or os.getenv("GDK_SCALE") or os.getenv("QT_SCALE_FACTOR")) or SCALE PATHSEP = package.config:sub(1, 1) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index 0723ec8d..b112ca29 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local config = require "core.config" diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 9978092e..596b94f7 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local config = require "core.config" local Doc = require "core.doc" diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua index ac811b2a..29e7c648 100644 --- a/data/plugins/contextmenu.lua +++ b/data/plugins/contextmenu.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index ee03f034..3501f472 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local common = require "core.common" diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 0004c7ea..d5deb9e8 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local style = require "core.style" local DocView = require "core.docview" diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index af8f7895..146a952d 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index b1afa0f0..e2857d9a 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_css.lua b/data/plugins/language_css.lua index 96c3eb8d..0dff28ac 100644 --- a/data/plugins/language_css.lua +++ b/data/plugins/language_css.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_html.lua b/data/plugins/language_html.lua index ff61fa7f..2019f0dc 100644 --- a/data/plugins/language_html.lua +++ b/data/plugins/language_html.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_js.lua b/data/plugins/language_js.lua index 2e6d5d87..0e350e7a 100644 --- a/data/plugins/language_js.lua +++ b/data/plugins/language_js.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_lua.lua b/data/plugins/language_lua.lua index 993e53c8..07545c1f 100644 --- a/data/plugins/language_lua.lua +++ b/data/plugins/language_lua.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index f4e537ef..c68acddc 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" local style = require "core.style" local core = require "core" diff --git a/data/plugins/language_python.lua b/data/plugins/language_python.lua index 8bc6fbd4..aa34db0f 100644 --- a/data/plugins/language_python.lua +++ b/data/plugins/language_python.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_xml.lua b/data/plugins/language_xml.lua index 297b6d73..bf75a79a 100644 --- a/data/plugins/language_xml.lua +++ b/data/plugins/language_xml.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 96745659..83c35a8d 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local config = require "core.config" local style = require "core.style" local DocView = require "core.docview" diff --git a/data/plugins/macro.lua b/data/plugins/macro.lua index 2678363a..bd51c17a 100644 --- a/data/plugins/macro.lua +++ b/data/plugins/macro.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index e06dd6d9..d7a79120 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local keymap = require "core.keymap" diff --git a/data/plugins/quote.lua b/data/plugins/quote.lua index c714cbf8..d651e463 100644 --- a/data/plugins/quote.lua +++ b/data/plugins/quote.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/reflow.lua b/data/plugins/reflow.lua index cbaa31ef..58deabe3 100644 --- a/data/plugins/reflow.lua +++ b/data/plugins/reflow.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local config = require "core.config" local command = require "core.command" diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index 698b7b3a..cc9db5bf 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/tabularize.lua b/data/plugins/tabularize.lua index 4cdae6ea..7519a387 100644 --- a/data/plugins/tabularize.lua +++ b/data/plugins/tabularize.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local translate = require "core.doc.translate" diff --git a/data/plugins/toolbarview.lua b/data/plugins/toolbarview.lua index 1a879877..241fc430 100644 --- a/data/plugins/toolbarview.lua +++ b/data/plugins/toolbarview.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 36235e5c..444353f8 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/trimwhitespace.lua b/data/plugins/trimwhitespace.lua index 79886c67..15d587f5 100644 --- a/data/plugins/trimwhitespace.lua +++ b/data/plugins/trimwhitespace.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local command = require "core.command" local Doc = require "core.doc" diff --git a/data/plugins/workspace.lua b/data/plugins/workspace.lua index f2e678eb..0dc40cb2 100644 --- a/data/plugins/workspace.lua +++ b/data/plugins/workspace.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local DocView = require "core.docview" From 048d250f5e0bc4b95d5769680b04bfeb7f440476 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 24 Mar 2022 12:46:38 -0400 Subject: [PATCH 205/409] Added in notes to the changelog. --- changelog.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/changelog.md b/changelog.md index fef160b6..b27f5f4f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,73 @@ This files document the changes done in Lite XL for each release. +### 2.1 + +Upgraded Lua to 5.4, which should improve performance, and provide useful extra functionality. +It should also be more available out of the box with most modern linux/unix-based package +managers. + +Removed `dmon`, and implemented independent backends for dirmonitoring. Also more cleanly +split out dirmonitoring into its own class in lua, from core.init. We should now support +FreeBSD; and any other system that uses `kqueue` as their dirmonitoring library. We also +have a dummy-backend, which reverts transparnetly to scanning if there is some issue with +applying OS-level watches (such as system limits). + +Removed `libagg` and the font renderer; compacted all font rendering into a single renderer.c +file which uses `libfreetype` directly. Now allows for ad-hoc bolding, italics, and underlining +of fonts. + +Removed `reproc` and replaced this with a simple POSIX/Windows implementation in `process.c`. +This allows for greater tweakability (i.e. we can now `break` for debugging purposes), +performance (startup time of subprocesses is noticeably shorter), and simplicity +(we no longer have to link reproc, or winsock, on windows). + +Split out `Node` and `EmptyView` into their own lua files, for plugin extensibility reasons. + +Revamped StatusView API, so that plugins can more easily coexist with each other. + +Removed `cp_replace`, and replaced this with a core plugin, `drawwhitespace.lua`. + +Made distinction between line and block comments, and added all appropriate functionality +to the commenting/uncommenting lines. + +Added in line paste mode, if you copy without a selection. + +May improvements to treeview, including keyboard navigation of treeview, and ability to +specify single vs. double-click behavior. + +Added in soft line wrapping as core plugin, under `linewrapping.lua`, with an +F10 to activate. + +Bumped plugin mod-version number, as the rendering interface for docviews has changed. + +Added in meson wraps for freetype, pcre2, and SDL2 which target public, rather than +lite-xl maintained repos. + +Added in the ability to set up font fallback groups in the font renderer, if a token +doesn't have a corresponding glyph. + +Added in a native plugin interface that allows for C-level interfacing with a +statically-linked lite-xl. The implementation of this may change in future. + +Improved fuzzy_matching to probably give you something closer to what you're +looking for. + +Improved handling of alternate keyboard layouts. + +Improved ability for plugins to be loaded at a given time, by making the convention +of defining a config for the plugin use `common.merge` to merge existing hashes +together, rather than overwriting. + +Added in the ability to specify mouseclicks in the keymap, allowing for easy binds of +`ctrl+lclick`, and the like. + +Changed interface for keyhandling; now, all components should return true if they've +handled the event. + +Added in a default keymap for `core:restart`, `ctrl+shift+r`. + +Many, many, many more changes that are too numerous to list. + ### 2.0.5 Revamp the project's user module so that modifications are immediately applied. From 3479890ce503005ad531f05377dff7036d6677ea Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 24 Mar 2022 12:01:46 -0400 Subject: [PATCH 206/409] Soft Line Wrapping (#636) Added in soft line wrapping. --- data/core/commands/doc.lua | 42 +-- data/core/commandview.lua | 5 +- data/core/docview.lua | 63 ++-- data/plugins/linewrapping.lua | 523 ++++++++++++++++++++++++++++++++++ 4 files changed, 581 insertions(+), 52 deletions(-) create mode 100644 data/plugins/linewrapping.lua diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index ed35913a..597e3de4 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -549,29 +549,29 @@ local commands = { local translations = { - ["previous-char"] = translate.previous_char, - ["next-char"] = translate.next_char, - ["previous-word-start"] = translate.previous_word_start, - ["next-word-end"] = translate.next_word_end, - ["previous-block-start"] = translate.previous_block_start, - ["next-block-end"] = translate.next_block_end, - ["start-of-doc"] = translate.start_of_doc, - ["end-of-doc"] = translate.end_of_doc, - ["start-of-line"] = translate.start_of_line, - ["end-of-line"] = translate.end_of_line, - ["start-of-word"] = translate.start_of_word, - ["start-of-indentation"] = translate.start_of_indentation, - ["end-of-word"] = translate.end_of_word, - ["previous-line"] = DocView.translate.previous_line, - ["next-line"] = DocView.translate.next_line, - ["previous-page"] = DocView.translate.previous_page, - ["next-page"] = DocView.translate.next_page, + ["previous-char"] = translate, + ["next-char"] = translate, + ["previous-word-start"] = translate, + ["next-word-end"] = translate, + ["previous-block-start"] = translate, + ["next-block-end"] = translate, + ["start-of-doc"] = translate, + ["end-of-doc"] = translate, + ["start-of-line"] = translate, + ["end-of-line"] = translate, + ["start-of-word"] = translate, + ["start-of-indentation"] = translate, + ["end-of-word"] = translate, + ["previous-line"] = DocView.translate, + ["next-line"] = DocView.translate, + ["previous-page"] = DocView.translate, + ["next-page"] = DocView.translate, } -for name, fn in pairs(translations) do - commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end - commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end - commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end +for name, obj in pairs(translations) do + commands["doc:move-to-" .. name] = function() doc():move_to(obj[name:gsub("-", "_")], dv()) end + commands["doc:select-to-" .. name] = function() doc():select_to(obj[name:gsub("-", "_")], dv()) end + commands["doc:delete-to-" .. name] = function() doc():delete_to(obj[name:gsub("-", "_")], dv()) end end commands["doc:move-to-previous-char"] = function() diff --git a/data/core/commandview.lua b/data/core/commandview.lua index b91f1394..4396313f 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -56,8 +56,8 @@ function CommandView:get_name() end -function CommandView:get_line_screen_position() - local x = CommandView.super.get_line_screen_position(self, 1) +function CommandView:get_line_screen_position(line, col) + local x = CommandView.super.get_line_screen_position(self, 1, col) local _, y = self:get_content_offset() local lh = self:get_line_height() return x, y + (self.size.y - lh) / 2 @@ -243,6 +243,7 @@ function CommandView:draw_line_gutter(idx, x, y) x = x + style.padding.x renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color) core.pop_clip_rect() + return self:get_line_height() end diff --git a/data/core/docview.lua b/data/core/docview.lua index 397b455e..ac3c19d9 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -121,14 +121,18 @@ function DocView:get_gutter_width() end -function DocView:get_line_screen_position(idx) +function DocView:get_line_screen_position(line, col) local x, y = self:get_content_offset() local lh = self:get_line_height() local gw = self:get_gutter_width() - return x + gw, y + (idx-1) * lh + style.padding.y + y = y + (line-1) * lh + style.padding.y + if col then + return x + gw + self:get_col_x_offset(line, col), y + else + return x + gw, y + end end - function DocView:get_line_text_y_offset() local lh = self:get_line_height() local th = self:get_font():get_height() @@ -198,8 +202,9 @@ end function DocView:scroll_to_line(line, ignore_if_visible, instant) local min, max = self:get_visible_line_range() if not (ignore_if_visible and line > min and line < max) then - local lh = self:get_line_height() - self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2) + local x, y = self:get_line_screen_position(line) + local ox, oy = self:get_content_offset() + self.scroll.to.y = math.max(0, y - oy - self.size.y / 2) if instant then self.scroll.y = self.scroll.to.y end @@ -208,10 +213,10 @@ end function DocView:scroll_to_make_visible(line, col) - local min = self:get_line_height() * (line - 1) - local max = self:get_line_height() * (line + 2) - self.size.y - self.scroll.to.y = math.min(self.scroll.to.y, min) - self.scroll.to.y = math.max(self.scroll.to.y, max) + local ox, oy = self:get_content_offset() + local _, ly = self:get_line_screen_position(line, col) + local lh = self:get_line_height() + self.scroll.to.y = common.clamp(self.scroll.to.y, ly - oy - self.size.y + lh * 2, ly - oy - lh) local gw = self:get_gutter_width() local xoffset = self:get_col_x_offset(line, col) local xmargin = 3 * self:get_font():get_width(' ') @@ -314,14 +319,15 @@ function DocView:draw_line_highlight(x, y) end -function DocView:draw_line_text(idx, x, y) +function DocView:draw_line_text(line, x, y) local default_font = self:get_font() local tx, ty = x, y + self:get_line_text_y_offset() - for _, type, text in self.doc.highlighter:each_token(idx) do + for _, type, text in self.doc.highlighter:each_token(line) do local color = style.syntax[type] local font = style.syntax_fonts[type] or default_font tx = renderer.draw_text(font, text, tx, ty, color) end + return self:get_line_height() end function DocView:draw_caret(x, y) @@ -329,7 +335,7 @@ function DocView:draw_caret(x, y) renderer.draw_rect(x, y, style.caret_width, lh, style.caret) end -function DocView:draw_line_body(idx, x, y) +function DocView:draw_line_body(line, x, y) -- draw highlight if any selection ends on this line local draw_highlight = false local hcl = config.highlight_current_line @@ -352,14 +358,14 @@ function DocView:draw_line_body(idx, x, y) end -- draw selection if it overlaps this line + local lh = self:get_line_height() for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do - if idx >= line1 and idx <= line2 then - local text = self.doc.lines[idx] - if line1 ~= idx then col1 = 1 end - if line2 ~= idx then col2 = #text + 1 end - local x1 = x + self:get_col_x_offset(idx, col1) - local x2 = x + self:get_col_x_offset(idx, col2) - local lh = self:get_line_height() + if line >= line1 and line <= line2 then + local text = self.doc.lines[line] + if line1 ~= line then col1 = 1 end + if line2 ~= line then col2 = #text + 1 end + local x1 = x + self:get_col_x_offset(line, col1) + local x2 = x + self:get_col_x_offset(line, col2) if x1 ~= x2 then renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) end @@ -367,20 +373,22 @@ function DocView:draw_line_body(idx, x, y) end -- draw line's text - self:draw_line_text(idx, x, y) + return self:draw_line_text(line, x, y) end -function DocView:draw_line_gutter(idx, x, y, width) +function DocView:draw_line_gutter(line, x, y, width) local color = style.line_number for _, line1, _, line2 in self.doc:get_selections(true) do - if idx >= line1 and idx <= line2 then + if line >= line1 and line <= line2 then color = style.line_number2 break end end x = x + style.padding.x - common.draw_text(self:get_font(), color, idx, "right", x, y, width, self:get_line_height()) + local lh = self:get_line_height() + common.draw_text(self:get_font(), color, line, "right", x, y + yoffset, width, lh) + return lh end @@ -394,8 +402,7 @@ function DocView:draw_overlay() and system.window_has_focus() then if config.disable_blink or (core.blink_timer - core.blink_start) % T < T / 2 then - local x, y = self:get_line_screen_position(line) - self:draw_caret(x + self:get_col_x_offset(line, col), y) + self:draw_caret(self:get_line_screen_position(line, col)) end end end @@ -413,8 +420,7 @@ function DocView:draw() local x, y = self:get_line_screen_position(minline) local gw, gpad = self:get_gutter_width() for i = minline, maxline do - self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) - y = y + lh + y = y + (self:draw_line_gutter(i, self.position.x, y, gpad and gw - gpad or gw) or lh) end local pos = self.position @@ -423,8 +429,7 @@ function DocView:draw() -- right side it is redundant with the Node's clip. core.push_clip_rect(pos.x + gw, pos.y, self.size.x - gw, self.size.y) for i = minline, maxline do - self:draw_line_body(i, x, y) - y = y + lh + y = y + (self:draw_line_body(i, x, y) or lh) end self:draw_overlay() core.pop_clip_rect() diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua new file mode 100644 index 00000000..aecc2bab --- /dev/null +++ b/data/plugins/linewrapping.lua @@ -0,0 +1,523 @@ +-- mod-version:2 -- lite-xl 2.0 +local core = require "core" +local common = require "core.common" +local DocView = require "core.docview" +local Doc = require "core.doc" +local style = require "core.style" +local config = require "core.config" +local command = require "core.command" +local keymap = require "core.keymap" +local translate = require "core.doc.translate" + +config.plugins.linewrapping = { + -- The type of wrapping to perform. Can be "letter" or "word". + mode = "letter", + -- If nil, uses the DocView's size, otherwise, uses this exact width. + width_override = nil, + -- Whether or not to draw a guide + guide = true, + -- Whether or not we should indent ourselves like the first line of a wrapped block. + indent = true, + -- Whether or not to enable wrapping by default when opening files. + enable_by_default = true +} + +local LineWrapping = {} + +-- Computes the breaks for a given line, width and mode. Returns a list of columns +-- at which the line should be broken. +function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) + local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, 1, 0, 0 + local splits = { 1 } + for idx, type, text in doc.highlighter:each_token(line) do + local font = style.syntax_fonts[type] or default_font + if idx == 1 and config.plugins.linewrapping.indent then + local _, indent_end = text:find("^%s+") + if indent_end then begin_width = font:get_width(text:sub(1, indent_end)) end + end + local w = font:get_width(text) + if xoffset + w > width then + for char in common.utf8_chars(text) do + w = font:get_width(char) + xoffset = xoffset + w + if xoffset > width then + if mode == "word" then + table.insert(splits, last_space + 1) + xoffset = xoffset - last_width + w + begin_width + else + table.insert(splits, i) + xoffset = w + begin_width + end + elseif char == ' ' then + last_space = i + last_width = xoffset + end + i = i + #char + end + else + xoffset = xoffset + w + i = i + #text + end + end + return splits, begin_width +end + +-- breaks are held in a single table that contains n*2 elements, where n is the amount of line breaks. +-- each element represents line and column of the break. line_offset will check from the specified line +-- if the first line has not changed breaks, it will stop there. +function LineWrapping.reconstruct_breaks(docview, default_font, width, line_offset) + if width ~= math.huge then + local doc = docview.doc + -- two elements per wrapped line; first maps to original line number, second to column number. + docview.wrapped_lines = { } + -- one element per actual line; maps to the first index of in wrapped_lines for this line + docview.wrapped_line_to_idx = { } + -- one element per actual line; gives the indent width for the acutal line + docview.wrapped_line_offsets = { } + docview.wrapped_settings = { ["width"] = width, ["font"] = default_font } + for i = line_offset or 1, #doc.lines do + local breaks, offset = LineWrapping.compute_line_breaks(doc, default_font, i, width, config.plugins.linewrapping.mode) + table.insert(docview.wrapped_line_offsets, offset) + for k, col in ipairs(breaks) do + table.insert(docview.wrapped_lines, i) + table.insert(docview.wrapped_lines, col) + end + end + -- list of indices for wrapped_lines, that are based on original line number + -- holds the index to the first in the wrapped_lines list. + local last_wrap = nil + for i = 1, #docview.wrapped_lines, 2 do + if not last_wrap or last_wrap ~= docview.wrapped_lines[i] then + table.insert(docview.wrapped_line_to_idx, (i + 1) / 2) + last_wrap = docview.wrapped_lines[i] + end + end + else + docview.wrapped_lines = nil + docview.wrapped_line_to_idx = nil + docview.wrapped_line_offsets = nil + docview.wrapped_settings = nil + end +end + +-- When we have an insertion or deletion, we have four sections of text. +-- 1. The unaffected section, located prior to the cursor. This is completely ignored. +-- 2. The beginning of the affected line prior to the insertion or deletion. Begins on column 1 of the selection. +-- 3. The removed/pasted lines. +-- 4. Every line after the modification, begins one line after the selection in the initial document. +function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines) + -- Step 1: Determine the index for the line for #2. + local old_idx1 = docview.wrapped_line_to_idx[old_line1] or 1 + -- Step 2: Determine the index of the line for #4. + local old_idx2 = (docview.wrapped_line_to_idx[old_line2 + 1] or ((#docview.wrapped_lines / 2) + 1)) - 1 + -- Step 3: Remove all old breaks for the old lines from the table, and all old widths from wrapped_line_offsets. + local offset = (old_idx1 - 1) * 2 + 1 + for i = old_idx1, old_idx2 do + table.remove(docview.wrapped_lines, offset) + table.remove(docview.wrapped_lines, offset) + end + for i = old_line1, old_line2 do + table.remove(docview.wrapped_line_offsets, old_line1) + end + -- Step 4: Shift the line number of wrapped_lines past #4 by the amount of inserted/deleted lines. + if net_lines ~= 0 then + for i = offset, #docview.wrapped_lines, 2 do + docview.wrapped_lines[i] = docview.wrapped_lines[i] + net_lines + end + end + -- Step 5: Compute the breaks and offsets for the lines for #2 and #3. Insert them into the table. + local new_line1 = old_line1 + local new_line2 = old_line2 + net_lines + for line = new_line1, new_line2 do + local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode) + table.insert(docview.wrapped_line_offsets, line, begin_width) + for i,b in ipairs(breaks) do + table.insert(docview.wrapped_lines, offset, b) + table.insert(docview.wrapped_lines, offset, line) + offset = offset + 2 + end + end + -- Step 6: Recompute the wrapped_line_to_idx cache from #2. + local line = old_line1 + offset = (old_idx1 - 1) * 2 + 1 + while offset < #docview.wrapped_lines do + if docview.wrapped_lines[offset + 1] == 1 then + docview.wrapped_line_to_idx[line] = ((offset - 1) / 2) + 1 + line = line + 1 + end + offset = offset + 2 + end + while line <= #docview.wrapped_line_to_idx do + table.remove(docview.wrapped_line_to_idx) + end +end + +-- Draws a guide if applicable to show where wrapping is occurring. +function LineWrapping.draw_guide(docview) + if config.plugins.linewrapping.guide and docview.wrapped_settings.width ~= math.huge then + local x, y = docview:get_content_offset() + local gw = docview:get_gutter_width() + renderer.draw_rect(x + gw + docview.wrapped_settings.width, y, 1, core.root_view.size.y, style.selection) + end +end + +function LineWrapping.update_docview_breaks(docview) + local x,y,w,h = docview:get_scrollbar_rect() + local width = config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w) + if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then + docview.scroll.to.x = 0 + LineWrapping.reconstruct_breaks(docview, docview:get_font(), width) + end +end + +local function get_idx_line_col(docview, idx) + local doc = docview.doc + if not docview.wrapped_settings then + if idx > #doc.lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end + return idx, 1 + end + if idx < 1 then return 1, 1 end + local offset = (idx - 1) * 2 + 1 + if offset > #docview.wrapped_lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end + return docview.wrapped_lines[offset], docview.wrapped_lines[offset + 1] +end + +local function get_idx_line_length(docview, idx) + local doc = docview.doc + if not docview.wrapped_settings then + if idx > #doc.lines then return #doc.lines[#doc.lines] + 1 end + return #doc.lines[idx] + end + local offset = (idx - 1) * 2 + 1 + local start = docview.wrapped_lines[offset + 1] + if docview.wrapped_lines[offset + 2] and docview.wrapped_lines[offset + 2] == docview.wrapped_lines[offset] then + return docview.wrapped_lines[offset + 3] - docview.wrapped_lines[offset + 1] + else + return #doc.lines[docview.wrapped_lines[offset]] - docview.wrapped_lines[offset + 1] + 1 + end +end + +local function get_total_wrapped_lines(docview) + if not docview.wrapped_settings then return docview.doc and #docview.doc.lines end + return #docview.wrapped_lines / 2 +end + +-- If line end, gives the end of an index line, rather than the first character of the next line. +local function get_line_idx_col_count(docview, line, col, line_end, ndoc) + local doc = docview.doc + if not docview.wrapped_settings then return common.clamp(line, 1, #doc.lines), col, 1, 1 end + if line > #doc.lines then return get_line_idx_col_count(docview, #doc.lines, #doc.lines[#doc.lines] + 1) end + line = math.max(line, 1) + local idx = docview.wrapped_line_to_idx[line] or 1 + local ncol, scol = 1, 1 + if col then + local i = idx + 1 + while line == docview.wrapped_lines[(i - 1) * 2 + 1] and col >= docview.wrapped_lines[(i - 1) * 2 + 2] do + local nscol = docview.wrapped_lines[(i - 1) * 2 + 2] + if line_end and col == nscol then + break + end + scol = nscol + i = i + 1 + idx = idx + 1 + end + ncol = (col - scol) + 1 + end + local count = (docview.wrapped_line_to_idx[line + 1] or (get_total_wrapped_lines(docview) + 1)) - (docview.wrapped_line_to_idx[line] or get_total_wrapped_lines(docview)) + return idx, ncol, count, scol +end + +local function get_line_col_from_index_and_x(docview, idx, x) + local doc = docview.doc + local line, col = get_idx_line_col(docview, idx) + if idx < 1 then return 1, 1 end + local xoffset, last_i, i = (col ~= 1 and docview.wrapped_line_offsets[line] or 0), col, 1 + if x < xoffset then return line, col end + local default_font = docview:get_font() + for _, type, text in docview.doc.highlighter:each_token(line) do + local font, w = style.syntax_fonts[type] or default_font, 0 + for char in common.utf8_chars(text) do + if i >= col then + if xoffset >= x then + return line, (xoffset - x > (w / 2) and last_i or i) + end + w = font:get_width(char) + xoffset = xoffset + w + end + last_i = i + i = i + #char + end + end + return line, #doc.lines[line] +end + + +local open_files = {} + +local old_doc_insert = Doc.raw_insert +function Doc:raw_insert(line, col, text, undo_stack, time) + local old_lines = #self.lines + old_doc_insert(self, line, col, text, undo_stack, time) + if open_files[self] then + for i,docview in ipairs(open_files[self]) do + if docview.wrapped_settings then + local lines = #self.lines - old_lines + LineWrapping.update_breaks(docview, line, line, lines) + end + end + end +end + +local old_doc_remove = Doc.raw_remove +function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) + local old_lines = #self.lines + old_doc_remove(self, line1, col1, line2, col2, undo_stack, time) + if open_files[self] then + for i,docview in ipairs(open_files[self]) do + if docview.wrapped_settings then + local lines = #self.lines - old_lines + LineWrapping.update_breaks(docview, line1, line2, lines) + end + end + end +end + +local old_doc_update = DocView.update +function DocView:update() + old_doc_update(self) + if self.wrapped_settings and self.size.x > 0 then + LineWrapping.update_docview_breaks(self) + end +end + +function DocView:get_scrollable_size() + if not config.scroll_past_end then + return self:get_line_height() * get_total_wrapped_lines(self) + style.padding.y * 2 + end + return self:get_line_height() * (get_total_wrapped_lines(self) - 1) + self.size.y +end + +local old_new = DocView.new +function DocView:new(doc) + old_new(self, doc) + if not open_files[doc] then open_files[doc] = {} end + table.insert(open_files[doc], self) + if config.plugins.linewrapping.enable_by_default then + LineWrapping.update_docview_breaks(self) + end +end + +local old_scroll_to_make_visible = DocView.scroll_to_make_visible +function DocView:scroll_to_make_visible(line, col) + old_scroll_to_make_visible(self, line, col) + if self.wrapped_settings then self.scroll.to.x = 0 end +end + +local old_get_visible_line_range = DocView.get_visible_line_range +function DocView:get_visible_line_range() + if not self.wrapped_settings then return old_get_visible_line_range(self) end + local x, y, x2, y2 = self:get_content_bounds() + local lh = self:get_line_height() + local minline = get_idx_line_col(self, math.max(1, math.floor(y / lh))) + local maxline = get_idx_line_col(self, math.min(get_total_wrapped_lines(self), math.floor(y2 / lh) + 1)) + return minline, maxline +end + +local old_get_x_offset_col = DocView.get_x_offset_col +function DocView:get_x_offset_col(line, x) + if not self.wrapped_settings then return old_get_x_offset_col(self, line, x) end + local idx = get_line_idx_col_count(self, line) + return get_line_col_from_index_and_x(self, idx, x) +end + +-- If line end is true, returns the end of the previous line, in a multi-line break. +local old_get_col_x_offset = DocView.get_col_x_offset +function DocView:get_col_x_offset(line, col, line_end) + if not self.wrapped_settings then return old_get_col_x_offset(self, line, col) end + local idx, ncol, count, scol = get_line_idx_col_count(self, line, col, line_end) + local xoffset, i = (scol ~= 1 and self.wrapped_line_offsets[line] or 0), 1 + local default_font = self:get_font() + for _, type, text in self.doc.highlighter:each_token(line) do + if i + #text >= scol then + if i < scol then + text = text:sub(scol - i + 1) + i = scol + end + local font = style.syntax_fonts[type] or default_font + for char in common.utf8_chars(text) do + if i >= col then + return xoffset + end + xoffset = xoffset + font:get_width(char) + i = i + #char + end + else + i = i + #text + end + end + return xoffset +end + +local old_get_line_screen_position = DocView.get_line_screen_position +function DocView:get_line_screen_position(line, col) + if not self.wrapped_settings then return old_get_line_screen_position(self, line, col) end + local idx, ncol, count = get_line_idx_col_count(self, line, col) + local x, y = self:get_content_offset() + local lh = self:get_line_height() + local gw = self:get_gutter_width() + return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y +end + +local old_resolve_screen_position = DocView.resolve_screen_position +function DocView:resolve_screen_position(x, y) + if not self.wrapped_settings then return old_resolve_screen_position(self, x, y) end + local ox, oy = self:get_line_screen_position(1) + local idx = common.clamp(math.floor((y - oy) / self:get_line_height()) + 1, 1, get_total_wrapped_lines(self)) + return get_line_col_from_index_and_x(self, idx, x - ox) +end + +local old_draw_line_text = DocView.draw_line_text +function DocView:draw_line_text(line, x, y) + if not self.wrapped_settings then return old_draw_line_text(self, line, x, y) end + local default_font = self:get_font() + local tx, ty, begin_width = x, y + self:get_line_text_y_offset(), self.wrapped_line_offsets[line] + local lh = self:get_line_height() + local idx, _, count = get_line_idx_col_count(self, line) + local total_offset = 1 + for _, type, text in self.doc.highlighter:each_token(line) do + local color = style.syntax[type] + local font = style.syntax_fonts[type] or default_font + local token_offset = 1 + -- Split tokens if we're at the end of the document. + while text ~= nil and token_offset <= #text do + local next_line, next_line_start_col = get_idx_line_col(self, idx + 1) + if next_line ~= line then + next_line_start_col = #self.doc.lines[line] + end + local max_length = next_line_start_col - total_offset + local rendered_text = text:sub(token_offset, token_offset + max_length - 1) + tx = renderer.draw_text(font, rendered_text, tx, ty, color) + total_offset = total_offset + #rendered_text + if total_offset ~= next_line_start_col or max_length == 0 then break end + token_offset = token_offset + #rendered_text + idx = idx + 1 + tx, ty = x + begin_width, ty + lh + end + end + return lh * count +end + +local old_draw_line_body = DocView.draw_line_body +function DocView:draw_line_body(line, x, y) + if not self.wrapped_settings then return old_draw_line_body(self, line, x, y) end + local lh = self:get_line_height() + local idx0 = get_line_idx_col_count(self, line) + for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do + if line >= line1 and line <= line2 then + if line1 ~= line then col1 = 1 end + if line2 ~= line then col2 = #self.doc.lines[line] + 1 end + if col1 ~= col2 then + local idx1, ncol1 = get_line_idx_col_count(self, line, col1) + local idx2, ncol2 = get_line_idx_col_count(self, line, col2) + for i = idx1, idx2 do + local x1, x2 = x + (idx1 == i and self:get_col_x_offset(line1, col1) or 0) + if idx2 == i then + x2 = x + self:get_col_x_offset(line, col2) + else + x2 = x + self:get_col_x_offset(line, get_idx_line_length(self, i, line) + 1, true) + end + renderer.draw_rect(x1, y + (i - idx0) * lh, x2 - x1, lh, style.selection) + end + end + end + end + local draw_highlight = nil + for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do + -- draw line highlight if caret is on this line + if draw_highlight ~= false and config.highlight_current_line + and line1 == line and core.active_view == self then + draw_highlight = (line1 == line2 and col1 == col2) + end + end + if draw_highlight then + local _, _, count = get_line_idx_col_count(self, line) + for i=1,count do + self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1)) + end + end + -- draw line's text + return self:draw_line_text(line, x, y) +end + +local old_draw = DocView.draw +function DocView:draw() + old_draw(self) + if self.wrapped_settings then + LineWrapping.draw_guide(self) + end +end + +local old_draw_line_gutter = DocView.draw_line_gutter +function DocView:draw_line_gutter(line, x, y, width) + local lh = self:get_line_height() + local _, _, count = get_line_idx_col_count(self, line) + return (old_draw_line_gutter(self, line, x, y, width) or lh) * count +end + +local old_translate_end_of_line = translate.end_of_line +function translate.end_of_line(doc, line, col) + if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_end_of_line(doc, line, col) end + local idx, ncol = get_line_idx_col_count(core.active_view, line, col) + local nline, ncol2 = get_idx_line_col(core.active_view, idx + 1) + if nline ~= line then return line, math.huge end + return line, ncol2 - 1 +end + +local old_translate_start_of_line = translate.start_of_line +function translate.start_of_line(doc, line, col) + if not core.active_view or core.active_view.doc ~= doc or not core.active_view.wrapped_settings then old_translate_start_of_line(doc, line, col) end + local idx, ncol = get_line_idx_col_count(core.active_view, line, col) + local nline, ncol2 = get_idx_line_col(core.active_view, idx - 1) + if nline ~= line then return line, 1 end + return line, ncol2 + 1 +end + +local old_previous_line = DocView.translate.previous_line +function DocView.translate.previous_line(doc, line, col, dv) + if not dv.wrapped_settings then return old_previous_line(doc, line, col, dv) end + local idx, ncol = get_line_idx_col_count(dv, line, col) + return get_line_col_from_index_and_x(dv, idx - 1, dv:get_col_x_offset(line, col)) +end + +local old_next_line = DocView.translate.next_line +function DocView.translate.next_line(doc, line, col, dv) + if not dv.wrapped_settings then return old_next_line(doc, line, col, dv) end + local idx, ncol = get_line_idx_col_count(dv, line, col) + return get_line_col_from_index_and_x(dv, idx + 1, dv:get_col_x_offset(line, col)) +end + +command.add(nil, { + ["line-wrapping:enable"] = function() + if core.active_view and core.active_view.doc then + LineWrapping.update_docview_breaks(core.active_view) + end + end, + ["line-wrapping:disable"] = function() + if core.active_view and core.active_view.doc then + LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge) + end + end, + ["line-wrapping:toggle"] = function() + if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then + command.perform("line-wrapping:disable") + else + command.perform("line-wrapping:enable") + end + end +}) + +keymap.add { + ["f10"] = "line-wrapping:toggle", +} + +return LineWrapping From 11dfb5b1cab4909ac65c42dc5f5040c325e8759e Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 24 Mar 2022 20:12:45 -0400 Subject: [PATCH 207/409] Apparenlty these were issues? What? --- data/core/docview.lua | 4 ++-- data/plugins/linewrapping.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/core/docview.lua b/data/core/docview.lua index ac3c19d9..0fd82b83 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -341,7 +341,7 @@ function DocView:draw_line_body(line, x, y) local hcl = config.highlight_current_line if hcl ~= false then for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do - if line1 == idx then + if line1 == line then if hcl == "no_selection" then if (line1 ~= line2) or (col1 ~= col2) then draw_highlight = false @@ -387,7 +387,7 @@ function DocView:draw_line_gutter(line, x, y, width) end x = x + style.padding.x local lh = self:get_line_height() - common.draw_text(self:get_font(), color, line, "right", x, y + yoffset, width, lh) + common.draw_text(self:get_font(), color, line, "right", x, y, width, lh) return lh end diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index aecc2bab..0b645065 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -1,4 +1,4 @@ --- mod-version:2 -- lite-xl 2.0 +-- mod-version:3 -- lite-xl 2.1 local core = require "core" local common = require "core.common" local DocView = require "core.docview" From 562e284d0469889b583ec0d1d67e9741eb7d6eec Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 30 Mar 2022 21:29:49 -0400 Subject: [PATCH 208/409] Fixed some minor issues with linewrapping. --- data/plugins/linewrapping.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index 0b645065..8bc8cd37 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -9,7 +9,7 @@ local command = require "core.command" local keymap = require "core.keymap" local translate = require "core.doc.translate" -config.plugins.linewrapping = { +config.plugins.linewrapping = common.merge({ -- The type of wrapping to perform. Can be "letter" or "word". mode = "letter", -- If nil, uses the DocView's size, otherwise, uses this exact width. @@ -20,14 +20,14 @@ config.plugins.linewrapping = { indent = true, -- Whether or not to enable wrapping by default when opening files. enable_by_default = true -} +}, config.plugins.linewrapping) local LineWrapping = {} -- Computes the breaks for a given line, width and mode. Returns a list of columns -- at which the line should be broken. function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) - local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, 1, 0, 0 + local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, nil, 0, 0 local splits = { 1 } for idx, type, text in doc.highlighter:each_token(line) do local font = style.syntax_fonts[type] or default_font @@ -41,13 +41,14 @@ function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) w = font:get_width(char) xoffset = xoffset + w if xoffset > width then - if mode == "word" then + if mode == "word" and last_space then table.insert(splits, last_space + 1) - xoffset = xoffset - last_width + w + begin_width - else + xoffset = w + begin_width + (xoffset - last_width) + else table.insert(splits, i) xoffset = w + begin_width end + last_space = nil elseif char == ' ' then last_space = i last_width = xoffset From 974fd9c8d5478d3e1de648ef17b424a564e4a844 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 2 Apr 2022 17:03:29 -0400 Subject: [PATCH 209/409] Fixed windows dirmonitor issues. --- data/core/dirwatch.lua | 24 ++++++++++++------------ src/api/dirmonitor.c | 2 +- src/api/dirmonitor/win32.c | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 2bcf5508..772a7f5f 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -36,20 +36,20 @@ function dirwatch:watch(directory, bool) if not self.watched[directory] and not self.scanned[directory] then if PLATFORM == "Windows" then if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then - -- Get the highest level of directory that is common to this directory, and the original. - local target = directory - while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do - target = common.dirname(target) - end - if target ~= self.windows_watch_top then - local value = self.monitor:watch(target) - if value and value < 0 then - return self:scan(directory) + -- Get the highest level of directory that is common to this directory, and the original. + local target = directory + while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do + target = common.dirname(target) + end + if target ~= self.windows_watch_top then + local value = self.monitor:watch(target) + if value and value < 0 then + return self:scan(directory) + end + self.windows_watch_top = target + end end - self.windows_watch_top = target self.windows_watch_count = self.windows_watch_count + 1 - end - end self.watched[directory] = true else local value = self.monitor:watch(directory) diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 3c79c0d9..6d14fbe1 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -70,7 +70,7 @@ static int f_dirmonitor_watch(lua_State *L) { } static int f_dirmonitor_unwatch(lua_State *L) { - remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2)); + remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), lua_tonumber(L, 2)); return 0; } diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c index 9dd2a254..d5945856 100644 --- a/src/api/dirmonitor/win32.c +++ b/src/api/dirmonitor/win32.c @@ -3,7 +3,7 @@ struct dirmonitor { HANDLE handle; - char buffer[8192]; + char buffer[64512]; OVERLAPPED overlapped; bool running; }; @@ -52,8 +52,8 @@ int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(in monitor->running = false; - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { - change_callback(info->FileNameLength, (char*)info->FileName, data); + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { + change_callback(info->FileNameLength / sizeof(WCHAR), (char*)info->FileName, data); if (!info->NextEntryOffset) break; } @@ -74,4 +74,4 @@ int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { close_monitor_handle(monitor); -} \ No newline at end of file +} From 04adb10f977c64a3b0ada97f4b259f06c8d5b88e Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 3 Apr 2022 16:20:48 -0400 Subject: [PATCH 210/409] Added in check in case of nil. --- data/core/dirwatch.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 772a7f5f..53f600c5 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -202,7 +202,9 @@ function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse if recurse_pred(dir, f.filename, entries_count, t_elapsed) then local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred) recurse_complete = recurse_complete and complete - entries_count = n + if n ~= nil then + entries_count = n + end else recurse_complete = false end From 683d23e3ba503c551cec851d231060714cdc48bf Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 4 Apr 2022 10:38:43 -0400 Subject: [PATCH 211/409] Changed calculation to use ascender, which should work better. (#912) --- src/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer.c b/src/renderer.c index c2fa233e..347bb0c7 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -195,7 +195,7 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial font->face = face; font->size = size; font->height = (short)((face->height / (float)face->units_per_EM) * font->size); - font->baseline = (short)((face->bbox.yMax / (float)face->units_per_EM) * font->size); + font->baseline = (short)((face->ascender / (float)face->units_per_EM) * font->size); font->antialiasing = antialiasing; font->hinting = hinting; font->style = style; From ffe698cef797634016cdd7da229fdd2e4f079e3c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 10 Apr 2022 04:11:59 +0200 Subject: [PATCH 212/409] Consider last document line to gather `autocomplete` symbols --- data/plugins/autocomplete.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index 0723ec8d..e2528f2e 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -81,7 +81,7 @@ core.add_thread(function() local i = 1 local s = {} local symbols_count = 0 - while i < #doc.lines do + while i <= #doc.lines do for sym in doc.lines[i]:gmatch(config.symbol_pattern) do if not s[sym] then symbols_count = symbols_count + 1 From e5c55e8abc3dbc129cd04b19b4051710c77de7c3 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 10 Apr 2022 04:20:46 +0200 Subject: [PATCH 213/409] Early `break` if `autocomplete` needs to update --- data/plugins/autocomplete.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index e2528f2e..086a41db 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -139,6 +139,7 @@ core.add_thread(function() for _, doc in ipairs(core.docs) do if not cache_is_valid(doc) then valid = false + break end end end From d4f84e1affb0cd3e9759883c1f059dbe881b88fb Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Sat, 9 Apr 2022 12:06:14 +0200 Subject: [PATCH 214/409] Add syntax symbols for auto-complete --- data/plugins/autocomplete.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index 0723ec8d..9ce032cb 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -76,10 +76,19 @@ local max_symbols = config.plugins.autocomplete.max_symbols core.add_thread(function() local cache = setmetatable({}, { __mode = "k" }) + local function get_syntax_symbols(symbols, doc) + if doc.syntax then + for sym in pairs(doc.syntax.symbols) do + symbols[sym] = true + end + end + end + local function get_symbols(doc) - if doc.disable_symbols then return {} end - local i = 1 local s = {} + get_syntax_symbols(s, doc) + if doc.disable_symbols then return s end + local i = 1 local symbols_count = 0 while i < #doc.lines do for sym in doc.lines[i]:gmatch(config.symbol_pattern) do From 202e42b568a50547e3c1b32f6eed41ad6ad071a3 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 02:37:21 +0200 Subject: [PATCH 215/409] Avoid calling `View:scrollbar_overlaps_point` uselessly `View:on_mouse_moved` already updated `self.hovered_scrollbar`, so use that instead. --- data/core/docview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/docview.lua b/data/core/docview.lua index 397b455e..a3f9ebd2 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -227,7 +227,7 @@ end function DocView:on_mouse_moved(x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...) - if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then + if self.hovered_scrollbar or self.dragging_scrollbar then self.cursor = "arrow" else self.cursor = "ibeam" From 052c140787825f678dc7d181d01bd43d621072e7 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 02:38:30 +0200 Subject: [PATCH 216/409] Fix `DocView:on_mouse_released` not considering all parameters --- data/core/docview.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/docview.lua b/data/core/docview.lua index a3f9ebd2..65a3778e 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -270,8 +270,8 @@ function DocView:mouse_selection(doc, snap_type, line1, col1, line2, col2) end -function DocView:on_mouse_released(button) - DocView.super.on_mouse_released(self, button) +function DocView:on_mouse_released(...) + DocView.super.on_mouse_released(self, ...) self.mouse_selecting = nil end From 48c371a638dede68ba4ca13846d1a4717b324437 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 02:49:11 +0200 Subject: [PATCH 217/409] Add scrollbar "track" and resize on hover --- data/core/style.lua | 2 + data/core/view.lua | 98 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/data/core/style.lua b/data/core/style.lua index 470a0904..70ad502c 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -4,6 +4,7 @@ local style = {} style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) } style.divider_size = common.round(1 * SCALE) style.scrollbar_size = common.round(4 * SCALE) +style.expanded_scrollbar_size = common.round(12 * SCALE) style.caret_width = common.round(2 * SCALE) style.tab_width = common.round(170 * SCALE) @@ -43,6 +44,7 @@ style.line_number2 = { common.color "#83838f" } -- With cursor style.line_highlight = { common.color "#343438" } style.scrollbar = { common.color "#414146" } style.scrollbar2 = { common.color "#4b4b52" } -- Hovered +style.scrollbar_track = { common.color "#252529" } style.nagbar = { common.color "#FF0000" } style.nagbar_text = { common.color "#FFFFFF" } style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" } diff --git a/data/core/view.lua b/data/core/view.lua index a3664a55..9d305945 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -18,6 +18,13 @@ function View:new() self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } } self.cursor = "arrow" self.scrollable = false + self.scrollbar = { + x = { thumb = 0, track = 0 }, + y = { thumb = 0, track = 0 }, + w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } }, + h = { thumb = 0, track = 0 }, + } + self.scrollbar_alpha = { value = 0, to = 0 } end function View:move_towards(t, k, dest, rate) @@ -57,29 +64,62 @@ function View:get_scrollable_size() end +function View:get_scrollbar_track_rect() + local sz = self:get_scrollable_size() + if sz <= self.size.y or sz == math.huge then + return 0, 0, 0, 0 + end + local width = style.scrollbar_size + if self.hovered_scrollbar_track or self.dragging_scrollbar then + width = style.expanded_scrollbar_size + end + return + self.position.x + self.size.x - width, + self.position.y, + width, + self.size.y +end + + function View:get_scrollbar_rect() local sz = self:get_scrollable_size() if sz <= self.size.y or sz == math.huge then return 0, 0, 0, 0 end local h = math.max(20, self.size.y * self.size.y / sz) + local width = style.scrollbar_size + if self.hovered_scrollbar_track or self.dragging_scrollbar then + width = style.expanded_scrollbar_size + end return - self.position.x + self.size.x - style.scrollbar_size, + self.position.x + self.size.x - width, self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y), - style.scrollbar_size, + width, h end function View:scrollbar_overlaps_point(x, y) local sx, sy, sw, sh = self:get_scrollbar_rect() - return x >= sx - sw * 3 and x < sx + sw and y >= sy and y < sy + sh + return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y >= sy and y < sy + sh +end + +function View:scrollbar_track_overlaps_point(x, y) + local sx, sy, sw, sh = self:get_scrollbar_track_rect() + return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y >= sy and y < sy + sh end function View:on_mouse_pressed(button, x, y, clicks) - if self:scrollbar_overlaps_point(x, y) then - self.dragging_scrollbar = true + if self:scrollbar_track_overlaps_point(x, y) then + if self:scrollbar_overlaps_point(x, y) then + self.dragging_scrollbar = true + else + local _, _, _, sh = self:get_scrollbar_rect() + local ly = (y - self.position.y) - sh / 2 + local pct = common.clamp(ly / self.size.y, 0, 100) + self.scroll.to.y = self:get_scrollable_size() * pct + end return true end end @@ -96,6 +136,7 @@ function View:on_mouse_moved(x, y, dx, dy) self.scroll.to.y = self.scroll.to.y + delta end self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y) + self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y) end @@ -132,10 +173,33 @@ function View:clamp_scroll_position() end +function View:update_scrollbar() + local x, y, w, h = self:get_scrollbar_rect() + self.scrollbar.w.to.thumb = w + self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3) + self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb + self.scrollbar.y.thumb = y + self.scrollbar.h.thumb = h + + local x, y, w, h = self:get_scrollbar_track_rect() + self.scrollbar.w.to.track = w + self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3) + self.scrollbar.x.track = x + w - self.scrollbar.w.track + self.scrollbar.y.track = y + self.scrollbar.h.track = h + + -- we use 100 for a smoother transition + self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0 + self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3) +end + + function View:update() self:clamp_scroll_position() self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3) self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3) + + self:update_scrollbar() end @@ -146,11 +210,29 @@ function View:draw_background(color) end -function View:draw_scrollbar() - local x, y, w, h = self:get_scrollbar_rect() +function View:draw_scrollbar_track() + if not (self.hovered_scrollbar_track or self.dragging_scrollbar) + and self.scrollbar_alpha.value == 0 then + return + end + local color = { table.unpack(style.scrollbar_track) } + color[4] = color[4] * self.scrollbar_alpha.value / 100 + renderer.draw_rect(self.scrollbar.x.track, self.scrollbar.y.track, + self.scrollbar.w.track, self.scrollbar.h.track, color) +end + + +function View:draw_scrollbar_thumb() local highlight = self.hovered_scrollbar or self.dragging_scrollbar local color = highlight and style.scrollbar2 or style.scrollbar - renderer.draw_rect(x, y, w, h, color) + renderer.draw_rect(self.scrollbar.x.thumb, self.scrollbar.y.thumb, + self.scrollbar.w.thumb, self.scrollbar.h.thumb, color) +end + + +function View:draw_scrollbar() + self:draw_scrollbar_track() + self:draw_scrollbar_thumb() end From 4f434d1a414cbcc6768d53bf40b01454fe9f03b0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 02:49:44 +0200 Subject: [PATCH 218/409] Show `arrow` cursor when hovering `DocView` scrollbar track --- data/core/docview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/docview.lua b/data/core/docview.lua index 65a3778e..1ed8f429 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -227,7 +227,7 @@ end function DocView:on_mouse_moved(x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...) - if self.hovered_scrollbar or self.dragging_scrollbar then + if self.hovered_scrollbar_track or self.dragging_scrollbar then self.cursor = "arrow" else self.cursor = "ibeam" From 43086a9c24f663300c2555162b56642f3457c044 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 03:20:27 +0200 Subject: [PATCH 219/409] Fix missing pixel in scrollbar --- data/core/view.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/view.lua b/data/core/view.lua index 9d305945..f1c9a949 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -101,12 +101,12 @@ end function View:scrollbar_overlaps_point(x, y) local sx, sy, sw, sh = self:get_scrollbar_rect() - return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y >= sy and y < sy + sh + return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh end function View:scrollbar_track_overlaps_point(x, y) local sx, sy, sw, sh = self:get_scrollbar_track_rect() - return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y >= sy and y < sy + sh + return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh end From 93b31211cb32497f84e5013ce67d9ecc85bcd28c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 12 Apr 2022 04:05:59 +0200 Subject: [PATCH 220/409] Apply `drawwhitespace` plugin only to `DocView`s --- data/plugins/drawwhitespace.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 9c28faa6..694eeb1a 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -42,6 +42,7 @@ end local draw_line_text = DocView.draw_line_text function DocView:draw_line_text(idx, x, y) + if getmetatable(self) ~= DocView then return draw_line_text(self, idx, x, y) end local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) local ty = y + self:get_line_text_y_offset() local tx From d323917538345bd8bd40524d2aeb035a67187fb4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 3 Apr 2022 16:20:48 -0400 Subject: [PATCH 221/409] Added in check in case of nil. --- data/core/dirwatch.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 2bcf5508..2dac59f3 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -202,7 +202,9 @@ function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse if recurse_pred(dir, f.filename, entries_count, t_elapsed) then local _, complete, n = dirwatch.get_directory_files(dir, root, f.filename, t, entries_count, recurse_pred) recurse_complete = recurse_complete and complete - entries_count = n + if n ~= nil then + entries_count = n + end else recurse_complete = false end From fff10a26121e0fcada6a1b60cb263459bb6ed1f8 Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 15 Apr 2022 17:34:46 +0200 Subject: [PATCH 222/409] Cleanup (#826) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update meson.build - add logic to loop over more lua names (in the future more names might be discovered) - disable warnings and errors on dependencies * adding missing includes and checks, correct data types, pointer mess […] - various functions from string.h were used but never defined - logic was done across multiple different data types with different signedness, got all of them up to snuff - give 0 sized array size of 1 (array of size 0 is illegal, but rewriting the code is out of the scope of this commit) - add preprocessor that marks possibly unused argument as such (does not mean they will get optimized out or anything) - correctly initialize structs with all data needed All these were found by generating the project using `meson -Dwarning_level=3 -Dwerror=true` * remove undefined behavior, correct data types * Comment manual bit manipulation to be investigated * check for more edge cases, replace multiple cleanups with goto * remove system specific includes --- meson.build | 34 +++++++++++------- src/api/dirmonitor.c | 7 +--- src/api/process.c | 83 +++++++++++++++++++++++++++++--------------- src/api/renderer.c | 11 +++--- src/api/system.c | 20 ++++++----- src/rencache.c | 9 ++--- src/rencache.h | 7 ++-- src/renderer.c | 65 ++++++++++++++++++++++++---------- src/renderer.h | 6 ++++ src/renwindow.c | 6 ++-- 10 files changed, 158 insertions(+), 90 deletions(-) diff --git a/meson.build b/meson.build index 75cd8cf3..9dbe9bec 100644 --- a/meson.build +++ b/meson.build @@ -69,30 +69,38 @@ endif if not get_option('source-only') libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) - lua_fallback = ['lua', 'lua_dep'] - lua_quick_fallback = [] - if get_option('wrap_mode') == 'forcefallback' - lua_quick_fallback = lua_fallback - endif - lua_dep = dependency('lua5.4', fallback: lua_quick_fallback, required : false) - if not lua_dep.found() - lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'], + default_fallback_options = ['warning_level=0', 'werror=false'] + + # Lua has no official .pc file + # so distros come up with their own names + lua_names = [ + 'lua5.4', # Debian + 'lua-5.4', # FreeBSD + 'lua', # Fedora + ] + + foreach lua : lua_names + last_lua = (lua == lua_names[-1]) + lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua, version: '>= 5.4', - default_options: ['default_library=static', 'line_editing=false', 'interpreter=false'] + default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false'] ) - endif + if lua_dep.found() + break + endif + endforeach pcre2_dep = dependency('libpcre2-8', fallback: ['pcre2', 'libpcre2_8'], - default_options: ['default_library=static', 'grep=false', 'test=false'] + default_options: default_fallback_options + ['default_library=static', 'grep=false', 'test=false'] ) freetype_dep = dependency('freetype2', fallback: ['freetype2', 'freetype_dep'], - default_options: ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled'] + default_options: default_fallback_options + ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled'] ) sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'], - default_options: ['default_library=static'] + default_options: default_fallback_options + ['default_library=static'] ) lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl] diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 3c79c0d9..0ebf1680 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -1,12 +1,7 @@ #include "api.h" #include -#ifdef _WIN32 +#ifdef DIRMONITOR_WIN32 #include -#elif __linux__ - #include - #include -#else - #include #endif #include #include diff --git a/src/api/process.c b/src/api/process.c index ecbe076e..1be1286c 100644 --- a/src/api/process.c +++ b/src/api/process.c @@ -5,6 +5,7 @@ #include #include #include +#include #if _WIN32 // https://stackoverflow.com/questions/60645/overlapped-i-o-on-anonymous-pipe @@ -21,19 +22,23 @@ #define READ_BUF_SIZE 2048 +#if _WIN32 +typedef HANDLE process_handle; +#else +typedef int process_handle; +#endif + typedef struct { bool running; int returncode, deadline; long pid; #if _WIN32 PROCESS_INFORMATION process_information; - HANDLE child_pipes[3][2]; OVERLAPPED overlapped[2]; bool reading[2]; char buffer[2][READ_BUF_SIZE]; - #else - int child_pipes[3][2]; #endif + process_handle child_pipes[3][2]; } process_t; typedef enum { @@ -91,7 +96,7 @@ static bool poll_process(process_t* proc, int timeout) { #endif if (timeout) SDL_Delay(5); - } while (timeout == WAIT_INFINITE || SDL_GetTicks() - ticks < timeout); + } while (timeout == WAIT_INFINITE || (int)SDL_GetTicks() - ticks < timeout); return proc->running; } @@ -117,8 +122,9 @@ static bool signal_process(process_t* proc, signal_e sig) { } static int process_start(lua_State* L) { + int retval = 1; size_t env_len = 0, key_len, val_len; - const char *cmd[256], *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL; + const char *cmd[256] = { NULL }, *env_names[256] = { NULL }, *env_values[256] = { NULL }, *cwd = NULL; bool detach = false; int deadline = 10, new_fds[3] = { STDIN_FD, STDOUT_FD, STDERR_FD }; luaL_checktype(L, 1, LUA_TTABLE); @@ -134,7 +140,11 @@ static int process_start(lua_State* L) { lua_rawget(L, 1); cmd[i-1] = luaL_checkstring(L, -1); } - cmd[cmd_len] = NULL; + + // this should never trip + // but if it does we are in deep trouble + assert(cmd[0]); + if (arg_len > 1) { lua_getfield(L, 2, "env"); if (!lua_isnil(L, -1)) { @@ -158,8 +168,14 @@ static int process_start(lua_State* L) { lua_getfield(L, 2, "stdout"); new_fds[STDOUT_FD] = luaL_optnumber(L, -1, STDOUT_FD); lua_getfield(L, 2, "stderr"); new_fds[STDERR_FD] = luaL_optnumber(L, -1, STDERR_FD); for (int stream = STDIN_FD; stream <= STDERR_FD; ++stream) { - if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT) - return luaL_error(L, "redirect to handles, FILE* and paths are not supported"); + if (new_fds[stream] > STDERR_FD || new_fds[stream] < REDIRECT_PARENT) { + for (size_t i = 0; i < env_len; ++i) { + free((char*)env_names[i]); + free((char*)env_values[i]); + } + retval = luaL_error(L, "redirect to handles, FILE* and paths are not supported"); + goto cleanup; + } } } @@ -188,16 +204,21 @@ static int process_start(lua_State* L) { sprintf(pipeNameBuffer, "\\\\.\\Pipe\\RemoteExeAnon.%08lx.%08lx", GetCurrentProcessId(), InterlockedIncrement(&PipeSerialNumber)); self->child_pipes[i][0] = CreateNamedPipeA(pipeNameBuffer, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, READ_BUF_SIZE, READ_BUF_SIZE, 0, NULL); - if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE) - return luaL_error(L, "Error creating read pipe: %d.", GetLastError()); + if (self->child_pipes[i][0] == INVALID_HANDLE_VALUE) { + retval = luaL_error(L, "Error creating read pipe: %d.", GetLastError()); + goto cleanup; + } self->child_pipes[i][1] = CreateFileA(pipeNameBuffer, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (self->child_pipes[i][1] == INVALID_HANDLE_VALUE) { CloseHandle(self->child_pipes[i][0]); - return luaL_error(L, "Error creating write pipe: %d.", GetLastError()); + retval = luaL_error(L, "Error creating write pipe: %d.", GetLastError()); + goto cleanup; } if (!SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 1 : 0], HANDLE_FLAG_INHERIT, 0) || - !SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1)) - return luaL_error(L, "Error inheriting pipes: %d.", GetLastError()); + !SetHandleInformation(self->child_pipes[i][i == STDIN_FD ? 0 : 1], HANDLE_FLAG_INHERIT, 1)) { + retval = luaL_error(L, "Error inheriting pipes: %d.", GetLastError()); + goto cleanup; + } } } break; } @@ -237,24 +258,25 @@ static int process_start(lua_State* L) { environmentBlock[offset++] = 0; if (env_len > 0) MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, environmentBlock, offset, (LPWSTR)wideEnvironmentBlock, sizeof(wideEnvironmentBlock)); - if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) - return luaL_error(L, "Error creating a process: %d.", GetLastError()); + if (!CreateProcess(NULL, commandLine, NULL, NULL, true, (detach ? DETACHED_PROCESS : CREATE_NO_WINDOW) | CREATE_UNICODE_ENVIRONMENT, env_len > 0 ? wideEnvironmentBlock : NULL, cwd, &siStartInfo, &self->process_information)) { + retval = luaL_error(L, "Error creating a process: %d.", GetLastError()); + goto cleanup; + } self->pid = (long)self->process_information.dwProcessId; if (detach) CloseHandle(self->process_information.hProcess); CloseHandle(self->process_information.hThread); #else for (int i = 0; i < 3; ++i) { // Make only the parents fd's non-blocking. Children should block. - if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1) - return luaL_error(L, "Error creating pipes: %s", strerror(errno)); + if (pipe(self->child_pipes[i]) || fcntl(self->child_pipes[i][i == STDIN_FD ? 1 : 0], F_SETFL, O_NONBLOCK) == -1) { + retval = luaL_error(L, "Error creating pipes: %s", strerror(errno)); + goto cleanup; + } } self->pid = (long)fork(); if (self->pid < 0) { - for (int i = 0; i < 3; ++i) { - close(self->child_pipes[i][0]); - close(self->child_pipes[i][1]); - } - return luaL_error(L, "Error running fork: %s.", strerror(errno)); + retval = luaL_error(L, "Error running fork: %s.", strerror(errno)); + goto cleanup; } else if (!self->pid) { setpgrp(); for (int stream = 0; stream < 3; ++stream) { @@ -265,23 +287,28 @@ static int process_start(lua_State* L) { dup2(self->child_pipes[new_fds[stream]][new_fds[stream] == STDIN_FD ? 0 : 1], stream); close(self->child_pipes[stream][stream == STDIN_FD ? 1 : 0]); } - int set; + size_t set; for (set = 0; set < env_len && setenv(env_names[set], env_values[set], 1) == 0; ++set); if (set == env_len && (!detach || setsid() != -1) && (!cwd || chdir(cwd) != -1)) - execvp((const char*)cmd[0], (char* const*)cmd); + execvp(cmd[0], (char** const)cmd); const char* msg = strerror(errno); - int result = write(STDERR_FD, msg, strlen(msg)+1); + size_t result = write(STDERR_FD, msg, strlen(msg)+1); _exit(result == strlen(msg)+1 ? -1 : -2); } #endif + cleanup: for (size_t i = 0; i < env_len; ++i) { free((char*)env_names[i]); free((char*)env_values[i]); } - for (int stream = 0; stream < 3; ++stream) - close_fd(&self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]); + for (int stream = 0; stream < 3; ++stream) { + process_handle* pipe = &self->child_pipes[stream][stream == STDIN_FD ? 0 : 1]; + if (*pipe) { + close_fd(pipe); + } + } self->running = true; - return 1; + return retval; } static int g_read(lua_State* L, int stream, unsigned long read_size) { diff --git a/src/api/renderer.c b/src/api/renderer.c index 0fc21707..3ab33b1a 100644 --- a/src/api/renderer.c +++ b/src/api/renderer.c @@ -1,3 +1,4 @@ +#include #include "api.h" #include "../renderer.h" #include "../rencache.h" @@ -166,14 +167,14 @@ static int f_get_size(lua_State *L) { } -static int f_begin_frame(lua_State *L) { - rencache_begin_frame(L); +static int f_begin_frame(UNUSED lua_State *L) { + rencache_begin_frame(); return 0; } -static int f_end_frame(lua_State *L) { - rencache_end_frame(L); +static int f_end_frame(UNUSED lua_State *L) { + rencache_end_frame(); return 0; } @@ -214,7 +215,7 @@ static int f_draw_text(lua_State *L) { float x = luaL_checknumber(L, 3); int y = luaL_checknumber(L, 4); RenColor color = checkcolor(L, 5, 255); - x = rencache_draw_text(L, fonts, text, x, y, color); + x = rencache_draw_text(fonts, text, x, y, color); lua_pushnumber(L, x); return 1; } diff --git a/src/api/system.c b/src/api/system.c index f1f9c3c7..45231d0b 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -45,7 +45,7 @@ struct HitTestInfo { }; typedef struct HitTestInfo HitTestInfo; -static HitTestInfo window_hit_info[1] = {{0, 0}}; +static HitTestInfo window_hit_info[1] = {{0, 0, 0}}; #define RESIZE_FROM_TOP 0 #define RESIZE_FROM_RIGHT 0 @@ -705,13 +705,15 @@ static int f_set_window_opacity(lua_State *L) { return 1; } +typedef void (*fptr)(void); + typedef struct lua_function_node { const char *symbol; - void *address; + fptr address; } lua_function_node; -#define P(FUNC) { "lua_" #FUNC, (void*)(lua_##FUNC) } -#define U(FUNC) { "luaL_" #FUNC, (void*)(luaL_##FUNC) } +#define P(FUNC) { "lua_" #FUNC, (fptr)(lua_##FUNC) } +#define U(FUNC) { "luaL_" #FUNC, (fptr)(luaL_##FUNC) } static void* api_require(const char* symbol) { static lua_function_node nodes[] = { P(atpanic), P(checkstack), @@ -749,9 +751,9 @@ static void* api_require(const char* symbol) { #endif }; - for (int i = 0; i < sizeof(nodes) / sizeof(lua_function_node); ++i) { + for (size_t i = 0; i < sizeof(nodes) / sizeof(lua_function_node); ++i) { if (strcmp(nodes[i].symbol, symbol) == 0) - return nodes[i].address; + return *(void**)(&nodes[i].address); } return NULL; } @@ -775,10 +777,12 @@ static int f_load_native_plugin(lua_State *L) { const char *basename = strrchr(name, '.'); basename = !basename ? name : basename + 1; snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_lite_xl_%s", basename); - int (*ext_entrypoint) (lua_State *L, void*) = SDL_LoadFunction(library, entrypoint_name); + int (*ext_entrypoint) (lua_State *L, void* (*)(const char*)); + *(void**)(&ext_entrypoint) = SDL_LoadFunction(library, entrypoint_name); if (!ext_entrypoint) { snprintf(entrypoint_name, sizeof(entrypoint_name), "luaopen_%s", basename); - int (*entrypoint)(lua_State *L) = SDL_LoadFunction(library, entrypoint_name); + int (*entrypoint)(lua_State *L); + *(void**)(&entrypoint) = SDL_LoadFunction(library, entrypoint_name); if (!entrypoint) return luaL_error(L, "Unable to load %s: Can't find %s(lua_State *L, void *XL)", name, entrypoint_name); result = entrypoint(L); diff --git a/src/rencache.c b/src/rencache.c index 5686d984..c847ce34 100644 --- a/src/rencache.c +++ b/src/rencache.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "rencache.h" @@ -28,7 +29,7 @@ typedef struct { RenColor color; RenFont *fonts[FONT_FALLBACK_MAX]; float text_x; - char text[0]; + char text[]; } Command; static unsigned cells_buf1[CELLS_X * CELLS_Y]; @@ -134,7 +135,7 @@ void rencache_draw_rect(RenRect rect, RenColor color) { } } -float rencache_draw_text(lua_State *L, RenFont **fonts, const char *text, float x, int y, RenColor color) +float rencache_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) { float width = ren_font_group_get_width(fonts, text); RenRect rect = { x, y, (int)width, ren_font_group_get_height(fonts) }; @@ -159,7 +160,7 @@ void rencache_invalidate(void) { } -void rencache_begin_frame(lua_State *L) { +void rencache_begin_frame() { /* reset all cells if the screen width/height has changed */ int w, h; ren_get_size(&w, &h); @@ -200,7 +201,7 @@ static void push_rect(RenRect r, int *count) { } -void rencache_end_frame(lua_State *L) { +void rencache_end_frame() { /* update cells from commands */ Command *cmd = NULL; RenRect cr = screen_rect; diff --git a/src/rencache.h b/src/rencache.h index 75bb5051..fedbada6 100644 --- a/src/rencache.h +++ b/src/rencache.h @@ -8,10 +8,9 @@ void rencache_show_debug(bool enable); void rencache_set_clip_rect(RenRect rect); void rencache_draw_rect(RenRect rect, RenColor color); -float rencache_draw_text(lua_State *L, RenFont **font, - const char *text, float x, int y, RenColor color); +float rencache_draw_text(RenFont **font, const char *text, float x, int y, RenColor color); void rencache_invalidate(void); -void rencache_begin_frame(lua_State *L); -void rencache_end_frame(lua_State *L); +void rencache_begin_frame(); +void rencache_end_frame(); #endif diff --git a/src/renderer.c b/src/renderer.c index 347bb0c7..bfee2f4a 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -43,27 +43,28 @@ typedef struct RenFont { FT_Face face; GlyphSet* sets[SUBPIXEL_BITMAPS_CACHED][MAX_LOADABLE_GLYPHSETS]; float size, space_advance, tab_advance; - short max_height, baseline, height; + unsigned short max_height, baseline, height; ERenFontAntialiasing antialiasing; ERenFontHinting hinting; unsigned char style; - char path[0]; + char path[1]; } RenFont; static const char* utf8_to_codepoint(const char *p, unsigned *dst) { + const unsigned char *up = (unsigned char*)p; unsigned res, n; switch (*p & 0xf0) { - case 0xf0 : res = *p & 0x07; n = 3; break; - case 0xe0 : res = *p & 0x0f; n = 2; break; + case 0xf0 : res = *up & 0x07; n = 3; break; + case 0xe0 : res = *up & 0x0f; n = 2; break; case 0xd0 : - case 0xc0 : res = *p & 0x1f; n = 1; break; - default : res = *p; n = 0; break; + case 0xc0 : res = *up & 0x1f; n = 1; break; + default : res = *up; n = 0; break; } while (n--) { - res = (res << 6) | (*(++p) & 0x3f); + res = (res << 6) | (*(++up) & 0x3f); } *dst = res; - return p + 1; + return (const char*)up + 1; } static int font_set_load_options(RenFont* font) { @@ -136,7 +137,7 @@ static void font_load_glyphset(RenFont* font, int idx) { if (pen_x == 0) continue; set->surface = check_alloc(SDL_CreateRGBSurface(0, pen_x, font->max_height, font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 24 : 8, 0, 0, 0, 0)); - unsigned char* pixels = set->surface->pixels; + uint8_t* pixels = set->surface->pixels; for (int i = 0; i < MAX_GLYPHSET; ++i) { int glyph_index = FT_Get_Char_Index(font->face, i + idx * MAX_GLYPHSET); if (!glyph_index || FT_Load_Glyph(font->face, glyph_index, load_option)) @@ -145,11 +146,11 @@ static void font_load_glyphset(RenFont* font, int idx) { font_set_style(&slot->outline, (64 / bitmaps_cached) * j, font->style); if (FT_Render_Glyph(slot, render_option)) continue; - for (int line = 0; line < slot->bitmap.rows; ++line) { + for (unsigned int line = 0; line < slot->bitmap.rows; ++line) { int target_offset = set->surface->pitch * line + set->metrics[i].x0 * byte_width; int source_offset = line * slot->bitmap.pitch; if (font->antialiasing == FONT_ANTIALIASING_NONE) { - for (int column = 0; column < slot->bitmap.width; ++column) { + for (unsigned int column = 0; column < slot->bitmap.width; ++column) { int current_source_offset = source_offset + (column / 8); int source_pixel = slot->bitmap.buffer[current_source_offset]; pixels[++target_offset] = ((source_pixel >> (7 - (column % 8))) & 0x1) << 7; @@ -169,6 +170,9 @@ static GlyphSet* font_get_glyphset(RenFont* font, unsigned int codepoint, int su } static RenFont* font_group_get_glyph(GlyphSet** set, GlyphMetric** metric, RenFont** fonts, unsigned int codepoint, int bitmap_index) { + if (!metric) { + return NULL; + } if (bitmap_index < 0) bitmap_index += SUBPIXEL_BITMAPS_CACHED; for (int i = 0; i < FONT_FALLBACK_MAX && fonts[i]; ++i) { @@ -177,7 +181,7 @@ static RenFont* font_group_get_glyph(GlyphSet** set, GlyphMetric** metric, RenFo if ((*metric)->loaded || codepoint < 0xFF) return fonts[i]; } - if (!(*metric)->loaded && codepoint > 0xFF && codepoint != 0x25A1) + if (*metric && !(*metric)->loaded && codepoint > 0xFF && codepoint != 0x25A1) return font_group_get_glyph(set, metric, fonts, 0x25A1, bitmap_index); return fonts[0]; } @@ -233,7 +237,11 @@ void ren_font_group_set_tab_size(RenFont **fonts, int n) { } int ren_font_group_get_tab_size(RenFont **fonts) { - return font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance / fonts[0]->space_advance; + int advance = font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance; + if (fonts[0]->space_advance) { + advance /= fonts[0]->space_advance; + } + return advance; } float ren_font_group_get_size(RenFont **fonts) { @@ -251,6 +259,8 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) { unsigned int codepoint; text = utf8_to_codepoint(text, &codepoint); RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, 0); + if (!metric) + break; width += (!font || metric->xadvance) ? metric->xadvance : fonts[0]->space_advance; } const int surface_scale = renwin_surface_scale(&window_renderer); @@ -266,7 +276,7 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor y *= surface_scale; int bytes_per_pixel = surface->format->BytesPerPixel; const char* end = text + strlen(text); - unsigned char* destination_pixels = surface->pixels; + uint8_t* destination_pixels = surface->pixels; int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height; while (text < end) { @@ -274,13 +284,15 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor text = utf8_to_codepoint(text, &codepoint); GlyphSet* set = NULL; GlyphMetric* metric = NULL; RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, (int)(fmod(pen_x, 1.0) * SUBPIXEL_BITMAPS_CACHED)); + if (!metric) + break; int start_x = floor(pen_x) + metric->bitmap_left; int end_x = (metric->x1 - metric->x0) + start_x; int glyph_end = metric->x1, glyph_start = metric->x0; if (!metric->loaded && codepoint > 0xFF) ren_draw_rect((RenRect){ start_x + 1, y, font->space_advance - 1, ren_font_group_get_height(fonts) }, color); if (set->surface && color.a > 0 && end_x >= clip.x && start_x < clip_end_x) { - unsigned char* source_pixels = set->surface->pixels; + uint8_t* source_pixels = set->surface->pixels; for (int line = metric->y0; line < metric->y1; ++line) { int target_y = line + y - metric->bitmap_top + font->baseline * surface_scale; if (target_y < clip.y) @@ -294,15 +306,30 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor start_x += offset; glyph_start += offset; } - unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel]; - unsigned char* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)]; + uint32_t* destination_pixel = (uint32_t*)&(destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel]); + uint8_t* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)]; for (int x = glyph_start; x < glyph_end; ++x) { - unsigned int destination_color = *destination_pixel; + uint32_t destination_color = *destination_pixel; + // the standard way of doing this would be SDL_GetRGBA, but that introduces a performance regression. needs to be investigated SDL_Color dst = { (destination_color & surface->format->Rmask) >> surface->format->Rshift, (destination_color & surface->format->Gmask) >> surface->format->Gshift, (destination_color & surface->format->Bmask) >> surface->format->Bshift, (destination_color & surface->format->Amask) >> surface->format->Ashift }; - SDL_Color src = { *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *source_pixel++ }; + SDL_Color src; + + if (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL) { + src.r = *(source_pixel++); + src.g = *(source_pixel++); + } + else { + src.r = *(source_pixel); + src.g = *(source_pixel); + } + + src.b = *(source_pixel++); + src.a = 0xFF; + r = (color.r * src.r * color.a + dst.r * (65025 - src.r * color.a) + 32767) / 65025; g = (color.g * src.g * color.a + dst.g * (65025 - src.g * color.a) + 32767) / 65025; b = (color.b * src.b * color.a + dst.b * (65025 - src.b * color.a) + 32767) / 65025; + // the standard way of doing this would be SDL_GetRGBA, but that introduces a performance regression. needs to be investigated *destination_pixel++ = dst.a << surface->format->Ashift | r << surface->format->Rshift | g << surface->format->Gshift | b << surface->format->Bshift; } } diff --git a/src/renderer.h b/src/renderer.h index a97706ff..89c70d5e 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -5,6 +5,12 @@ #include #include +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + #define FONT_FALLBACK_MAX 4 typedef struct RenFont RenFont; typedef enum { FONT_HINTING_NONE, FONT_HINTING_SLIGHT, FONT_HINTING_FULL } ERenFontHinting; diff --git a/src/renwindow.c b/src/renwindow.c index f67002f8..c2aa0096 100644 --- a/src/renwindow.c +++ b/src/renwindow.c @@ -27,7 +27,7 @@ static void setup_renderer(RenWindow *ren, int w, int h) { #endif -void renwin_init_surface(RenWindow *ren) { +void renwin_init_surface(UNUSED RenWindow *ren) { #ifdef LITE_USE_SDL_RENDERER if (ren->surface) { SDL_FreeSurface(ren->surface); @@ -39,7 +39,7 @@ void renwin_init_surface(RenWindow *ren) { #endif } -int renwin_surface_scale(RenWindow *ren) { +int renwin_surface_scale(UNUSED RenWindow *ren) { #ifdef LITE_USE_SDL_RENDERER return ren->surface_scale; #else @@ -72,7 +72,7 @@ SDL_Surface *renwin_get_surface(RenWindow *ren) { #endif } -void renwin_resize_surface(RenWindow *ren) { +void renwin_resize_surface(UNUSED RenWindow *ren) { #ifdef LITE_USE_SDL_RENDERER int new_w, new_h; SDL_GL_GetDrawableSize(ren->window, &new_w, &new_h); From c112bd8d7c3ce12036aa86e7b55511afbd7cc6ea Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 17 Apr 2022 13:01:45 -0400 Subject: [PATCH 223/409] Significantly improved performance of tokenization for larger documents by by default not requiring tokenization. --- data/plugins/linewrapping.lua | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index 8bc8cd37..c7ae6278 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -9,29 +9,42 @@ local command = require "core.command" local keymap = require "core.keymap" local translate = require "core.doc.translate" + config.plugins.linewrapping = common.merge({ -- The type of wrapping to perform. Can be "letter" or "word". mode = "letter", - -- If nil, uses the DocView's size, otherwise, uses this exact width. + -- If nil, uses the DocView's size, otherwise, uses this exact width. Can be a function. width_override = nil, -- Whether or not to draw a guide guide = true, -- Whether or not we should indent ourselves like the first line of a wrapped block. indent = true, -- Whether or not to enable wrapping by default when opening files. - enable_by_default = true + enable_by_default = false, + -- Requires tokenization + require_tokenization = false }, config.plugins.linewrapping) local LineWrapping = {} +-- Optimzation function. The tokenizer is relatively slow (at present), and +-- so if we don't need to run it, should be run sparingly. +local function spew_tokens(doc, line) if line < math.huge then return math.huge, "normal", doc.lines[line] end end +local function get_tokens(doc, line) + if config.plugins.linewrapping.require_tokenization then + return doc.highlighter:each_token(line) + end + return spew_tokens, doc, line +end + -- Computes the breaks for a given line, width and mode. Returns a list of columns -- at which the line should be broken. function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) local xoffset, last_i, i, last_space, last_width, begin_width = 0, 1, 1, nil, 0, 0 local splits = { 1 } - for idx, type, text in doc.highlighter:each_token(line) do + for idx, type, text in get_tokens(doc, line) do local font = style.syntax_fonts[type] or default_font - if idx == 1 and config.plugins.linewrapping.indent then + if idx == 1 or idx == math.huge and config.plugins.linewrapping.indent then local _, indent_end = text:find("^%s+") if indent_end then begin_width = font:get_width(text:sub(1, indent_end)) end end @@ -164,7 +177,8 @@ end function LineWrapping.update_docview_breaks(docview) local x,y,w,h = docview:get_scrollbar_rect() - local width = config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w) + local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview)) + or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w) if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then docview.scroll.to.x = 0 LineWrapping.reconstruct_breaks(docview, docview:get_font(), width) @@ -235,7 +249,7 @@ local function get_line_col_from_index_and_x(docview, idx, x) local xoffset, last_i, i = (col ~= 1 and docview.wrapped_line_offsets[line] or 0), col, 1 if x < xoffset then return line, col end local default_font = docview:get_font() - for _, type, text in docview.doc.highlighter:each_token(line) do + for _, type, text in doc.highlighter:each_token(line) do local font, w = style.syntax_fonts[type] or default_font, 0 for char in common.utf8_chars(text) do if i >= col then From 5453a27f910fdf69ccd0f9cb0cbce130615cbccd Mon Sep 17 00:00:00 2001 From: Philip Bergwerf Date: Mon, 18 Apr 2022 20:22:25 +0200 Subject: [PATCH 224/409] Add triple single quotes multiline strings to `language_python` --- data/plugins/language_python.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/plugins/language_python.lua b/data/plugins/language_python.lua index 8bc6fbd4..85a7858b 100644 --- a/data/plugins/language_python.lua +++ b/data/plugins/language_python.lua @@ -9,6 +9,7 @@ syntax.add { patterns = { { pattern = { "#", "\n" }, type = "comment" }, { pattern = { '[ruU]?"""', '"""'; '\\' }, type = "string" }, + { pattern = { "[ruU]?'''", "'''", '\\' }, type = "string" }, { pattern = { '[ruU]?"', '"', '\\' }, type = "string" }, { pattern = { "[ruU]?'", "'", '\\' }, type = "string" }, { pattern = "0x[%da-fA-F]+", type = "number" }, From 9a5f8e72d0f34d6dca83b4fc41612fc97df6c1c9 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 18 Apr 2022 21:14:50 +0200 Subject: [PATCH 225/409] Add `DATADIR` and `USERDIR` explanation in created user module --- data/core/init.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/core/init.lua b/data/core/init.lua index 75534073..5eab80e6 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -469,6 +469,10 @@ local style = require "core.style" -- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 14 * SCALE) -- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE) -- +-- DATADIR is the location of the installed Lite XL Lua code, default color +-- schemes and fonts. +-- USERDIR is the location of the Lite XL configuration directory. +-- -- font names used by lite: -- style.font : user interface -- style.big_font : big text in welcome screen From 915625b74f0b241229187ab05d716ed592cf2873 Mon Sep 17 00:00:00 2001 From: Jan Date: Wed, 20 Apr 2022 22:30:41 +0200 Subject: [PATCH 226/409] Update SDL to 2.0.20 (#884) --- meson.build | 32 +++++++++++++++++++++++++++++++- subprojects/sdl2.wrap | 14 +++++++------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/meson.build b/meson.build index 9dbe9bec..0e5c62ac 100644 --- a/meson.build +++ b/meson.build @@ -99,8 +99,38 @@ if not get_option('source-only') default_options: default_fallback_options + ['default_library=static', 'zlib=disabled', 'bzip2=disabled', 'png=disabled', 'harfbuzz=disabled', 'brotli=disabled'] ) + + sdl_options = ['default_library=static'] + + # we explicitly need these + sdl_options += 'use_loadso=enabled' + sdl_options += 'prefer_dlopen=true' + sdl_options += 'use_video=enabled' + sdl_options += 'use_atomic=enabled' + sdl_options += 'use_threads=enabled' + # investigate if this is truly needed + # Do not remove before https://github.com/libsdl-org/SDL/issues/5413 is released + sdl_options += 'use_events=enabled' + + # we leave this up to what the host system has + sdl_options += 'use_video_x11=auto' + sdl_options += 'use_video_wayland=auto' + + # we don't need these + sdl_options += 'use_timers=disabled' + sdl_options += 'use_sensor=disabled' + sdl_options += 'use_haptic=disabled' + sdl_options += 'use_audio=disabled' + sdl_options += 'use_cpuinfo=disabled' + sdl_options += 'use_joystick=disabled' + sdl_options += 'use_video_opengl=disabled' + sdl_options += 'use_video_openglesv2=disabled' + sdl_options += 'use_video_vulkan=disabled' + sdl_options += 'use_video_offscreen=disabled' + sdl_options += 'use_power=disabled' + sdl_dep = dependency('sdl2', fallback: ['sdl2', 'sdl2_dep'], - default_options: default_fallback_options + ['default_library=static'] + default_options: default_fallback_options + sdl_options ) lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl] diff --git a/subprojects/sdl2.wrap b/subprojects/sdl2.wrap index 9ef9e1d3..b1affeb5 100644 --- a/subprojects/sdl2.wrap +++ b/subprojects/sdl2.wrap @@ -1,11 +1,11 @@ [wrap-file] -directory = SDL2-2.0.18 -source_url = https://www.libsdl.org/release/SDL2-2.0.18.tar.gz -source_filename = SDL2-2.0.18.tar.gz -source_hash = 94d40cd73dbfa10bb6eadfbc28f355992bb2d6ef6761ad9d4074eff95ee5711c -patch_filename = sdl2_2.0.18-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.18-2/get_patch -patch_hash = cd77f33395d3d019bb89217b9da41fc640ed8c78cbbbebc5c662155a25e2820e +directory = SDL2-2.0.20 +source_url = https://libsdl.org/release/SDL2-2.0.20.tar.gz +source_filename = SDL2-2.0.20.tar.gz +source_hash = c56aba1d7b5b0e7e999e4a7698c70b63a3394ff9704b5f6e1c57e0c16f04dd06 +patch_filename = sdl2_2.0.20-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.20-3/get_patch +patch_hash = ade644ba46cefa4f1f9e57aa23bacc5dabf762d1f90d8416a1e1e4b0b7a188c4 [provide] sdl2 = sdl2_dep From bbac7e479cb6ff46bac5deb72dcb2d54581df37e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 20 Apr 2022 23:00:48 +0200 Subject: [PATCH 227/409] Set the correct working directory for the AppImage version (#937) --- data/core/start.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/core/start.lua b/data/core/start.lua index b3bcc18e..b6f1ee6a 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -34,3 +34,12 @@ table.pack = table.pack or pack or function(...) return {...} end table.unpack = table.unpack or unpack bit32 = bit32 or require "core.bit" + +-- Because AppImages change the working directory before running the executable, +-- we need to change it back to the original one. +-- https://github.com/AppImage/AppImageKit/issues/172 +-- https://github.com/AppImage/AppImageKit/pull/191 +local appimage_owd = os.getenv("OWD") +if os.getenv("APPIMAGE") and appimage_owd then + system.chdir(appimage_owd) +end From 1439b59d653f02e1c768d74d952aa211b9255f4f Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 20 Apr 2022 23:13:42 +0200 Subject: [PATCH 228/409] Improve performance of `ren_draw_rect` (#935) We color a 1x1 `SDL_Surface` with the desired color. This surface is then stretched over the area we need to cover using `SDL_BlitScaled`. This way we avoid having to do the blending ourselves. --- src/renderer.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/renderer.c b/src/renderer.c index bfee2f4a..f5f08045 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -18,6 +18,9 @@ static RenWindow window_renderer = {0}; static FT_Library library; +// draw_rect_surface is used as a 1x1 surface to simplify ren_draw_rect with blending +static SDL_Surface *draw_rect_surface; + static void* check_alloc(void *ptr) { if (!ptr) { fprintf(stderr, "Fatal error: memory allocation failed\n"); @@ -370,31 +373,21 @@ void ren_draw_rect(RenRect rect, RenColor color) { y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2; SDL_Surface *surface = renwin_get_surface(&window_renderer); - uint32_t *d = surface->pixels; - d += x1 + y1 * surface->w; - int dr = surface->w - (x2 - x1); + SDL_Rect dest_rect = { x1, y1, x2 - x1, y2 - y1 }; if (color.a == 0xff) { uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b); - SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 }; - SDL_FillRect(surface, &rect, translated); + SDL_FillRect(surface, &dest_rect, translated); } else { - RenColor current_color; - RenColor blended_color; - for (int j = y1; j < y2; j++) { - for (int i = x1; i < x2; i++, d++) - { - SDL_GetRGB(*d, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b); - blended_color = blend_pixel(current_color, color); - *d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b); - } - d += dr; - } + uint32_t *pixel = (uint32_t *)draw_rect_surface->pixels; + *pixel = SDL_MapRGBA(draw_rect_surface->format, color.r, color.g, color.b, color.a); + SDL_BlitScaled(draw_rect_surface, NULL, surface, &dest_rect); } } /*************** Window Management ****************/ void ren_free_window_resources() { renwin_free(&window_renderer); + SDL_FreeSurface(draw_rect_surface); } void ren_init(SDL_Window *win) { @@ -407,6 +400,8 @@ void ren_init(SDL_Window *win) { window_renderer.window = win; renwin_init_surface(&window_renderer); renwin_clip_to_surface(&window_renderer); + draw_rect_surface = SDL_CreateRGBSurface(0, 1, 1, 32, + 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); } From b9957138acec8ff73c79988970a1882c945abd3f Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 21 Apr 2022 00:41:15 +0200 Subject: [PATCH 229/409] Add `launchable` tag to AppStream xml --- resources/linux/org.lite_xl.lite_xl.appdata.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/linux/org.lite_xl.lite_xl.appdata.xml b/resources/linux/org.lite_xl.lite_xl.appdata.xml index 49c23430..1932af90 100644 --- a/resources/linux/org.lite_xl.lite_xl.appdata.xml +++ b/resources/linux/org.lite_xl.lite_xl.appdata.xml @@ -6,6 +6,7 @@ Lite XL A lightweight text editor written in Lua + org.lite_xl.lite_xl.desktop

From 97174706fe82b5fdef4f0f7661c4e31aa0e060d4 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 24 Apr 2022 13:40:58 -0400 Subject: [PATCH 230/409] Asynchronous Reads for Dirmonitor (#930) Change dirmonitor reads to be synchronous, in a secondary thread. --- data/core/dirwatch.lua | 4 ++ data/core/init.lua | 14 +---- src/api/dirmonitor.c | 111 +++++++++++++++++++++++------------ src/api/dirmonitor/dummy.c | 26 ++------ src/api/dirmonitor/inotify.c | 69 ++++++++++------------ src/api/dirmonitor/kqueue.c | 48 +++++++-------- src/api/dirmonitor/win32.c | 111 +++++++++++++++-------------------- src/meson.build | 2 - 8 files changed, 190 insertions(+), 195 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 53f600c5..314a33c0 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -85,7 +85,9 @@ end -- designed to be run inside a coroutine. function dirwatch:check(change_callback, scan_time, wait_time) + local had_change = false self.monitor:check(function(id) + had_change = true if PLATFORM == "Windows" then change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) elseif self.reverse_watched[id] then @@ -98,6 +100,7 @@ function dirwatch:check(change_callback, scan_time, wait_time) local new_modified = system.get_file_info(directory).modified if old_modified < new_modified then change_callback(directory) + had_change = true self.scanned[directory] = new_modified end end @@ -106,6 +109,7 @@ function dirwatch:check(change_callback, scan_time, wait_time) start_time = system.get_time() end end + return had_change end diff --git a/data/core/init.lua b/data/core/init.lua index 75534073..62f311c2 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -269,7 +269,7 @@ function core.add_project_directory(path) -- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds. topdir.watch_thread = core.add_thread(function() while true do - topdir.watch:check(function(target) + local changed = topdir.watch:check(function(target) if target == topdir.name then return refresh_directory(topdir) end local dirpath = target:sub(#topdir.name + 2) local abs_dirpath = topdir.name .. PATHSEP .. dirpath @@ -280,7 +280,7 @@ function core.add_project_directory(path) end return refresh_directory(topdir, dirpath) end, 0.01, 0.01) - coroutine.yield(0.05) + coroutine.yield(changed and 0.05 or 0) end end) @@ -1116,14 +1116,6 @@ function core.try(fn, ...) return false, err end -local scheduled_rescan = {} - -function core.has_pending_rescan() - for _ in pairs(scheduled_rescan) do - return true - end -end - function core.on_event(type, ...) local did_keymap = false if type == "textinput" then @@ -1270,8 +1262,8 @@ function core.run() local idle_iterations = 0 while true do core.frame_start = system.get_time() + local need_more_work = run_threads() local did_redraw = core.step() - local need_more_work = run_threads() or core.has_pending_rescan() if core.restart_request or core.quit_request then break end if not did_redraw and not need_more_work then idle_iterations = idle_iterations + 1 diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 6d14fbe1..56360d1f 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -1,83 +1,115 @@ #include "api.h" +#include #include -#ifdef _WIN32 - #include -#elif __linux__ - #include - #include -#else - #include -#endif #include #include -#include -#include #include #include -#ifndef DIRMONITOR_BACKEND -#error No dirmonitor backend defined -#endif +static unsigned int DIR_EVENT_TYPE = 0; -#define GLUE_HELPER(x, y) x##y -#define GLUE(x, y) GLUE_HELPER(x, y) +struct dirmonitor { + SDL_Thread* thread; + SDL_mutex* mutex; + char buffer[64512]; + volatile int length; + struct dirmonitor_internal* internal; +}; -#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND) -#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND) -#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND) -#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND) -#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND) -struct dirmonitor {}; // dirmonitor struct is defined in each backend +struct dirmonitor_internal* init_dirmonitor(); +void deinit_dirmonitor(struct dirmonitor_internal*); +int get_changes_dirmonitor(struct dirmonitor_internal*, char*, int); +int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*); +int add_dirmonitor(struct dirmonitor_internal*, const char*); +void remove_dirmonitor(struct dirmonitor_internal*, int); -// define functions so we know their signature -struct dirmonitor* init_dirmonitor(); -void deinit_dirmonitor(struct dirmonitor*); -int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*); -int add_dirmonitor(struct dirmonitor*, const char*); -void remove_dirmonitor(struct dirmonitor*, int); static int f_check_dir_callback(int watch_id, const char* path, void* L) { lua_pushvalue(L, -1); - #ifdef DIRMONITOR_WIN32 - char buffer[PATH_MAX*4]; - int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); - lua_pushlstring(L, buffer, count); - #else + if (path) + lua_pushlstring(L, path, watch_id); + else lua_pushnumber(L, watch_id); - #endif lua_call(L, 1, 1); int result = lua_toboolean(L, -1); lua_pop(L, 1); return !result; } + +static int dirmonitor_check_thread(void* data) { + struct dirmonitor* monitor = data; + while (monitor->length >= 0) { + if (monitor->length == 0) { + int result = get_changes_dirmonitor(monitor->internal, monitor->buffer, sizeof(monitor->buffer)); + SDL_LockMutex(monitor->mutex); + if (monitor->length == 0) + monitor->length = result; + SDL_UnlockMutex(monitor->mutex); + } + SDL_Delay(1); + SDL_Event event = { .type = DIR_EVENT_TYPE }; + SDL_PushEvent(&event); + } + return 0; +} + + static int f_dirmonitor_new(lua_State* L) { - struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**)); - *monitor = init_dirmonitor(); + if (DIR_EVENT_TYPE == 0) + DIR_EVENT_TYPE = SDL_RegisterEvents(1); + struct dirmonitor* monitor = lua_newuserdata(L, sizeof(struct dirmonitor)); luaL_setmetatable(L, API_TYPE_DIRMONITOR); + memset(monitor, 0, sizeof(struct dirmonitor)); + monitor->internal = init_dirmonitor(); + if (monitor->internal) + monitor->thread = SDL_CreateThread(dirmonitor_check_thread, "dirmonitor_check_thread", monitor); return 1; } + static int f_dirmonitor_gc(lua_State* L) { - deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))); + struct dirmonitor* monitor = luaL_checkudata(L, 1, API_TYPE_DIRMONITOR); + SDL_LockMutex(monitor->mutex); + monitor->length = -1; + deinit_dirmonitor(monitor->internal); + SDL_UnlockMutex(monitor->mutex); + SDL_WaitThread(monitor->thread, NULL); + free(monitor->internal); + SDL_DestroyMutex(monitor->mutex); return 0; } + static int f_dirmonitor_watch(lua_State *L) { - lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2))); + lua_pushnumber(L, add_dirmonitor(((struct dirmonitor*)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))->internal, luaL_checkstring(L, 2))); return 1; } + static int f_dirmonitor_unwatch(lua_State *L) { - remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), lua_tonumber(L, 2)); + remove_dirmonitor(((struct dirmonitor*)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))->internal, lua_tonumber(L, 2)); return 0; } + static int f_dirmonitor_check(lua_State* L) { - lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L)); + struct dirmonitor* monitor = luaL_checkudata(L, 1, API_TYPE_DIRMONITOR); + SDL_LockMutex(monitor->mutex); + if (monitor->length < 0) + lua_pushnil(L); + else if (monitor->length > 0) { + if (translate_changes_dirmonitor(monitor->internal, monitor->buffer, monitor->length, f_check_dir_callback, L) == 0) + monitor->length = 0; + lua_pushboolean(L, 1); + } else + lua_pushboolean(L, 0); + SDL_UnlockMutex(monitor->mutex); return 1; } + + static const luaL_Reg dirmonitor_lib[] = { { "new", f_dirmonitor_new }, { "__gc", f_dirmonitor_gc }, @@ -87,6 +119,7 @@ static const luaL_Reg dirmonitor_lib[] = { {NULL, NULL} }; + int luaopen_dirmonitor(lua_State* L) { luaL_newmetatable(L, API_TYPE_DIRMONITOR); luaL_setfuncs(L, dirmonitor_lib, 0); diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c index cc0b41ee..9c19d27d 100644 --- a/src/api/dirmonitor/dummy.c +++ b/src/api/dirmonitor/dummy.c @@ -1,22 +1,8 @@ #include -struct dirmonitor { -}; - -struct dirmonitor* init_dirmonitor_dummy() { - return NULL; -} - -void deinit_dirmonitor_dummy(struct dirmonitor* monitor) { -} - -int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - return -1; -} - -int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) { - return -1; -} - -void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) { -} \ No newline at end of file +struct dirmonitor_internal* init_dirmonitor() { return NULL; } +void deinit_dirmonitor(struct dirmonitor_internal*) { } +int get_changes_dirmonitor(struct dirmonitor_internal*, char*, size_t) { return -1; } +int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*) { return -1; } +int add_dirmonitor(struct dirmonitor_internal*, const char*) { return -1; } +void remove_dirmonitor(struct dirmonitor_internal*, int) { } diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index c284e501..ca756d77 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -1,58 +1,53 @@ #include -#include -#include +#include #include -#include -#include -#include +#include -struct dirmonitor { + +struct dirmonitor_internal { int fd; + // a pipe is used to wake the thread in case of exit + int sig[2]; }; -struct dirmonitor* init_dirmonitor_inotify() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); +struct dirmonitor_internal* init_dirmonitor() { + struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = inotify_init(); - fcntl(monitor->fd, F_SETFL, O_NONBLOCK); - - + pipe(monitor->sig); return monitor; } -void deinit_dirmonitor_inotify(struct dirmonitor* monitor) { + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close(monitor->sig[0]); + close(monitor->sig[1]); close(monitor->fd); - free(monitor); } -int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - char buf[PATH_MAX + sizeof(struct inotify_event)]; - ssize_t offset = 0; - while (1) { - ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); - - if (len == -1 && errno != EAGAIN) { - return errno; - } - - if (len <= 0) { - return 0; - } - - while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { - change_callback(((const struct inotify_event *)buf)->wd, NULL, data); - len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; - memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); - offset = len; - } - } +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int length) { + fd_set set; + FD_ZERO(&set); + FD_SET(monitor->fd, &set); + FD_SET(monitor->sig[0], &set); + select(FD_SETSIZE, &set, NULL, NULL, NULL); + return read(monitor->fd, buffer, length); } -int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) { + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int length, int (*change_callback)(int, const char*, void*), void* data) { + for (struct inotify_event* info = (struct inotify_event*)buffer; (char*)info < buffer + length; info = (struct inotify_event*)((char*)info + sizeof(struct inotify_event))) + change_callback(info->wd, NULL, data); + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); } -void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) { + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { inotify_rm_watch(monitor->fd, fd); -} \ No newline at end of file +} diff --git a/src/api/dirmonitor/kqueue.c b/src/api/dirmonitor/kqueue.c index e7f041bd..7c6e89d8 100644 --- a/src/api/dirmonitor/kqueue.c +++ b/src/api/dirmonitor/kqueue.c @@ -5,40 +5,41 @@ #include #include -struct dirmonitor { +struct dirmonitor_internal { int fd; }; -struct dirmonitor* init_dirmonitor_kqueue() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + +struct dirmonitor_internal* init_dirmonitor() { + struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = kqueue(); return monitor; } -void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) { + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { close(monitor->fd); - free(monitor); } -int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - struct kevent event; - while (1) { - struct timespec tm = {0}; - int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); - if (nev == -1) { - return errno; - } - - if (nev <= 0) { - return 0; - } - - change_callback(event.ident, NULL, data); - } +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) { + int nev = kevent(monitor->fd, NULL, 0, (struct kevent*)buffer, buffer_size / sizeof(kevent), NULL); + if (nev == -1) + return -1; + if (nev <= 0) + return 0; + return nev * sizeof(struct kevent); } -int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) { + for (struct kevent* info = (struct kevent*)buffer; (char*)info < buffer + buffer_size; info = (struct kevent*)(((char*)info) + sizeof(kevent))) + change_callback(info->ident, NULL, data); + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { int fd = open(path, O_RDONLY); struct kevent change; @@ -48,6 +49,7 @@ int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { return fd; } -void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) { + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close(fd); -} \ No newline at end of file +} diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c index d5945856..34caa6de 100644 --- a/src/api/dirmonitor/win32.c +++ b/src/api/dirmonitor/win32.c @@ -1,77 +1,62 @@ -#include #include -struct dirmonitor { + +struct dirmonitor_internal { HANDLE handle; - char buffer[64512]; - OVERLAPPED overlapped; - bool running; }; -struct dirmonitor* init_dirmonitor_win32() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); - return monitor; -} - -static void close_monitor_handle(struct dirmonitor* monitor) { - if (monitor->handle) { - if (monitor->running) { - BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); - DWORD error = GetLastError(); - if (result == TRUE || error != ERROR_NOT_FOUND) { - DWORD bytes_transferred; - GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); - } - monitor->running = false; - } - CloseHandle(monitor->handle); - } - monitor->handle = NULL; -} - -void deinit_dirmonitor_win32(struct dirmonitor* monitor) { - close_monitor_handle(monitor); - free(monitor); -} - -int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - if (!monitor->running) { - if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) { - return GetLastError(); - } - monitor->running = true; +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) { + HANDLE handle = monitor->handle; + if (handle && handle != INVALID_HANDLE_VALUE) { + DWORD bytes_transferred; + if (ReadDirectoryChangesW(handle, buffer, buffer_size, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, &bytes_transferred, NULL, NULL) == 0) + return 0; + return bytes_transferred; } - - DWORD bytes_transferred; - - if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { - int error = GetLastError(); - return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; - } - - monitor->running = false; - - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { - change_callback(info->FileNameLength / sizeof(WCHAR), (char*)info->FileName, data); - if (!info->NextEntryOffset) - break; - } - - monitor->running = false; return 0; } -int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { - close_monitor_handle(monitor); - monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { - return 1; - } - monitor->handle = NULL; - return -1; + +struct dirmonitor* init_dirmonitor() { + return calloc(sizeof(struct dirmonitor_internal), 1); } -void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { + +static void close_monitor_handle(struct dirmonitor_internal* monitor) { + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { + HANDLE handle = monitor->handle; + monitor->handle = NULL; + CancelIoEx(handle, NULL); + CloseHandle(handle); + } +} + + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close_monitor_handle(monitor); +} + + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) { + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)buffer; (char*)info < buffer + buffer_size; info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { + char transform_buffer[PATH_MAX*4]; + int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength, transform_buffer, PATH_MAX*4 - 1, NULL, NULL); + change_callback(count, buffer, data); + if (!info->NextEntryOffset) + break; + } + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + return !monitor->handle || monitor->handle == INVALID_HANDLE_VALUE ? -1 : 1; +} + + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close_monitor_handle(monitor); } diff --git a/src/meson.build b/src/meson.build index 5ad95bdd..b73e4513 100644 --- a/src/meson.build +++ b/src/meson.build @@ -41,8 +41,6 @@ lite_sources += [ 'api/dirmonitor.c', 'api/dirmonitor/' + dirmonitor_backend + '.c', ] -lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend -lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper() lite_rc = [] From d8f202e251c1ab6ec9b9c0e2a2b0b5cdc0ce070e Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 24 Apr 2022 21:13:18 -0400 Subject: [PATCH 231/409] Make sure pipes are closed on exec. --- src/api/dirmonitor/inotify.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index ca756d77..260b074b 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -2,6 +2,7 @@ #include #include #include +#include struct dirmonitor_internal { @@ -15,6 +16,8 @@ struct dirmonitor_internal* init_dirmonitor() { struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = inotify_init(); pipe(monitor->sig); + fcntl(monitor->sig[0], F_SETFD, FD_CLOEXEC); + fcntl(monitor->sig[1], F_SETFD, FD_CLOEXEC); return monitor; } From f42dbb0060640892d79ac98944c74cad1402c3ca Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 02:35:35 +0200 Subject: [PATCH 232/409] Add animation categories to enable finer transitions control (#941) * Allow finer control over transitions * Add categories to transitions --- data/core/commandview.lua | 10 +++++----- data/core/config.lua | 10 ++++++++++ data/core/contextmenu.lua | 24 +++--------------------- data/core/logview.lua | 4 ++-- data/core/nagview.lua | 6 +++--- data/core/node.lua | 4 ++-- data/core/rootview.lua | 10 +++++----- data/core/statusview.lua | 4 ++-- data/core/view.lua | 16 ++++++++-------- data/plugins/treeview.lua | 4 ++-- 10 files changed, 42 insertions(+), 50 deletions(-) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index b91f1394..2b91e30e 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -202,31 +202,31 @@ function CommandView:update() end -- update gutter text color brightness - self:move_towards("gutter_text_brightness", 0, 0.1) + self:move_towards("gutter_text_brightness", 0, 0.1, "commandview") -- update gutter width local dest = self:get_font():get_width(self.label) + style.padding.x if self.size.y <= 0 then self.gutter_width = dest else - self:move_towards("gutter_width", dest) + self:move_towards("gutter_width", dest, nil, "commandview") end -- update suggestions box height local lh = self:get_suggestion_line_height() local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0 - self:move_towards("suggestions_height", dest) + self:move_towards("suggestions_height", dest, nil, "commandview") -- update suggestion cursor offset local dest = math.min(self.suggestion_idx, max_suggestions) * self:get_suggestion_line_height() - self:move_towards("selection_offset", dest) + self:move_towards("selection_offset", dest, nil, "commandview") -- update size based on whether this is the active_view local dest = 0 if self == core.active_view then dest = style.font:get_height() + style.padding.y * 2 end - self:move_towards(self.size, "y", dest) + self:move_towards(self.size, "y", dest, nil, "commandview") end diff --git a/data/core/config.lua b/data/core/config.lua index 0f3b8ad3..417291fe 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -21,6 +21,16 @@ config.tab_type = "soft" config.line_limit = 80 config.max_project_files = 2000 config.transitions = true +config.disabled_transitions = { + scroll = false, + commandview = false, + contextmenu = false, + logview = false, + nagbar = false, + tabs = false, + tab_drag = false, + statusbar = false, +} config.animation_rate = 1.0 config.blink_period = 0.8 config.disable_blink = false diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index 4cb3e492..1c9839e7 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -5,6 +5,7 @@ local config = require "core.config" local keymap = require "core.keymap" local style = require "core.style" local Object = require "core.object" +local View = require "core.view" local border_width = 1 local divider_width = 1 @@ -187,30 +188,11 @@ function ContextMenu:on_mouse_pressed(button, px, py, clicks) return caught end --- copied from core.docview -function ContextMenu:move_towards(t, k, dest, rate) - if type(t) ~= "table" then - return self:move_towards(self, t, k, dest, rate) - end - local val = t[k] - if not config.transitions or math.abs(val - dest) < 0.5 then - t[k] = dest - else - rate = rate or 0.5 - if config.fps ~= 60 or config.animation_rate ~= 1 then - local dt = 60 / config.fps - rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt) - end - t[k] = common.lerp(val, dest, rate) - end - if val ~= dest then - core.redraw = true - end -end +ContextMenu.move_towards = View.move_towards function ContextMenu:update() if self.show_context_menu then - self:move_towards("height", self.items.height) + self:move_towards("height", self.items.height, nil, "contextmenu") end end diff --git a/data/core/logview.lua b/data/core/logview.lua index aa1ad84a..d079a5de 100644 --- a/data/core/logview.lua +++ b/data/core/logview.lua @@ -118,13 +118,13 @@ function LogView:update() local expanding = self.expanding[1] if expanding then - self:move_towards(expanding, "current", expanding.target) + self:move_towards(expanding, "current", expanding.target, nil, "logview") if expanding.current == expanding.target then table.remove(self.expanding, 1) end end - self:move_towards("yoffset", 0) + self:move_towards("yoffset", 0, nil, "logview") LogView.super.update(self) end diff --git a/data/core/nagview.lua b/data/core/nagview.lua index 0c9e575e..c34d2a64 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -170,10 +170,10 @@ function NagView:update() NagView.super.update(self) if self.visible and core.active_view == self and self.title then - self:move_towards(self, "show_height", self:get_target_height()) - self:move_towards(self, "underline_progress", 1) + self:move_towards(self, "show_height", self:get_target_height(), nil, "nagbar") + self:move_towards(self, "underline_progress", 1, nil, "nagbar") else - self:move_towards(self, "show_height", 0) + self:move_towards(self, "show_height", 0, nil, "nagbar") if self.show_height <= 0 then self.title = nil self.message = nil diff --git a/data/core/node.lua b/data/core/node.lua index 52fc3d6d..56e0889a 100644 --- a/data/core/node.lua +++ b/data/core/node.lua @@ -468,8 +468,8 @@ function Node:update() end self:tab_hovered_update(self.hovered.x, self.hovered.y) local tab_width = self:target_tab_width() - self:move_towards("tab_shift", tab_width * (self.tab_offset - 1)) - self:move_towards("tab_width", tab_width) + self:move_towards("tab_shift", tab_width * (self.tab_offset - 1), nil, "tabs") + self:move_towards("tab_width", tab_width, nil, "tabs") else self.a:update() self.b:update() diff --git a/data/core/rootview.lua b/data/core/rootview.lua index 8fa57cf5..b6cd95bb 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -297,12 +297,12 @@ end function RootView:interpolate_drag_overlay(overlay) - self:move_towards(overlay, "x", overlay.to.x) - self:move_towards(overlay, "y", overlay.to.y) - self:move_towards(overlay, "w", overlay.to.w) - self:move_towards(overlay, "h", overlay.to.h) + self:move_towards(overlay, "x", overlay.to.x, nil, "tab_drag") + self:move_towards(overlay, "y", overlay.to.y, nil, "tab_drag") + self:move_towards(overlay, "w", overlay.to.w, nil, "tab_drag") + self:move_towards(overlay, "h", overlay.to.h, nil, "tab_drag") - self:move_towards(overlay, "opacity", overlay.visible and 100 or 0) + self:move_towards(overlay, "opacity", overlay.visible and 100 or 0, nil, "tab_drag") overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100 end diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b322a688..b1a45385 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -1042,14 +1042,14 @@ function StatusView:update() if not self.visible and self.size.y <= 0 then return elseif not self.visible and self.size.y > 0 then - self:move_towards(self.size, "y", 0) + self:move_towards(self.size, "y", 0, nil, "statusbar") return end local height = style.font:get_height() + style.padding.y * 2; if self.size.y + 1 < height then - self:move_towards(self.size, "y", height) + self:move_towards(self.size, "y", height, nil, "statusbar") else self.size.y = height end diff --git a/data/core/view.lua b/data/core/view.lua index f1c9a949..cdf07f4c 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -27,13 +27,13 @@ function View:new() self.scrollbar_alpha = { value = 0, to = 0 } end -function View:move_towards(t, k, dest, rate) +function View:move_towards(t, k, dest, rate, name) if type(t) ~= "table" then - return self:move_towards(self, t, k, dest, rate) + return self:move_towards(self, t, k, dest, rate, name) end local val = t[k] local diff = math.abs(val - dest) - if not config.transitions or diff < 0.5 then + if not config.transitions or diff < 0.5 or config.disabled_transitions[name] then t[k] = dest else rate = rate or 0.5 @@ -176,28 +176,28 @@ end function View:update_scrollbar() local x, y, w, h = self:get_scrollbar_rect() self.scrollbar.w.to.thumb = w - self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3) + self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll") self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb self.scrollbar.y.thumb = y self.scrollbar.h.thumb = h local x, y, w, h = self:get_scrollbar_track_rect() self.scrollbar.w.to.track = w - self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3) + self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll") self.scrollbar.x.track = x + w - self.scrollbar.w.track self.scrollbar.y.track = y self.scrollbar.h.track = h -- we use 100 for a smoother transition self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0 - self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3) + self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3, "scroll") end function View:update() self:clamp_scroll_position() - self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3) - self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3) + self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll") + self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll") self:update_scrollbar() end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 36235e5c..2357fb7d 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -284,14 +284,14 @@ function TreeView:update() self.size.x = dest self.init_size = false else - self:move_towards(self.size, "x", dest) + self:move_towards(self.size, "x", dest, nil, "treeview") end if not self.visible then return end local duration = system.get_time() - self.tooltip.begin if self.hovered_item and self.tooltip.x and duration > tooltip_delay then - self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate) + self:move_towards(self.tooltip, "alpha", tooltip_alpha, tooltip_alpha_rate, "treeview") else self.tooltip.alpha = 0 end From 7dd83bb73740bdbc8f3aa81c4fa4f8bfc56b0320 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 04:33:46 +0200 Subject: [PATCH 233/409] Fix `ren_font_group_get_tab_size` returning unexpected values We were casting the `xadvance` to an int, so in some cases the resulting tab size was wrong. --- src/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer.c b/src/renderer.c index f5f08045..a2c2ddb9 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -240,7 +240,7 @@ void ren_font_group_set_tab_size(RenFont **fonts, int n) { } int ren_font_group_get_tab_size(RenFont **fonts) { - int advance = font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance; + float advance = font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance; if (fonts[0]->space_advance) { advance /= fonts[0]->space_advance; } From e572c58f247a3062e6b93ab35957654b37a3daee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jefferson=20Gonz=C3=A1lez?= Date: Tue, 26 Apr 2022 09:42:02 -0400 Subject: [PATCH 234/409] Add utf8 support to tokenizer (#945) * add utf8 support to tokenizer * wrap utf8 functions in string table using a 'u' prefix * document new utf8 functions --- data/core/start.lua | 2 + data/core/tokenizer.lua | 55 +- data/core/utf8string.lua | 30 + docs/api/string.lua | 165 ++ docs/api/utf8.lua | 187 ++ src/api/api.c | 2 + src/api/utf8.c | 1305 ++++++++++++++ src/meson.build | 1 + src/unidata.h | 3710 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 5431 insertions(+), 26 deletions(-) create mode 100644 data/core/utf8string.lua create mode 100644 docs/api/string.lua create mode 100644 docs/api/utf8.lua create mode 100644 src/api/utf8.c create mode 100644 src/unidata.h diff --git a/data/core/start.lua b/data/core/start.lua index b6f1ee6a..a08ddc16 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -35,6 +35,8 @@ table.unpack = table.unpack or unpack bit32 = bit32 or require "core.bit" +require "core.utf8string" + -- Because AppImages change the working directory before running the executable, -- we need to change it back to the original one. -- https://github.com/AppImage/AppImageKit/issues/172 diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index bb3faa03..e0c630a4 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -6,7 +6,7 @@ local tokenizer = {} local function push_token(t, type, text) local prev_type = t[#t-1] local prev_text = t[#t] - if prev_type and (prev_type == type or prev_text:find("^%s*$")) then + if prev_type and (prev_type == type or prev_text:ufind("^%s*$")) then t[#t-1] = type t[#t] = prev_text .. text else @@ -38,12 +38,12 @@ local function push_tokens(t, syn, pattern, full_text, find_results) local fin = find_results[i + 1] - 1 local type = pattern.type[i - 2] -- ↑ (i - 2) to convert from [3; n] to [1; n] - local text = full_text:sub(start, fin) + local text = full_text:usub(start, fin) push_token(t, syn.symbols[text] or type, text) end else local start, fin = find_results[1], find_results[2] - local text = full_text:sub(start, fin) + local text = full_text:usub(start, fin) push_token(t, syn.symbols[text] or pattern.type, text) end end @@ -52,12 +52,12 @@ end -- State is a 32-bit number that is four separate bytes, illustrating how many -- differnet delimiters we have open, and which subsyntaxes we have active. -- At most, there are 3 subsyntaxes active at the same time. Beyond that, --- does not support further highlighting. +-- does not support further highlighting. -- You can think of it as a maximum 4 integer (0-255) stack. It always has -- 1 integer in it. Calling `push_subsyntax` increases the stack depth. Calling -- `pop_subsyntax` decreases it. The integers represent the index of a pattern --- that we're following in the syntax. The top of the stack can be any valid +-- that we're following in the syntax. The top of the stack can be any valid -- pattern index, any integer lower in the stack must represent a pattern that -- specifies a subsyntax. @@ -92,6 +92,9 @@ local function retrieve_syntax_state(incoming_syntax, state) return current_syntax, subsyntax_info, current_pattern_idx, current_level end +---@param incoming_syntax table +---@param text string +---@param state integer function tokenizer.tokenize(incoming_syntax, text, state) local res = {} local i = 1 @@ -102,22 +105,22 @@ function tokenizer.tokenize(incoming_syntax, text, state) state = state or 0 -- incoming_syntax : the parent syntax of the file. - -- state : a 32-bit number representing syntax state (see above) - + -- state : a 32-bit number representing syntax state (see above) + -- current_syntax : the syntax we're currently in. -- subsyntax_info : info about the delimiters of this subsyntax. -- current_pattern_idx: the index of the pattern we're on for this syntax. -- current_level : how many subsyntaxes deep we are. local current_syntax, subsyntax_info, current_pattern_idx, current_level = retrieve_syntax_state(incoming_syntax, state) - + -- Should be used to set the state variable. Don't modify it directly. local function set_subsyntax_pattern_idx(pattern_idx) current_pattern_idx = pattern_idx state = bit32.replace(state, pattern_idx, current_level*8, 8) end - - + + local function push_subsyntax(entering_syntax, pattern_idx) set_subsyntax_pattern_idx(pattern_idx) current_level = current_level + 1 @@ -126,15 +129,15 @@ function tokenizer.tokenize(incoming_syntax, text, state) entering_syntax.syntax or syntax.get(entering_syntax.syntax) current_pattern_idx = 0 end - + local function pop_subsyntax() set_subsyntax_pattern_idx(0) current_level = current_level - 1 set_subsyntax_pattern_idx(0) - current_syntax, subsyntax_info, current_pattern_idx, current_level = + current_syntax, subsyntax_info, current_pattern_idx, current_level = retrieve_syntax_state(incoming_syntax, state) end - + local function find_text(text, p, offset, at_start, close) local target, res = p.pattern or p.regex, { 1, offset - 1 } local p_idx = close and 2 or 1 @@ -143,14 +146,14 @@ function tokenizer.tokenize(incoming_syntax, text, state) if p.whole_line == nil then p.whole_line = { } end if p.whole_line[p_idx] == nil then -- Match patterns that start with '^' - p.whole_line[p_idx] = code:match("^%^") and true or false + p.whole_line[p_idx] = code:umatch("^%^") and true or false if p.whole_line[p_idx] then -- Remove '^' from the beginning of the pattern if type(target) == "table" then - target[p_idx] = code:sub(2) + target[p_idx] = code:usub(2) else - p.pattern = p.pattern and code:sub(2) - p.regex = p.regex and code:sub(2) + p.pattern = p.pattern and code:usub(2) + p.regex = p.regex and code:usub(2) end end end @@ -170,7 +173,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) while text:byte(next) and common.is_utf8_cont(text, next) do next = next + 1 end - res = p.pattern and { text:find((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } + res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } if res[1] and close and target[3] then local count = 0 @@ -185,7 +188,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) until not res[1] or not close or not target[3] return table.unpack(res) end - + while i <= #text do -- continue trying to match the end pattern of a pair if we have a state set if current_pattern_idx > 0 then @@ -198,12 +201,12 @@ function tokenizer.tokenize(incoming_syntax, text, state) -- precedence over ending the delimiter in the subsyntax. if subsyntax_info then local ss, se = find_text(text, subsyntax_info, i, false, true) - -- If we find that we end the subsyntax before the + -- If we find that we end the subsyntax before the -- delimiter, push the token, and signal we shouldn't -- treat the bit after as a token to be normally parsed -- (as it's the syntax delimiter). if ss and (s == nil or ss < s) then - push_token(res, p.type, text:sub(i, ss - 1)) + push_token(res, p.type, text:usub(i, ss - 1)) i = ss cont = false end @@ -212,11 +215,11 @@ function tokenizer.tokenize(incoming_syntax, text, state) -- continue on as normal. if cont then if s then - push_token(res, p.type, text:sub(i, e)) + push_token(res, p.type, text:usub(i, e)) set_subsyntax_pattern_idx(0) i = e + 1 else - push_token(res, p.type, text:sub(i)) + push_token(res, p.type, text:usub(i)) break end end @@ -227,7 +230,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) if subsyntax_info then local s, e = find_text(text, subsyntax_info, i, true, true) if s then - push_token(res, subsyntax_info.type, text:sub(i, e)) + push_token(res, subsyntax_info.type, text:usub(i, e)) -- On finding unescaped delimiter, pop it. pop_subsyntax() i = e + 1 @@ -246,7 +249,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) -- If we have a subsyntax, push that onto the subsyntax stack. if p.syntax then push_subsyntax(p, n) - else + else set_subsyntax_pattern_idx(n) end end @@ -264,7 +267,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do n = n + 1 end - push_token(res, "normal", text:sub(i, i + n)) + push_token(res, "normal", text:usub(i, i + n)) i = i + n + 1 end end diff --git a/data/core/utf8string.lua b/data/core/utf8string.lua new file mode 100644 index 00000000..1a2da19b --- /dev/null +++ b/data/core/utf8string.lua @@ -0,0 +1,30 @@ +-------------------------------------------------------------------------------- +-- inject utf8 functions to strings +-------------------------------------------------------------------------------- + +string.ubyte = utf8.byte +string.uchar = utf8.char +string.ufind = utf8.find +string.ugmatch = utf8.gmatch +string.ugsub = utf8.gsub +string.ulen = utf8.len +string.ulower = utf8.lower +string.umatch = utf8.match +string.ureverse = utf8.reverse +string.usub = utf8.sub +string.uupper = utf8.upper + +string.uescape = utf8.escape +string.ucharpos = utf8.charpos +string.unext = utf8.next +string.uinsert = utf8.insert +string.uremove = utf8.remove +string.uwidth = utf8.width +string.uwidthindex = utf8.widthindex +string.utitle = utf8.title +string.ufold = utf8.fold +string.uncasecmp = utf8.ncasecmp + +string.uoffset = utf8.offset +string.ucodepoint = utf8.codepoint +string.ucodes = utf8.codes diff --git a/docs/api/string.lua b/docs/api/string.lua new file mode 100644 index 00000000..0872b462 --- /dev/null +++ b/docs/api/string.lua @@ -0,0 +1,165 @@ +---@meta + +---UTF-8 equivalent of string.byte +---@param s string +---@param i? integer +---@param j? integer +---@return integer +---@return ... +function string.ubyte(s, i, j) end + +---UTF-8 equivalent of string.char +---@param byte integer +---@param ... integer +---@return string +---@return ... +function string.uchar(byte, ...) end + +---UTF-8 equivalent of string.find +---@param s string +---@param pattern string +---@param init? integer +---@param plain? boolean +---@return integer start +---@return integer end +---@return ... captured +function string.ufind(s, pattern, init, plain) end + +---UTF-8 equivalent of string.gmatch +---@param s string +---@param pattern string +---@param init? integer +---@return fun():string, ... +function string.ugmatch(s, pattern, init) end + +---UTF-8 equivalent of string.gsub +---@param s string +---@param pattern string +---@param repl string|table|function +---@param n integer +---@return string +---@return integer count +function string.ugsub(s, pattern, repl, n) end + +---UTF-8 equivalent of string.len +---@param s string +---@return integer +function string.ulen(s) end + +---UTF-8 equivalent of string.lower +---@param s string +---@return string +function string.ulower(s) end + +---UTF-8 equivalent of string.match +---@param s string +---@param pattern string +---@param init? integer +---@return string | number captured +function string.umatch(s, pattern, init) end + +---UTF-8 equivalent of string.reverse +---@param s string +---@return string +function string.ureverse(s) end + +---UTF-8 equivalent of string.sub +---@param s string +---@param i integer +---@param j? integer +---@return string +function string.usub(s, i, j) end + +---UTF-8 equivalent of string.upper +---@param s string +---@return string +function string.uupper(s) end + +---Equivalent to utf8.escape() +---@param s string +---@return string utf8_string +function string.uescape(s) end + + +---Equivalent to utf8.charpos() +---@param s string +---@param charpos? integer +---@param index? integer +---@return integer charpos +---@return integer codepoint +function string.ucharpos(s, charpos, index) end + +---Equivalent to utf8.next() +---@param s string +---@param charpos? integer +---@param index? integer +---@return integer charpos +---@return integer codepoint +function string.unext(s, charpos, index) end + +---Equivalent to utf8.insert() +---@param s string +---@param idx? integer +---@param substring string +---return string new_string +function string.uinsert(s, idx, substring) end + +---Equivalent to utf8.remove() +---@param s string +---@param start? integer +---@param stop? integer +---return string new_string +function string.uremove(s, start, stop) end + +---Equivalent to utf8.width() +---@param s string +---@param ambi_is_double? boolean +---@param default_width? integer +---@return integer width +function string.uwidth(s, ambi_is_double, default_width) end + +---Equivalent to utf8.widthindex() +---@param s string +---@param location integer +---@param ambi_is_double? boolean +---@param default_width? integer +---@return integer idx +---@return integer offset +---@return integer width +function string.uwidthindex(s, location, ambi_is_double, default_width) end + +---Equivalent to utf8.title() +---@param s string +---return string new_string +function string.utitle(s) end + +---Equivalent to utf8.fold() +---@param s string +---return string new_string +function string.ufold(s) end + +---Equivalent to utf8.ncasecmp() +---@param a string +---@param b string +---@return integer result +function string.uncasecmp(a, b) end + +---Equivalent to utf8.offset() +---@param s string +---@param n integer +---@param i? integer +---@return integer position_in_bytes +function string.uoffset(s, n, i) end + +---Equivalent to utf8.codepoint() +---@param s string +---@param i? integer +---@param j? integer +---@return integer code +---@return ... +function string.ucodepoint(s, i, j) end + +---Equivalent to utf8.codes() +---@param s string +---@return fun():integer, integer +function string.ucodes(s) end diff --git a/docs/api/utf8.lua b/docs/api/utf8.lua new file mode 100644 index 00000000..d4dff5a9 --- /dev/null +++ b/docs/api/utf8.lua @@ -0,0 +1,187 @@ +---@meta + +---UTF-8 equivalent of string.byte +---@param s string +---@param i? integer +---@param j? integer +---@return integer +---@return ... +function utf8.byte(s, i, j) end + +---UTF-8 equivalent of string.char +---@param byte integer +---@param ... integer +---@return string +---@return ... +function utf8.char(byte, ...) end + +---UTF-8 equivalent of string.find +---@param s string +---@param pattern string +---@param init? integer +---@param plain? boolean +---@return integer start +---@return integer end +---@return ... captured +function utf8.find(s, pattern, init, plain) end + +---UTF-8 equivalent of string.gmatch +---@param s string +---@param pattern string +---@param init? integer +---@return fun():string, ... +function utf8.gmatch(s, pattern, init) end + +---UTF-8 equivalent of string.gsub +---@param s string +---@param pattern string +---@param repl string|table|function +---@param n integer +---@return string +---@return integer count +function utf8.gsub(s, pattern, repl, n) end + +---UTF-8 equivalent of string.len +---@param s string +---@return integer +function utf8.len(s) end + +---UTF-8 equivalent of string.lower +---@param s string +---@return string +function utf8.lower(s) end + +---UTF-8 equivalent of string.match +---@param s string +---@param pattern string +---@param init? integer +---@return string | number captured +function utf8.match(s, pattern, init) end + +---UTF-8 equivalent of string.reverse +---@param s string +---@return string +function utf8.reverse(s) end + +---UTF-8 equivalent of string.sub +---@param s string +---@param i integer +---@param j? integer +---@return string +function utf8.sub(s, i, j) end + +---UTF-8 equivalent of string.upper +---@param s string +---@return string +function utf8.upper(s) end + +---Escape a str to UTF-8 format string. It support several escape format: +---* %ddd - which ddd is a decimal number at any length: change Unicode code point to UTF-8 format. +---* %{ddd} - same as %nnn but has bracket around. +---* %uddd - same as %ddd, u stands Unicode +---* %u{ddd} - same as %{ddd} +---* %xhhh - hexadigit version of %ddd +---* %x{hhh} same as %xhhh. +---* %? - '?' stands for any other character: escape this character. +---Example: +---```lua +---local u = utf8.escape +---print(u"%123%u123%{123}%u{123}%xABC%x{ABC}") +---print(u"%%123%?%d%%u") +---``` +---@param s string +---@return string utf8_string +function utf8.escape(s) end + +---Convert UTF-8 position to byte offset. if only index is given, return byte +---offset of this UTF-8 char index. if both charpos and index is given, a new +---charpos will be calculated, by add/subtract UTF-8 char index to current +---charpos. in all cases, it returns a new char position, and code point +---(a number) at this position. +---@param s string +---@param charpos? integer +---@param index? integer +---@return integer charpos +---@return integer codepoint +function utf8.charpos(s, charpos, index) end + +---Iterate though the UTF-8 string s. If only s is given, it can used as a iterator: +---```lua +--- for pos, code in utf8.next, "utf8-string" do +--- -- ... +--- end +---```` +---If only charpos is given, return the next byte offset of in string. if +---charpos and index is given, a new charpos will be calculated, by add/subtract +---UTF-8 char offset to current charpos. in all case, it return a new char +---position (in bytes), and code point (a number) at this position. +---@param s string +---@param charpos? integer +---@param index? integer +---@return integer charpos +---@return integer codepoint +function utf8.next(s, charpos, index) end + +---Insert a substring to s. If idx is given, insert substring before char at +---this index, otherwise substring will concat to s. idx can be negative. +---@param s string +---@param idx? integer +---@param substring string +---return string new_string +function utf8.insert(s, idx, substring) end + +---Delete a substring in s. If neither start nor stop is given, delete the last +---UTF-8 char in s, otherwise delete char from start to end of s. if stop is +---given, delete char from start to stop (include start and stop). start and +---stop can be negative. +---@param s string +---@param start? integer +---@param stop? integer +---return string new_string +function utf8.remove(s, start, stop) end + +---Calculate the width of UTF-8 string s. if ambi_is_double is given, the +---ambiguous width character's width is 2, otherwise it's 1. fullwidth/doublewidth +---character's width is 2, and other character's width is 1. if default_width is +---given, it will be the width of unprintable character, used display a +---non-character mark for these characters. if s is a code point, return the +---width of this code point. +---@param s string +---@param ambi_is_double? boolean +---@param default_width? integer +---@return integer width +function utf8.width(s, ambi_is_double, default_width) end + +---Return the character index at given location in string s. this is a reverse +---operation of utf8.width(). this function returns a index of location, and a +---offset in UTF-8 encoding. e.g. if cursor is at the second column (middle) +---of the wide char, offset will be 2. the width of character at idx is +---returned, also. +---@param s string +---@param location integer +---@param ambi_is_double? boolean +---@param default_width? integer +---@return integer idx +---@return integer offset +---@return integer width +function utf8.widthindex(s, location, ambi_is_double, default_width) end + +---Convert UTF-8 string s to title-case, used to compare by ignore case. if s +---is a number, it's treat as a code point and return a convert code point +---(number). utf8.lower/utf8.pper has the same extension. +---@param s string +---return string new_string +function utf8.title(s) end + +---Convert UTF-8 string s to folded case, used to compare by ignore case. if s +---is a number, it's treat as a code point and return a convert code point +---(number). utf8.lower/utf8.pper has the same extension. +---@param s string +---return string new_string +function utf8.fold(s) end + +---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b. +---@param a string +---@param b string +---@return integer result +function utf8.ncasecmp(a, b) end diff --git a/src/api/api.c b/src/api/api.c index 1a6e516d..67a05f19 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -5,6 +5,7 @@ int luaopen_renderer(lua_State *L); int luaopen_regex(lua_State *L); int luaopen_process(lua_State *L); int luaopen_dirmonitor(lua_State* L); +int luaopen_utf8(lua_State* L); static const luaL_Reg libs[] = { { "system", luaopen_system }, @@ -12,6 +13,7 @@ static const luaL_Reg libs[] = { { "regex", luaopen_regex }, { "process", luaopen_process }, { "dirmonitor", luaopen_dirmonitor }, + { "utf8", luaopen_utf8 }, { NULL, NULL } }; diff --git a/src/api/utf8.c b/src/api/utf8.c new file mode 100644 index 00000000..e1a4ebfe --- /dev/null +++ b/src/api/utf8.c @@ -0,0 +1,1305 @@ +/* + * Integration of https://github.com/starwing/luautf8 + * + * Copyright (c) 2018 Xavier Wang + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + + +#include +#include + +#include "../unidata.h" + +/* UTF-8 string operations */ + +#define UTF8_BUFFSZ 8 +#define UTF8_MAX 0x7FFFFFFFu +#define UTF8_MAXCP 0x10FFFFu +#define iscont(p) ((*(p) & 0xC0) == 0x80) +#define CAST(tp,expr) ((tp)(expr)) + +#ifndef LUA_QL +# define LUA_QL(x) "'" x "'" +#endif + +static int utf8_invalid (utfint ch) +{ return (ch > UTF8_MAXCP || (0xD800u <= ch && ch <= 0xDFFFu)); } + +static size_t utf8_encode (char *buff, utfint x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= UTF8_MAX); + if (x < 0x80) /* ascii? */ + buff[UTF8_BUFFSZ - 1] = x & 0x7F; + else { /* need continuation bytes */ + utfint mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8_BUFFSZ - (n++)] = 0x80 | (x & 0x3f); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8_BUFFSZ - n] = ((~mfb << 1) | x) & 0xFF; /* add first byte */ + } + return n; +} + +static const char *utf8_decode (const char *s, utfint *val, int strict) { + static const utfint limits[] = + {~0u, 0x80u, 0x800u, 0x10000u, 0x200000u, 0x4000000u}; + unsigned int c = (unsigned char)s[0]; + utfint res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ + unsigned int cc = (unsigned char)s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + } + res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 5 || res > UTF8_MAX || res < limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (strict) { + /* check for invalid code points; too large or surrogates */ + if (res > UTF8_MAXCP || (0xD800u <= res && res <= 0xDFFFu)) + return NULL; + } + if (val) *val = res; + return s + 1; /* +1 to include first byte */ +} + +static const char *utf8_prev (const char *s, const char *e) { + while (s < e && iscont(e - 1)) --e; + return s < e ? e - 1 : s; +} + +static const char *utf8_next (const char *s, const char *e) { + while (s < e && iscont(s + 1)) ++s; + return s < e ? s + 1 : e; +} + +static size_t utf8_length (const char *s, const char *e) { + size_t i; + for (i = 0; s < e; ++i) + s = utf8_next(s, e); + return i; +} + +static const char *utf8_offset (const char *s, const char *e, lua_Integer offset, lua_Integer idx) { + const char *p = s + offset - 1; + if (idx >= 0) { + while (p < e && idx > 0) + p = utf8_next(p, e), --idx; + return idx == 0 ? p : NULL; + } else { + while (s < p && idx < 0) + p = utf8_prev(s, p), ++idx; + return idx == 0 ? p : NULL; + } +} + +static const char *utf8_relat (const char *s, const char *e, int idx) { + return idx >= 0 ? + utf8_offset(s, e, 1, idx - 1) : + utf8_offset(s, e, e-s+1, idx); +} + +static int utf8_range(const char *s, const char *e, lua_Integer *i, lua_Integer *j) { + const char *ps = utf8_relat(s, e, CAST(int, *i)); + const char *pe = utf8_relat(s, e, CAST(int, *j)); + *i = (ps ? ps : (*i > 0 ? e : s)) - s; + *j = (pe ? utf8_next(pe, e) : (*j > 0 ? e : s)) - s; + return *i < *j; +} + + +/* Unicode character categories */ + +#define table_size(t) (sizeof(t)/sizeof((t)[0])) + +#define utf8_categories(X) \ + X('a', alpha) \ + X('c', cntrl) \ + X('d', digit) \ + X('l', lower) \ + X('p', punct) \ + X('s', space) \ + X('t', compose) \ + X('u', upper) \ + X('x', xdigit) + +#define utf8_converters(X) \ + X(lower) \ + X(upper) \ + X(title) \ + X(fold) + +static int find_in_range (range_table *t, size_t size, utfint ch) { + size_t begin, end; + + begin = 0; + end = size; + + while (begin < end) { + size_t mid = (begin + end) / 2; + if (t[mid].last < ch) + begin = mid + 1; + else if (t[mid].first > ch) + end = mid; + else + return (ch - t[mid].first) % t[mid].step == 0; + } + + return 0; +} + +static int convert_char (conv_table *t, size_t size, utfint ch) { + size_t begin, end; + + begin = 0; + end = size; + + while (begin < end) { + size_t mid = (begin + end) / 2; + if (t[mid].last < ch) + begin = mid + 1; + else if (t[mid].first > ch) + end = mid; + else if ((ch - t[mid].first) % t[mid].step == 0) + return ch + t[mid].offset; + else + return ch; + } + + return ch; +} + +#define define_category(cls, name) static int utf8_is##name (utfint ch)\ +{ return find_in_range(name##_table, table_size(name##_table), ch); } +#define define_converter(name) static utfint utf8_to##name (utfint ch) \ +{ return convert_char(to##name##_table, table_size(to##name##_table), ch); } +utf8_categories(define_category) +utf8_converters(define_converter) +#undef define_category +#undef define_converter + +static int utf8_isgraph (utfint ch) { + if (find_in_range(space_table, table_size(space_table), ch)) + return 0; + if (find_in_range(graph_table, table_size(graph_table), ch)) + return 1; + if (find_in_range(compose_table, table_size(compose_table), ch)) + return 1; + return 0; +} + +static int utf8_isalnum (utfint ch) { + if (find_in_range(alpha_table, table_size(alpha_table), ch)) + return 1; + if (find_in_range(alnum_extend_table, table_size(alnum_extend_table), ch)) + return 1; + return 0; +} + +static int utf8_width (utfint ch, int ambi_is_single) { + if (find_in_range(doublewidth_table, table_size(doublewidth_table), ch)) + return 2; + if (find_in_range(ambiwidth_table, table_size(ambiwidth_table), ch)) + return ambi_is_single ? 1 : 2; + if (find_in_range(compose_table, table_size(compose_table), ch)) + return 0; + if (find_in_range(unprintable_table, table_size(unprintable_table), ch)) + return 0; + return 1; +} + + +/* string module compatible interface */ + +static int typeerror (lua_State *L, int idx, const char *tname) +{ return luaL_error(L, "%s expected, got %s", tname, luaL_typename(L, idx)); } + +static const char *check_utf8 (lua_State *L, int idx, const char **end) { + size_t len; + const char *s = luaL_checklstring(L, idx, &len); + if (end) *end = s+len; + return s; +} + +static const char *to_utf8 (lua_State *L, int idx, const char **end) { + size_t len; + const char *s = lua_tolstring(L, idx, &len); + if (end) *end = s+len; + return s; +} + +static const char *utf8_safe_decode (lua_State *L, const char *p, utfint *pval) { + p = utf8_decode(p, pval, 0); + if (p == NULL) luaL_error(L, "invalid UTF-8 code"); + return p; +} + +static void add_utf8char (luaL_Buffer *b, utfint ch) { + char buff[UTF8_BUFFSZ]; + size_t n = utf8_encode(buff, ch); + luaL_addlstring(b, buff+UTF8_BUFFSZ-n, n); +} + +static lua_Integer byte_relat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + +static int Lutf8_len (lua_State *L) { + size_t len, n; + const char *s = luaL_checklstring(L, 1, &len), *p, *e; + lua_Integer posi = byte_relat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = byte_relat(luaL_optinteger(L, 3, -1), len); + int lax = lua_toboolean(L, 4); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of string"); + luaL_argcheck(L, --pose < (lua_Integer)len, 3, + "final position out of string"); + for (n = 0, p=s+posi, e=s+pose+1; p < e; ++n) { + if (lax) + p = utf8_next(p, e); + else { + utfint ch; + const char *np = utf8_decode(p, &ch, !lax); + if (np == NULL || utf8_invalid(ch)) { + lua_pushnil(L); + lua_pushinteger(L, p - s + 1); + return 2; + } + p = np; + } + } + lua_pushinteger(L, n); + return 1; +} + +static int Lutf8_sub (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_checkinteger(L, 2); + lua_Integer pose = luaL_optinteger(L, 3, -1); + if (utf8_range(s, e, &posi, &pose)) + lua_pushlstring(L, s+posi, pose-posi); + else + lua_pushliteral(L, ""); + return 1; +} + +static int Lutf8_reverse (lua_State *L) { + luaL_Buffer b; + const char *prev, *pprev, *ends, *e, *s = check_utf8(L, 1, &e); + (void) ends; + int lax = lua_toboolean(L, 2); + luaL_buffinit(L, &b); + if (lax) { + for (prev = e; s < prev; e = prev) { + prev = utf8_prev(s, prev); + luaL_addlstring(&b, prev, e-prev); + } + } else { + for (prev = e; s < prev; prev = pprev) { + utfint code = 0; + ends = utf8_safe_decode(L, pprev = utf8_prev(s, prev), &code); + assert(ends == prev); + if (utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + if (!utf8_iscompose(code)) { + luaL_addlstring(&b, pprev, e-pprev); + e = pprev; + } + } + } + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_byte (lua_State *L) { + size_t n = 0; + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_optinteger(L, 2, 1); + lua_Integer pose = luaL_optinteger(L, 3, posi); + if (utf8_range(s, e, &posi, &pose)) { + for (e = s + pose, s = s + posi; s < e; ++n) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + lua_pushinteger(L, ch); + } + } + return CAST(int, n); +} + +static int Lutf8_codepoint (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + size_t len = e-s; + lua_Integer posi = byte_relat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = byte_relat(luaL_optinteger(L, 3, posi), len); + int lax = lua_toboolean(L, 4); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of range"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of range"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi + 1); + luaL_checkstack(L, n, "string slice too long"); + n = 0; /* count the number of returns */ + se = s + pose; /* string end */ + for (n = 0, s += posi - 1; s < se;) { + utfint code = 0; + s = utf8_safe_decode(L, s, &code); + if (!lax && utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, code); + n++; + } + return n; +} + +static int Lutf8_char (lua_State *L) { + int i, n = lua_gettop(L); /* number of arguments */ + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; ++i) { + lua_Integer code = luaL_checkinteger(L, i); + luaL_argcheck(L, code <= UTF8_MAXCP, i, "value out of range"); + add_utf8char(&b, CAST(utfint, code)); + } + luaL_pushresult(&b); + return 1; +} + +#define bind_converter(name) \ +static int Lutf8_##name (lua_State *L) { \ + int t = lua_type(L, 1); \ + if (t == LUA_TNUMBER) \ + lua_pushinteger(L, utf8_to##name(CAST(utfint, lua_tointeger(L, 1)))); \ + else if (t == LUA_TSTRING) { \ + luaL_Buffer b; \ + const char *e, *s = to_utf8(L, 1, &e); \ + luaL_buffinit(L, &b); \ + while (s < e) { \ + utfint ch = 0; \ + s = utf8_safe_decode(L, s, &ch); \ + add_utf8char(&b, utf8_to##name(ch)); \ + } \ + luaL_pushresult(&b); \ + } \ + else return typeerror(L, 1, "number/string"); \ + return 1; \ +} +utf8_converters(bind_converter) +#undef bind_converter + + +/* unicode extra interface */ + +static const char *parse_escape (lua_State *L, const char *s, const char *e, int hex, utfint *pch) { + utfint code = 0; + int in_bracket = 0; + if (*s == '{') ++s, in_bracket = 1; + for (; s < e; ++s) { + utfint ch = (unsigned char)*s; + if (ch >= '0' && ch <= '9') ch = ch - '0'; + else if (hex && ch >= 'A' && ch <= 'F') ch = 10 + (ch - 'A'); + else if (hex && ch >= 'a' && ch <= 'f') ch = 10 + (ch - 'a'); + else if (!in_bracket) break; + else if (ch == '}') { ++s; break; } + else luaL_error(L, "invalid escape '%c'", ch); + code *= hex ? 16 : 10; + code += ch; + } + *pch = code; + return s; +} + +static int Lutf8_escape (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + luaL_Buffer b; + luaL_buffinit(L, &b); + while (s < e) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + if (ch == '%') { + int hex = 0; + switch (*s) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': case '{': + break; + case 'x': case 'X': hex = 1; /* fall through */ + case 'u': case 'U': if (s+1 < e) { ++s; break; } + /* fall through */ + default: + s = utf8_safe_decode(L, s, &ch); + goto next; + } + s = parse_escape(L, s, e, hex, &ch); + } +next: + add_utf8char(&b, ch); + } + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_insert (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + size_t sublen; + const char *subs; + luaL_Buffer b; + int nargs = 2; + const char *first = e; + if (lua_type(L, 2) == LUA_TNUMBER) { + int idx = (int)lua_tointeger(L, 2); + if (idx != 0) first = utf8_relat(s, e, idx); + luaL_argcheck(L, first, 2, "invalid index"); + ++nargs; + } + subs = luaL_checklstring(L, nargs, &sublen); + luaL_buffinit(L, &b); + luaL_addlstring(&b, s, first-s); + luaL_addlstring(&b, subs, sublen); + luaL_addlstring(&b, first, e-first); + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_remove (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_optinteger(L, 2, -1); + lua_Integer pose = luaL_optinteger(L, 3, -1); + if (!utf8_range(s, e, &posi, &pose)) + lua_settop(L, 1); + else { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addlstring(&b, s, posi); + luaL_addlstring(&b, s+pose, e-s-pose); + luaL_pushresult(&b); + } + return 1; +} + +static int push_offset (lua_State *L, const char *s, const char *e, lua_Integer offset, lua_Integer idx) { + utfint ch = 0; + const char *p; + if (idx != 0) + p = utf8_offset(s, e, offset, idx); + else if (p = s+offset-1, iscont(p)) + p = utf8_prev(s, p); + if (p == NULL || p == e) return 0; + utf8_decode(p, &ch, 0); + lua_pushinteger(L, p-s+1); + lua_pushinteger(L, ch); + return 2; +} + +static int Lutf8_charpos (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer offset = 1; + if (lua_isnoneornil(L, 3)) { + lua_Integer idx = luaL_optinteger(L, 2, 0); + if (idx > 0) --idx; + else if (idx < 0) offset = e-s+1; + return push_offset(L, s, e, offset, idx); + } + offset = byte_relat(luaL_optinteger(L, 2, 1), e-s); + if (offset < 1) offset = 1; + return push_offset(L, s, e, offset, luaL_checkinteger(L, 3)); +} + +static int Lutf8_offset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = byte_relat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of range"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscont(s + posi)) posi--; + } else { + if (iscont(s + posi)) + return luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscont(s + posi)); + n++; + } + } else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscont(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + lua_pushnil(L); + return 1; +} + +static int Lutf8_next (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer offset = byte_relat(luaL_optinteger(L, 2, 1), e-s); + lua_Integer idx = luaL_optinteger(L, 3, !lua_isnoneornil(L, 2)); + return push_offset(L, s, e, offset, idx); +} + +static int iter_aux (lua_State *L, int strict) { + const char *e, *s = check_utf8(L, 1, &e); + int n = CAST(int, lua_tointeger(L, 2)); + const char *p = n <= 0 ? s : utf8_next(s+n-1, e); + if (p < e) { + utfint code = 0; + utf8_safe_decode(L, p, &code); + if (strict && utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, p-s+1); + lua_pushinteger(L, code); + return 2; + } + return 0; /* no more codepoints */ +} + +static int iter_auxstrict (lua_State *L) { return iter_aux(L, 1); } +static int iter_auxlax (lua_State *L) { return iter_aux(L, 0); } + +static int Lutf8_codes (lua_State *L) { + int lax = lua_toboolean(L, 2); + luaL_checkstring(L, 1); + lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + +static int Lutf8_width (lua_State *L) { + int t = lua_type(L, 1); + int ambi_is_single = !lua_toboolean(L, 2); + int default_width = CAST(int, luaL_optinteger(L, 3, 0)); + if (t == LUA_TNUMBER) { + size_t chwidth = utf8_width(CAST(utfint, lua_tointeger(L, 1)), ambi_is_single); + if (chwidth == 0) chwidth = default_width; + lua_pushinteger(L, (lua_Integer)chwidth); + } else if (t != LUA_TSTRING) + return typeerror(L, 1, "number/string"); + else { + const char *e, *s = to_utf8(L, 1, &e); + int width = 0; + while (s < e) { + utfint ch = 0; + int chwidth; + s = utf8_safe_decode(L, s, &ch); + chwidth = utf8_width(ch, ambi_is_single); + width += chwidth == 0 ? default_width : chwidth; + } + lua_pushinteger(L, (lua_Integer)width); + } + return 1; +} + +static int Lutf8_widthindex (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + int width = CAST(int, luaL_checkinteger(L, 2)); + int ambi_is_single = !lua_toboolean(L, 3); + int default_width = CAST(int, luaL_optinteger(L, 4, 0)); + size_t idx = 1; + while (s < e) { + utfint ch = 0; + size_t chwidth; + s = utf8_safe_decode(L, s, &ch); + chwidth = utf8_width(ch, ambi_is_single); + if (chwidth == 0) chwidth = default_width; + width -= CAST(int, chwidth); + if (width <= 0) { + lua_pushinteger(L, idx); + lua_pushinteger(L, width + chwidth); + lua_pushinteger(L, chwidth); + return 3; + } + ++idx; + } + lua_pushinteger(L, (lua_Integer)idx); + return 1; +} + +static int Lutf8_ncasecmp (lua_State *L) { + const char *e1, *s1 = check_utf8(L, 1, &e1); + const char *e2, *s2 = check_utf8(L, 2, &e2); + while (s1 < e1 || s2 < e2) { + utfint ch1 = 0, ch2 = 0; + if (s1 == e1) + ch2 = 1; + else if (s2 == e2) + ch1 = 1; + else { + s1 = utf8_safe_decode(L, s1, &ch1); + s2 = utf8_safe_decode(L, s2, &ch2); + ch1 = utf8_tofold(ch1); + ch2 = utf8_tofold(ch2); + } + if (ch1 != ch2) { + lua_pushinteger(L, ch1 > ch2 ? 1 : -1); + return 1; + } + } + lua_pushinteger(L, 0); + return 1; +} + + +/* utf8 pattern matching implement */ + +#ifndef LUA_MAXCAPTURES +# define LUA_MAXCAPTURES 32 +#endif /* LUA_MAXCAPTURES */ + +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + + +typedef struct MatchState { + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + const char *src_init; /* init of source string */ + const char *src_end; /* end ('\0') of source string */ + const char *p_end; /* end ('\0') of pattern */ + lua_State *L; + int level; /* total number of captures (finished or unfinished) */ + struct { + const char *init; + ptrdiff_t len; + } capture[LUA_MAXCAPTURES]; +} MatchState; + +/* recursive function */ +static const char *match (MatchState *ms, const char *s, const char *p); + +/* maximum recursion depth for 'match' */ +#if !defined(MAXCCALLS) +#define MAXCCALLS 200 +#endif + +#define L_ESC '%' +#define SPECIALS "^$*+?.([%-" + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return luaL_error(ms->L, "invalid capture index %%%d", l + 1); + return l; +} + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + while (--level >= 0) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + +static const char *classend (MatchState *ms, const char *p) { + utfint ch = 0; + p = utf8_safe_decode(ms->L, p, &ch); + switch (ch) { + case L_ESC: { + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + return utf8_next(p, ms->p_end); + } + case '[': { + if (*p == '^') p++; + do { /* look for a `]' */ + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + if (*(p++) == L_ESC && p < ms->p_end) + p++; /* skip escapes (e.g. `%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + +static int match_class (utfint c, utfint cl) { + int res; + switch (utf8_tolower(cl)) { +#define X(cls, name) case cls: res = utf8_is##name(c); break; + utf8_categories(X) +#undef X + case 'g' : res = utf8_isgraph(c); break; + case 'w' : res = utf8_isalnum(c); break; + case 'z' : res = (c == 0); break; /* deprecated option */ + default: return (cl == c); + } + return (utf8_islower(cl) ? res : !res); +} + +static int matchbracketclass (MatchState *ms, utfint c, const char *p, const char *ec) { + int sig = 1; + assert(*p == '['); + if (*++p == '^') { + sig = 0; + p++; /* skip the `^' */ + } + while (p < ec) { + utfint ch = 0; + p = utf8_safe_decode(ms->L, p, &ch); + if (ch == L_ESC) { + p = utf8_safe_decode(ms->L, p, &ch); + if (match_class(c, ch)) + return sig; + } else { + utfint next = 0; + const char *np = utf8_safe_decode(ms->L, p, &next); + if (next == '-' && np < ec) { + p = utf8_safe_decode(ms->L, np, &next); + if (ch <= c && c <= next) + return sig; + } + else if (ch == c) return sig; + } + } + return !sig; +} + +static int singlematch (MatchState *ms, const char *s, const char *p, const char *ep) { + if (s >= ms->src_end) + return 0; + else { + utfint ch=0, pch=0; + utf8_safe_decode(ms->L, s, &ch); + p = utf8_safe_decode(ms->L, p, &pch); + switch (pch) { + case '.': return 1; /* matches any char */ + case L_ESC: utf8_safe_decode(ms->L, p, &pch); + return match_class(ch, pch); + case '[': return matchbracketclass(ms, ch, p-1, ep-1); + default: return pch == ch; + } + } +} + +static const char *matchbalance (MatchState *ms, const char *s, const char **p) { + utfint ch=0, begin=0, end=0; + *p = utf8_safe_decode(ms->L, *p, &begin); + if (*p >= ms->p_end) + luaL_error(ms->L, "malformed pattern " + "(missing arguments to " LUA_QL("%%b") ")"); + *p = utf8_safe_decode(ms->L, *p, &end); + s = utf8_safe_decode(ms->L, s, &ch); + if (ch != begin) return NULL; + else { + int cont = 1; + while (s < ms->src_end) { + s = utf8_safe_decode(ms->L, s, &ch); + if (ch == end) { + if (--cont == 0) return s; + } + else if (ch == begin) cont++; + } + } + return NULL; /* string ends out of balance */ +} + +static const char *max_expand (MatchState *ms, const char *s, const char *p, const char *ep) { + const char *m = s; /* matched end of single match p */ + while (singlematch(ms, m, p, ep)) + m = utf8_next(m, ms->src_end); + /* keeps trying to match with the maximum repetitions */ + while (s <= m) { + const char *res = match(ms, m, ep+1); + if (res) return res; + /* else didn't match; reduce 1 repetition to try again */ + if (s == m) break; + m = utf8_prev(s, m); + } + return NULL; +} + +static const char *min_expand (MatchState *ms, const char *s, const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (singlematch(ms, s, p, ep)) + s = utf8_next(s, ms->src_end); /* try with one more repetition */ + else return NULL; + } +} + +static const char *start_capture (MatchState *ms, const char *s, const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + +static const char *end_capture (MatchState *ms, const char *s, const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + +static const char *match (MatchState *ms, const char *s, const char *p) { + if (ms->matchdepth-- == 0) + luaL_error(ms->L, "pattern too complex"); + init: /* using goto's to optimize tail recursion */ + if (p != ms->p_end) { /* end of pattern? */ + utfint ch = 0; + utf8_safe_decode(ms->L, p, &ch); + switch (ch) { + case '(': { /* start capture */ + if (*(p + 1) == ')') /* position capture? */ + s = start_capture(ms, s, p + 2, CAP_POSITION); + else + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, p + 1); + break; + } + case '$': { + if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ + goto dflt; /* no; go to default */ + s = (s == ms->src_end) ? s : NULL; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequence not in the format class[*+?-]? */ + const char *prev_p = p; + p = utf8_safe_decode(ms->L, p+1, &ch); + switch (ch) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, &p); + if (s != NULL) + goto init; /* return match(ms, s, p + 4); */ + /* else fail (s == NULL) */ + break; + } + case 'f': { /* frontier? */ + const char *ep; utfint previous = 0, current = 0; + if (*p != '[') + luaL_error(ms->L, "missing " LUA_QL("[") " after " + LUA_QL("%%f") " in pattern"); + ep = classend(ms, p); /* points to what is next */ + if (s != ms->src_init) + utf8_decode(utf8_prev(ms->src_init, s), &previous, 0); + if (s != ms->src_end) + utf8_decode(s, ¤t, 0); + if (!matchbracketclass(ms, previous, p, ep - 1) && + matchbracketclass(ms, current, p, ep - 1)) { + p = ep; goto init; /* return match(ms, s, ep); */ + } + s = NULL; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, ch); + if (s != NULL) goto init; /* return match(ms, s, p + 2) */ + break; + } + default: p = prev_p; goto dflt; + } + break; + } + default: dflt: { /* pattern class plus optional suffix */ + const char *ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (*ep == '*' || *ep == '?' || *ep == '-') { /* accept empty? */ + p = ep + 1; goto init; /* return match(ms, s, ep + 1); */ + } else /* '+' or no suffix */ + s = NULL; /* fail */ + } else { /* matched once */ + const char *next_s = utf8_next(s, ms->src_end); + switch (*ep) { /* handle optional suffix */ + case '?': { /* optional */ + const char *res; + const char *next_ep = utf8_next(ep, ms->p_end); + if ((res = match(ms, next_s, next_ep)) != NULL) + s = res; + else { + p = next_ep; goto init; /* else return match(ms, s, ep + 1); */ + } + break; + } + case '+': /* 1 or more repetitions */ + s = next_s; /* 1 match already done */ + /* fall through */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s = next_s; p = ep; goto init; /* return match(ms, s + 1, ep); */ + } + } + break; + } + } + } + ms->matchdepth++; + return s; +} + +static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else { + const char *init; /* to search for a `*s2' inside `s1' */ + l2--; /* 1st char will be checked by `memchr' */ + l1 = l1-l2; /* `s2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct `l1' and `s1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + +static int get_index (const char *p, const char *s, const char *e) { + int idx; + for (idx = 0; s < e && s < p; ++idx) + s = utf8_next(s, e); + return s == p ? idx : idx - 1; +} + +static void push_onecapture (MatchState *ms, int i, const char *s, const char *e) { + if (i >= ms->level) { + if (i == 0) /* ms->level == 0, too */ + lua_pushlstring(ms->L, s, e - s); /* add whole match */ + else + luaL_error(ms->L, "invalid capture index"); + } else { + ptrdiff_t l = ms->capture[i].len; + if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); + if (l == CAP_POSITION) { + int idx = get_index(ms->capture[i].init, ms->src_init, ms->src_end); + lua_pushinteger(ms->L, idx+1); + } else + lua_pushlstring(ms->L, ms->capture[i].init, l); + } +} + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + +/* check whether pattern has no special characters */ +static int nospecials (const char *p, const char * ep) { + while (p < ep) { + if (strpbrk(p, SPECIALS)) + return 0; /* pattern has a special character */ + p += strlen(p) + 1; /* may have more after \0 */ + } + return 1; /* no special chars found */ +} + + +/* utf8 pattern matching interface */ + +static int find_aux (lua_State *L, int find) { + const char *es, *s = check_utf8(L, 1, &es); + const char *ep, *p = check_utf8(L, 2, &ep); + lua_Integer idx = luaL_optinteger(L, 3, 1); + const char *init; + if (!idx) idx = 1; + init = utf8_relat(s, es, CAST(int, idx)); + if (init == NULL) { + if (idx > 0) { + lua_pushnil(L); /* cannot find anything */ + return 1; + } + init = s; + } + /* explicit request or no special characters? */ + if (find && (lua_toboolean(L, 4) || nospecials(p, ep))) { + /* do a plain search */ + const char *s2 = lmemfind(init, es-init, p, ep-p); + if (s2) { + const char *e2 = s2 + (ep - p); + if (iscont(e2)) e2 = utf8_next(e2, es); + lua_pushinteger(L, idx = get_index(s2, s, es) + 1); + lua_pushinteger(L, idx + get_index(e2, s2, es) - 1); + return 2; + } + } else { + MatchState ms; + int anchor = (*p == '^'); + if (anchor) p++; /* skip anchor character */ + if (idx < 0) idx += utf8_length(s, es)+1; /* TODO not very good */ + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + do { + const char *res; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + if ((res=match(&ms, init, p)) != NULL) { + if (find) { + lua_pushinteger(L, idx); /* start */ + lua_pushinteger(L, idx + utf8_length(init, res) - 1); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } else + return push_captures(&ms, init, res); + } + if (init == es) break; + idx += 1; + init = utf8_next(init, es); + } while (init <= es && !anchor); + } + lua_pushnil(L); /* not found */ + return 1; +} + +static int Lutf8_find (lua_State *L) { return find_aux(L, 1); } +static int Lutf8_match (lua_State *L) { return find_aux(L, 0); } + +static int gmatch_aux (lua_State *L) { + MatchState ms; + const char *es, *s = check_utf8(L, lua_upvalueindex(1), &es); + const char *ep, *p = check_utf8(L, lua_upvalueindex(2), &ep); + const char *src; + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); + src <= ms.src_end; + src = utf8_next(src, ms.src_end)) { + const char *e; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + if ((e = match(&ms, src, p)) != NULL) { + lua_Integer newstart = e-s; + if (e == src) newstart++; /* empty match? go at least one position */ + lua_pushinteger(L, newstart); + lua_replace(L, lua_upvalueindex(3)); + return push_captures(&ms, src, e); + } + if (src == ms.src_end) break; + } + return 0; /* not found */ +} + +static int Lutf8_gmatch (lua_State *L) { + luaL_checkstring(L, 1); + luaL_checkstring(L, 2); + lua_settop(L, 2); + lua_pushinteger(L, 0); + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { + const char *new_end, *news = to_utf8(ms->L, 3, &new_end); + while (news < new_end) { + utfint ch = 0; + news = utf8_safe_decode(ms->L, news, &ch); + if (ch != L_ESC) + add_utf8char(b, ch); + else { + news = utf8_safe_decode(ms->L, news, &ch); /* skip ESC */ + if (!utf8_isdigit(ch)) { + if (ch != L_ESC) + luaL_error(ms->L, "invalid use of " LUA_QL("%c") + " in replacement string", L_ESC); + add_utf8char(b, ch); + } else if (ch == '0') + luaL_addlstring(b, s, e-s); + else { + push_onecapture(ms, ch-'1', s, e); + luaL_addvalue(b); /* add capture to accumulated result */ + } + } + } +} + +static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, const char *e, int tr) { + lua_State *L = ms->L; + switch (tr) { + case LUA_TFUNCTION: { + int n; + lua_pushvalue(L, 3); + n = push_captures(ms, s, e); + lua_call(L, n, 1); + break; + } + case LUA_TTABLE: { + push_onecapture(ms, 0, s, e); + lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); + return; + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); + lua_pushlstring(L, s, e - s); /* keep original text */ + } else if (!lua_isstring(L, -1)) + luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); + luaL_addvalue(b); /* add result to accumulator */ +} + +static int Lutf8_gsub (lua_State *L) { + const char *es, *s = check_utf8(L, 1, &es); + const char *ep, *p = check_utf8(L, 2, &ep); + int tr = lua_type(L, 3); + lua_Integer max_s = luaL_optinteger(L, 4, (es-s)+1); + int anchor = (*p == '^'); + lua_Integer n = 0; + MatchState ms; + luaL_Buffer b; + luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table expected"); + luaL_buffinit(L, &b); + if (anchor) p++; /* skip anchor character */ + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + while (n < max_s) { + const char *e; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + e = match(&ms, s, p); + if (e) { + n++; + add_value(&ms, &b, s, e, tr); + } + if (e && e > s) /* non empty match? */ + s = e; /* skip it */ + else if (s < es) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + add_utf8char(&b, ch); + } else break; + if (anchor) break; + } + luaL_addlstring(&b, s, es-s); + luaL_pushresult(&b); + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + + +/* lua module import interface */ + +#if LUA_VERSION_NUM >= 502 +static const char UTF8PATT[] = "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"; +#else +static const char UTF8PATT[] = "[%z\1-\x7F\xC2-\xF4][\x80-\xBF]*"; +#endif + +int luaopen_utf8 (lua_State *L) { + luaL_Reg libs[] = { +#define ENTRY(name) { #name, Lutf8_##name } + ENTRY(offset), + ENTRY(codes), + ENTRY(codepoint), + + ENTRY(len), + ENTRY(sub), + ENTRY(reverse), + ENTRY(lower), + ENTRY(upper), + ENTRY(title), + ENTRY(fold), + ENTRY(byte), + ENTRY(char), + ENTRY(escape), + ENTRY(insert), + ENTRY(remove), + ENTRY(charpos), + ENTRY(next), + ENTRY(width), + ENTRY(widthindex), + ENTRY(ncasecmp), + ENTRY(find), + ENTRY(gmatch), + ENTRY(gsub), + ENTRY(match), +#undef ENTRY + { NULL, NULL } + }; + + luaL_newlib(L, libs); + + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)-1); + lua_setfield(L, -2, "charpattern"); + + return 1; +} diff --git a/src/meson.build b/src/meson.build index 5ad95bdd..7229ac72 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,7 @@ lite_sources = [ 'api/regex.c', 'api/system.c', 'api/process.c', + 'api/utf8.c', 'renderer.c', 'renwindow.c', 'rencache.c', diff --git a/src/unidata.h b/src/unidata.h new file mode 100644 index 00000000..13713f6c --- /dev/null +++ b/src/unidata.h @@ -0,0 +1,3710 @@ +/* + * unidata.h - generated by parseucd.lua + */ +#ifndef unidata_h +#define unidata_h + +#ifndef utfint +# define utfint utfint +typedef unsigned int utfint; +#endif + +typedef struct range_table { + utfint first; + utfint last; + int step; +} range_table; + +typedef struct conv_table { + utfint first; + utfint last; + int step; + int offset; +} conv_table; + +static struct range_table alpha_table[] = { + { 0x41, 0x5A, 1 }, + { 0x61, 0x7A, 1 }, + { 0xAA, 0xB5, 11 }, + { 0xBA, 0xC0, 6 }, + { 0xC1, 0xD6, 1 }, + { 0xD8, 0xF6, 1 }, + { 0xF8, 0x2C1, 1 }, + { 0x2C6, 0x2D1, 1 }, + { 0x2E0, 0x2E4, 1 }, + { 0x2EC, 0x2EE, 2 }, + { 0x345, 0x370, 43 }, + { 0x371, 0x374, 1 }, + { 0x376, 0x377, 1 }, + { 0x37A, 0x37D, 1 }, + { 0x37F, 0x386, 7 }, + { 0x388, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x3A1, 1 }, + { 0x3A3, 0x3F5, 1 }, + { 0x3F7, 0x481, 1 }, + { 0x48A, 0x52F, 1 }, + { 0x531, 0x556, 1 }, + { 0x559, 0x560, 7 }, + { 0x561, 0x588, 1 }, + { 0x5B0, 0x5BD, 1 }, + { 0x5BF, 0x5C1, 2 }, + { 0x5C2, 0x5C4, 2 }, + { 0x5C5, 0x5C7, 2 }, + { 0x5D0, 0x5EA, 1 }, + { 0x5EF, 0x5F2, 1 }, + { 0x610, 0x61A, 1 }, + { 0x620, 0x657, 1 }, + { 0x659, 0x65F, 1 }, + { 0x66E, 0x6D3, 1 }, + { 0x6D5, 0x6DC, 1 }, + { 0x6E1, 0x6E8, 1 }, + { 0x6ED, 0x6EF, 1 }, + { 0x6FA, 0x6FC, 1 }, + { 0x6FF, 0x710, 17 }, + { 0x711, 0x73F, 1 }, + { 0x74D, 0x7B1, 1 }, + { 0x7CA, 0x7EA, 1 }, + { 0x7F4, 0x7F5, 1 }, + { 0x7FA, 0x800, 6 }, + { 0x801, 0x817, 1 }, + { 0x81A, 0x82C, 1 }, + { 0x840, 0x858, 1 }, + { 0x860, 0x86A, 1 }, + { 0x870, 0x887, 1 }, + { 0x889, 0x88E, 1 }, + { 0x8A0, 0x8C9, 1 }, + { 0x8D4, 0x8DF, 1 }, + { 0x8E3, 0x8E9, 1 }, + { 0x8F0, 0x93B, 1 }, + { 0x93D, 0x94C, 1 }, + { 0x94E, 0x950, 1 }, + { 0x955, 0x963, 1 }, + { 0x971, 0x983, 1 }, + { 0x985, 0x98C, 1 }, + { 0x98F, 0x990, 1 }, + { 0x993, 0x9A8, 1 }, + { 0x9AA, 0x9B0, 1 }, + { 0x9B2, 0x9B6, 4 }, + { 0x9B7, 0x9B9, 1 }, + { 0x9BD, 0x9C4, 1 }, + { 0x9C7, 0x9C8, 1 }, + { 0x9CB, 0x9CC, 1 }, + { 0x9CE, 0x9D7, 9 }, + { 0x9DC, 0x9DD, 1 }, + { 0x9DF, 0x9E3, 1 }, + { 0x9F0, 0x9F1, 1 }, + { 0x9FC, 0xA01, 5 }, + { 0xA02, 0xA03, 1 }, + { 0xA05, 0xA0A, 1 }, + { 0xA0F, 0xA10, 1 }, + { 0xA13, 0xA28, 1 }, + { 0xA2A, 0xA30, 1 }, + { 0xA32, 0xA33, 1 }, + { 0xA35, 0xA36, 1 }, + { 0xA38, 0xA39, 1 }, + { 0xA3E, 0xA42, 1 }, + { 0xA47, 0xA48, 1 }, + { 0xA4B, 0xA4C, 1 }, + { 0xA51, 0xA59, 8 }, + { 0xA5A, 0xA5C, 1 }, + { 0xA5E, 0xA70, 18 }, + { 0xA71, 0xA75, 1 }, + { 0xA81, 0xA83, 1 }, + { 0xA85, 0xA8D, 1 }, + { 0xA8F, 0xA91, 1 }, + { 0xA93, 0xAA8, 1 }, + { 0xAAA, 0xAB0, 1 }, + { 0xAB2, 0xAB3, 1 }, + { 0xAB5, 0xAB9, 1 }, + { 0xABD, 0xAC5, 1 }, + { 0xAC7, 0xAC9, 1 }, + { 0xACB, 0xACC, 1 }, + { 0xAD0, 0xAE0, 16 }, + { 0xAE1, 0xAE3, 1 }, + { 0xAF9, 0xAFC, 1 }, + { 0xB01, 0xB03, 1 }, + { 0xB05, 0xB0C, 1 }, + { 0xB0F, 0xB10, 1 }, + { 0xB13, 0xB28, 1 }, + { 0xB2A, 0xB30, 1 }, + { 0xB32, 0xB33, 1 }, + { 0xB35, 0xB39, 1 }, + { 0xB3D, 0xB44, 1 }, + { 0xB47, 0xB48, 1 }, + { 0xB4B, 0xB4C, 1 }, + { 0xB56, 0xB57, 1 }, + { 0xB5C, 0xB5D, 1 }, + { 0xB5F, 0xB63, 1 }, + { 0xB71, 0xB82, 17 }, + { 0xB83, 0xB85, 2 }, + { 0xB86, 0xB8A, 1 }, + { 0xB8E, 0xB90, 1 }, + { 0xB92, 0xB95, 1 }, + { 0xB99, 0xB9A, 1 }, + { 0xB9C, 0xB9E, 2 }, + { 0xB9F, 0xBA3, 4 }, + { 0xBA4, 0xBA8, 4 }, + { 0xBA9, 0xBAA, 1 }, + { 0xBAE, 0xBB9, 1 }, + { 0xBBE, 0xBC2, 1 }, + { 0xBC6, 0xBC8, 1 }, + { 0xBCA, 0xBCC, 1 }, + { 0xBD0, 0xBD7, 7 }, + { 0xC00, 0xC03, 1 }, + { 0xC05, 0xC0C, 1 }, + { 0xC0E, 0xC10, 1 }, + { 0xC12, 0xC28, 1 }, + { 0xC2A, 0xC39, 1 }, + { 0xC3D, 0xC44, 1 }, + { 0xC46, 0xC48, 1 }, + { 0xC4A, 0xC4C, 1 }, + { 0xC55, 0xC56, 1 }, + { 0xC58, 0xC5A, 1 }, + { 0xC5D, 0xC60, 3 }, + { 0xC61, 0xC63, 1 }, + { 0xC80, 0xC83, 1 }, + { 0xC85, 0xC8C, 1 }, + { 0xC8E, 0xC90, 1 }, + { 0xC92, 0xCA8, 1 }, + { 0xCAA, 0xCB3, 1 }, + { 0xCB5, 0xCB9, 1 }, + { 0xCBD, 0xCC4, 1 }, + { 0xCC6, 0xCC8, 1 }, + { 0xCCA, 0xCCC, 1 }, + { 0xCD5, 0xCD6, 1 }, + { 0xCDD, 0xCDE, 1 }, + { 0xCE0, 0xCE3, 1 }, + { 0xCF1, 0xCF2, 1 }, + { 0xD00, 0xD0C, 1 }, + { 0xD0E, 0xD10, 1 }, + { 0xD12, 0xD3A, 1 }, + { 0xD3D, 0xD44, 1 }, + { 0xD46, 0xD48, 1 }, + { 0xD4A, 0xD4C, 1 }, + { 0xD4E, 0xD54, 6 }, + { 0xD55, 0xD57, 1 }, + { 0xD5F, 0xD63, 1 }, + { 0xD7A, 0xD7F, 1 }, + { 0xD81, 0xD83, 1 }, + { 0xD85, 0xD96, 1 }, + { 0xD9A, 0xDB1, 1 }, + { 0xDB3, 0xDBB, 1 }, + { 0xDBD, 0xDC0, 3 }, + { 0xDC1, 0xDC6, 1 }, + { 0xDCF, 0xDD4, 1 }, + { 0xDD6, 0xDD8, 2 }, + { 0xDD9, 0xDDF, 1 }, + { 0xDF2, 0xDF3, 1 }, + { 0xE01, 0xE3A, 1 }, + { 0xE40, 0xE46, 1 }, + { 0xE4D, 0xE81, 52 }, + { 0xE82, 0xE86, 2 }, + { 0xE87, 0xE8A, 1 }, + { 0xE8C, 0xEA3, 1 }, + { 0xEA5, 0xEA7, 2 }, + { 0xEA8, 0xEB9, 1 }, + { 0xEBB, 0xEBD, 1 }, + { 0xEC0, 0xEC4, 1 }, + { 0xEC6, 0xECD, 7 }, + { 0xEDC, 0xEDF, 1 }, + { 0xF00, 0xF40, 64 }, + { 0xF41, 0xF47, 1 }, + { 0xF49, 0xF6C, 1 }, + { 0xF71, 0xF81, 1 }, + { 0xF88, 0xF97, 1 }, + { 0xF99, 0xFBC, 1 }, + { 0x1000, 0x1036, 1 }, + { 0x1038, 0x103B, 3 }, + { 0x103C, 0x103F, 1 }, + { 0x1050, 0x108F, 1 }, + { 0x109A, 0x109D, 1 }, + { 0x10A0, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x10D0, 0x10FA, 1 }, + { 0x10FC, 0x1248, 1 }, + { 0x124A, 0x124D, 1 }, + { 0x1250, 0x1256, 1 }, + { 0x1258, 0x125A, 2 }, + { 0x125B, 0x125D, 1 }, + { 0x1260, 0x1288, 1 }, + { 0x128A, 0x128D, 1 }, + { 0x1290, 0x12B0, 1 }, + { 0x12B2, 0x12B5, 1 }, + { 0x12B8, 0x12BE, 1 }, + { 0x12C0, 0x12C2, 2 }, + { 0x12C3, 0x12C5, 1 }, + { 0x12C8, 0x12D6, 1 }, + { 0x12D8, 0x1310, 1 }, + { 0x1312, 0x1315, 1 }, + { 0x1318, 0x135A, 1 }, + { 0x1380, 0x138F, 1 }, + { 0x13A0, 0x13F5, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1401, 0x166C, 1 }, + { 0x166F, 0x167F, 1 }, + { 0x1681, 0x169A, 1 }, + { 0x16A0, 0x16EA, 1 }, + { 0x16EE, 0x16F8, 1 }, + { 0x1700, 0x1713, 1 }, + { 0x171F, 0x1733, 1 }, + { 0x1740, 0x1753, 1 }, + { 0x1760, 0x176C, 1 }, + { 0x176E, 0x1770, 1 }, + { 0x1772, 0x1773, 1 }, + { 0x1780, 0x17B3, 1 }, + { 0x17B6, 0x17C8, 1 }, + { 0x17D7, 0x17DC, 5 }, + { 0x1820, 0x1878, 1 }, + { 0x1880, 0x18AA, 1 }, + { 0x18B0, 0x18F5, 1 }, + { 0x1900, 0x191E, 1 }, + { 0x1920, 0x192B, 1 }, + { 0x1930, 0x1938, 1 }, + { 0x1950, 0x196D, 1 }, + { 0x1970, 0x1974, 1 }, + { 0x1980, 0x19AB, 1 }, + { 0x19B0, 0x19C9, 1 }, + { 0x1A00, 0x1A1B, 1 }, + { 0x1A20, 0x1A5E, 1 }, + { 0x1A61, 0x1A74, 1 }, + { 0x1AA7, 0x1ABF, 24 }, + { 0x1AC0, 0x1ACC, 12 }, + { 0x1ACD, 0x1ACE, 1 }, + { 0x1B00, 0x1B33, 1 }, + { 0x1B35, 0x1B43, 1 }, + { 0x1B45, 0x1B4C, 1 }, + { 0x1B80, 0x1BA9, 1 }, + { 0x1BAC, 0x1BAF, 1 }, + { 0x1BBA, 0x1BE5, 1 }, + { 0x1BE7, 0x1BF1, 1 }, + { 0x1C00, 0x1C36, 1 }, + { 0x1C4D, 0x1C4F, 1 }, + { 0x1C5A, 0x1C7D, 1 }, + { 0x1C80, 0x1C88, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CBF, 1 }, + { 0x1CE9, 0x1CEC, 1 }, + { 0x1CEE, 0x1CF3, 1 }, + { 0x1CF5, 0x1CF6, 1 }, + { 0x1CFA, 0x1D00, 6 }, + { 0x1D01, 0x1DBF, 1 }, + { 0x1DE7, 0x1DF4, 1 }, + { 0x1E00, 0x1F15, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F20, 0x1F45, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F60, 0x1F7D, 1 }, + { 0x1F80, 0x1FB4, 1 }, + { 0x1FB6, 0x1FBC, 1 }, + { 0x1FBE, 0x1FC2, 4 }, + { 0x1FC3, 0x1FC4, 1 }, + { 0x1FC6, 0x1FCC, 1 }, + { 0x1FD0, 0x1FD3, 1 }, + { 0x1FD6, 0x1FDB, 1 }, + { 0x1FE0, 0x1FEC, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FFC, 1 }, + { 0x2071, 0x207F, 14 }, + { 0x2090, 0x209C, 1 }, + { 0x2102, 0x2107, 5 }, + { 0x210A, 0x2113, 1 }, + { 0x2115, 0x2119, 4 }, + { 0x211A, 0x211D, 1 }, + { 0x2124, 0x212A, 2 }, + { 0x212B, 0x212D, 1 }, + { 0x212F, 0x2139, 1 }, + { 0x213C, 0x213F, 1 }, + { 0x2145, 0x2149, 1 }, + { 0x214E, 0x2160, 18 }, + { 0x2161, 0x2188, 1 }, + { 0x24B6, 0x24E9, 1 }, + { 0x2C00, 0x2CE4, 1 }, + { 0x2CEB, 0x2CEE, 1 }, + { 0x2CF2, 0x2CF3, 1 }, + { 0x2D00, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0x2D30, 0x2D67, 1 }, + { 0x2D6F, 0x2D80, 17 }, + { 0x2D81, 0x2D96, 1 }, + { 0x2DA0, 0x2DA6, 1 }, + { 0x2DA8, 0x2DAE, 1 }, + { 0x2DB0, 0x2DB6, 1 }, + { 0x2DB8, 0x2DBE, 1 }, + { 0x2DC0, 0x2DC6, 1 }, + { 0x2DC8, 0x2DCE, 1 }, + { 0x2DD0, 0x2DD6, 1 }, + { 0x2DD8, 0x2DDE, 1 }, + { 0x2DE0, 0x2DFF, 1 }, + { 0x2E2F, 0x3005, 470 }, + { 0x3006, 0x3007, 1 }, + { 0x3021, 0x3029, 1 }, + { 0x3031, 0x3035, 1 }, + { 0x3038, 0x303C, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x309D, 0x309F, 1 }, + { 0x30A1, 0x30FA, 1 }, + { 0x30FC, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x31A0, 0x31BF, 1 }, + { 0x31F0, 0x31FF, 1 }, + { 0x3400, 0x4DBF, 1 }, + { 0x4E00, 0xA48C, 1 }, + { 0xA4D0, 0xA4FD, 1 }, + { 0xA500, 0xA60C, 1 }, + { 0xA610, 0xA61F, 1 }, + { 0xA62A, 0xA62B, 1 }, + { 0xA640, 0xA66E, 1 }, + { 0xA674, 0xA67B, 1 }, + { 0xA67F, 0xA6EF, 1 }, + { 0xA717, 0xA71F, 1 }, + { 0xA722, 0xA788, 1 }, + { 0xA78B, 0xA7CA, 1 }, + { 0xA7D0, 0xA7D1, 1 }, + { 0xA7D3, 0xA7D5, 2 }, + { 0xA7D6, 0xA7D9, 1 }, + { 0xA7F2, 0xA805, 1 }, + { 0xA807, 0xA827, 1 }, + { 0xA840, 0xA873, 1 }, + { 0xA880, 0xA8C3, 1 }, + { 0xA8C5, 0xA8F2, 45 }, + { 0xA8F3, 0xA8F7, 1 }, + { 0xA8FB, 0xA8FD, 2 }, + { 0xA8FE, 0xA8FF, 1 }, + { 0xA90A, 0xA92A, 1 }, + { 0xA930, 0xA952, 1 }, + { 0xA960, 0xA97C, 1 }, + { 0xA980, 0xA9B2, 1 }, + { 0xA9B4, 0xA9BF, 1 }, + { 0xA9CF, 0xA9E0, 17 }, + { 0xA9E1, 0xA9EF, 1 }, + { 0xA9FA, 0xA9FE, 1 }, + { 0xAA00, 0xAA36, 1 }, + { 0xAA40, 0xAA4D, 1 }, + { 0xAA60, 0xAA76, 1 }, + { 0xAA7A, 0xAABE, 1 }, + { 0xAAC0, 0xAAC2, 2 }, + { 0xAADB, 0xAADD, 1 }, + { 0xAAE0, 0xAAEF, 1 }, + { 0xAAF2, 0xAAF5, 1 }, + { 0xAB01, 0xAB06, 1 }, + { 0xAB09, 0xAB0E, 1 }, + { 0xAB11, 0xAB16, 1 }, + { 0xAB20, 0xAB26, 1 }, + { 0xAB28, 0xAB2E, 1 }, + { 0xAB30, 0xAB5A, 1 }, + { 0xAB5C, 0xAB69, 1 }, + { 0xAB70, 0xABEA, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xD7B0, 0xD7C6, 1 }, + { 0xD7CB, 0xD7FB, 1 }, + { 0xF900, 0xFA6D, 1 }, + { 0xFA70, 0xFAD9, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFB1D, 0xFB28, 1 }, + { 0xFB2A, 0xFB36, 1 }, + { 0xFB38, 0xFB3C, 1 }, + { 0xFB3E, 0xFB40, 2 }, + { 0xFB41, 0xFB43, 2 }, + { 0xFB44, 0xFB46, 2 }, + { 0xFB47, 0xFBB1, 1 }, + { 0xFBD3, 0xFD3D, 1 }, + { 0xFD50, 0xFD8F, 1 }, + { 0xFD92, 0xFDC7, 1 }, + { 0xFDF0, 0xFDFB, 1 }, + { 0xFE70, 0xFE74, 1 }, + { 0xFE76, 0xFEFC, 1 }, + { 0xFF21, 0xFF3A, 1 }, + { 0xFF41, 0xFF5A, 1 }, + { 0xFF66, 0xFFBE, 1 }, + { 0xFFC2, 0xFFC7, 1 }, + { 0xFFCA, 0xFFCF, 1 }, + { 0xFFD2, 0xFFD7, 1 }, + { 0xFFDA, 0xFFDC, 1 }, + { 0x10000, 0x1000B, 1 }, + { 0x1000D, 0x10026, 1 }, + { 0x10028, 0x1003A, 1 }, + { 0x1003C, 0x1003D, 1 }, + { 0x1003F, 0x1004D, 1 }, + { 0x10050, 0x1005D, 1 }, + { 0x10080, 0x100FA, 1 }, + { 0x10140, 0x10174, 1 }, + { 0x10280, 0x1029C, 1 }, + { 0x102A0, 0x102D0, 1 }, + { 0x10300, 0x1031F, 1 }, + { 0x1032D, 0x1034A, 1 }, + { 0x10350, 0x1037A, 1 }, + { 0x10380, 0x1039D, 1 }, + { 0x103A0, 0x103C3, 1 }, + { 0x103C8, 0x103CF, 1 }, + { 0x103D1, 0x103D5, 1 }, + { 0x10400, 0x1049D, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10500, 0x10527, 1 }, + { 0x10530, 0x10563, 1 }, + { 0x10570, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10600, 0x10736, 1 }, + { 0x10740, 0x10755, 1 }, + { 0x10760, 0x10767, 1 }, + { 0x10780, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10800, 0x10805, 1 }, + { 0x10808, 0x1080A, 2 }, + { 0x1080B, 0x10835, 1 }, + { 0x10837, 0x10838, 1 }, + { 0x1083C, 0x1083F, 3 }, + { 0x10840, 0x10855, 1 }, + { 0x10860, 0x10876, 1 }, + { 0x10880, 0x1089E, 1 }, + { 0x108E0, 0x108F2, 1 }, + { 0x108F4, 0x108F5, 1 }, + { 0x10900, 0x10915, 1 }, + { 0x10920, 0x10939, 1 }, + { 0x10980, 0x109B7, 1 }, + { 0x109BE, 0x109BF, 1 }, + { 0x10A00, 0x10A03, 1 }, + { 0x10A05, 0x10A06, 1 }, + { 0x10A0C, 0x10A13, 1 }, + { 0x10A15, 0x10A17, 1 }, + { 0x10A19, 0x10A35, 1 }, + { 0x10A60, 0x10A7C, 1 }, + { 0x10A80, 0x10A9C, 1 }, + { 0x10AC0, 0x10AC7, 1 }, + { 0x10AC9, 0x10AE4, 1 }, + { 0x10B00, 0x10B35, 1 }, + { 0x10B40, 0x10B55, 1 }, + { 0x10B60, 0x10B72, 1 }, + { 0x10B80, 0x10B91, 1 }, + { 0x10C00, 0x10C48, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x10D00, 0x10D27, 1 }, + { 0x10E80, 0x10EA9, 1 }, + { 0x10EAB, 0x10EAC, 1 }, + { 0x10EB0, 0x10EB1, 1 }, + { 0x10F00, 0x10F1C, 1 }, + { 0x10F27, 0x10F30, 9 }, + { 0x10F31, 0x10F45, 1 }, + { 0x10F70, 0x10F81, 1 }, + { 0x10FB0, 0x10FC4, 1 }, + { 0x10FE0, 0x10FF6, 1 }, + { 0x11000, 0x11045, 1 }, + { 0x11071, 0x11075, 1 }, + { 0x11082, 0x110B8, 1 }, + { 0x110C2, 0x110D0, 14 }, + { 0x110D1, 0x110E8, 1 }, + { 0x11100, 0x11132, 1 }, + { 0x11144, 0x11147, 1 }, + { 0x11150, 0x11172, 1 }, + { 0x11176, 0x11180, 10 }, + { 0x11181, 0x111BF, 1 }, + { 0x111C1, 0x111C4, 1 }, + { 0x111CE, 0x111CF, 1 }, + { 0x111DA, 0x111DC, 2 }, + { 0x11200, 0x11211, 1 }, + { 0x11213, 0x11234, 1 }, + { 0x11237, 0x1123E, 7 }, + { 0x11280, 0x11286, 1 }, + { 0x11288, 0x1128A, 2 }, + { 0x1128B, 0x1128D, 1 }, + { 0x1128F, 0x1129D, 1 }, + { 0x1129F, 0x112A8, 1 }, + { 0x112B0, 0x112E8, 1 }, + { 0x11300, 0x11303, 1 }, + { 0x11305, 0x1130C, 1 }, + { 0x1130F, 0x11310, 1 }, + { 0x11313, 0x11328, 1 }, + { 0x1132A, 0x11330, 1 }, + { 0x11332, 0x11333, 1 }, + { 0x11335, 0x11339, 1 }, + { 0x1133D, 0x11344, 1 }, + { 0x11347, 0x11348, 1 }, + { 0x1134B, 0x1134C, 1 }, + { 0x11350, 0x11357, 7 }, + { 0x1135D, 0x11363, 1 }, + { 0x11400, 0x11441, 1 }, + { 0x11443, 0x11445, 1 }, + { 0x11447, 0x1144A, 1 }, + { 0x1145F, 0x11461, 1 }, + { 0x11480, 0x114C1, 1 }, + { 0x114C4, 0x114C5, 1 }, + { 0x114C7, 0x11580, 185 }, + { 0x11581, 0x115B5, 1 }, + { 0x115B8, 0x115BE, 1 }, + { 0x115D8, 0x115DD, 1 }, + { 0x11600, 0x1163E, 1 }, + { 0x11640, 0x11644, 4 }, + { 0x11680, 0x116B5, 1 }, + { 0x116B8, 0x11700, 72 }, + { 0x11701, 0x1171A, 1 }, + { 0x1171D, 0x1172A, 1 }, + { 0x11740, 0x11746, 1 }, + { 0x11800, 0x11838, 1 }, + { 0x118A0, 0x118DF, 1 }, + { 0x118FF, 0x11906, 1 }, + { 0x11909, 0x1190C, 3 }, + { 0x1190D, 0x11913, 1 }, + { 0x11915, 0x11916, 1 }, + { 0x11918, 0x11935, 1 }, + { 0x11937, 0x11938, 1 }, + { 0x1193B, 0x1193C, 1 }, + { 0x1193F, 0x11942, 1 }, + { 0x119A0, 0x119A7, 1 }, + { 0x119AA, 0x119D7, 1 }, + { 0x119DA, 0x119DF, 1 }, + { 0x119E1, 0x119E3, 2 }, + { 0x119E4, 0x11A00, 28 }, + { 0x11A01, 0x11A32, 1 }, + { 0x11A35, 0x11A3E, 1 }, + { 0x11A50, 0x11A97, 1 }, + { 0x11A9D, 0x11AB0, 19 }, + { 0x11AB1, 0x11AF8, 1 }, + { 0x11C00, 0x11C08, 1 }, + { 0x11C0A, 0x11C36, 1 }, + { 0x11C38, 0x11C3E, 1 }, + { 0x11C40, 0x11C72, 50 }, + { 0x11C73, 0x11C8F, 1 }, + { 0x11C92, 0x11CA7, 1 }, + { 0x11CA9, 0x11CB6, 1 }, + { 0x11D00, 0x11D06, 1 }, + { 0x11D08, 0x11D09, 1 }, + { 0x11D0B, 0x11D36, 1 }, + { 0x11D3A, 0x11D3C, 2 }, + { 0x11D3D, 0x11D3F, 2 }, + { 0x11D40, 0x11D41, 1 }, + { 0x11D43, 0x11D46, 3 }, + { 0x11D47, 0x11D60, 25 }, + { 0x11D61, 0x11D65, 1 }, + { 0x11D67, 0x11D68, 1 }, + { 0x11D6A, 0x11D8E, 1 }, + { 0x11D90, 0x11D91, 1 }, + { 0x11D93, 0x11D96, 1 }, + { 0x11D98, 0x11EE0, 328 }, + { 0x11EE1, 0x11EF6, 1 }, + { 0x11FB0, 0x12000, 80 }, + { 0x12001, 0x12399, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x12480, 0x12543, 1 }, + { 0x12F90, 0x12FF0, 1 }, + { 0x13000, 0x1342E, 1 }, + { 0x14400, 0x14646, 1 }, + { 0x16800, 0x16A38, 1 }, + { 0x16A40, 0x16A5E, 1 }, + { 0x16A70, 0x16ABE, 1 }, + { 0x16AD0, 0x16AED, 1 }, + { 0x16B00, 0x16B2F, 1 }, + { 0x16B40, 0x16B43, 1 }, + { 0x16B63, 0x16B77, 1 }, + { 0x16B7D, 0x16B8F, 1 }, + { 0x16E40, 0x16E7F, 1 }, + { 0x16F00, 0x16F4A, 1 }, + { 0x16F4F, 0x16F87, 1 }, + { 0x16F8F, 0x16F9F, 1 }, + { 0x16FE0, 0x16FE1, 1 }, + { 0x16FE3, 0x16FF0, 13 }, + { 0x16FF1, 0x17000, 15 }, + { 0x17001, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B150, 0x1B152, 1 }, + { 0x1B164, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1BC00, 0x1BC6A, 1 }, + { 0x1BC70, 0x1BC7C, 1 }, + { 0x1BC80, 0x1BC88, 1 }, + { 0x1BC90, 0x1BC99, 1 }, + { 0x1BC9E, 0x1D400, 5986 }, + { 0x1D401, 0x1D454, 1 }, + { 0x1D456, 0x1D49C, 1 }, + { 0x1D49E, 0x1D49F, 1 }, + { 0x1D4A2, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D51E, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D552, 0x1D6A5, 1 }, + { 0x1D6A8, 0x1D6C0, 1 }, + { 0x1D6C2, 0x1D6DA, 1 }, + { 0x1D6DC, 0x1D6FA, 1 }, + { 0x1D6FC, 0x1D714, 1 }, + { 0x1D716, 0x1D734, 1 }, + { 0x1D736, 0x1D74E, 1 }, + { 0x1D750, 0x1D76E, 1 }, + { 0x1D770, 0x1D788, 1 }, + { 0x1D78A, 0x1D7A8, 1 }, + { 0x1D7AA, 0x1D7C2, 1 }, + { 0x1D7C4, 0x1D7CB, 1 }, + { 0x1DF00, 0x1DF1E, 1 }, + { 0x1E000, 0x1E006, 1 }, + { 0x1E008, 0x1E018, 1 }, + { 0x1E01B, 0x1E021, 1 }, + { 0x1E023, 0x1E024, 1 }, + { 0x1E026, 0x1E02A, 1 }, + { 0x1E100, 0x1E12C, 1 }, + { 0x1E137, 0x1E13D, 1 }, + { 0x1E14E, 0x1E290, 322 }, + { 0x1E291, 0x1E2AD, 1 }, + { 0x1E2C0, 0x1E2EB, 1 }, + { 0x1E7E0, 0x1E7E6, 1 }, + { 0x1E7E8, 0x1E7EB, 1 }, + { 0x1E7ED, 0x1E7EE, 1 }, + { 0x1E7F0, 0x1E7FE, 1 }, + { 0x1E800, 0x1E8C4, 1 }, + { 0x1E900, 0x1E943, 1 }, + { 0x1E947, 0x1E94B, 4 }, + { 0x1EE00, 0x1EE03, 1 }, + { 0x1EE05, 0x1EE1F, 1 }, + { 0x1EE21, 0x1EE22, 1 }, + { 0x1EE24, 0x1EE27, 3 }, + { 0x1EE29, 0x1EE32, 1 }, + { 0x1EE34, 0x1EE37, 1 }, + { 0x1EE39, 0x1EE3B, 2 }, + { 0x1EE42, 0x1EE47, 5 }, + { 0x1EE49, 0x1EE4D, 2 }, + { 0x1EE4E, 0x1EE4F, 1 }, + { 0x1EE51, 0x1EE52, 1 }, + { 0x1EE54, 0x1EE57, 3 }, + { 0x1EE59, 0x1EE61, 2 }, + { 0x1EE62, 0x1EE64, 2 }, + { 0x1EE67, 0x1EE6A, 1 }, + { 0x1EE6C, 0x1EE72, 1 }, + { 0x1EE74, 0x1EE77, 1 }, + { 0x1EE79, 0x1EE7C, 1 }, + { 0x1EE7E, 0x1EE80, 2 }, + { 0x1EE81, 0x1EE89, 1 }, + { 0x1EE8B, 0x1EE9B, 1 }, + { 0x1EEA1, 0x1EEA3, 1 }, + { 0x1EEA5, 0x1EEA9, 1 }, + { 0x1EEAB, 0x1EEBB, 1 }, + { 0x1F130, 0x1F149, 1 }, + { 0x1F150, 0x1F169, 1 }, + { 0x1F170, 0x1F189, 1 }, + { 0x20000, 0x2A6DF, 1 }, + { 0x2A700, 0x2B738, 1 }, + { 0x2B740, 0x2B81D, 1 }, + { 0x2B820, 0x2CEA1, 1 }, + { 0x2CEB0, 0x2EBE0, 1 }, + { 0x2F800, 0x2FA1D, 1 }, + { 0x30000, 0x3134A, 1 }, +}; + +static struct range_table lower_table[] = { + { 0x61, 0x7A, 1 }, + { 0xAA, 0xB5, 11 }, + { 0xBA, 0xDF, 37 }, + { 0xE0, 0xF6, 1 }, + { 0xF8, 0xFF, 1 }, + { 0x101, 0x137, 2 }, + { 0x138, 0x148, 2 }, + { 0x149, 0x177, 2 }, + { 0x17A, 0x17E, 2 }, + { 0x17F, 0x180, 1 }, + { 0x183, 0x185, 2 }, + { 0x188, 0x18C, 4 }, + { 0x18D, 0x192, 5 }, + { 0x195, 0x199, 4 }, + { 0x19A, 0x19B, 1 }, + { 0x19E, 0x1A1, 3 }, + { 0x1A3, 0x1A5, 2 }, + { 0x1A8, 0x1AA, 2 }, + { 0x1AB, 0x1AD, 2 }, + { 0x1B0, 0x1B4, 4 }, + { 0x1B6, 0x1B9, 3 }, + { 0x1BA, 0x1BD, 3 }, + { 0x1BE, 0x1BF, 1 }, + { 0x1C6, 0x1CC, 3 }, + { 0x1CE, 0x1DC, 2 }, + { 0x1DD, 0x1EF, 2 }, + { 0x1F0, 0x1F3, 3 }, + { 0x1F5, 0x1F9, 4 }, + { 0x1FB, 0x233, 2 }, + { 0x234, 0x239, 1 }, + { 0x23C, 0x23F, 3 }, + { 0x240, 0x242, 2 }, + { 0x247, 0x24F, 2 }, + { 0x250, 0x293, 1 }, + { 0x295, 0x2B8, 1 }, + { 0x2C0, 0x2C1, 1 }, + { 0x2E0, 0x2E4, 1 }, + { 0x345, 0x371, 44 }, + { 0x373, 0x377, 4 }, + { 0x37A, 0x37D, 1 }, + { 0x390, 0x3AC, 28 }, + { 0x3AD, 0x3CE, 1 }, + { 0x3D0, 0x3D1, 1 }, + { 0x3D5, 0x3D7, 1 }, + { 0x3D9, 0x3EF, 2 }, + { 0x3F0, 0x3F3, 1 }, + { 0x3F5, 0x3FB, 3 }, + { 0x3FC, 0x430, 52 }, + { 0x431, 0x45F, 1 }, + { 0x461, 0x481, 2 }, + { 0x48B, 0x4BF, 2 }, + { 0x4C2, 0x4CE, 2 }, + { 0x4CF, 0x52F, 2 }, + { 0x560, 0x588, 1 }, + { 0x10D0, 0x10FA, 1 }, + { 0x10FD, 0x10FF, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1C80, 0x1C88, 1 }, + { 0x1D00, 0x1DBF, 1 }, + { 0x1E01, 0x1E95, 2 }, + { 0x1E96, 0x1E9D, 1 }, + { 0x1E9F, 0x1EFF, 2 }, + { 0x1F00, 0x1F07, 1 }, + { 0x1F10, 0x1F15, 1 }, + { 0x1F20, 0x1F27, 1 }, + { 0x1F30, 0x1F37, 1 }, + { 0x1F40, 0x1F45, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F60, 0x1F67, 1 }, + { 0x1F70, 0x1F7D, 1 }, + { 0x1F80, 0x1F87, 1 }, + { 0x1F90, 0x1F97, 1 }, + { 0x1FA0, 0x1FA7, 1 }, + { 0x1FB0, 0x1FB4, 1 }, + { 0x1FB6, 0x1FB7, 1 }, + { 0x1FBE, 0x1FC2, 4 }, + { 0x1FC3, 0x1FC4, 1 }, + { 0x1FC6, 0x1FC7, 1 }, + { 0x1FD0, 0x1FD3, 1 }, + { 0x1FD6, 0x1FD7, 1 }, + { 0x1FE0, 0x1FE7, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FF7, 1 }, + { 0x2071, 0x207F, 14 }, + { 0x2090, 0x209C, 1 }, + { 0x210A, 0x210E, 4 }, + { 0x210F, 0x2113, 4 }, + { 0x212F, 0x2139, 5 }, + { 0x213C, 0x213D, 1 }, + { 0x2146, 0x2149, 1 }, + { 0x214E, 0x2170, 34 }, + { 0x2171, 0x217F, 1 }, + { 0x2184, 0x24D0, 844 }, + { 0x24D1, 0x24E9, 1 }, + { 0x2C30, 0x2C5F, 1 }, + { 0x2C61, 0x2C65, 4 }, + { 0x2C66, 0x2C6C, 2 }, + { 0x2C71, 0x2C73, 2 }, + { 0x2C74, 0x2C76, 2 }, + { 0x2C77, 0x2C7D, 1 }, + { 0x2C81, 0x2CE3, 2 }, + { 0x2CE4, 0x2CEC, 8 }, + { 0x2CEE, 0x2CF3, 5 }, + { 0x2D00, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0xA641, 0xA66D, 2 }, + { 0xA681, 0xA69B, 2 }, + { 0xA69C, 0xA69D, 1 }, + { 0xA723, 0xA72F, 2 }, + { 0xA730, 0xA731, 1 }, + { 0xA733, 0xA76F, 2 }, + { 0xA770, 0xA778, 1 }, + { 0xA77A, 0xA77C, 2 }, + { 0xA77F, 0xA787, 2 }, + { 0xA78C, 0xA78E, 2 }, + { 0xA791, 0xA793, 2 }, + { 0xA794, 0xA795, 1 }, + { 0xA797, 0xA7A9, 2 }, + { 0xA7AF, 0xA7B5, 6 }, + { 0xA7B7, 0xA7C3, 2 }, + { 0xA7C8, 0xA7CA, 2 }, + { 0xA7D1, 0xA7D9, 2 }, + { 0xA7F6, 0xA7F8, 2 }, + { 0xA7F9, 0xA7FA, 1 }, + { 0xAB30, 0xAB5A, 1 }, + { 0xAB5C, 0xAB68, 1 }, + { 0xAB70, 0xABBF, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFF41, 0xFF5A, 1 }, + { 0x10428, 0x1044F, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10780, 0x10783, 3 }, + { 0x10784, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x118C0, 0x118DF, 1 }, + { 0x16E60, 0x16E7F, 1 }, + { 0x1D41A, 0x1D433, 1 }, + { 0x1D44E, 0x1D454, 1 }, + { 0x1D456, 0x1D467, 1 }, + { 0x1D482, 0x1D49B, 1 }, + { 0x1D4B6, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D4CF, 1 }, + { 0x1D4EA, 0x1D503, 1 }, + { 0x1D51E, 0x1D537, 1 }, + { 0x1D552, 0x1D56B, 1 }, + { 0x1D586, 0x1D59F, 1 }, + { 0x1D5BA, 0x1D5D3, 1 }, + { 0x1D5EE, 0x1D607, 1 }, + { 0x1D622, 0x1D63B, 1 }, + { 0x1D656, 0x1D66F, 1 }, + { 0x1D68A, 0x1D6A5, 1 }, + { 0x1D6C2, 0x1D6DA, 1 }, + { 0x1D6DC, 0x1D6E1, 1 }, + { 0x1D6FC, 0x1D714, 1 }, + { 0x1D716, 0x1D71B, 1 }, + { 0x1D736, 0x1D74E, 1 }, + { 0x1D750, 0x1D755, 1 }, + { 0x1D770, 0x1D788, 1 }, + { 0x1D78A, 0x1D78F, 1 }, + { 0x1D7AA, 0x1D7C2, 1 }, + { 0x1D7C4, 0x1D7C9, 1 }, + { 0x1D7CB, 0x1DF00, 1845 }, + { 0x1DF01, 0x1DF09, 1 }, + { 0x1DF0B, 0x1DF1E, 1 }, + { 0x1E922, 0x1E943, 1 }, +}; + +static struct range_table upper_table[] = { + { 0x41, 0x5A, 1 }, + { 0xC0, 0xD6, 1 }, + { 0xD8, 0xDE, 1 }, + { 0x100, 0x136, 2 }, + { 0x139, 0x147, 2 }, + { 0x14A, 0x178, 2 }, + { 0x179, 0x17D, 2 }, + { 0x181, 0x182, 1 }, + { 0x184, 0x186, 2 }, + { 0x187, 0x189, 2 }, + { 0x18A, 0x18B, 1 }, + { 0x18E, 0x191, 1 }, + { 0x193, 0x194, 1 }, + { 0x196, 0x198, 1 }, + { 0x19C, 0x19D, 1 }, + { 0x19F, 0x1A0, 1 }, + { 0x1A2, 0x1A6, 2 }, + { 0x1A7, 0x1A9, 2 }, + { 0x1AC, 0x1AE, 2 }, + { 0x1AF, 0x1B1, 2 }, + { 0x1B2, 0x1B3, 1 }, + { 0x1B5, 0x1B7, 2 }, + { 0x1B8, 0x1BC, 4 }, + { 0x1C4, 0x1CD, 3 }, + { 0x1CF, 0x1DB, 2 }, + { 0x1DE, 0x1EE, 2 }, + { 0x1F1, 0x1F4, 3 }, + { 0x1F6, 0x1F8, 1 }, + { 0x1FA, 0x232, 2 }, + { 0x23A, 0x23B, 1 }, + { 0x23D, 0x23E, 1 }, + { 0x241, 0x243, 2 }, + { 0x244, 0x246, 1 }, + { 0x248, 0x24E, 2 }, + { 0x370, 0x372, 2 }, + { 0x376, 0x37F, 9 }, + { 0x386, 0x388, 2 }, + { 0x389, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x391, 2 }, + { 0x392, 0x3A1, 1 }, + { 0x3A3, 0x3AB, 1 }, + { 0x3CF, 0x3D2, 3 }, + { 0x3D3, 0x3D4, 1 }, + { 0x3D8, 0x3EE, 2 }, + { 0x3F4, 0x3F7, 3 }, + { 0x3F9, 0x3FA, 1 }, + { 0x3FD, 0x42F, 1 }, + { 0x460, 0x480, 2 }, + { 0x48A, 0x4C0, 2 }, + { 0x4C1, 0x4CD, 2 }, + { 0x4D0, 0x52E, 2 }, + { 0x531, 0x556, 1 }, + { 0x10A0, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x13A0, 0x13F5, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CBF, 1 }, + { 0x1E00, 0x1E94, 2 }, + { 0x1E9E, 0x1EFE, 2 }, + { 0x1F08, 0x1F0F, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F28, 0x1F2F, 1 }, + { 0x1F38, 0x1F3F, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F68, 0x1F6F, 1 }, + { 0x1FB8, 0x1FBB, 1 }, + { 0x1FC8, 0x1FCB, 1 }, + { 0x1FD8, 0x1FDB, 1 }, + { 0x1FE8, 0x1FEC, 1 }, + { 0x1FF8, 0x1FFB, 1 }, + { 0x2102, 0x2107, 5 }, + { 0x210B, 0x210D, 1 }, + { 0x2110, 0x2112, 1 }, + { 0x2115, 0x2119, 4 }, + { 0x211A, 0x211D, 1 }, + { 0x2124, 0x212A, 2 }, + { 0x212B, 0x212D, 1 }, + { 0x2130, 0x2133, 1 }, + { 0x213E, 0x213F, 1 }, + { 0x2145, 0x2160, 27 }, + { 0x2161, 0x216F, 1 }, + { 0x2183, 0x24B6, 819 }, + { 0x24B7, 0x24CF, 1 }, + { 0x2C00, 0x2C2F, 1 }, + { 0x2C60, 0x2C62, 2 }, + { 0x2C63, 0x2C64, 1 }, + { 0x2C67, 0x2C6D, 2 }, + { 0x2C6E, 0x2C70, 1 }, + { 0x2C72, 0x2C75, 3 }, + { 0x2C7E, 0x2C80, 1 }, + { 0x2C82, 0x2CE2, 2 }, + { 0x2CEB, 0x2CED, 2 }, + { 0x2CF2, 0xA640, 31054 }, + { 0xA642, 0xA66C, 2 }, + { 0xA680, 0xA69A, 2 }, + { 0xA722, 0xA72E, 2 }, + { 0xA732, 0xA76E, 2 }, + { 0xA779, 0xA77D, 2 }, + { 0xA77E, 0xA786, 2 }, + { 0xA78B, 0xA78D, 2 }, + { 0xA790, 0xA792, 2 }, + { 0xA796, 0xA7AA, 2 }, + { 0xA7AB, 0xA7AE, 1 }, + { 0xA7B0, 0xA7B4, 1 }, + { 0xA7B6, 0xA7C4, 2 }, + { 0xA7C5, 0xA7C7, 1 }, + { 0xA7C9, 0xA7D0, 7 }, + { 0xA7D6, 0xA7D8, 2 }, + { 0xA7F5, 0xFF21, 22316 }, + { 0xFF22, 0xFF3A, 1 }, + { 0x10400, 0x10427, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x10570, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x118A0, 0x118BF, 1 }, + { 0x16E40, 0x16E5F, 1 }, + { 0x1D400, 0x1D419, 1 }, + { 0x1D434, 0x1D44D, 1 }, + { 0x1D468, 0x1D481, 1 }, + { 0x1D49C, 0x1D49E, 2 }, + { 0x1D49F, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B5, 1 }, + { 0x1D4D0, 0x1D4E9, 1 }, + { 0x1D504, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D538, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D56C, 0x1D585, 1 }, + { 0x1D5A0, 0x1D5B9, 1 }, + { 0x1D5D4, 0x1D5ED, 1 }, + { 0x1D608, 0x1D621, 1 }, + { 0x1D63C, 0x1D655, 1 }, + { 0x1D670, 0x1D689, 1 }, + { 0x1D6A8, 0x1D6C0, 1 }, + { 0x1D6E2, 0x1D6FA, 1 }, + { 0x1D71C, 0x1D734, 1 }, + { 0x1D756, 0x1D76E, 1 }, + { 0x1D790, 0x1D7A8, 1 }, + { 0x1D7CA, 0x1E900, 4406 }, + { 0x1E901, 0x1E921, 1 }, + { 0x1F130, 0x1F149, 1 }, + { 0x1F150, 0x1F169, 1 }, + { 0x1F170, 0x1F189, 1 }, +}; + +static struct range_table xdigit_table[] = { + { 0x30, 0x39, 1 }, + { 0x41, 0x46, 1 }, + { 0x61, 0x66, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0xFF21, 0xFF26, 1 }, + { 0xFF41, 0xFF46, 1 }, +}; + +static struct range_table space_table[] = { + { 0x9, 0xD, 1 }, + { 0x20, 0x85, 101 }, + { 0xA0, 0x1680, 5600 }, + { 0x2000, 0x200A, 1 }, + { 0x2028, 0x2029, 1 }, + { 0x202F, 0x205F, 48 }, + { 0x3000, 0x3000, 1 }, +}; + +static struct range_table unprintable_table[] = { + { 0xAD, 0x34F, 674 }, + { 0x61C, 0x115F, 2883 }, + { 0x1160, 0x17B4, 1620 }, + { 0x17B5, 0x180B, 86 }, + { 0x180C, 0x180F, 1 }, + { 0x200B, 0x200F, 1 }, + { 0x202A, 0x202E, 1 }, + { 0x2060, 0x206F, 1 }, + { 0x3164, 0xFE00, 52380 }, + { 0xFE01, 0xFE0F, 1 }, + { 0xFEFF, 0xFFA0, 161 }, + { 0xFFF0, 0xFFF8, 1 }, + { 0x1BCA0, 0x1BCA3, 1 }, + { 0x1D173, 0x1D17A, 1 }, + { 0xE0000, 0xE0FFF, 1 }, +}; + +static struct range_table graph_table[] = { + { 0x20, 0x7E, 1 }, + { 0xA0, 0xAC, 1 }, + { 0xAE, 0x2FF, 1 }, + { 0x370, 0x377, 1 }, + { 0x37A, 0x37F, 1 }, + { 0x384, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x3A1, 1 }, + { 0x3A3, 0x482, 1 }, + { 0x48A, 0x52F, 1 }, + { 0x531, 0x556, 1 }, + { 0x559, 0x58A, 1 }, + { 0x58D, 0x58F, 1 }, + { 0x5BE, 0x5C0, 2 }, + { 0x5C3, 0x5C6, 3 }, + { 0x5D0, 0x5EA, 1 }, + { 0x5EF, 0x5F4, 1 }, + { 0x606, 0x60F, 1 }, + { 0x61B, 0x61D, 2 }, + { 0x61E, 0x64A, 1 }, + { 0x660, 0x66F, 1 }, + { 0x671, 0x6D5, 1 }, + { 0x6DE, 0x6E5, 7 }, + { 0x6E6, 0x6E9, 3 }, + { 0x6EE, 0x70D, 1 }, + { 0x710, 0x712, 2 }, + { 0x713, 0x72F, 1 }, + { 0x74D, 0x7A5, 1 }, + { 0x7B1, 0x7C0, 15 }, + { 0x7C1, 0x7EA, 1 }, + { 0x7F4, 0x7FA, 1 }, + { 0x7FE, 0x815, 1 }, + { 0x81A, 0x824, 10 }, + { 0x828, 0x830, 8 }, + { 0x831, 0x83E, 1 }, + { 0x840, 0x858, 1 }, + { 0x85E, 0x860, 2 }, + { 0x861, 0x86A, 1 }, + { 0x870, 0x88E, 1 }, + { 0x8A0, 0x8C9, 1 }, + { 0x903, 0x939, 1 }, + { 0x93B, 0x93D, 2 }, + { 0x93E, 0x940, 1 }, + { 0x949, 0x94C, 1 }, + { 0x94E, 0x950, 1 }, + { 0x958, 0x961, 1 }, + { 0x964, 0x980, 1 }, + { 0x982, 0x983, 1 }, + { 0x985, 0x98C, 1 }, + { 0x98F, 0x990, 1 }, + { 0x993, 0x9A8, 1 }, + { 0x9AA, 0x9B0, 1 }, + { 0x9B2, 0x9B6, 4 }, + { 0x9B7, 0x9B9, 1 }, + { 0x9BD, 0x9BF, 2 }, + { 0x9C0, 0x9C7, 7 }, + { 0x9C8, 0x9CB, 3 }, + { 0x9CC, 0x9CE, 2 }, + { 0x9DC, 0x9DD, 1 }, + { 0x9DF, 0x9E1, 1 }, + { 0x9E6, 0x9FD, 1 }, + { 0xA03, 0xA05, 2 }, + { 0xA06, 0xA0A, 1 }, + { 0xA0F, 0xA10, 1 }, + { 0xA13, 0xA28, 1 }, + { 0xA2A, 0xA30, 1 }, + { 0xA32, 0xA33, 1 }, + { 0xA35, 0xA36, 1 }, + { 0xA38, 0xA39, 1 }, + { 0xA3E, 0xA40, 1 }, + { 0xA59, 0xA5C, 1 }, + { 0xA5E, 0xA66, 8 }, + { 0xA67, 0xA6F, 1 }, + { 0xA72, 0xA74, 1 }, + { 0xA76, 0xA83, 13 }, + { 0xA85, 0xA8D, 1 }, + { 0xA8F, 0xA91, 1 }, + { 0xA93, 0xAA8, 1 }, + { 0xAAA, 0xAB0, 1 }, + { 0xAB2, 0xAB3, 1 }, + { 0xAB5, 0xAB9, 1 }, + { 0xABD, 0xAC0, 1 }, + { 0xAC9, 0xACB, 2 }, + { 0xACC, 0xAD0, 4 }, + { 0xAE0, 0xAE1, 1 }, + { 0xAE6, 0xAF1, 1 }, + { 0xAF9, 0xB02, 9 }, + { 0xB03, 0xB05, 2 }, + { 0xB06, 0xB0C, 1 }, + { 0xB0F, 0xB10, 1 }, + { 0xB13, 0xB28, 1 }, + { 0xB2A, 0xB30, 1 }, + { 0xB32, 0xB33, 1 }, + { 0xB35, 0xB39, 1 }, + { 0xB3D, 0xB40, 3 }, + { 0xB47, 0xB48, 1 }, + { 0xB4B, 0xB4C, 1 }, + { 0xB5C, 0xB5D, 1 }, + { 0xB5F, 0xB61, 1 }, + { 0xB66, 0xB77, 1 }, + { 0xB83, 0xB85, 2 }, + { 0xB86, 0xB8A, 1 }, + { 0xB8E, 0xB90, 1 }, + { 0xB92, 0xB95, 1 }, + { 0xB99, 0xB9A, 1 }, + { 0xB9C, 0xB9E, 2 }, + { 0xB9F, 0xBA3, 4 }, + { 0xBA4, 0xBA8, 4 }, + { 0xBA9, 0xBAA, 1 }, + { 0xBAE, 0xBB9, 1 }, + { 0xBBF, 0xBC1, 2 }, + { 0xBC2, 0xBC6, 4 }, + { 0xBC7, 0xBC8, 1 }, + { 0xBCA, 0xBCC, 1 }, + { 0xBD0, 0xBE6, 22 }, + { 0xBE7, 0xBFA, 1 }, + { 0xC01, 0xC03, 1 }, + { 0xC05, 0xC0C, 1 }, + { 0xC0E, 0xC10, 1 }, + { 0xC12, 0xC28, 1 }, + { 0xC2A, 0xC39, 1 }, + { 0xC3D, 0xC41, 4 }, + { 0xC42, 0xC44, 1 }, + { 0xC58, 0xC5A, 1 }, + { 0xC5D, 0xC60, 3 }, + { 0xC61, 0xC66, 5 }, + { 0xC67, 0xC6F, 1 }, + { 0xC77, 0xC80, 1 }, + { 0xC82, 0xC8C, 1 }, + { 0xC8E, 0xC90, 1 }, + { 0xC92, 0xCA8, 1 }, + { 0xCAA, 0xCB3, 1 }, + { 0xCB5, 0xCB9, 1 }, + { 0xCBD, 0xCBE, 1 }, + { 0xCC0, 0xCC1, 1 }, + { 0xCC3, 0xCC4, 1 }, + { 0xCC7, 0xCC8, 1 }, + { 0xCCA, 0xCCB, 1 }, + { 0xCDD, 0xCDE, 1 }, + { 0xCE0, 0xCE1, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xCF1, 0xCF2, 1 }, + { 0xD02, 0xD0C, 1 }, + { 0xD0E, 0xD10, 1 }, + { 0xD12, 0xD3A, 1 }, + { 0xD3D, 0xD3F, 2 }, + { 0xD40, 0xD46, 6 }, + { 0xD47, 0xD48, 1 }, + { 0xD4A, 0xD4C, 1 }, + { 0xD4E, 0xD4F, 1 }, + { 0xD54, 0xD56, 1 }, + { 0xD58, 0xD61, 1 }, + { 0xD66, 0xD7F, 1 }, + { 0xD82, 0xD83, 1 }, + { 0xD85, 0xD96, 1 }, + { 0xD9A, 0xDB1, 1 }, + { 0xDB3, 0xDBB, 1 }, + { 0xDBD, 0xDC0, 3 }, + { 0xDC1, 0xDC6, 1 }, + { 0xDD0, 0xDD1, 1 }, + { 0xDD8, 0xDDE, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xDF2, 0xDF4, 1 }, + { 0xE01, 0xE30, 1 }, + { 0xE32, 0xE33, 1 }, + { 0xE3F, 0xE46, 1 }, + { 0xE4F, 0xE5B, 1 }, + { 0xE81, 0xE82, 1 }, + { 0xE84, 0xE86, 2 }, + { 0xE87, 0xE8A, 1 }, + { 0xE8C, 0xEA3, 1 }, + { 0xEA5, 0xEA7, 2 }, + { 0xEA8, 0xEB0, 1 }, + { 0xEB2, 0xEB3, 1 }, + { 0xEBD, 0xEC0, 3 }, + { 0xEC1, 0xEC4, 1 }, + { 0xEC6, 0xED0, 10 }, + { 0xED1, 0xED9, 1 }, + { 0xEDC, 0xEDF, 1 }, + { 0xF00, 0xF17, 1 }, + { 0xF1A, 0xF34, 1 }, + { 0xF36, 0xF3A, 2 }, + { 0xF3B, 0xF47, 1 }, + { 0xF49, 0xF6C, 1 }, + { 0xF7F, 0xF85, 6 }, + { 0xF88, 0xF8C, 1 }, + { 0xFBE, 0xFC5, 1 }, + { 0xFC7, 0xFCC, 1 }, + { 0xFCE, 0xFDA, 1 }, + { 0x1000, 0x102C, 1 }, + { 0x1031, 0x1038, 7 }, + { 0x103B, 0x103C, 1 }, + { 0x103F, 0x1057, 1 }, + { 0x105A, 0x105D, 1 }, + { 0x1061, 0x1070, 1 }, + { 0x1075, 0x1081, 1 }, + { 0x1083, 0x1084, 1 }, + { 0x1087, 0x108C, 1 }, + { 0x108E, 0x109C, 1 }, + { 0x109E, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x10D0, 0x1248, 1 }, + { 0x124A, 0x124D, 1 }, + { 0x1250, 0x1256, 1 }, + { 0x1258, 0x125A, 2 }, + { 0x125B, 0x125D, 1 }, + { 0x1260, 0x1288, 1 }, + { 0x128A, 0x128D, 1 }, + { 0x1290, 0x12B0, 1 }, + { 0x12B2, 0x12B5, 1 }, + { 0x12B8, 0x12BE, 1 }, + { 0x12C0, 0x12C2, 2 }, + { 0x12C3, 0x12C5, 1 }, + { 0x12C8, 0x12D6, 1 }, + { 0x12D8, 0x1310, 1 }, + { 0x1312, 0x1315, 1 }, + { 0x1318, 0x135A, 1 }, + { 0x1360, 0x137C, 1 }, + { 0x1380, 0x1399, 1 }, + { 0x13A0, 0x13F5, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1400, 0x169C, 1 }, + { 0x16A0, 0x16F8, 1 }, + { 0x1700, 0x1711, 1 }, + { 0x1715, 0x171F, 10 }, + { 0x1720, 0x1731, 1 }, + { 0x1734, 0x1736, 1 }, + { 0x1740, 0x1751, 1 }, + { 0x1760, 0x176C, 1 }, + { 0x176E, 0x1770, 1 }, + { 0x1780, 0x17B3, 1 }, + { 0x17B6, 0x17BE, 8 }, + { 0x17BF, 0x17C5, 1 }, + { 0x17C7, 0x17C8, 1 }, + { 0x17D4, 0x17DC, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x17F0, 0x17F9, 1 }, + { 0x1800, 0x180A, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1820, 0x1878, 1 }, + { 0x1880, 0x1884, 1 }, + { 0x1887, 0x18A8, 1 }, + { 0x18AA, 0x18B0, 6 }, + { 0x18B1, 0x18F5, 1 }, + { 0x1900, 0x191E, 1 }, + { 0x1923, 0x1926, 1 }, + { 0x1929, 0x192B, 1 }, + { 0x1930, 0x1931, 1 }, + { 0x1933, 0x1938, 1 }, + { 0x1940, 0x1944, 4 }, + { 0x1945, 0x196D, 1 }, + { 0x1970, 0x1974, 1 }, + { 0x1980, 0x19AB, 1 }, + { 0x19B0, 0x19C9, 1 }, + { 0x19D0, 0x19DA, 1 }, + { 0x19DE, 0x1A16, 1 }, + { 0x1A19, 0x1A1A, 1 }, + { 0x1A1E, 0x1A55, 1 }, + { 0x1A57, 0x1A61, 10 }, + { 0x1A63, 0x1A64, 1 }, + { 0x1A6D, 0x1A72, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1AA0, 0x1AAD, 1 }, + { 0x1B04, 0x1B33, 1 }, + { 0x1B3B, 0x1B3D, 2 }, + { 0x1B3E, 0x1B41, 1 }, + { 0x1B43, 0x1B4C, 1 }, + { 0x1B50, 0x1B6A, 1 }, + { 0x1B74, 0x1B7E, 1 }, + { 0x1B82, 0x1BA1, 1 }, + { 0x1BA6, 0x1BA7, 1 }, + { 0x1BAA, 0x1BAE, 4 }, + { 0x1BAF, 0x1BE5, 1 }, + { 0x1BE7, 0x1BEA, 3 }, + { 0x1BEB, 0x1BEC, 1 }, + { 0x1BEE, 0x1BF2, 4 }, + { 0x1BF3, 0x1BFC, 9 }, + { 0x1BFD, 0x1C2B, 1 }, + { 0x1C34, 0x1C35, 1 }, + { 0x1C3B, 0x1C49, 1 }, + { 0x1C4D, 0x1C88, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CC7, 1 }, + { 0x1CD3, 0x1CE1, 14 }, + { 0x1CE9, 0x1CEC, 1 }, + { 0x1CEE, 0x1CF3, 1 }, + { 0x1CF5, 0x1CF7, 1 }, + { 0x1CFA, 0x1D00, 6 }, + { 0x1D01, 0x1DBF, 1 }, + { 0x1E00, 0x1F15, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F20, 0x1F45, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F60, 0x1F7D, 1 }, + { 0x1F80, 0x1FB4, 1 }, + { 0x1FB6, 0x1FC4, 1 }, + { 0x1FC6, 0x1FD3, 1 }, + { 0x1FD6, 0x1FDB, 1 }, + { 0x1FDD, 0x1FEF, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FFE, 1 }, + { 0x2000, 0x200A, 1 }, + { 0x2010, 0x2027, 1 }, + { 0x202F, 0x205F, 1 }, + { 0x2070, 0x2071, 1 }, + { 0x2074, 0x208E, 1 }, + { 0x2090, 0x209C, 1 }, + { 0x20A0, 0x20C0, 1 }, + { 0x2100, 0x218B, 1 }, + { 0x2190, 0x2426, 1 }, + { 0x2440, 0x244A, 1 }, + { 0x2460, 0x2B73, 1 }, + { 0x2B76, 0x2B95, 1 }, + { 0x2B97, 0x2CEE, 1 }, + { 0x2CF2, 0x2CF3, 1 }, + { 0x2CF9, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0x2D30, 0x2D67, 1 }, + { 0x2D6F, 0x2D70, 1 }, + { 0x2D80, 0x2D96, 1 }, + { 0x2DA0, 0x2DA6, 1 }, + { 0x2DA8, 0x2DAE, 1 }, + { 0x2DB0, 0x2DB6, 1 }, + { 0x2DB8, 0x2DBE, 1 }, + { 0x2DC0, 0x2DC6, 1 }, + { 0x2DC8, 0x2DCE, 1 }, + { 0x2DD0, 0x2DD6, 1 }, + { 0x2DD8, 0x2DDE, 1 }, + { 0x2E00, 0x2E5D, 1 }, + { 0x2E80, 0x2E99, 1 }, + { 0x2E9B, 0x2EF3, 1 }, + { 0x2F00, 0x2FD5, 1 }, + { 0x2FF0, 0x2FFB, 1 }, + { 0x3000, 0x3029, 1 }, + { 0x3030, 0x303F, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x309B, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x3190, 0x31E3, 1 }, + { 0x31F0, 0x321E, 1 }, + { 0x3220, 0xA48C, 1 }, + { 0xA490, 0xA4C6, 1 }, + { 0xA4D0, 0xA62B, 1 }, + { 0xA640, 0xA66E, 1 }, + { 0xA673, 0xA67E, 11 }, + { 0xA67F, 0xA69D, 1 }, + { 0xA6A0, 0xA6EF, 1 }, + { 0xA6F2, 0xA6F7, 1 }, + { 0xA700, 0xA7CA, 1 }, + { 0xA7D0, 0xA7D1, 1 }, + { 0xA7D3, 0xA7D5, 2 }, + { 0xA7D6, 0xA7D9, 1 }, + { 0xA7F2, 0xA801, 1 }, + { 0xA803, 0xA805, 1 }, + { 0xA807, 0xA80A, 1 }, + { 0xA80C, 0xA824, 1 }, + { 0xA827, 0xA82B, 1 }, + { 0xA830, 0xA839, 1 }, + { 0xA840, 0xA877, 1 }, + { 0xA880, 0xA8C3, 1 }, + { 0xA8CE, 0xA8D9, 1 }, + { 0xA8F2, 0xA8FE, 1 }, + { 0xA900, 0xA925, 1 }, + { 0xA92E, 0xA946, 1 }, + { 0xA952, 0xA953, 1 }, + { 0xA95F, 0xA97C, 1 }, + { 0xA983, 0xA9B2, 1 }, + { 0xA9B4, 0xA9B5, 1 }, + { 0xA9BA, 0xA9BB, 1 }, + { 0xA9BE, 0xA9CD, 1 }, + { 0xA9CF, 0xA9D9, 1 }, + { 0xA9DE, 0xA9E4, 1 }, + { 0xA9E6, 0xA9FE, 1 }, + { 0xAA00, 0xAA28, 1 }, + { 0xAA2F, 0xAA30, 1 }, + { 0xAA33, 0xAA34, 1 }, + { 0xAA40, 0xAA42, 1 }, + { 0xAA44, 0xAA4B, 1 }, + { 0xAA4D, 0xAA50, 3 }, + { 0xAA51, 0xAA59, 1 }, + { 0xAA5C, 0xAA7B, 1 }, + { 0xAA7D, 0xAAAF, 1 }, + { 0xAAB1, 0xAAB5, 4 }, + { 0xAAB6, 0xAAB9, 3 }, + { 0xAABA, 0xAABD, 1 }, + { 0xAAC0, 0xAAC2, 2 }, + { 0xAADB, 0xAAEB, 1 }, + { 0xAAEE, 0xAAF5, 1 }, + { 0xAB01, 0xAB06, 1 }, + { 0xAB09, 0xAB0E, 1 }, + { 0xAB11, 0xAB16, 1 }, + { 0xAB20, 0xAB26, 1 }, + { 0xAB28, 0xAB2E, 1 }, + { 0xAB30, 0xAB6B, 1 }, + { 0xAB70, 0xABE4, 1 }, + { 0xABE6, 0xABE7, 1 }, + { 0xABE9, 0xABEC, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xD7B0, 0xD7C6, 1 }, + { 0xD7CB, 0xD7FB, 1 }, + { 0xF900, 0xFA6D, 1 }, + { 0xFA70, 0xFAD9, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFB1D, 0xFB1F, 2 }, + { 0xFB20, 0xFB36, 1 }, + { 0xFB38, 0xFB3C, 1 }, + { 0xFB3E, 0xFB40, 2 }, + { 0xFB41, 0xFB43, 2 }, + { 0xFB44, 0xFB46, 2 }, + { 0xFB47, 0xFBC2, 1 }, + { 0xFBD3, 0xFD8F, 1 }, + { 0xFD92, 0xFDC7, 1 }, + { 0xFDCF, 0xFDF0, 33 }, + { 0xFDF1, 0xFDFF, 1 }, + { 0xFE10, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFE70, 0xFE74, 1 }, + { 0xFE76, 0xFEFC, 1 }, + { 0xFF01, 0xFF9D, 1 }, + { 0xFFA0, 0xFFBE, 1 }, + { 0xFFC2, 0xFFC7, 1 }, + { 0xFFCA, 0xFFCF, 1 }, + { 0xFFD2, 0xFFD7, 1 }, + { 0xFFDA, 0xFFDC, 1 }, + { 0xFFE0, 0xFFE6, 1 }, + { 0xFFE8, 0xFFEE, 1 }, + { 0xFFFC, 0xFFFD, 1 }, + { 0x10000, 0x1000B, 1 }, + { 0x1000D, 0x10026, 1 }, + { 0x10028, 0x1003A, 1 }, + { 0x1003C, 0x1003D, 1 }, + { 0x1003F, 0x1004D, 1 }, + { 0x10050, 0x1005D, 1 }, + { 0x10080, 0x100FA, 1 }, + { 0x10100, 0x10102, 1 }, + { 0x10107, 0x10133, 1 }, + { 0x10137, 0x1018E, 1 }, + { 0x10190, 0x1019C, 1 }, + { 0x101A0, 0x101D0, 48 }, + { 0x101D1, 0x101FC, 1 }, + { 0x10280, 0x1029C, 1 }, + { 0x102A0, 0x102D0, 1 }, + { 0x102E1, 0x102FB, 1 }, + { 0x10300, 0x10323, 1 }, + { 0x1032D, 0x1034A, 1 }, + { 0x10350, 0x10375, 1 }, + { 0x10380, 0x1039D, 1 }, + { 0x1039F, 0x103C3, 1 }, + { 0x103C8, 0x103D5, 1 }, + { 0x10400, 0x1049D, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10500, 0x10527, 1 }, + { 0x10530, 0x10563, 1 }, + { 0x1056F, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10600, 0x10736, 1 }, + { 0x10740, 0x10755, 1 }, + { 0x10760, 0x10767, 1 }, + { 0x10780, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10800, 0x10805, 1 }, + { 0x10808, 0x1080A, 2 }, + { 0x1080B, 0x10835, 1 }, + { 0x10837, 0x10838, 1 }, + { 0x1083C, 0x1083F, 3 }, + { 0x10840, 0x10855, 1 }, + { 0x10857, 0x1089E, 1 }, + { 0x108A7, 0x108AF, 1 }, + { 0x108E0, 0x108F2, 1 }, + { 0x108F4, 0x108F5, 1 }, + { 0x108FB, 0x1091B, 1 }, + { 0x1091F, 0x10939, 1 }, + { 0x1093F, 0x10980, 65 }, + { 0x10981, 0x109B7, 1 }, + { 0x109BC, 0x109CF, 1 }, + { 0x109D2, 0x10A00, 1 }, + { 0x10A10, 0x10A13, 1 }, + { 0x10A15, 0x10A17, 1 }, + { 0x10A19, 0x10A35, 1 }, + { 0x10A40, 0x10A48, 1 }, + { 0x10A50, 0x10A58, 1 }, + { 0x10A60, 0x10A9F, 1 }, + { 0x10AC0, 0x10AE4, 1 }, + { 0x10AEB, 0x10AF6, 1 }, + { 0x10B00, 0x10B35, 1 }, + { 0x10B39, 0x10B55, 1 }, + { 0x10B58, 0x10B72, 1 }, + { 0x10B78, 0x10B91, 1 }, + { 0x10B99, 0x10B9C, 1 }, + { 0x10BA9, 0x10BAF, 1 }, + { 0x10C00, 0x10C48, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x10CFA, 0x10D23, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x10E60, 0x10E7E, 1 }, + { 0x10E80, 0x10EA9, 1 }, + { 0x10EAD, 0x10EB0, 3 }, + { 0x10EB1, 0x10F00, 79 }, + { 0x10F01, 0x10F27, 1 }, + { 0x10F30, 0x10F45, 1 }, + { 0x10F51, 0x10F59, 1 }, + { 0x10F70, 0x10F81, 1 }, + { 0x10F86, 0x10F89, 1 }, + { 0x10FB0, 0x10FCB, 1 }, + { 0x10FE0, 0x10FF6, 1 }, + { 0x11000, 0x11002, 2 }, + { 0x11003, 0x11037, 1 }, + { 0x11047, 0x1104D, 1 }, + { 0x11052, 0x1106F, 1 }, + { 0x11071, 0x11072, 1 }, + { 0x11075, 0x11082, 13 }, + { 0x11083, 0x110B2, 1 }, + { 0x110B7, 0x110B8, 1 }, + { 0x110BB, 0x110BC, 1 }, + { 0x110BE, 0x110C1, 1 }, + { 0x110D0, 0x110E8, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11103, 0x11126, 1 }, + { 0x1112C, 0x11136, 10 }, + { 0x11137, 0x11147, 1 }, + { 0x11150, 0x11172, 1 }, + { 0x11174, 0x11176, 1 }, + { 0x11182, 0x111B5, 1 }, + { 0x111BF, 0x111C8, 1 }, + { 0x111CD, 0x111CE, 1 }, + { 0x111D0, 0x111DF, 1 }, + { 0x111E1, 0x111F4, 1 }, + { 0x11200, 0x11211, 1 }, + { 0x11213, 0x1122E, 1 }, + { 0x11232, 0x11233, 1 }, + { 0x11235, 0x11238, 3 }, + { 0x11239, 0x1123D, 1 }, + { 0x11280, 0x11286, 1 }, + { 0x11288, 0x1128A, 2 }, + { 0x1128B, 0x1128D, 1 }, + { 0x1128F, 0x1129D, 1 }, + { 0x1129F, 0x112A9, 1 }, + { 0x112B0, 0x112DE, 1 }, + { 0x112E0, 0x112E2, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11302, 0x11303, 1 }, + { 0x11305, 0x1130C, 1 }, + { 0x1130F, 0x11310, 1 }, + { 0x11313, 0x11328, 1 }, + { 0x1132A, 0x11330, 1 }, + { 0x11332, 0x11333, 1 }, + { 0x11335, 0x11339, 1 }, + { 0x1133D, 0x11341, 2 }, + { 0x11342, 0x11344, 1 }, + { 0x11347, 0x11348, 1 }, + { 0x1134B, 0x1134D, 1 }, + { 0x11350, 0x1135D, 13 }, + { 0x1135E, 0x11363, 1 }, + { 0x11400, 0x11437, 1 }, + { 0x11440, 0x11441, 1 }, + { 0x11445, 0x11447, 2 }, + { 0x11448, 0x1145B, 1 }, + { 0x1145D, 0x1145F, 2 }, + { 0x11460, 0x11461, 1 }, + { 0x11480, 0x114AF, 1 }, + { 0x114B1, 0x114B2, 1 }, + { 0x114B9, 0x114BB, 2 }, + { 0x114BC, 0x114BE, 2 }, + { 0x114C1, 0x114C4, 3 }, + { 0x114C5, 0x114C7, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11580, 0x115AE, 1 }, + { 0x115B0, 0x115B1, 1 }, + { 0x115B8, 0x115BB, 1 }, + { 0x115BE, 0x115C1, 3 }, + { 0x115C2, 0x115DB, 1 }, + { 0x11600, 0x11632, 1 }, + { 0x1163B, 0x1163C, 1 }, + { 0x1163E, 0x11641, 3 }, + { 0x11642, 0x11644, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x11660, 0x1166C, 1 }, + { 0x11680, 0x116AA, 1 }, + { 0x116AC, 0x116AE, 2 }, + { 0x116AF, 0x116B6, 7 }, + { 0x116B8, 0x116B9, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11700, 0x1171A, 1 }, + { 0x11720, 0x11721, 1 }, + { 0x11726, 0x11730, 10 }, + { 0x11731, 0x11746, 1 }, + { 0x11800, 0x1182E, 1 }, + { 0x11838, 0x1183B, 3 }, + { 0x118A0, 0x118F2, 1 }, + { 0x118FF, 0x11906, 1 }, + { 0x11909, 0x1190C, 3 }, + { 0x1190D, 0x11913, 1 }, + { 0x11915, 0x11916, 1 }, + { 0x11918, 0x1192F, 1 }, + { 0x11931, 0x11935, 1 }, + { 0x11937, 0x11938, 1 }, + { 0x1193D, 0x1193F, 2 }, + { 0x11940, 0x11942, 1 }, + { 0x11944, 0x11946, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x119A0, 0x119A7, 1 }, + { 0x119AA, 0x119D3, 1 }, + { 0x119DC, 0x119DF, 1 }, + { 0x119E1, 0x119E4, 1 }, + { 0x11A00, 0x11A0B, 11 }, + { 0x11A0C, 0x11A32, 1 }, + { 0x11A39, 0x11A3A, 1 }, + { 0x11A3F, 0x11A46, 1 }, + { 0x11A50, 0x11A57, 7 }, + { 0x11A58, 0x11A5C, 4 }, + { 0x11A5D, 0x11A89, 1 }, + { 0x11A97, 0x11A9A, 3 }, + { 0x11A9B, 0x11AA2, 1 }, + { 0x11AB0, 0x11AF8, 1 }, + { 0x11C00, 0x11C08, 1 }, + { 0x11C0A, 0x11C2F, 1 }, + { 0x11C3E, 0x11C40, 2 }, + { 0x11C41, 0x11C45, 1 }, + { 0x11C50, 0x11C6C, 1 }, + { 0x11C70, 0x11C8F, 1 }, + { 0x11CA9, 0x11CB1, 8 }, + { 0x11CB4, 0x11D00, 76 }, + { 0x11D01, 0x11D06, 1 }, + { 0x11D08, 0x11D09, 1 }, + { 0x11D0B, 0x11D30, 1 }, + { 0x11D46, 0x11D50, 10 }, + { 0x11D51, 0x11D59, 1 }, + { 0x11D60, 0x11D65, 1 }, + { 0x11D67, 0x11D68, 1 }, + { 0x11D6A, 0x11D8E, 1 }, + { 0x11D93, 0x11D94, 1 }, + { 0x11D96, 0x11D98, 2 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x11EE0, 0x11EF2, 1 }, + { 0x11EF5, 0x11EF8, 1 }, + { 0x11FB0, 0x11FC0, 16 }, + { 0x11FC1, 0x11FF1, 1 }, + { 0x11FFF, 0x12399, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x12470, 0x12474, 1 }, + { 0x12480, 0x12543, 1 }, + { 0x12F90, 0x12FF2, 1 }, + { 0x13000, 0x1342E, 1 }, + { 0x14400, 0x14646, 1 }, + { 0x16800, 0x16A38, 1 }, + { 0x16A40, 0x16A5E, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16A6E, 0x16ABE, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16AD0, 0x16AED, 1 }, + { 0x16AF5, 0x16B00, 11 }, + { 0x16B01, 0x16B2F, 1 }, + { 0x16B37, 0x16B45, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x16B5B, 0x16B61, 1 }, + { 0x16B63, 0x16B77, 1 }, + { 0x16B7D, 0x16B8F, 1 }, + { 0x16E40, 0x16E9A, 1 }, + { 0x16F00, 0x16F4A, 1 }, + { 0x16F50, 0x16F87, 1 }, + { 0x16F93, 0x16F9F, 1 }, + { 0x16FE0, 0x16FE3, 1 }, + { 0x16FF0, 0x16FF1, 1 }, + { 0x17000, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B150, 0x1B152, 1 }, + { 0x1B164, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1BC00, 0x1BC6A, 1 }, + { 0x1BC70, 0x1BC7C, 1 }, + { 0x1BC80, 0x1BC88, 1 }, + { 0x1BC90, 0x1BC99, 1 }, + { 0x1BC9C, 0x1BC9F, 3 }, + { 0x1CF50, 0x1CFC3, 1 }, + { 0x1D000, 0x1D0F5, 1 }, + { 0x1D100, 0x1D126, 1 }, + { 0x1D129, 0x1D164, 1 }, + { 0x1D166, 0x1D16A, 4 }, + { 0x1D16B, 0x1D16D, 1 }, + { 0x1D183, 0x1D184, 1 }, + { 0x1D18C, 0x1D1A9, 1 }, + { 0x1D1AE, 0x1D1EA, 1 }, + { 0x1D200, 0x1D241, 1 }, + { 0x1D245, 0x1D2E0, 155 }, + { 0x1D2E1, 0x1D2F3, 1 }, + { 0x1D300, 0x1D356, 1 }, + { 0x1D360, 0x1D378, 1 }, + { 0x1D400, 0x1D454, 1 }, + { 0x1D456, 0x1D49C, 1 }, + { 0x1D49E, 0x1D49F, 1 }, + { 0x1D4A2, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D51E, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D552, 0x1D6A5, 1 }, + { 0x1D6A8, 0x1D7CB, 1 }, + { 0x1D7CE, 0x1D9FF, 1 }, + { 0x1DA37, 0x1DA3A, 1 }, + { 0x1DA6D, 0x1DA74, 1 }, + { 0x1DA76, 0x1DA83, 1 }, + { 0x1DA85, 0x1DA8B, 1 }, + { 0x1DF00, 0x1DF1E, 1 }, + { 0x1E100, 0x1E12C, 1 }, + { 0x1E137, 0x1E13D, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E14E, 0x1E14F, 1 }, + { 0x1E290, 0x1E2AD, 1 }, + { 0x1E2C0, 0x1E2EB, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E2FF, 0x1E7E0, 1249 }, + { 0x1E7E1, 0x1E7E6, 1 }, + { 0x1E7E8, 0x1E7EB, 1 }, + { 0x1E7ED, 0x1E7EE, 1 }, + { 0x1E7F0, 0x1E7FE, 1 }, + { 0x1E800, 0x1E8C4, 1 }, + { 0x1E8C7, 0x1E8CF, 1 }, + { 0x1E900, 0x1E943, 1 }, + { 0x1E94B, 0x1E950, 5 }, + { 0x1E951, 0x1E959, 1 }, + { 0x1E95E, 0x1E95F, 1 }, + { 0x1EC71, 0x1ECB4, 1 }, + { 0x1ED01, 0x1ED3D, 1 }, + { 0x1EE00, 0x1EE03, 1 }, + { 0x1EE05, 0x1EE1F, 1 }, + { 0x1EE21, 0x1EE22, 1 }, + { 0x1EE24, 0x1EE27, 3 }, + { 0x1EE29, 0x1EE32, 1 }, + { 0x1EE34, 0x1EE37, 1 }, + { 0x1EE39, 0x1EE3B, 2 }, + { 0x1EE42, 0x1EE47, 5 }, + { 0x1EE49, 0x1EE4D, 2 }, + { 0x1EE4E, 0x1EE4F, 1 }, + { 0x1EE51, 0x1EE52, 1 }, + { 0x1EE54, 0x1EE57, 3 }, + { 0x1EE59, 0x1EE61, 2 }, + { 0x1EE62, 0x1EE64, 2 }, + { 0x1EE67, 0x1EE6A, 1 }, + { 0x1EE6C, 0x1EE72, 1 }, + { 0x1EE74, 0x1EE77, 1 }, + { 0x1EE79, 0x1EE7C, 1 }, + { 0x1EE7E, 0x1EE80, 2 }, + { 0x1EE81, 0x1EE89, 1 }, + { 0x1EE8B, 0x1EE9B, 1 }, + { 0x1EEA1, 0x1EEA3, 1 }, + { 0x1EEA5, 0x1EEA9, 1 }, + { 0x1EEAB, 0x1EEBB, 1 }, + { 0x1EEF0, 0x1EEF1, 1 }, + { 0x1F000, 0x1F02B, 1 }, + { 0x1F030, 0x1F093, 1 }, + { 0x1F0A0, 0x1F0AE, 1 }, + { 0x1F0B1, 0x1F0BF, 1 }, + { 0x1F0C1, 0x1F0CF, 1 }, + { 0x1F0D1, 0x1F0F5, 1 }, + { 0x1F100, 0x1F1AD, 1 }, + { 0x1F1E6, 0x1F202, 1 }, + { 0x1F210, 0x1F23B, 1 }, + { 0x1F240, 0x1F248, 1 }, + { 0x1F250, 0x1F251, 1 }, + { 0x1F260, 0x1F265, 1 }, + { 0x1F300, 0x1F6D7, 1 }, + { 0x1F6DD, 0x1F6EC, 1 }, + { 0x1F6F0, 0x1F6FC, 1 }, + { 0x1F700, 0x1F773, 1 }, + { 0x1F780, 0x1F7D8, 1 }, + { 0x1F7E0, 0x1F7EB, 1 }, + { 0x1F7F0, 0x1F800, 16 }, + { 0x1F801, 0x1F80B, 1 }, + { 0x1F810, 0x1F847, 1 }, + { 0x1F850, 0x1F859, 1 }, + { 0x1F860, 0x1F887, 1 }, + { 0x1F890, 0x1F8AD, 1 }, + { 0x1F8B0, 0x1F8B1, 1 }, + { 0x1F900, 0x1FA53, 1 }, + { 0x1FA60, 0x1FA6D, 1 }, + { 0x1FA70, 0x1FA74, 1 }, + { 0x1FA78, 0x1FA7C, 1 }, + { 0x1FA80, 0x1FA86, 1 }, + { 0x1FA90, 0x1FAAC, 1 }, + { 0x1FAB0, 0x1FABA, 1 }, + { 0x1FAC0, 0x1FAC5, 1 }, + { 0x1FAD0, 0x1FAD9, 1 }, + { 0x1FAE0, 0x1FAE7, 1 }, + { 0x1FAF0, 0x1FAF6, 1 }, + { 0x1FB00, 0x1FB92, 1 }, + { 0x1FB94, 0x1FBCA, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, + { 0x20000, 0x2A6DF, 1 }, + { 0x2A700, 0x2B738, 1 }, + { 0x2B740, 0x2B81D, 1 }, + { 0x2B820, 0x2CEA1, 1 }, + { 0x2CEB0, 0x2EBE0, 1 }, + { 0x2F800, 0x2FA1D, 1 }, + { 0x30000, 0x3134A, 1 }, +}; + +static struct range_table compose_table[] = { + { 0x300, 0x36F, 1 }, + { 0x483, 0x489, 1 }, + { 0x591, 0x5BD, 1 }, + { 0x5BF, 0x5C1, 2 }, + { 0x5C2, 0x5C4, 2 }, + { 0x5C5, 0x5C7, 2 }, + { 0x610, 0x61A, 1 }, + { 0x64B, 0x65F, 1 }, + { 0x670, 0x6D6, 102 }, + { 0x6D7, 0x6DC, 1 }, + { 0x6DF, 0x6E4, 1 }, + { 0x6E7, 0x6E8, 1 }, + { 0x6EA, 0x6ED, 1 }, + { 0x711, 0x730, 31 }, + { 0x731, 0x74A, 1 }, + { 0x7A6, 0x7B0, 1 }, + { 0x7EB, 0x7F3, 1 }, + { 0x7FD, 0x816, 25 }, + { 0x817, 0x819, 1 }, + { 0x81B, 0x823, 1 }, + { 0x825, 0x827, 1 }, + { 0x829, 0x82D, 1 }, + { 0x859, 0x85B, 1 }, + { 0x898, 0x89F, 1 }, + { 0x8CA, 0x8E1, 1 }, + { 0x8E3, 0x902, 1 }, + { 0x93A, 0x93C, 2 }, + { 0x941, 0x948, 1 }, + { 0x94D, 0x951, 4 }, + { 0x952, 0x957, 1 }, + { 0x962, 0x963, 1 }, + { 0x981, 0x9BC, 59 }, + { 0x9BE, 0x9C1, 3 }, + { 0x9C2, 0x9C4, 1 }, + { 0x9CD, 0x9D7, 10 }, + { 0x9E2, 0x9E3, 1 }, + { 0x9FE, 0xA01, 3 }, + { 0xA02, 0xA3C, 58 }, + { 0xA41, 0xA42, 1 }, + { 0xA47, 0xA48, 1 }, + { 0xA4B, 0xA4D, 1 }, + { 0xA51, 0xA70, 31 }, + { 0xA71, 0xA75, 4 }, + { 0xA81, 0xA82, 1 }, + { 0xABC, 0xAC1, 5 }, + { 0xAC2, 0xAC5, 1 }, + { 0xAC7, 0xAC8, 1 }, + { 0xACD, 0xAE2, 21 }, + { 0xAE3, 0xAFA, 23 }, + { 0xAFB, 0xAFF, 1 }, + { 0xB01, 0xB3C, 59 }, + { 0xB3E, 0xB3F, 1 }, + { 0xB41, 0xB44, 1 }, + { 0xB4D, 0xB55, 8 }, + { 0xB56, 0xB57, 1 }, + { 0xB62, 0xB63, 1 }, + { 0xB82, 0xBBE, 60 }, + { 0xBC0, 0xBCD, 13 }, + { 0xBD7, 0xC00, 41 }, + { 0xC04, 0xC3C, 56 }, + { 0xC3E, 0xC40, 1 }, + { 0xC46, 0xC48, 1 }, + { 0xC4A, 0xC4D, 1 }, + { 0xC55, 0xC56, 1 }, + { 0xC62, 0xC63, 1 }, + { 0xC81, 0xCBC, 59 }, + { 0xCBF, 0xCC2, 3 }, + { 0xCC6, 0xCCC, 6 }, + { 0xCCD, 0xCD5, 8 }, + { 0xCD6, 0xCE2, 12 }, + { 0xCE3, 0xD00, 29 }, + { 0xD01, 0xD3B, 58 }, + { 0xD3C, 0xD3E, 2 }, + { 0xD41, 0xD44, 1 }, + { 0xD4D, 0xD57, 10 }, + { 0xD62, 0xD63, 1 }, + { 0xD81, 0xDCA, 73 }, + { 0xDCF, 0xDD2, 3 }, + { 0xDD3, 0xDD4, 1 }, + { 0xDD6, 0xDDF, 9 }, + { 0xE31, 0xE34, 3 }, + { 0xE35, 0xE3A, 1 }, + { 0xE47, 0xE4E, 1 }, + { 0xEB1, 0xEB4, 3 }, + { 0xEB5, 0xEBC, 1 }, + { 0xEC8, 0xECD, 1 }, + { 0xF18, 0xF19, 1 }, + { 0xF35, 0xF39, 2 }, + { 0xF71, 0xF7E, 1 }, + { 0xF80, 0xF84, 1 }, + { 0xF86, 0xF87, 1 }, + { 0xF8D, 0xF97, 1 }, + { 0xF99, 0xFBC, 1 }, + { 0xFC6, 0x102D, 103 }, + { 0x102E, 0x1030, 1 }, + { 0x1032, 0x1037, 1 }, + { 0x1039, 0x103A, 1 }, + { 0x103D, 0x103E, 1 }, + { 0x1058, 0x1059, 1 }, + { 0x105E, 0x1060, 1 }, + { 0x1071, 0x1074, 1 }, + { 0x1082, 0x1085, 3 }, + { 0x1086, 0x108D, 7 }, + { 0x109D, 0x135D, 704 }, + { 0x135E, 0x135F, 1 }, + { 0x1712, 0x1714, 1 }, + { 0x1732, 0x1733, 1 }, + { 0x1752, 0x1753, 1 }, + { 0x1772, 0x1773, 1 }, + { 0x17B4, 0x17B5, 1 }, + { 0x17B7, 0x17BD, 1 }, + { 0x17C6, 0x17C9, 3 }, + { 0x17CA, 0x17D3, 1 }, + { 0x17DD, 0x180B, 46 }, + { 0x180C, 0x180D, 1 }, + { 0x180F, 0x1885, 118 }, + { 0x1886, 0x18A9, 35 }, + { 0x1920, 0x1922, 1 }, + { 0x1927, 0x1928, 1 }, + { 0x1932, 0x1939, 7 }, + { 0x193A, 0x193B, 1 }, + { 0x1A17, 0x1A18, 1 }, + { 0x1A1B, 0x1A56, 59 }, + { 0x1A58, 0x1A5E, 1 }, + { 0x1A60, 0x1A62, 2 }, + { 0x1A65, 0x1A6C, 1 }, + { 0x1A73, 0x1A7C, 1 }, + { 0x1A7F, 0x1AB0, 49 }, + { 0x1AB1, 0x1ACE, 1 }, + { 0x1B00, 0x1B03, 1 }, + { 0x1B34, 0x1B3A, 1 }, + { 0x1B3C, 0x1B42, 6 }, + { 0x1B6B, 0x1B73, 1 }, + { 0x1B80, 0x1B81, 1 }, + { 0x1BA2, 0x1BA5, 1 }, + { 0x1BA8, 0x1BA9, 1 }, + { 0x1BAB, 0x1BAD, 1 }, + { 0x1BE6, 0x1BE8, 2 }, + { 0x1BE9, 0x1BED, 4 }, + { 0x1BEF, 0x1BF1, 1 }, + { 0x1C2C, 0x1C33, 1 }, + { 0x1C36, 0x1C37, 1 }, + { 0x1CD0, 0x1CD2, 1 }, + { 0x1CD4, 0x1CE0, 1 }, + { 0x1CE2, 0x1CE8, 1 }, + { 0x1CED, 0x1CF4, 7 }, + { 0x1CF8, 0x1CF9, 1 }, + { 0x1DC0, 0x1DFF, 1 }, + { 0x200C, 0x20D0, 196 }, + { 0x20D1, 0x20F0, 1 }, + { 0x2CEF, 0x2CF1, 1 }, + { 0x2D7F, 0x2DE0, 97 }, + { 0x2DE1, 0x2DFF, 1 }, + { 0x302A, 0x302F, 1 }, + { 0x3099, 0x309A, 1 }, + { 0xA66F, 0xA672, 1 }, + { 0xA674, 0xA67D, 1 }, + { 0xA69E, 0xA69F, 1 }, + { 0xA6F0, 0xA6F1, 1 }, + { 0xA802, 0xA806, 4 }, + { 0xA80B, 0xA825, 26 }, + { 0xA826, 0xA82C, 6 }, + { 0xA8C4, 0xA8C5, 1 }, + { 0xA8E0, 0xA8F1, 1 }, + { 0xA8FF, 0xA926, 39 }, + { 0xA927, 0xA92D, 1 }, + { 0xA947, 0xA951, 1 }, + { 0xA980, 0xA982, 1 }, + { 0xA9B3, 0xA9B6, 3 }, + { 0xA9B7, 0xA9B9, 1 }, + { 0xA9BC, 0xA9BD, 1 }, + { 0xA9E5, 0xAA29, 68 }, + { 0xAA2A, 0xAA2E, 1 }, + { 0xAA31, 0xAA32, 1 }, + { 0xAA35, 0xAA36, 1 }, + { 0xAA43, 0xAA4C, 9 }, + { 0xAA7C, 0xAAB0, 52 }, + { 0xAAB2, 0xAAB4, 1 }, + { 0xAAB7, 0xAAB8, 1 }, + { 0xAABE, 0xAABF, 1 }, + { 0xAAC1, 0xAAEC, 43 }, + { 0xAAED, 0xAAF6, 9 }, + { 0xABE5, 0xABE8, 3 }, + { 0xABED, 0xFB1E, 20273 }, + { 0xFE00, 0xFE0F, 1 }, + { 0xFE20, 0xFE2F, 1 }, + { 0xFF9E, 0xFF9F, 1 }, + { 0x101FD, 0x102E0, 227 }, + { 0x10376, 0x1037A, 1 }, + { 0x10A01, 0x10A03, 1 }, + { 0x10A05, 0x10A06, 1 }, + { 0x10A0C, 0x10A0F, 1 }, + { 0x10A38, 0x10A3A, 1 }, + { 0x10A3F, 0x10AE5, 166 }, + { 0x10AE6, 0x10D24, 574 }, + { 0x10D25, 0x10D27, 1 }, + { 0x10EAB, 0x10EAC, 1 }, + { 0x10F46, 0x10F50, 1 }, + { 0x10F82, 0x10F85, 1 }, + { 0x11001, 0x11038, 55 }, + { 0x11039, 0x11046, 1 }, + { 0x11070, 0x11073, 3 }, + { 0x11074, 0x1107F, 11 }, + { 0x11080, 0x11081, 1 }, + { 0x110B3, 0x110B6, 1 }, + { 0x110B9, 0x110BA, 1 }, + { 0x110C2, 0x11100, 62 }, + { 0x11101, 0x11102, 1 }, + { 0x11127, 0x1112B, 1 }, + { 0x1112D, 0x11134, 1 }, + { 0x11173, 0x11180, 13 }, + { 0x11181, 0x111B6, 53 }, + { 0x111B7, 0x111BE, 1 }, + { 0x111C9, 0x111CC, 1 }, + { 0x111CF, 0x1122F, 96 }, + { 0x11230, 0x11231, 1 }, + { 0x11234, 0x11236, 2 }, + { 0x11237, 0x1123E, 7 }, + { 0x112DF, 0x112E3, 4 }, + { 0x112E4, 0x112EA, 1 }, + { 0x11300, 0x11301, 1 }, + { 0x1133B, 0x1133C, 1 }, + { 0x1133E, 0x11340, 2 }, + { 0x11357, 0x11366, 15 }, + { 0x11367, 0x1136C, 1 }, + { 0x11370, 0x11374, 1 }, + { 0x11438, 0x1143F, 1 }, + { 0x11442, 0x11444, 1 }, + { 0x11446, 0x1145E, 24 }, + { 0x114B0, 0x114B3, 3 }, + { 0x114B4, 0x114B8, 1 }, + { 0x114BA, 0x114BD, 3 }, + { 0x114BF, 0x114C0, 1 }, + { 0x114C2, 0x114C3, 1 }, + { 0x115AF, 0x115B2, 3 }, + { 0x115B3, 0x115B5, 1 }, + { 0x115BC, 0x115BD, 1 }, + { 0x115BF, 0x115C0, 1 }, + { 0x115DC, 0x115DD, 1 }, + { 0x11633, 0x1163A, 1 }, + { 0x1163D, 0x1163F, 2 }, + { 0x11640, 0x116AB, 107 }, + { 0x116AD, 0x116B0, 3 }, + { 0x116B1, 0x116B5, 1 }, + { 0x116B7, 0x1171D, 102 }, + { 0x1171E, 0x1171F, 1 }, + { 0x11722, 0x11725, 1 }, + { 0x11727, 0x1172B, 1 }, + { 0x1182F, 0x11837, 1 }, + { 0x11839, 0x1183A, 1 }, + { 0x11930, 0x1193B, 11 }, + { 0x1193C, 0x1193E, 2 }, + { 0x11943, 0x119D4, 145 }, + { 0x119D5, 0x119D7, 1 }, + { 0x119DA, 0x119DB, 1 }, + { 0x119E0, 0x11A01, 33 }, + { 0x11A02, 0x11A0A, 1 }, + { 0x11A33, 0x11A38, 1 }, + { 0x11A3B, 0x11A3E, 1 }, + { 0x11A47, 0x11A51, 10 }, + { 0x11A52, 0x11A56, 1 }, + { 0x11A59, 0x11A5B, 1 }, + { 0x11A8A, 0x11A96, 1 }, + { 0x11A98, 0x11A99, 1 }, + { 0x11C30, 0x11C36, 1 }, + { 0x11C38, 0x11C3D, 1 }, + { 0x11C3F, 0x11C92, 83 }, + { 0x11C93, 0x11CA7, 1 }, + { 0x11CAA, 0x11CB0, 1 }, + { 0x11CB2, 0x11CB3, 1 }, + { 0x11CB5, 0x11CB6, 1 }, + { 0x11D31, 0x11D36, 1 }, + { 0x11D3A, 0x11D3C, 2 }, + { 0x11D3D, 0x11D3F, 2 }, + { 0x11D40, 0x11D45, 1 }, + { 0x11D47, 0x11D90, 73 }, + { 0x11D91, 0x11D95, 4 }, + { 0x11D97, 0x11EF3, 348 }, + { 0x11EF4, 0x16AF0, 19452 }, + { 0x16AF1, 0x16AF4, 1 }, + { 0x16B30, 0x16B36, 1 }, + { 0x16F4F, 0x16F8F, 64 }, + { 0x16F90, 0x16F92, 1 }, + { 0x16FE4, 0x1BC9D, 19641 }, + { 0x1BC9E, 0x1CF00, 4706 }, + { 0x1CF01, 0x1CF2D, 1 }, + { 0x1CF30, 0x1CF46, 1 }, + { 0x1D165, 0x1D167, 2 }, + { 0x1D168, 0x1D169, 1 }, + { 0x1D16E, 0x1D172, 1 }, + { 0x1D17B, 0x1D182, 1 }, + { 0x1D185, 0x1D18B, 1 }, + { 0x1D1AA, 0x1D1AD, 1 }, + { 0x1D242, 0x1D244, 1 }, + { 0x1DA00, 0x1DA36, 1 }, + { 0x1DA3B, 0x1DA6C, 1 }, + { 0x1DA75, 0x1DA84, 15 }, + { 0x1DA9B, 0x1DA9F, 1 }, + { 0x1DAA1, 0x1DAAF, 1 }, + { 0x1E000, 0x1E006, 1 }, + { 0x1E008, 0x1E018, 1 }, + { 0x1E01B, 0x1E021, 1 }, + { 0x1E023, 0x1E024, 1 }, + { 0x1E026, 0x1E02A, 1 }, + { 0x1E130, 0x1E136, 1 }, + { 0x1E2AE, 0x1E2EC, 62 }, + { 0x1E2ED, 0x1E2EF, 1 }, + { 0x1E8D0, 0x1E8D6, 1 }, + { 0x1E944, 0x1E94A, 1 }, + { 0xE0020, 0xE007F, 1 }, + { 0xE0100, 0xE01EF, 1 }, +}; + +static struct range_table cntrl_table[] = { + { 0x0, 0x1F, 1 }, + { 0x7F, 0x9F, 1 }, + { 0xAD, 0x600, 1363 }, + { 0x601, 0x605, 1 }, + { 0x61C, 0x6DD, 193 }, + { 0x70F, 0x890, 385 }, + { 0x891, 0x8E2, 81 }, + { 0x180E, 0x200B, 2045 }, + { 0x200C, 0x200F, 1 }, + { 0x202A, 0x202E, 1 }, + { 0x2060, 0x2064, 1 }, + { 0x2066, 0x206F, 1 }, + { 0xE000, 0xE000, 0 }, + { 0xE001, 0xF8FF, 1 }, + { 0xFEFF, 0xFFF9, 250 }, + { 0xFFFA, 0xFFFB, 1 }, + { 0x110BD, 0x110CD, 16 }, + { 0x13430, 0x13438, 1 }, + { 0x1BCA0, 0x1BCA3, 1 }, + { 0x1D173, 0x1D17A, 1 }, + { 0xE0001, 0xE0020, 31 }, + { 0xE0021, 0xE007F, 1 }, + { 0xF0000, 0xF0000, 0 }, + { 0xF0001, 0xFFFFD, 1 }, + { 0x100000, 0x100000, 0 }, + { 0x100001, 0x10FFFD, 1 }, +}; + +static struct range_table digit_table[] = { + { 0x30, 0x39, 1 }, + { 0x660, 0x669, 1 }, + { 0x6F0, 0x6F9, 1 }, + { 0x7C0, 0x7C9, 1 }, + { 0x966, 0x96F, 1 }, + { 0x9E6, 0x9EF, 1 }, + { 0xA66, 0xA6F, 1 }, + { 0xAE6, 0xAEF, 1 }, + { 0xB66, 0xB6F, 1 }, + { 0xBE6, 0xBEF, 1 }, + { 0xC66, 0xC6F, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xD66, 0xD6F, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xE50, 0xE59, 1 }, + { 0xED0, 0xED9, 1 }, + { 0xF20, 0xF29, 1 }, + { 0x1040, 0x1049, 1 }, + { 0x1090, 0x1099, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1946, 0x194F, 1 }, + { 0x19D0, 0x19D9, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1B50, 0x1B59, 1 }, + { 0x1BB0, 0x1BB9, 1 }, + { 0x1C40, 0x1C49, 1 }, + { 0x1C50, 0x1C59, 1 }, + { 0xA620, 0xA629, 1 }, + { 0xA8D0, 0xA8D9, 1 }, + { 0xA900, 0xA909, 1 }, + { 0xA9D0, 0xA9D9, 1 }, + { 0xA9F0, 0xA9F9, 1 }, + { 0xAA50, 0xAA59, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x11066, 0x1106F, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11136, 0x1113F, 1 }, + { 0x111D0, 0x111D9, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11450, 0x11459, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11730, 0x11739, 1 }, + { 0x118E0, 0x118E9, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x11C50, 0x11C59, 1 }, + { 0x11D50, 0x11D59, 1 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x1D7CE, 0x1D7FF, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E950, 0x1E959, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, +}; + +static struct range_table alnum_extend_table[] = { + { 0x30, 0x39, 1 }, + { 0xB2, 0xB3, 1 }, + { 0xB9, 0xBC, 3 }, + { 0xBD, 0xBE, 1 }, + { 0x660, 0x669, 1 }, + { 0x6F0, 0x6F9, 1 }, + { 0x7C0, 0x7C9, 1 }, + { 0x966, 0x96F, 1 }, + { 0x9E6, 0x9EF, 1 }, + { 0x9F4, 0x9F9, 1 }, + { 0xA66, 0xA6F, 1 }, + { 0xAE6, 0xAEF, 1 }, + { 0xB66, 0xB6F, 1 }, + { 0xB72, 0xB77, 1 }, + { 0xBE6, 0xBF2, 1 }, + { 0xC66, 0xC6F, 1 }, + { 0xC78, 0xC7E, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xD58, 0xD5E, 1 }, + { 0xD66, 0xD78, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xE50, 0xE59, 1 }, + { 0xED0, 0xED9, 1 }, + { 0xF20, 0xF33, 1 }, + { 0x1040, 0x1049, 1 }, + { 0x1090, 0x1099, 1 }, + { 0x1369, 0x137C, 1 }, + { 0x16EE, 0x16F0, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x17F0, 0x17F9, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1946, 0x194F, 1 }, + { 0x19D0, 0x19DA, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1B50, 0x1B59, 1 }, + { 0x1BB0, 0x1BB9, 1 }, + { 0x1C40, 0x1C49, 1 }, + { 0x1C50, 0x1C59, 1 }, + { 0x2070, 0x2074, 4 }, + { 0x2075, 0x2079, 1 }, + { 0x2080, 0x2089, 1 }, + { 0x2150, 0x2182, 1 }, + { 0x2185, 0x2189, 1 }, + { 0x2460, 0x249B, 1 }, + { 0x24EA, 0x24FF, 1 }, + { 0x2776, 0x2793, 1 }, + { 0x2CFD, 0x3007, 778 }, + { 0x3021, 0x3029, 1 }, + { 0x3038, 0x303A, 1 }, + { 0x3192, 0x3195, 1 }, + { 0x3220, 0x3229, 1 }, + { 0x3248, 0x324F, 1 }, + { 0x3251, 0x325F, 1 }, + { 0x3280, 0x3289, 1 }, + { 0x32B1, 0x32BF, 1 }, + { 0xA620, 0xA629, 1 }, + { 0xA6E6, 0xA6EF, 1 }, + { 0xA830, 0xA835, 1 }, + { 0xA8D0, 0xA8D9, 1 }, + { 0xA900, 0xA909, 1 }, + { 0xA9D0, 0xA9D9, 1 }, + { 0xA9F0, 0xA9F9, 1 }, + { 0xAA50, 0xAA59, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0x10107, 0x10133, 1 }, + { 0x10140, 0x10178, 1 }, + { 0x1018A, 0x1018B, 1 }, + { 0x102E1, 0x102FB, 1 }, + { 0x10320, 0x10323, 1 }, + { 0x10341, 0x1034A, 9 }, + { 0x103D1, 0x103D5, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x10858, 0x1085F, 1 }, + { 0x10879, 0x1087F, 1 }, + { 0x108A7, 0x108AF, 1 }, + { 0x108FB, 0x108FF, 1 }, + { 0x10916, 0x1091B, 1 }, + { 0x109BC, 0x109BD, 1 }, + { 0x109C0, 0x109CF, 1 }, + { 0x109D2, 0x109FF, 1 }, + { 0x10A40, 0x10A48, 1 }, + { 0x10A7D, 0x10A7E, 1 }, + { 0x10A9D, 0x10A9F, 1 }, + { 0x10AEB, 0x10AEF, 1 }, + { 0x10B58, 0x10B5F, 1 }, + { 0x10B78, 0x10B7F, 1 }, + { 0x10BA9, 0x10BAF, 1 }, + { 0x10CFA, 0x10CFF, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x10E60, 0x10E7E, 1 }, + { 0x10F1D, 0x10F26, 1 }, + { 0x10F51, 0x10F54, 1 }, + { 0x10FC5, 0x10FCB, 1 }, + { 0x11052, 0x1106F, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11136, 0x1113F, 1 }, + { 0x111D0, 0x111D9, 1 }, + { 0x111E1, 0x111F4, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11450, 0x11459, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11730, 0x1173B, 1 }, + { 0x118E0, 0x118F2, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x11C50, 0x11C6C, 1 }, + { 0x11D50, 0x11D59, 1 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x11FC0, 0x11FD4, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x16B5B, 0x16B61, 1 }, + { 0x16E80, 0x16E96, 1 }, + { 0x1D2E0, 0x1D2F3, 1 }, + { 0x1D360, 0x1D378, 1 }, + { 0x1D7CE, 0x1D7FF, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E8C7, 0x1E8CF, 1 }, + { 0x1E950, 0x1E959, 1 }, + { 0x1EC71, 0x1ECAB, 1 }, + { 0x1ECAD, 0x1ECAF, 1 }, + { 0x1ECB1, 0x1ECB4, 1 }, + { 0x1ED01, 0x1ED2D, 1 }, + { 0x1ED2F, 0x1ED3D, 1 }, + { 0x1F100, 0x1F10C, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, +}; + +static struct range_table punct_table[] = { + { 0x21, 0x2F, 1 }, + { 0x3A, 0x40, 1 }, + { 0x5B, 0x60, 1 }, + { 0x7B, 0x7E, 1 }, + { 0xA1, 0xA5, 1 }, + { 0xA7, 0xA8, 1 }, + { 0xAB, 0xAC, 1 }, + { 0xAF, 0xB1, 2 }, + { 0xB4, 0xB6, 2 }, + { 0xB7, 0xB8, 1 }, + { 0xBB, 0xBF, 4 }, + { 0xD7, 0xF7, 32 }, + { 0x2C2, 0x2C5, 1 }, + { 0x2D2, 0x2DF, 1 }, + { 0x2E5, 0x2EB, 1 }, + { 0x2ED, 0x2EF, 2 }, + { 0x2F0, 0x2FF, 1 }, + { 0x375, 0x37E, 9 }, + { 0x384, 0x385, 1 }, + { 0x387, 0x3F6, 111 }, + { 0x55A, 0x55F, 1 }, + { 0x589, 0x58A, 1 }, + { 0x58F, 0x5BE, 47 }, + { 0x5C0, 0x5C6, 3 }, + { 0x5F3, 0x5F4, 1 }, + { 0x606, 0x60D, 1 }, + { 0x61B, 0x61D, 2 }, + { 0x61E, 0x61F, 1 }, + { 0x66A, 0x66D, 1 }, + { 0x6D4, 0x700, 44 }, + { 0x701, 0x70D, 1 }, + { 0x7F7, 0x7F9, 1 }, + { 0x7FE, 0x7FF, 1 }, + { 0x830, 0x83E, 1 }, + { 0x85E, 0x888, 42 }, + { 0x964, 0x965, 1 }, + { 0x970, 0x9F2, 130 }, + { 0x9F3, 0x9FB, 8 }, + { 0x9FD, 0xA76, 121 }, + { 0xAF0, 0xAF1, 1 }, + { 0xBF9, 0xC77, 126 }, + { 0xC84, 0xDF4, 368 }, + { 0xE3F, 0xE4F, 16 }, + { 0xE5A, 0xE5B, 1 }, + { 0xF04, 0xF12, 1 }, + { 0xF14, 0xF3A, 38 }, + { 0xF3B, 0xF3D, 1 }, + { 0xF85, 0xFD0, 75 }, + { 0xFD1, 0xFD4, 1 }, + { 0xFD9, 0xFDA, 1 }, + { 0x104A, 0x104F, 1 }, + { 0x10FB, 0x1360, 613 }, + { 0x1361, 0x1368, 1 }, + { 0x1400, 0x166E, 622 }, + { 0x169B, 0x169C, 1 }, + { 0x16EB, 0x16ED, 1 }, + { 0x1735, 0x1736, 1 }, + { 0x17D4, 0x17D6, 1 }, + { 0x17D8, 0x17DB, 1 }, + { 0x1800, 0x180A, 1 }, + { 0x1944, 0x1945, 1 }, + { 0x1A1E, 0x1A1F, 1 }, + { 0x1AA0, 0x1AA6, 1 }, + { 0x1AA8, 0x1AAD, 1 }, + { 0x1B5A, 0x1B60, 1 }, + { 0x1B7D, 0x1B7E, 1 }, + { 0x1BFC, 0x1BFF, 1 }, + { 0x1C3B, 0x1C3F, 1 }, + { 0x1C7E, 0x1C7F, 1 }, + { 0x1CC0, 0x1CC7, 1 }, + { 0x1CD3, 0x1FBD, 746 }, + { 0x1FBF, 0x1FC1, 1 }, + { 0x1FCD, 0x1FCF, 1 }, + { 0x1FDD, 0x1FDF, 1 }, + { 0x1FED, 0x1FEF, 1 }, + { 0x1FFD, 0x1FFE, 1 }, + { 0x2010, 0x2027, 1 }, + { 0x2030, 0x205E, 1 }, + { 0x207A, 0x207E, 1 }, + { 0x208A, 0x208E, 1 }, + { 0x20A0, 0x20C0, 1 }, + { 0x2118, 0x2140, 40 }, + { 0x2141, 0x2144, 1 }, + { 0x214B, 0x2190, 69 }, + { 0x2191, 0x2194, 1 }, + { 0x219A, 0x219B, 1 }, + { 0x21A0, 0x21A6, 3 }, + { 0x21AE, 0x21CE, 32 }, + { 0x21CF, 0x21D2, 3 }, + { 0x21D4, 0x21F4, 32 }, + { 0x21F5, 0x22FF, 1 }, + { 0x2308, 0x230B, 1 }, + { 0x2320, 0x2321, 1 }, + { 0x2329, 0x232A, 1 }, + { 0x237C, 0x239B, 31 }, + { 0x239C, 0x23B3, 1 }, + { 0x23DC, 0x23E1, 1 }, + { 0x25B7, 0x25C1, 10 }, + { 0x25F8, 0x25FF, 1 }, + { 0x266F, 0x2768, 249 }, + { 0x2769, 0x2775, 1 }, + { 0x27C0, 0x27FF, 1 }, + { 0x2900, 0x2AFF, 1 }, + { 0x2B30, 0x2B44, 1 }, + { 0x2B47, 0x2B4C, 1 }, + { 0x2CF9, 0x2CFC, 1 }, + { 0x2CFE, 0x2CFF, 1 }, + { 0x2D70, 0x2E00, 144 }, + { 0x2E01, 0x2E2E, 1 }, + { 0x2E30, 0x2E4F, 1 }, + { 0x2E52, 0x2E5D, 1 }, + { 0x3001, 0x3003, 1 }, + { 0x3008, 0x3011, 1 }, + { 0x3014, 0x301F, 1 }, + { 0x3030, 0x303D, 13 }, + { 0x309B, 0x309C, 1 }, + { 0x30A0, 0x30FB, 91 }, + { 0xA4FE, 0xA4FF, 1 }, + { 0xA60D, 0xA60F, 1 }, + { 0xA673, 0xA67E, 11 }, + { 0xA6F2, 0xA6F7, 1 }, + { 0xA700, 0xA716, 1 }, + { 0xA720, 0xA721, 1 }, + { 0xA789, 0xA78A, 1 }, + { 0xA838, 0xA874, 60 }, + { 0xA875, 0xA877, 1 }, + { 0xA8CE, 0xA8CF, 1 }, + { 0xA8F8, 0xA8FA, 1 }, + { 0xA8FC, 0xA92E, 50 }, + { 0xA92F, 0xA95F, 48 }, + { 0xA9C1, 0xA9CD, 1 }, + { 0xA9DE, 0xA9DF, 1 }, + { 0xAA5C, 0xAA5F, 1 }, + { 0xAADE, 0xAADF, 1 }, + { 0xAAF0, 0xAAF1, 1 }, + { 0xAB5B, 0xAB6A, 15 }, + { 0xAB6B, 0xABEB, 128 }, + { 0xFB29, 0xFBB2, 137 }, + { 0xFBB3, 0xFBC2, 1 }, + { 0xFD3E, 0xFD3F, 1 }, + { 0xFDFC, 0xFE10, 20 }, + { 0xFE11, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFF01, 0xFF0F, 1 }, + { 0xFF1A, 0xFF20, 1 }, + { 0xFF3B, 0xFF40, 1 }, + { 0xFF5B, 0xFF65, 1 }, + { 0xFFE0, 0xFFE3, 1 }, + { 0xFFE5, 0xFFE6, 1 }, + { 0xFFE9, 0xFFEC, 1 }, + { 0x10100, 0x10102, 1 }, + { 0x1039F, 0x103D0, 49 }, + { 0x1056F, 0x10857, 744 }, + { 0x1091F, 0x1093F, 32 }, + { 0x10A50, 0x10A58, 1 }, + { 0x10A7F, 0x10AF0, 113 }, + { 0x10AF1, 0x10AF6, 1 }, + { 0x10B39, 0x10B3F, 1 }, + { 0x10B99, 0x10B9C, 1 }, + { 0x10EAD, 0x10F55, 168 }, + { 0x10F56, 0x10F59, 1 }, + { 0x10F86, 0x10F89, 1 }, + { 0x11047, 0x1104D, 1 }, + { 0x110BB, 0x110BC, 1 }, + { 0x110BE, 0x110C1, 1 }, + { 0x11140, 0x11143, 1 }, + { 0x11174, 0x11175, 1 }, + { 0x111C5, 0x111C8, 1 }, + { 0x111CD, 0x111DB, 14 }, + { 0x111DD, 0x111DF, 1 }, + { 0x11238, 0x1123D, 1 }, + { 0x112A9, 0x1144B, 418 }, + { 0x1144C, 0x1144F, 1 }, + { 0x1145A, 0x1145B, 1 }, + { 0x1145D, 0x114C6, 105 }, + { 0x115C1, 0x115D7, 1 }, + { 0x11641, 0x11643, 1 }, + { 0x11660, 0x1166C, 1 }, + { 0x116B9, 0x1173C, 131 }, + { 0x1173D, 0x1173E, 1 }, + { 0x1183B, 0x11944, 265 }, + { 0x11945, 0x11946, 1 }, + { 0x119E2, 0x11A3F, 93 }, + { 0x11A40, 0x11A46, 1 }, + { 0x11A9A, 0x11A9C, 1 }, + { 0x11A9E, 0x11AA2, 1 }, + { 0x11C41, 0x11C45, 1 }, + { 0x11C70, 0x11C71, 1 }, + { 0x11EF7, 0x11EF8, 1 }, + { 0x11FDD, 0x11FE0, 1 }, + { 0x11FFF, 0x12470, 1137 }, + { 0x12471, 0x12474, 1 }, + { 0x12FF1, 0x12FF2, 1 }, + { 0x16A6E, 0x16A6F, 1 }, + { 0x16AF5, 0x16B37, 66 }, + { 0x16B38, 0x16B3B, 1 }, + { 0x16B44, 0x16E97, 851 }, + { 0x16E98, 0x16E9A, 1 }, + { 0x16FE2, 0x1BC9F, 19645 }, + { 0x1D6C1, 0x1D6DB, 26 }, + { 0x1D6FB, 0x1D715, 26 }, + { 0x1D735, 0x1D74F, 26 }, + { 0x1D76F, 0x1D789, 26 }, + { 0x1D7A9, 0x1D7C3, 26 }, + { 0x1DA87, 0x1DA8B, 1 }, + { 0x1E2FF, 0x1E95E, 1631 }, + { 0x1E95F, 0x1ECB0, 849 }, + { 0x1EEF0, 0x1EEF1, 1 }, + { 0x1F3FB, 0x1F3FF, 1 }, +}; + +static struct conv_table tolower_table[] = { + { 0x41, 0x5A, 1, 32 }, + { 0xC0, 0xD6, 1, 32 }, + { 0xD8, 0xDE, 1, 32 }, + { 0x100, 0x12E, 2, 1 }, + { 0x130, 0x130, 1, -199 }, + { 0x132, 0x136, 2, 1 }, + { 0x139, 0x147, 2, 1 }, + { 0x14A, 0x176, 2, 1 }, + { 0x178, 0x178, 1, -121 }, + { 0x179, 0x17D, 2, 1 }, + { 0x181, 0x181, 1, 210 }, + { 0x182, 0x184, 2, 1 }, + { 0x186, 0x186, 1, 206 }, + { 0x187, 0x187, 1, 1 }, + { 0x189, 0x18A, 1, 205 }, + { 0x18B, 0x18B, 1, 1 }, + { 0x18E, 0x18E, 1, 79 }, + { 0x18F, 0x18F, 1, 202 }, + { 0x190, 0x190, 1, 203 }, + { 0x191, 0x191, 1, 1 }, + { 0x193, 0x193, 1, 205 }, + { 0x194, 0x194, 1, 207 }, + { 0x196, 0x196, 1, 211 }, + { 0x197, 0x197, 1, 209 }, + { 0x198, 0x198, 1, 1 }, + { 0x19C, 0x19C, 1, 211 }, + { 0x19D, 0x19D, 1, 213 }, + { 0x19F, 0x19F, 1, 214 }, + { 0x1A0, 0x1A4, 2, 1 }, + { 0x1A6, 0x1A6, 1, 218 }, + { 0x1A7, 0x1A7, 1, 1 }, + { 0x1A9, 0x1A9, 1, 218 }, + { 0x1AC, 0x1AC, 1, 1 }, + { 0x1AE, 0x1AE, 1, 218 }, + { 0x1AF, 0x1AF, 1, 1 }, + { 0x1B1, 0x1B2, 1, 217 }, + { 0x1B3, 0x1B5, 2, 1 }, + { 0x1B7, 0x1B7, 1, 219 }, + { 0x1B8, 0x1BC, 4, 1 }, + { 0x1C4, 0x1C4, 1, 2 }, + { 0x1C5, 0x1C5, 1, 1 }, + { 0x1C7, 0x1C7, 1, 2 }, + { 0x1C8, 0x1C8, 1, 1 }, + { 0x1CA, 0x1CA, 1, 2 }, + { 0x1CB, 0x1DB, 2, 1 }, + { 0x1DE, 0x1EE, 2, 1 }, + { 0x1F1, 0x1F1, 1, 2 }, + { 0x1F2, 0x1F4, 2, 1 }, + { 0x1F6, 0x1F6, 1, -97 }, + { 0x1F7, 0x1F7, 1, -56 }, + { 0x1F8, 0x21E, 2, 1 }, + { 0x220, 0x220, 1, -130 }, + { 0x222, 0x232, 2, 1 }, + { 0x23A, 0x23A, 1, 10795 }, + { 0x23B, 0x23B, 1, 1 }, + { 0x23D, 0x23D, 1, -163 }, + { 0x23E, 0x23E, 1, 10792 }, + { 0x241, 0x241, 1, 1 }, + { 0x243, 0x243, 1, -195 }, + { 0x244, 0x244, 1, 69 }, + { 0x245, 0x245, 1, 71 }, + { 0x246, 0x24E, 2, 1 }, + { 0x370, 0x372, 2, 1 }, + { 0x376, 0x376, 1, 1 }, + { 0x37F, 0x37F, 1, 116 }, + { 0x386, 0x386, 1, 38 }, + { 0x388, 0x38A, 1, 37 }, + { 0x38C, 0x38C, 1, 64 }, + { 0x38E, 0x38F, 1, 63 }, + { 0x391, 0x3A1, 1, 32 }, + { 0x3A3, 0x3AB, 1, 32 }, + { 0x3CF, 0x3CF, 1, 8 }, + { 0x3D8, 0x3EE, 2, 1 }, + { 0x3F4, 0x3F4, 1, -60 }, + { 0x3F7, 0x3F7, 1, 1 }, + { 0x3F9, 0x3F9, 1, -7 }, + { 0x3FA, 0x3FA, 1, 1 }, + { 0x3FD, 0x3FF, 1, -130 }, + { 0x400, 0x40F, 1, 80 }, + { 0x410, 0x42F, 1, 32 }, + { 0x460, 0x480, 2, 1 }, + { 0x48A, 0x4BE, 2, 1 }, + { 0x4C0, 0x4C0, 1, 15 }, + { 0x4C1, 0x4CD, 2, 1 }, + { 0x4D0, 0x52E, 2, 1 }, + { 0x531, 0x556, 1, 48 }, + { 0x10A0, 0x10C5, 1, 7264 }, + { 0x10C7, 0x10CD, 6, 7264 }, + { 0x13A0, 0x13EF, 1, 38864 }, + { 0x13F0, 0x13F5, 1, 8 }, + { 0x1C90, 0x1CBA, 1, -3008 }, + { 0x1CBD, 0x1CBF, 1, -3008 }, + { 0x1E00, 0x1E94, 2, 1 }, + { 0x1E9E, 0x1E9E, 1, -7615 }, + { 0x1EA0, 0x1EFE, 2, 1 }, + { 0x1F08, 0x1F0F, 1, -8 }, + { 0x1F18, 0x1F1D, 1, -8 }, + { 0x1F28, 0x1F2F, 1, -8 }, + { 0x1F38, 0x1F3F, 1, -8 }, + { 0x1F48, 0x1F4D, 1, -8 }, + { 0x1F59, 0x1F5F, 2, -8 }, + { 0x1F68, 0x1F6F, 1, -8 }, + { 0x1F88, 0x1F8F, 1, -8 }, + { 0x1F98, 0x1F9F, 1, -8 }, + { 0x1FA8, 0x1FAF, 1, -8 }, + { 0x1FB8, 0x1FB9, 1, -8 }, + { 0x1FBA, 0x1FBB, 1, -74 }, + { 0x1FBC, 0x1FBC, 1, -9 }, + { 0x1FC8, 0x1FCB, 1, -86 }, + { 0x1FCC, 0x1FCC, 1, -9 }, + { 0x1FD8, 0x1FD9, 1, -8 }, + { 0x1FDA, 0x1FDB, 1, -100 }, + { 0x1FE8, 0x1FE9, 1, -8 }, + { 0x1FEA, 0x1FEB, 1, -112 }, + { 0x1FEC, 0x1FEC, 1, -7 }, + { 0x1FF8, 0x1FF9, 1, -128 }, + { 0x1FFA, 0x1FFB, 1, -126 }, + { 0x1FFC, 0x1FFC, 1, -9 }, + { 0x2126, 0x2126, 1, -7517 }, + { 0x212A, 0x212A, 1, -8383 }, + { 0x212B, 0x212B, 1, -8262 }, + { 0x2132, 0x2132, 1, 28 }, + { 0x2160, 0x216F, 1, 16 }, + { 0x2183, 0x2183, 1, 1 }, + { 0x24B6, 0x24CF, 1, 26 }, + { 0x2C00, 0x2C2F, 1, 48 }, + { 0x2C60, 0x2C60, 1, 1 }, + { 0x2C62, 0x2C62, 1, -10743 }, + { 0x2C63, 0x2C63, 1, -3814 }, + { 0x2C64, 0x2C64, 1, -10727 }, + { 0x2C67, 0x2C6B, 2, 1 }, + { 0x2C6D, 0x2C6D, 1, -10780 }, + { 0x2C6E, 0x2C6E, 1, -10749 }, + { 0x2C6F, 0x2C6F, 1, -10783 }, + { 0x2C70, 0x2C70, 1, -10782 }, + { 0x2C72, 0x2C75, 3, 1 }, + { 0x2C7E, 0x2C7F, 1, -10815 }, + { 0x2C80, 0x2CE2, 2, 1 }, + { 0x2CEB, 0x2CED, 2, 1 }, + { 0x2CF2, 0xA640, 31054, 1 }, + { 0xA642, 0xA66C, 2, 1 }, + { 0xA680, 0xA69A, 2, 1 }, + { 0xA722, 0xA72E, 2, 1 }, + { 0xA732, 0xA76E, 2, 1 }, + { 0xA779, 0xA77B, 2, 1 }, + { 0xA77D, 0xA77D, 1, -35332 }, + { 0xA77E, 0xA786, 2, 1 }, + { 0xA78B, 0xA78B, 1, 1 }, + { 0xA78D, 0xA78D, 1, -42280 }, + { 0xA790, 0xA792, 2, 1 }, + { 0xA796, 0xA7A8, 2, 1 }, + { 0xA7AA, 0xA7AA, 1, -42308 }, + { 0xA7AB, 0xA7AB, 1, -42319 }, + { 0xA7AC, 0xA7AC, 1, -42315 }, + { 0xA7AD, 0xA7AD, 1, -42305 }, + { 0xA7AE, 0xA7AE, 1, -42308 }, + { 0xA7B0, 0xA7B0, 1, -42258 }, + { 0xA7B1, 0xA7B1, 1, -42282 }, + { 0xA7B2, 0xA7B2, 1, -42261 }, + { 0xA7B3, 0xA7B3, 1, 928 }, + { 0xA7B4, 0xA7C2, 2, 1 }, + { 0xA7C4, 0xA7C4, 1, -48 }, + { 0xA7C5, 0xA7C5, 1, -42307 }, + { 0xA7C6, 0xA7C6, 1, -35384 }, + { 0xA7C7, 0xA7C9, 2, 1 }, + { 0xA7D0, 0xA7D6, 6, 1 }, + { 0xA7D8, 0xA7F5, 29, 1 }, + { 0xFF21, 0xFF3A, 1, 32 }, + { 0x10400, 0x10427, 1, 40 }, + { 0x104B0, 0x104D3, 1, 40 }, + { 0x10570, 0x1057A, 1, 39 }, + { 0x1057C, 0x1058A, 1, 39 }, + { 0x1058C, 0x10592, 1, 39 }, + { 0x10594, 0x10595, 1, 39 }, + { 0x10C80, 0x10CB2, 1, 64 }, + { 0x118A0, 0x118BF, 1, 32 }, + { 0x16E40, 0x16E5F, 1, 32 }, + { 0x1E900, 0x1E921, 1, 34 }, +}; + +static struct conv_table toupper_table[] = { + { 0x61, 0x7A, 1, -32 }, + { 0xB5, 0xB5, 1, 743 }, + { 0xE0, 0xF6, 1, -32 }, + { 0xF8, 0xFE, 1, -32 }, + { 0xFF, 0xFF, 1, 121 }, + { 0x101, 0x12F, 2, -1 }, + { 0x131, 0x131, 1, -232 }, + { 0x133, 0x137, 2, -1 }, + { 0x13A, 0x148, 2, -1 }, + { 0x14B, 0x177, 2, -1 }, + { 0x17A, 0x17E, 2, -1 }, + { 0x17F, 0x17F, 1, -300 }, + { 0x180, 0x180, 1, 195 }, + { 0x183, 0x185, 2, -1 }, + { 0x188, 0x18C, 4, -1 }, + { 0x192, 0x192, 1, -1 }, + { 0x195, 0x195, 1, 97 }, + { 0x199, 0x199, 1, -1 }, + { 0x19A, 0x19A, 1, 163 }, + { 0x19E, 0x19E, 1, 130 }, + { 0x1A1, 0x1A5, 2, -1 }, + { 0x1A8, 0x1AD, 5, -1 }, + { 0x1B0, 0x1B4, 4, -1 }, + { 0x1B6, 0x1B9, 3, -1 }, + { 0x1BD, 0x1BD, 1, -1 }, + { 0x1BF, 0x1BF, 1, 56 }, + { 0x1C5, 0x1C5, 1, -1 }, + { 0x1C6, 0x1C6, 1, -2 }, + { 0x1C8, 0x1C8, 1, -1 }, + { 0x1C9, 0x1C9, 1, -2 }, + { 0x1CB, 0x1CB, 1, -1 }, + { 0x1CC, 0x1CC, 1, -2 }, + { 0x1CE, 0x1DC, 2, -1 }, + { 0x1DD, 0x1DD, 1, -79 }, + { 0x1DF, 0x1EF, 2, -1 }, + { 0x1F2, 0x1F2, 1, -1 }, + { 0x1F3, 0x1F3, 1, -2 }, + { 0x1F5, 0x1F9, 4, -1 }, + { 0x1FB, 0x21F, 2, -1 }, + { 0x223, 0x233, 2, -1 }, + { 0x23C, 0x23C, 1, -1 }, + { 0x23F, 0x240, 1, 10815 }, + { 0x242, 0x247, 5, -1 }, + { 0x249, 0x24F, 2, -1 }, + { 0x250, 0x250, 1, 10783 }, + { 0x251, 0x251, 1, 10780 }, + { 0x252, 0x252, 1, 10782 }, + { 0x253, 0x253, 1, -210 }, + { 0x254, 0x254, 1, -206 }, + { 0x256, 0x257, 1, -205 }, + { 0x259, 0x259, 1, -202 }, + { 0x25B, 0x25B, 1, -203 }, + { 0x25C, 0x25C, 1, 42319 }, + { 0x260, 0x260, 1, -205 }, + { 0x261, 0x261, 1, 42315 }, + { 0x263, 0x263, 1, -207 }, + { 0x265, 0x265, 1, 42280 }, + { 0x266, 0x266, 1, 42308 }, + { 0x268, 0x268, 1, -209 }, + { 0x269, 0x269, 1, -211 }, + { 0x26A, 0x26A, 1, 42308 }, + { 0x26B, 0x26B, 1, 10743 }, + { 0x26C, 0x26C, 1, 42305 }, + { 0x26F, 0x26F, 1, -211 }, + { 0x271, 0x271, 1, 10749 }, + { 0x272, 0x272, 1, -213 }, + { 0x275, 0x275, 1, -214 }, + { 0x27D, 0x27D, 1, 10727 }, + { 0x280, 0x280, 1, -218 }, + { 0x282, 0x282, 1, 42307 }, + { 0x283, 0x283, 1, -218 }, + { 0x287, 0x287, 1, 42282 }, + { 0x288, 0x288, 1, -218 }, + { 0x289, 0x289, 1, -69 }, + { 0x28A, 0x28B, 1, -217 }, + { 0x28C, 0x28C, 1, -71 }, + { 0x292, 0x292, 1, -219 }, + { 0x29D, 0x29D, 1, 42261 }, + { 0x29E, 0x29E, 1, 42258 }, + { 0x345, 0x345, 1, 84 }, + { 0x371, 0x373, 2, -1 }, + { 0x377, 0x377, 1, -1 }, + { 0x37B, 0x37D, 1, 130 }, + { 0x3AC, 0x3AC, 1, -38 }, + { 0x3AD, 0x3AF, 1, -37 }, + { 0x3B1, 0x3C1, 1, -32 }, + { 0x3C2, 0x3C2, 1, -31 }, + { 0x3C3, 0x3CB, 1, -32 }, + { 0x3CC, 0x3CC, 1, -64 }, + { 0x3CD, 0x3CE, 1, -63 }, + { 0x3D0, 0x3D0, 1, -62 }, + { 0x3D1, 0x3D1, 1, -57 }, + { 0x3D5, 0x3D5, 1, -47 }, + { 0x3D6, 0x3D6, 1, -54 }, + { 0x3D7, 0x3D7, 1, -8 }, + { 0x3D9, 0x3EF, 2, -1 }, + { 0x3F0, 0x3F0, 1, -86 }, + { 0x3F1, 0x3F1, 1, -80 }, + { 0x3F2, 0x3F2, 1, 7 }, + { 0x3F3, 0x3F3, 1, -116 }, + { 0x3F5, 0x3F5, 1, -96 }, + { 0x3F8, 0x3FB, 3, -1 }, + { 0x430, 0x44F, 1, -32 }, + { 0x450, 0x45F, 1, -80 }, + { 0x461, 0x481, 2, -1 }, + { 0x48B, 0x4BF, 2, -1 }, + { 0x4C2, 0x4CE, 2, -1 }, + { 0x4CF, 0x4CF, 1, -15 }, + { 0x4D1, 0x52F, 2, -1 }, + { 0x561, 0x586, 1, -48 }, + { 0x10D0, 0x10FA, 1, 3008 }, + { 0x10FD, 0x10FF, 1, 3008 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6254 }, + { 0x1C81, 0x1C81, 1, -6253 }, + { 0x1C82, 0x1C82, 1, -6244 }, + { 0x1C83, 0x1C84, 1, -6242 }, + { 0x1C85, 0x1C85, 1, -6243 }, + { 0x1C86, 0x1C86, 1, -6236 }, + { 0x1C87, 0x1C87, 1, -6181 }, + { 0x1C88, 0x1C88, 1, 35266 }, + { 0x1D79, 0x1D79, 1, 35332 }, + { 0x1D7D, 0x1D7D, 1, 3814 }, + { 0x1D8E, 0x1D8E, 1, 35384 }, + { 0x1E01, 0x1E95, 2, -1 }, + { 0x1E9B, 0x1E9B, 1, -59 }, + { 0x1EA1, 0x1EFF, 2, -1 }, + { 0x1F00, 0x1F07, 1, 8 }, + { 0x1F10, 0x1F15, 1, 8 }, + { 0x1F20, 0x1F27, 1, 8 }, + { 0x1F30, 0x1F37, 1, 8 }, + { 0x1F40, 0x1F45, 1, 8 }, + { 0x1F51, 0x1F57, 2, 8 }, + { 0x1F60, 0x1F67, 1, 8 }, + { 0x1F70, 0x1F71, 1, 74 }, + { 0x1F72, 0x1F75, 1, 86 }, + { 0x1F76, 0x1F77, 1, 100 }, + { 0x1F78, 0x1F79, 1, 128 }, + { 0x1F7A, 0x1F7B, 1, 112 }, + { 0x1F7C, 0x1F7D, 1, 126 }, + { 0x1F80, 0x1F87, 1, 8 }, + { 0x1F90, 0x1F97, 1, 8 }, + { 0x1FA0, 0x1FA7, 1, 8 }, + { 0x1FB0, 0x1FB1, 1, 8 }, + { 0x1FB3, 0x1FB3, 1, 9 }, + { 0x1FBE, 0x1FBE, 1, -7205 }, + { 0x1FC3, 0x1FC3, 1, 9 }, + { 0x1FD0, 0x1FD1, 1, 8 }, + { 0x1FE0, 0x1FE1, 1, 8 }, + { 0x1FE5, 0x1FE5, 1, 7 }, + { 0x1FF3, 0x1FF3, 1, 9 }, + { 0x214E, 0x214E, 1, -28 }, + { 0x2170, 0x217F, 1, -16 }, + { 0x2184, 0x2184, 1, -1 }, + { 0x24D0, 0x24E9, 1, -26 }, + { 0x2C30, 0x2C5F, 1, -48 }, + { 0x2C61, 0x2C61, 1, -1 }, + { 0x2C65, 0x2C65, 1, -10795 }, + { 0x2C66, 0x2C66, 1, -10792 }, + { 0x2C68, 0x2C6C, 2, -1 }, + { 0x2C73, 0x2C76, 3, -1 }, + { 0x2C81, 0x2CE3, 2, -1 }, + { 0x2CEC, 0x2CEE, 2, -1 }, + { 0x2CF3, 0x2CF3, 1, -1 }, + { 0x2D00, 0x2D25, 1, -7264 }, + { 0x2D27, 0x2D2D, 6, -7264 }, + { 0xA641, 0xA66D, 2, -1 }, + { 0xA681, 0xA69B, 2, -1 }, + { 0xA723, 0xA72F, 2, -1 }, + { 0xA733, 0xA76F, 2, -1 }, + { 0xA77A, 0xA77C, 2, -1 }, + { 0xA77F, 0xA787, 2, -1 }, + { 0xA78C, 0xA791, 5, -1 }, + { 0xA793, 0xA793, 1, -1 }, + { 0xA794, 0xA794, 1, 48 }, + { 0xA797, 0xA7A9, 2, -1 }, + { 0xA7B5, 0xA7C3, 2, -1 }, + { 0xA7C8, 0xA7CA, 2, -1 }, + { 0xA7D1, 0xA7D7, 6, -1 }, + { 0xA7D9, 0xA7F6, 29, -1 }, + { 0xAB53, 0xAB53, 1, -928 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF41, 0xFF5A, 1, -32 }, + { 0x10428, 0x1044F, 1, -40 }, + { 0x104D8, 0x104FB, 1, -40 }, + { 0x10597, 0x105A1, 1, -39 }, + { 0x105A3, 0x105B1, 1, -39 }, + { 0x105B3, 0x105B9, 1, -39 }, + { 0x105BB, 0x105BC, 1, -39 }, + { 0x10CC0, 0x10CF2, 1, -64 }, + { 0x118C0, 0x118DF, 1, -32 }, + { 0x16E60, 0x16E7F, 1, -32 }, + { 0x1E922, 0x1E943, 1, -34 }, +}; + +static struct conv_table totitle_table[] = { + { 0x61, 0x7A, 1, -32 }, + { 0xB5, 0xB5, 1, 743 }, + { 0xE0, 0xF6, 1, -32 }, + { 0xF8, 0xFE, 1, -32 }, + { 0xFF, 0xFF, 1, 121 }, + { 0x101, 0x12F, 2, -1 }, + { 0x131, 0x131, 1, -232 }, + { 0x133, 0x137, 2, -1 }, + { 0x13A, 0x148, 2, -1 }, + { 0x14B, 0x177, 2, -1 }, + { 0x17A, 0x17E, 2, -1 }, + { 0x17F, 0x17F, 1, -300 }, + { 0x180, 0x180, 1, 195 }, + { 0x183, 0x185, 2, -1 }, + { 0x188, 0x18C, 4, -1 }, + { 0x192, 0x192, 1, -1 }, + { 0x195, 0x195, 1, 97 }, + { 0x199, 0x199, 1, -1 }, + { 0x19A, 0x19A, 1, 163 }, + { 0x19E, 0x19E, 1, 130 }, + { 0x1A1, 0x1A5, 2, -1 }, + { 0x1A8, 0x1AD, 5, -1 }, + { 0x1B0, 0x1B4, 4, -1 }, + { 0x1B6, 0x1B9, 3, -1 }, + { 0x1BD, 0x1BD, 1, -1 }, + { 0x1BF, 0x1BF, 1, 56 }, + { 0x1C4, 0x1C4, 1, 1 }, + { 0x1C5, 0x1C5, 1, 0 }, + { 0x1C6, 0x1C6, 1, -1 }, + { 0x1C7, 0x1C7, 1, 1 }, + { 0x1C8, 0x1C8, 1, 0 }, + { 0x1C9, 0x1C9, 1, -1 }, + { 0x1CA, 0x1CA, 1, 1 }, + { 0x1CB, 0x1CB, 1, 0 }, + { 0x1CC, 0x1DC, 2, -1 }, + { 0x1DD, 0x1DD, 1, -79 }, + { 0x1DF, 0x1EF, 2, -1 }, + { 0x1F1, 0x1F1, 1, 1 }, + { 0x1F2, 0x1F2, 1, 0 }, + { 0x1F3, 0x1F5, 2, -1 }, + { 0x1F9, 0x21F, 2, -1 }, + { 0x223, 0x233, 2, -1 }, + { 0x23C, 0x23C, 1, -1 }, + { 0x23F, 0x240, 1, 10815 }, + { 0x242, 0x247, 5, -1 }, + { 0x249, 0x24F, 2, -1 }, + { 0x250, 0x250, 1, 10783 }, + { 0x251, 0x251, 1, 10780 }, + { 0x252, 0x252, 1, 10782 }, + { 0x253, 0x253, 1, -210 }, + { 0x254, 0x254, 1, -206 }, + { 0x256, 0x257, 1, -205 }, + { 0x259, 0x259, 1, -202 }, + { 0x25B, 0x25B, 1, -203 }, + { 0x25C, 0x25C, 1, 42319 }, + { 0x260, 0x260, 1, -205 }, + { 0x261, 0x261, 1, 42315 }, + { 0x263, 0x263, 1, -207 }, + { 0x265, 0x265, 1, 42280 }, + { 0x266, 0x266, 1, 42308 }, + { 0x268, 0x268, 1, -209 }, + { 0x269, 0x269, 1, -211 }, + { 0x26A, 0x26A, 1, 42308 }, + { 0x26B, 0x26B, 1, 10743 }, + { 0x26C, 0x26C, 1, 42305 }, + { 0x26F, 0x26F, 1, -211 }, + { 0x271, 0x271, 1, 10749 }, + { 0x272, 0x272, 1, -213 }, + { 0x275, 0x275, 1, -214 }, + { 0x27D, 0x27D, 1, 10727 }, + { 0x280, 0x280, 1, -218 }, + { 0x282, 0x282, 1, 42307 }, + { 0x283, 0x283, 1, -218 }, + { 0x287, 0x287, 1, 42282 }, + { 0x288, 0x288, 1, -218 }, + { 0x289, 0x289, 1, -69 }, + { 0x28A, 0x28B, 1, -217 }, + { 0x28C, 0x28C, 1, -71 }, + { 0x292, 0x292, 1, -219 }, + { 0x29D, 0x29D, 1, 42261 }, + { 0x29E, 0x29E, 1, 42258 }, + { 0x345, 0x345, 1, 84 }, + { 0x371, 0x373, 2, -1 }, + { 0x377, 0x377, 1, -1 }, + { 0x37B, 0x37D, 1, 130 }, + { 0x3AC, 0x3AC, 1, -38 }, + { 0x3AD, 0x3AF, 1, -37 }, + { 0x3B1, 0x3C1, 1, -32 }, + { 0x3C2, 0x3C2, 1, -31 }, + { 0x3C3, 0x3CB, 1, -32 }, + { 0x3CC, 0x3CC, 1, -64 }, + { 0x3CD, 0x3CE, 1, -63 }, + { 0x3D0, 0x3D0, 1, -62 }, + { 0x3D1, 0x3D1, 1, -57 }, + { 0x3D5, 0x3D5, 1, -47 }, + { 0x3D6, 0x3D6, 1, -54 }, + { 0x3D7, 0x3D7, 1, -8 }, + { 0x3D9, 0x3EF, 2, -1 }, + { 0x3F0, 0x3F0, 1, -86 }, + { 0x3F1, 0x3F1, 1, -80 }, + { 0x3F2, 0x3F2, 1, 7 }, + { 0x3F3, 0x3F3, 1, -116 }, + { 0x3F5, 0x3F5, 1, -96 }, + { 0x3F8, 0x3FB, 3, -1 }, + { 0x430, 0x44F, 1, -32 }, + { 0x450, 0x45F, 1, -80 }, + { 0x461, 0x481, 2, -1 }, + { 0x48B, 0x4BF, 2, -1 }, + { 0x4C2, 0x4CE, 2, -1 }, + { 0x4CF, 0x4CF, 1, -15 }, + { 0x4D1, 0x52F, 2, -1 }, + { 0x561, 0x586, 1, -48 }, + { 0x10D0, 0x10FA, 1, 0 }, + { 0x10FD, 0x10FF, 1, 0 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6254 }, + { 0x1C81, 0x1C81, 1, -6253 }, + { 0x1C82, 0x1C82, 1, -6244 }, + { 0x1C83, 0x1C84, 1, -6242 }, + { 0x1C85, 0x1C85, 1, -6243 }, + { 0x1C86, 0x1C86, 1, -6236 }, + { 0x1C87, 0x1C87, 1, -6181 }, + { 0x1C88, 0x1C88, 1, 35266 }, + { 0x1D79, 0x1D79, 1, 35332 }, + { 0x1D7D, 0x1D7D, 1, 3814 }, + { 0x1D8E, 0x1D8E, 1, 35384 }, + { 0x1E01, 0x1E95, 2, -1 }, + { 0x1E9B, 0x1E9B, 1, -59 }, + { 0x1EA1, 0x1EFF, 2, -1 }, + { 0x1F00, 0x1F07, 1, 8 }, + { 0x1F10, 0x1F15, 1, 8 }, + { 0x1F20, 0x1F27, 1, 8 }, + { 0x1F30, 0x1F37, 1, 8 }, + { 0x1F40, 0x1F45, 1, 8 }, + { 0x1F51, 0x1F57, 2, 8 }, + { 0x1F60, 0x1F67, 1, 8 }, + { 0x1F70, 0x1F71, 1, 74 }, + { 0x1F72, 0x1F75, 1, 86 }, + { 0x1F76, 0x1F77, 1, 100 }, + { 0x1F78, 0x1F79, 1, 128 }, + { 0x1F7A, 0x1F7B, 1, 112 }, + { 0x1F7C, 0x1F7D, 1, 126 }, + { 0x1F80, 0x1F87, 1, 8 }, + { 0x1F90, 0x1F97, 1, 8 }, + { 0x1FA0, 0x1FA7, 1, 8 }, + { 0x1FB0, 0x1FB1, 1, 8 }, + { 0x1FB3, 0x1FB3, 1, 9 }, + { 0x1FBE, 0x1FBE, 1, -7205 }, + { 0x1FC3, 0x1FC3, 1, 9 }, + { 0x1FD0, 0x1FD1, 1, 8 }, + { 0x1FE0, 0x1FE1, 1, 8 }, + { 0x1FE5, 0x1FE5, 1, 7 }, + { 0x1FF3, 0x1FF3, 1, 9 }, + { 0x214E, 0x214E, 1, -28 }, + { 0x2170, 0x217F, 1, -16 }, + { 0x2184, 0x2184, 1, -1 }, + { 0x24D0, 0x24E9, 1, -26 }, + { 0x2C30, 0x2C5F, 1, -48 }, + { 0x2C61, 0x2C61, 1, -1 }, + { 0x2C65, 0x2C65, 1, -10795 }, + { 0x2C66, 0x2C66, 1, -10792 }, + { 0x2C68, 0x2C6C, 2, -1 }, + { 0x2C73, 0x2C76, 3, -1 }, + { 0x2C81, 0x2CE3, 2, -1 }, + { 0x2CEC, 0x2CEE, 2, -1 }, + { 0x2CF3, 0x2CF3, 1, -1 }, + { 0x2D00, 0x2D25, 1, -7264 }, + { 0x2D27, 0x2D2D, 6, -7264 }, + { 0xA641, 0xA66D, 2, -1 }, + { 0xA681, 0xA69B, 2, -1 }, + { 0xA723, 0xA72F, 2, -1 }, + { 0xA733, 0xA76F, 2, -1 }, + { 0xA77A, 0xA77C, 2, -1 }, + { 0xA77F, 0xA787, 2, -1 }, + { 0xA78C, 0xA791, 5, -1 }, + { 0xA793, 0xA793, 1, -1 }, + { 0xA794, 0xA794, 1, 48 }, + { 0xA797, 0xA7A9, 2, -1 }, + { 0xA7B5, 0xA7C3, 2, -1 }, + { 0xA7C8, 0xA7CA, 2, -1 }, + { 0xA7D1, 0xA7D7, 6, -1 }, + { 0xA7D9, 0xA7F6, 29, -1 }, + { 0xAB53, 0xAB53, 1, -928 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF41, 0xFF5A, 1, -32 }, + { 0x10428, 0x1044F, 1, -40 }, + { 0x104D8, 0x104FB, 1, -40 }, + { 0x10597, 0x105A1, 1, -39 }, + { 0x105A3, 0x105B1, 1, -39 }, + { 0x105B3, 0x105B9, 1, -39 }, + { 0x105BB, 0x105BC, 1, -39 }, + { 0x10CC0, 0x10CF2, 1, -64 }, + { 0x118C0, 0x118DF, 1, -32 }, + { 0x16E60, 0x16E7F, 1, -32 }, + { 0x1E922, 0x1E943, 1, -34 }, +}; + +static struct conv_table tofold_table[] = { + { 0x41, 0x5A, 1, 32 }, + { 0xB5, 0xB5, 1, 775 }, + { 0xC0, 0xD6, 1, 32 }, + { 0xD8, 0xDE, 1, 32 }, + { 0x100, 0x12E, 2, 1 }, + { 0x132, 0x136, 2, 1 }, + { 0x139, 0x147, 2, 1 }, + { 0x14A, 0x176, 2, 1 }, + { 0x178, 0x178, 1, -121 }, + { 0x179, 0x17D, 2, 1 }, + { 0x17F, 0x17F, 1, -268 }, + { 0x181, 0x181, 1, 210 }, + { 0x182, 0x184, 2, 1 }, + { 0x186, 0x186, 1, 206 }, + { 0x187, 0x187, 1, 1 }, + { 0x189, 0x18A, 1, 205 }, + { 0x18B, 0x18B, 1, 1 }, + { 0x18E, 0x18E, 1, 79 }, + { 0x18F, 0x18F, 1, 202 }, + { 0x190, 0x190, 1, 203 }, + { 0x191, 0x191, 1, 1 }, + { 0x193, 0x193, 1, 205 }, + { 0x194, 0x194, 1, 207 }, + { 0x196, 0x196, 1, 211 }, + { 0x197, 0x197, 1, 209 }, + { 0x198, 0x198, 1, 1 }, + { 0x19C, 0x19C, 1, 211 }, + { 0x19D, 0x19D, 1, 213 }, + { 0x19F, 0x19F, 1, 214 }, + { 0x1A0, 0x1A4, 2, 1 }, + { 0x1A6, 0x1A6, 1, 218 }, + { 0x1A7, 0x1A7, 1, 1 }, + { 0x1A9, 0x1A9, 1, 218 }, + { 0x1AC, 0x1AC, 1, 1 }, + { 0x1AE, 0x1AE, 1, 218 }, + { 0x1AF, 0x1AF, 1, 1 }, + { 0x1B1, 0x1B2, 1, 217 }, + { 0x1B3, 0x1B5, 2, 1 }, + { 0x1B7, 0x1B7, 1, 219 }, + { 0x1B8, 0x1BC, 4, 1 }, + { 0x1C4, 0x1C4, 1, 2 }, + { 0x1C5, 0x1C5, 1, 1 }, + { 0x1C7, 0x1C7, 1, 2 }, + { 0x1C8, 0x1C8, 1, 1 }, + { 0x1CA, 0x1CA, 1, 2 }, + { 0x1CB, 0x1DB, 2, 1 }, + { 0x1DE, 0x1EE, 2, 1 }, + { 0x1F1, 0x1F1, 1, 2 }, + { 0x1F2, 0x1F4, 2, 1 }, + { 0x1F6, 0x1F6, 1, -97 }, + { 0x1F7, 0x1F7, 1, -56 }, + { 0x1F8, 0x21E, 2, 1 }, + { 0x220, 0x220, 1, -130 }, + { 0x222, 0x232, 2, 1 }, + { 0x23A, 0x23A, 1, 10795 }, + { 0x23B, 0x23B, 1, 1 }, + { 0x23D, 0x23D, 1, -163 }, + { 0x23E, 0x23E, 1, 10792 }, + { 0x241, 0x241, 1, 1 }, + { 0x243, 0x243, 1, -195 }, + { 0x244, 0x244, 1, 69 }, + { 0x245, 0x245, 1, 71 }, + { 0x246, 0x24E, 2, 1 }, + { 0x345, 0x345, 1, 116 }, + { 0x370, 0x372, 2, 1 }, + { 0x376, 0x376, 1, 1 }, + { 0x37F, 0x37F, 1, 116 }, + { 0x386, 0x386, 1, 38 }, + { 0x388, 0x38A, 1, 37 }, + { 0x38C, 0x38C, 1, 64 }, + { 0x38E, 0x38F, 1, 63 }, + { 0x391, 0x3A1, 1, 32 }, + { 0x3A3, 0x3AB, 1, 32 }, + { 0x3C2, 0x3C2, 1, 1 }, + { 0x3CF, 0x3CF, 1, 8 }, + { 0x3D0, 0x3D0, 1, -30 }, + { 0x3D1, 0x3D1, 1, -25 }, + { 0x3D5, 0x3D5, 1, -15 }, + { 0x3D6, 0x3D6, 1, -22 }, + { 0x3D8, 0x3EE, 2, 1 }, + { 0x3F0, 0x3F0, 1, -54 }, + { 0x3F1, 0x3F1, 1, -48 }, + { 0x3F4, 0x3F4, 1, -60 }, + { 0x3F5, 0x3F5, 1, -64 }, + { 0x3F7, 0x3F7, 1, 1 }, + { 0x3F9, 0x3F9, 1, -7 }, + { 0x3FA, 0x3FA, 1, 1 }, + { 0x3FD, 0x3FF, 1, -130 }, + { 0x400, 0x40F, 1, 80 }, + { 0x410, 0x42F, 1, 32 }, + { 0x460, 0x480, 2, 1 }, + { 0x48A, 0x4BE, 2, 1 }, + { 0x4C0, 0x4C0, 1, 15 }, + { 0x4C1, 0x4CD, 2, 1 }, + { 0x4D0, 0x52E, 2, 1 }, + { 0x531, 0x556, 1, 48 }, + { 0x10A0, 0x10C5, 1, 7264 }, + { 0x10C7, 0x10CD, 6, 7264 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6222 }, + { 0x1C81, 0x1C81, 1, -6221 }, + { 0x1C82, 0x1C82, 1, -6212 }, + { 0x1C83, 0x1C84, 1, -6210 }, + { 0x1C85, 0x1C85, 1, -6211 }, + { 0x1C86, 0x1C86, 1, -6204 }, + { 0x1C87, 0x1C87, 1, -6180 }, + { 0x1C88, 0x1C88, 1, 35267 }, + { 0x1C90, 0x1CBA, 1, -3008 }, + { 0x1CBD, 0x1CBF, 1, -3008 }, + { 0x1E00, 0x1E94, 2, 1 }, + { 0x1E9B, 0x1E9B, 1, -58 }, + { 0x1E9E, 0x1E9E, 1, -7615 }, + { 0x1EA0, 0x1EFE, 2, 1 }, + { 0x1F08, 0x1F0F, 1, -8 }, + { 0x1F18, 0x1F1D, 1, -8 }, + { 0x1F28, 0x1F2F, 1, -8 }, + { 0x1F38, 0x1F3F, 1, -8 }, + { 0x1F48, 0x1F4D, 1, -8 }, + { 0x1F59, 0x1F5F, 2, -8 }, + { 0x1F68, 0x1F6F, 1, -8 }, + { 0x1F88, 0x1F8F, 1, -8 }, + { 0x1F98, 0x1F9F, 1, -8 }, + { 0x1FA8, 0x1FAF, 1, -8 }, + { 0x1FB8, 0x1FB9, 1, -8 }, + { 0x1FBA, 0x1FBB, 1, -74 }, + { 0x1FBC, 0x1FBC, 1, -9 }, + { 0x1FBE, 0x1FBE, 1, -7173 }, + { 0x1FC8, 0x1FCB, 1, -86 }, + { 0x1FCC, 0x1FCC, 1, -9 }, + { 0x1FD8, 0x1FD9, 1, -8 }, + { 0x1FDA, 0x1FDB, 1, -100 }, + { 0x1FE8, 0x1FE9, 1, -8 }, + { 0x1FEA, 0x1FEB, 1, -112 }, + { 0x1FEC, 0x1FEC, 1, -7 }, + { 0x1FF8, 0x1FF9, 1, -128 }, + { 0x1FFA, 0x1FFB, 1, -126 }, + { 0x1FFC, 0x1FFC, 1, -9 }, + { 0x2126, 0x2126, 1, -7517 }, + { 0x212A, 0x212A, 1, -8383 }, + { 0x212B, 0x212B, 1, -8262 }, + { 0x2132, 0x2132, 1, 28 }, + { 0x2160, 0x216F, 1, 16 }, + { 0x2183, 0x2183, 1, 1 }, + { 0x24B6, 0x24CF, 1, 26 }, + { 0x2C00, 0x2C2F, 1, 48 }, + { 0x2C60, 0x2C60, 1, 1 }, + { 0x2C62, 0x2C62, 1, -10743 }, + { 0x2C63, 0x2C63, 1, -3814 }, + { 0x2C64, 0x2C64, 1, -10727 }, + { 0x2C67, 0x2C6B, 2, 1 }, + { 0x2C6D, 0x2C6D, 1, -10780 }, + { 0x2C6E, 0x2C6E, 1, -10749 }, + { 0x2C6F, 0x2C6F, 1, -10783 }, + { 0x2C70, 0x2C70, 1, -10782 }, + { 0x2C72, 0x2C75, 3, 1 }, + { 0x2C7E, 0x2C7F, 1, -10815 }, + { 0x2C80, 0x2CE2, 2, 1 }, + { 0x2CEB, 0x2CED, 2, 1 }, + { 0x2CF2, 0xA640, 31054, 1 }, + { 0xA642, 0xA66C, 2, 1 }, + { 0xA680, 0xA69A, 2, 1 }, + { 0xA722, 0xA72E, 2, 1 }, + { 0xA732, 0xA76E, 2, 1 }, + { 0xA779, 0xA77B, 2, 1 }, + { 0xA77D, 0xA77D, 1, -35332 }, + { 0xA77E, 0xA786, 2, 1 }, + { 0xA78B, 0xA78B, 1, 1 }, + { 0xA78D, 0xA78D, 1, -42280 }, + { 0xA790, 0xA792, 2, 1 }, + { 0xA796, 0xA7A8, 2, 1 }, + { 0xA7AA, 0xA7AA, 1, -42308 }, + { 0xA7AB, 0xA7AB, 1, -42319 }, + { 0xA7AC, 0xA7AC, 1, -42315 }, + { 0xA7AD, 0xA7AD, 1, -42305 }, + { 0xA7AE, 0xA7AE, 1, -42308 }, + { 0xA7B0, 0xA7B0, 1, -42258 }, + { 0xA7B1, 0xA7B1, 1, -42282 }, + { 0xA7B2, 0xA7B2, 1, -42261 }, + { 0xA7B3, 0xA7B3, 1, 928 }, + { 0xA7B4, 0xA7C2, 2, 1 }, + { 0xA7C4, 0xA7C4, 1, -48 }, + { 0xA7C5, 0xA7C5, 1, -42307 }, + { 0xA7C6, 0xA7C6, 1, -35384 }, + { 0xA7C7, 0xA7C9, 2, 1 }, + { 0xA7D0, 0xA7D6, 6, 1 }, + { 0xA7D8, 0xA7F5, 29, 1 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF21, 0xFF3A, 1, 32 }, + { 0x10400, 0x10427, 1, 40 }, + { 0x104B0, 0x104D3, 1, 40 }, + { 0x10570, 0x1057A, 1, 39 }, + { 0x1057C, 0x1058A, 1, 39 }, + { 0x1058C, 0x10592, 1, 39 }, + { 0x10594, 0x10595, 1, 39 }, + { 0x10C80, 0x10CB2, 1, 64 }, + { 0x118A0, 0x118BF, 1, 32 }, + { 0x16E40, 0x16E5F, 1, 32 }, + { 0x1E900, 0x1E921, 1, 34 }, +}; + +static struct range_table doublewidth_table[] = { + { 0x1100, 0x115F, 1 }, + { 0x231A, 0x231B, 1 }, + { 0x2329, 0x232A, 1 }, + { 0x23E9, 0x23EC, 1 }, + { 0x23F0, 0x23F3, 3 }, + { 0x25FD, 0x25FE, 1 }, + { 0x2614, 0x2615, 1 }, + { 0x2648, 0x2653, 1 }, + { 0x267F, 0x2693, 20 }, + { 0x26A1, 0x26AA, 9 }, + { 0x26AB, 0x26BD, 18 }, + { 0x26BE, 0x26C4, 6 }, + { 0x26C5, 0x26CE, 9 }, + { 0x26D4, 0x26EA, 22 }, + { 0x26F2, 0x26F3, 1 }, + { 0x26F5, 0x26FA, 5 }, + { 0x26FD, 0x2705, 8 }, + { 0x270A, 0x270B, 1 }, + { 0x2728, 0x274C, 36 }, + { 0x274E, 0x2753, 5 }, + { 0x2754, 0x2755, 1 }, + { 0x2757, 0x2795, 62 }, + { 0x2796, 0x2797, 1 }, + { 0x27B0, 0x27BF, 15 }, + { 0x2B1B, 0x2B1C, 1 }, + { 0x2B50, 0x2B55, 5 }, + { 0x2E80, 0x2E99, 1 }, + { 0x2E9B, 0x2EF3, 1 }, + { 0x2F00, 0x2FD5, 1 }, + { 0x2FF0, 0x2FFB, 1 }, + { 0x3000, 0x303E, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x3099, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x3190, 0x31E3, 1 }, + { 0x31F0, 0x321E, 1 }, + { 0x3220, 0x3247, 1 }, + { 0x3250, 0x4DBF, 1 }, + { 0x4E00, 0xA48C, 1 }, + { 0xA490, 0xA4C6, 1 }, + { 0xA960, 0xA97C, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xF900, 0xFAFF, 1 }, + { 0xFE10, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFF01, 0xFF60, 1 }, + { 0xFFE0, 0xFFE6, 1 }, + { 0x16FE0, 0x16FE4, 1 }, + { 0x16FF0, 0x16FF1, 1 }, + { 0x17000, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B150, 0x1B152, 1 }, + { 0x1B164, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1F004, 0x1F0CF, 203 }, + { 0x1F18E, 0x1F191, 3 }, + { 0x1F192, 0x1F19A, 1 }, + { 0x1F200, 0x1F202, 1 }, + { 0x1F210, 0x1F23B, 1 }, + { 0x1F240, 0x1F248, 1 }, + { 0x1F250, 0x1F251, 1 }, + { 0x1F260, 0x1F265, 1 }, + { 0x1F300, 0x1F320, 1 }, + { 0x1F32D, 0x1F335, 1 }, + { 0x1F337, 0x1F37C, 1 }, + { 0x1F37E, 0x1F393, 1 }, + { 0x1F3A0, 0x1F3CA, 1 }, + { 0x1F3CF, 0x1F3D3, 1 }, + { 0x1F3E0, 0x1F3F0, 1 }, + { 0x1F3F4, 0x1F3F8, 4 }, + { 0x1F3F9, 0x1F43E, 1 }, + { 0x1F440, 0x1F442, 2 }, + { 0x1F443, 0x1F4FC, 1 }, + { 0x1F4FF, 0x1F53D, 1 }, + { 0x1F54B, 0x1F54E, 1 }, + { 0x1F550, 0x1F567, 1 }, + { 0x1F57A, 0x1F595, 27 }, + { 0x1F596, 0x1F5A4, 14 }, + { 0x1F5FB, 0x1F64F, 1 }, + { 0x1F680, 0x1F6C5, 1 }, + { 0x1F6CC, 0x1F6D0, 4 }, + { 0x1F6D1, 0x1F6D2, 1 }, + { 0x1F6D5, 0x1F6D7, 1 }, + { 0x1F6DD, 0x1F6DF, 1 }, + { 0x1F6EB, 0x1F6EC, 1 }, + { 0x1F6F4, 0x1F6FC, 1 }, + { 0x1F7E0, 0x1F7EB, 1 }, + { 0x1F7F0, 0x1F90C, 284 }, + { 0x1F90D, 0x1F93A, 1 }, + { 0x1F93C, 0x1F945, 1 }, + { 0x1F947, 0x1F9FF, 1 }, + { 0x1FA70, 0x1FA74, 1 }, + { 0x1FA78, 0x1FA7C, 1 }, + { 0x1FA80, 0x1FA86, 1 }, + { 0x1FA90, 0x1FAAC, 1 }, + { 0x1FAB0, 0x1FABA, 1 }, + { 0x1FAC0, 0x1FAC5, 1 }, + { 0x1FAD0, 0x1FAD9, 1 }, + { 0x1FAE0, 0x1FAE7, 1 }, + { 0x1FAF0, 0x1FAF6, 1 }, + { 0x20000, 0x2FFFD, 1 }, + { 0x30000, 0x3FFFD, 1 }, +}; + +static struct range_table ambiwidth_table[] = { + { 0xA1, 0xA7, 3 }, + { 0xA8, 0xAA, 2 }, + { 0xAD, 0xAE, 1 }, + { 0xB0, 0xB4, 1 }, + { 0xB6, 0xBA, 1 }, + { 0xBC, 0xBF, 1 }, + { 0xC6, 0xD0, 10 }, + { 0xD7, 0xD8, 1 }, + { 0xDE, 0xE1, 1 }, + { 0xE6, 0xE8, 2 }, + { 0xE9, 0xEA, 1 }, + { 0xEC, 0xED, 1 }, + { 0xF0, 0xF2, 2 }, + { 0xF3, 0xF7, 4 }, + { 0xF8, 0xFA, 1 }, + { 0xFC, 0xFE, 2 }, + { 0x101, 0x111, 16 }, + { 0x113, 0x11B, 8 }, + { 0x126, 0x127, 1 }, + { 0x12B, 0x131, 6 }, + { 0x132, 0x133, 1 }, + { 0x138, 0x13F, 7 }, + { 0x140, 0x142, 1 }, + { 0x144, 0x148, 4 }, + { 0x149, 0x14B, 1 }, + { 0x14D, 0x152, 5 }, + { 0x153, 0x166, 19 }, + { 0x167, 0x16B, 4 }, + { 0x1CE, 0x1DC, 2 }, + { 0x251, 0x261, 16 }, + { 0x2C4, 0x2C7, 3 }, + { 0x2C9, 0x2CB, 1 }, + { 0x2CD, 0x2D0, 3 }, + { 0x2D8, 0x2DB, 1 }, + { 0x2DD, 0x2DF, 2 }, + { 0x300, 0x36F, 1 }, + { 0x391, 0x3A1, 1 }, + { 0x3A3, 0x3A9, 1 }, + { 0x3B1, 0x3C1, 1 }, + { 0x3C3, 0x3C9, 1 }, + { 0x401, 0x410, 15 }, + { 0x411, 0x44F, 1 }, + { 0x451, 0x2010, 7103 }, + { 0x2013, 0x2016, 1 }, + { 0x2018, 0x2019, 1 }, + { 0x201C, 0x201D, 1 }, + { 0x2020, 0x2022, 1 }, + { 0x2024, 0x2027, 1 }, + { 0x2030, 0x2032, 2 }, + { 0x2033, 0x2035, 2 }, + { 0x203B, 0x203E, 3 }, + { 0x2074, 0x207F, 11 }, + { 0x2081, 0x2084, 1 }, + { 0x20AC, 0x2103, 87 }, + { 0x2105, 0x2109, 4 }, + { 0x2113, 0x2116, 3 }, + { 0x2121, 0x2122, 1 }, + { 0x2126, 0x212B, 5 }, + { 0x2153, 0x2154, 1 }, + { 0x215B, 0x215E, 1 }, + { 0x2160, 0x216B, 1 }, + { 0x2170, 0x2179, 1 }, + { 0x2189, 0x2190, 7 }, + { 0x2191, 0x2199, 1 }, + { 0x21B8, 0x21B9, 1 }, + { 0x21D2, 0x21D4, 2 }, + { 0x21E7, 0x2200, 25 }, + { 0x2202, 0x2203, 1 }, + { 0x2207, 0x2208, 1 }, + { 0x220B, 0x220F, 4 }, + { 0x2211, 0x2215, 4 }, + { 0x221A, 0x221D, 3 }, + { 0x221E, 0x2220, 1 }, + { 0x2223, 0x2227, 2 }, + { 0x2228, 0x222C, 1 }, + { 0x222E, 0x2234, 6 }, + { 0x2235, 0x2237, 1 }, + { 0x223C, 0x223D, 1 }, + { 0x2248, 0x224C, 4 }, + { 0x2252, 0x2260, 14 }, + { 0x2261, 0x2264, 3 }, + { 0x2265, 0x2267, 1 }, + { 0x226A, 0x226B, 1 }, + { 0x226E, 0x226F, 1 }, + { 0x2282, 0x2283, 1 }, + { 0x2286, 0x2287, 1 }, + { 0x2295, 0x2299, 4 }, + { 0x22A5, 0x22BF, 26 }, + { 0x2312, 0x2460, 334 }, + { 0x2461, 0x24E9, 1 }, + { 0x24EB, 0x254B, 1 }, + { 0x2550, 0x2573, 1 }, + { 0x2580, 0x258F, 1 }, + { 0x2592, 0x2595, 1 }, + { 0x25A0, 0x25A1, 1 }, + { 0x25A3, 0x25A9, 1 }, + { 0x25B2, 0x25B3, 1 }, + { 0x25B6, 0x25B7, 1 }, + { 0x25BC, 0x25BD, 1 }, + { 0x25C0, 0x25C1, 1 }, + { 0x25C6, 0x25C8, 1 }, + { 0x25CB, 0x25CE, 3 }, + { 0x25CF, 0x25D1, 1 }, + { 0x25E2, 0x25E5, 1 }, + { 0x25EF, 0x2605, 22 }, + { 0x2606, 0x2609, 3 }, + { 0x260E, 0x260F, 1 }, + { 0x261C, 0x261E, 2 }, + { 0x2640, 0x2642, 2 }, + { 0x2660, 0x2661, 1 }, + { 0x2663, 0x2665, 1 }, + { 0x2667, 0x266A, 1 }, + { 0x266C, 0x266D, 1 }, + { 0x266F, 0x269E, 47 }, + { 0x269F, 0x26BF, 32 }, + { 0x26C6, 0x26CD, 1 }, + { 0x26CF, 0x26D3, 1 }, + { 0x26D5, 0x26E1, 1 }, + { 0x26E3, 0x26E8, 5 }, + { 0x26E9, 0x26EB, 2 }, + { 0x26EC, 0x26F1, 1 }, + { 0x26F4, 0x26F6, 2 }, + { 0x26F7, 0x26F9, 1 }, + { 0x26FB, 0x26FC, 1 }, + { 0x26FE, 0x26FF, 1 }, + { 0x273D, 0x2776, 57 }, + { 0x2777, 0x277F, 1 }, + { 0x2B56, 0x2B59, 1 }, + { 0x3248, 0x324F, 1 }, + { 0xE000, 0xF8FF, 1 }, + { 0xFE00, 0xFE0F, 1 }, + { 0xFFFD, 0x1F100, 61699 }, + { 0x1F101, 0x1F10A, 1 }, + { 0x1F110, 0x1F12D, 1 }, + { 0x1F130, 0x1F169, 1 }, + { 0x1F170, 0x1F18D, 1 }, + { 0x1F18F, 0x1F190, 1 }, + { 0x1F19B, 0x1F1AC, 1 }, + { 0xE0100, 0xE01EF, 1 }, + { 0xF0000, 0xFFFFD, 1 }, + { 0x100000, 0x10FFFD, 1 }, +}; + +#endif /* unidata_h */ From 0c456eb6641e98ca6400620cd570b37522b3bd4e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 15:48:59 +0200 Subject: [PATCH 235/409] Allow functions in `keymap` (#948) This allows `keymap.add` to map shortcuts to functions. If the function returns `false`, the next command is executed (as if the `predicate` of a `command` failed). --- data/core/keymap.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 6716fca9..d24eb85a 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -1,3 +1,4 @@ +local core = require "core" local command = require "core.command" local config = require "core.config" local keymap = {} @@ -42,7 +43,7 @@ function keymap.add(map, overwrite) if macos then stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd") end - if type(commands) == "string" then + if type(commands) == "string" or type(commands) == "function" then commands = { commands } end if overwrite then @@ -103,7 +104,16 @@ function keymap.on_key_pressed(k, ...) local commands, performed = keymap.map[stroke] if commands then for _, cmd in ipairs(commands) do - performed = command.perform(cmd, ...) + if type(cmd) == "function" then + local ok, res = core.try(cmd, ...) + if ok then + performed = not (res == false) + else + performed = true + end + else + performed = command.perform(cmd, ...) + end if performed then break end end return performed From 444b3e3c6665d1f44aa9a308685cde898cfb1749 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 15:54:11 +0200 Subject: [PATCH 236/409] Don't animate scrolling by dragging the scroll bar (#940) Also added the `config.animate_drag_scroll` option to re-enable the behavior. --- data/core/config.lua | 1 + data/core/view.lua | 3 +++ 2 files changed, 4 insertions(+) diff --git a/data/core/config.lua b/data/core/config.lua index 417291fe..691b9259 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -4,6 +4,7 @@ config.fps = 60 config.max_log_items = 80 config.message_timeout = 5 config.mouse_wheel_scroll = 50 * SCALE +config.animate_drag_scroll = false config.scroll_past_end = true config.file_size_limit = 10 config.ignore_files = { "^%." } diff --git a/data/core/view.lua b/data/core/view.lua index cdf07f4c..f9157c08 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -134,6 +134,9 @@ function View:on_mouse_moved(x, y, dx, dy) if self.dragging_scrollbar then local delta = self:get_scrollable_size() / self.size.y * dy self.scroll.to.y = self.scroll.to.y + delta + if not config.animate_drag_scroll then + self.scroll.y = self.scroll.to.y + end end self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y) self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y) From 6f65168b0d9fe763d855f5b394d75cf466d8195c Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 2 Apr 2022 17:03:29 -0400 Subject: [PATCH 237/409] Fixed windows dirmonitor issues. --- data/core/dirwatch.lua | 24 ++++++++++++------------ src/api/dirmonitor.c | 2 +- src/api/dirmonitor/win32.c | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 2dac59f3..53f600c5 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -36,20 +36,20 @@ function dirwatch:watch(directory, bool) if not self.watched[directory] and not self.scanned[directory] then if PLATFORM == "Windows" then if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then - -- Get the highest level of directory that is common to this directory, and the original. - local target = directory - while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do - target = common.dirname(target) - end - if target ~= self.windows_watch_top then - local value = self.monitor:watch(target) - if value and value < 0 then - return self:scan(directory) + -- Get the highest level of directory that is common to this directory, and the original. + local target = directory + while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do + target = common.dirname(target) + end + if target ~= self.windows_watch_top then + local value = self.monitor:watch(target) + if value and value < 0 then + return self:scan(directory) + end + self.windows_watch_top = target + end end - self.windows_watch_top = target self.windows_watch_count = self.windows_watch_count + 1 - end - end self.watched[directory] = true else local value = self.monitor:watch(directory) diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 0ebf1680..72d32a64 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -65,7 +65,7 @@ static int f_dirmonitor_watch(lua_State *L) { } static int f_dirmonitor_unwatch(lua_State *L) { - remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2)); + remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), lua_tonumber(L, 2)); return 0; } diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c index 9dd2a254..d5945856 100644 --- a/src/api/dirmonitor/win32.c +++ b/src/api/dirmonitor/win32.c @@ -3,7 +3,7 @@ struct dirmonitor { HANDLE handle; - char buffer[8192]; + char buffer[64512]; OVERLAPPED overlapped; bool running; }; @@ -52,8 +52,8 @@ int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(in monitor->running = false; - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { - change_callback(info->FileNameLength, (char*)info->FileName, data); + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { + change_callback(info->FileNameLength / sizeof(WCHAR), (char*)info->FileName, data); if (!info->NextEntryOffset) break; } @@ -74,4 +74,4 @@ int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { close_monitor_handle(monitor); -} \ No newline at end of file +} From 4bf485173663545d0ad19b543993eee4de9d27ce Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 24 Apr 2022 13:40:58 -0400 Subject: [PATCH 238/409] Asynchronous Reads for Dirmonitor (#930) Change dirmonitor reads to be synchronous, in a secondary thread. --- data/core/dirwatch.lua | 4 ++ data/core/init.lua | 14 +---- src/api/dirmonitor.c | 106 ++++++++++++++++++++++----------- src/api/dirmonitor/dummy.c | 26 ++------ src/api/dirmonitor/inotify.c | 69 ++++++++++------------ src/api/dirmonitor/kqueue.c | 48 +++++++-------- src/api/dirmonitor/win32.c | 111 +++++++++++++++-------------------- src/meson.build | 2 - 8 files changed, 190 insertions(+), 190 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 53f600c5..314a33c0 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -85,7 +85,9 @@ end -- designed to be run inside a coroutine. function dirwatch:check(change_callback, scan_time, wait_time) + local had_change = false self.monitor:check(function(id) + had_change = true if PLATFORM == "Windows" then change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) elseif self.reverse_watched[id] then @@ -98,6 +100,7 @@ function dirwatch:check(change_callback, scan_time, wait_time) local new_modified = system.get_file_info(directory).modified if old_modified < new_modified then change_callback(directory) + had_change = true self.scanned[directory] = new_modified end end @@ -106,6 +109,7 @@ function dirwatch:check(change_callback, scan_time, wait_time) start_time = system.get_time() end end + return had_change end diff --git a/data/core/init.lua b/data/core/init.lua index 5eab80e6..625fcd91 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -269,7 +269,7 @@ function core.add_project_directory(path) -- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds. topdir.watch_thread = core.add_thread(function() while true do - topdir.watch:check(function(target) + local changed = topdir.watch:check(function(target) if target == topdir.name then return refresh_directory(topdir) end local dirpath = target:sub(#topdir.name + 2) local abs_dirpath = topdir.name .. PATHSEP .. dirpath @@ -280,7 +280,7 @@ function core.add_project_directory(path) end return refresh_directory(topdir, dirpath) end, 0.01, 0.01) - coroutine.yield(0.05) + coroutine.yield(changed and 0.05 or 0) end end) @@ -1120,14 +1120,6 @@ function core.try(fn, ...) return false, err end -local scheduled_rescan = {} - -function core.has_pending_rescan() - for _ in pairs(scheduled_rescan) do - return true - end -end - function core.on_event(type, ...) local did_keymap = false if type == "textinput" then @@ -1274,8 +1266,8 @@ function core.run() local idle_iterations = 0 while true do core.frame_start = system.get_time() + local need_more_work = run_threads() local did_redraw = core.step() - local need_more_work = run_threads() or core.has_pending_rescan() if core.restart_request or core.quit_request then break end if not did_redraw and not need_more_work then idle_iterations = idle_iterations + 1 diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 72d32a64..56360d1f 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -1,78 +1,115 @@ #include "api.h" +#include #include -#ifdef DIRMONITOR_WIN32 - #include -#endif #include #include -#include -#include #include #include -#ifndef DIRMONITOR_BACKEND -#error No dirmonitor backend defined -#endif +static unsigned int DIR_EVENT_TYPE = 0; -#define GLUE_HELPER(x, y) x##y -#define GLUE(x, y) GLUE_HELPER(x, y) +struct dirmonitor { + SDL_Thread* thread; + SDL_mutex* mutex; + char buffer[64512]; + volatile int length; + struct dirmonitor_internal* internal; +}; -#define init_dirmonitor GLUE(init_dirmonitor_, DIRMONITOR_BACKEND) -#define deinit_dirmonitor GLUE(deinit_dirmonitor_, DIRMONITOR_BACKEND) -#define check_dirmonitor GLUE(check_dirmonitor_, DIRMONITOR_BACKEND) -#define add_dirmonitor GLUE(add_dirmonitor_, DIRMONITOR_BACKEND) -#define remove_dirmonitor GLUE(remove_dirmonitor_, DIRMONITOR_BACKEND) -struct dirmonitor {}; // dirmonitor struct is defined in each backend +struct dirmonitor_internal* init_dirmonitor(); +void deinit_dirmonitor(struct dirmonitor_internal*); +int get_changes_dirmonitor(struct dirmonitor_internal*, char*, int); +int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*); +int add_dirmonitor(struct dirmonitor_internal*, const char*); +void remove_dirmonitor(struct dirmonitor_internal*, int); -// define functions so we know their signature -struct dirmonitor* init_dirmonitor(); -void deinit_dirmonitor(struct dirmonitor*); -int check_dirmonitor(struct dirmonitor*, int (*)(int, const char*, void*), void*); -int add_dirmonitor(struct dirmonitor*, const char*); -void remove_dirmonitor(struct dirmonitor*, int); static int f_check_dir_callback(int watch_id, const char* path, void* L) { lua_pushvalue(L, -1); - #ifdef DIRMONITOR_WIN32 - char buffer[PATH_MAX*4]; - int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); - lua_pushlstring(L, buffer, count); - #else + if (path) + lua_pushlstring(L, path, watch_id); + else lua_pushnumber(L, watch_id); - #endif lua_call(L, 1, 1); int result = lua_toboolean(L, -1); lua_pop(L, 1); return !result; } + +static int dirmonitor_check_thread(void* data) { + struct dirmonitor* monitor = data; + while (monitor->length >= 0) { + if (monitor->length == 0) { + int result = get_changes_dirmonitor(monitor->internal, monitor->buffer, sizeof(monitor->buffer)); + SDL_LockMutex(monitor->mutex); + if (monitor->length == 0) + monitor->length = result; + SDL_UnlockMutex(monitor->mutex); + } + SDL_Delay(1); + SDL_Event event = { .type = DIR_EVENT_TYPE }; + SDL_PushEvent(&event); + } + return 0; +} + + static int f_dirmonitor_new(lua_State* L) { - struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**)); - *monitor = init_dirmonitor(); + if (DIR_EVENT_TYPE == 0) + DIR_EVENT_TYPE = SDL_RegisterEvents(1); + struct dirmonitor* monitor = lua_newuserdata(L, sizeof(struct dirmonitor)); luaL_setmetatable(L, API_TYPE_DIRMONITOR); + memset(monitor, 0, sizeof(struct dirmonitor)); + monitor->internal = init_dirmonitor(); + if (monitor->internal) + monitor->thread = SDL_CreateThread(dirmonitor_check_thread, "dirmonitor_check_thread", monitor); return 1; } + static int f_dirmonitor_gc(lua_State* L) { - deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))); + struct dirmonitor* monitor = luaL_checkudata(L, 1, API_TYPE_DIRMONITOR); + SDL_LockMutex(monitor->mutex); + monitor->length = -1; + deinit_dirmonitor(monitor->internal); + SDL_UnlockMutex(monitor->mutex); + SDL_WaitThread(monitor->thread, NULL); + free(monitor->internal); + SDL_DestroyMutex(monitor->mutex); return 0; } + static int f_dirmonitor_watch(lua_State *L) { - lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2))); + lua_pushnumber(L, add_dirmonitor(((struct dirmonitor*)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))->internal, luaL_checkstring(L, 2))); return 1; } + static int f_dirmonitor_unwatch(lua_State *L) { - remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), lua_tonumber(L, 2)); + remove_dirmonitor(((struct dirmonitor*)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))->internal, lua_tonumber(L, 2)); return 0; } + static int f_dirmonitor_check(lua_State* L) { - lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L)); + struct dirmonitor* monitor = luaL_checkudata(L, 1, API_TYPE_DIRMONITOR); + SDL_LockMutex(monitor->mutex); + if (monitor->length < 0) + lua_pushnil(L); + else if (monitor->length > 0) { + if (translate_changes_dirmonitor(monitor->internal, monitor->buffer, monitor->length, f_check_dir_callback, L) == 0) + monitor->length = 0; + lua_pushboolean(L, 1); + } else + lua_pushboolean(L, 0); + SDL_UnlockMutex(monitor->mutex); return 1; } + + static const luaL_Reg dirmonitor_lib[] = { { "new", f_dirmonitor_new }, { "__gc", f_dirmonitor_gc }, @@ -82,6 +119,7 @@ static const luaL_Reg dirmonitor_lib[] = { {NULL, NULL} }; + int luaopen_dirmonitor(lua_State* L) { luaL_newmetatable(L, API_TYPE_DIRMONITOR); luaL_setfuncs(L, dirmonitor_lib, 0); diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c index cc0b41ee..9c19d27d 100644 --- a/src/api/dirmonitor/dummy.c +++ b/src/api/dirmonitor/dummy.c @@ -1,22 +1,8 @@ #include -struct dirmonitor { -}; - -struct dirmonitor* init_dirmonitor_dummy() { - return NULL; -} - -void deinit_dirmonitor_dummy(struct dirmonitor* monitor) { -} - -int check_dirmonitor_dummy(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - return -1; -} - -int add_dirmonitor_dummy(struct dirmonitor* monitor, const char* path) { - return -1; -} - -void remove_dirmonitor_dummy(struct dirmonitor* monitor, int fd) { -} \ No newline at end of file +struct dirmonitor_internal* init_dirmonitor() { return NULL; } +void deinit_dirmonitor(struct dirmonitor_internal*) { } +int get_changes_dirmonitor(struct dirmonitor_internal*, char*, size_t) { return -1; } +int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*) { return -1; } +int add_dirmonitor(struct dirmonitor_internal*, const char*) { return -1; } +void remove_dirmonitor(struct dirmonitor_internal*, int) { } diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index c284e501..ca756d77 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -1,58 +1,53 @@ #include -#include -#include +#include #include -#include -#include -#include +#include -struct dirmonitor { + +struct dirmonitor_internal { int fd; + // a pipe is used to wake the thread in case of exit + int sig[2]; }; -struct dirmonitor* init_dirmonitor_inotify() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); +struct dirmonitor_internal* init_dirmonitor() { + struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = inotify_init(); - fcntl(monitor->fd, F_SETFL, O_NONBLOCK); - - + pipe(monitor->sig); return monitor; } -void deinit_dirmonitor_inotify(struct dirmonitor* monitor) { + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close(monitor->sig[0]); + close(monitor->sig[1]); close(monitor->fd); - free(monitor); } -int check_dirmonitor_inotify(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - char buf[PATH_MAX + sizeof(struct inotify_event)]; - ssize_t offset = 0; - while (1) { - ssize_t len = read(monitor->fd, &buf[offset], sizeof(buf) - offset); - - if (len == -1 && errno != EAGAIN) { - return errno; - } - - if (len <= 0) { - return 0; - } - - while (len > sizeof(struct inotify_event) && len >= ((struct inotify_event*)buf)->len + sizeof(struct inotify_event)) { - change_callback(((const struct inotify_event *)buf)->wd, NULL, data); - len -= sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len; - memmove(buf, &buf[sizeof(struct inotify_event) + ((struct inotify_event*)buf)->len], len); - offset = len; - } - } +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int length) { + fd_set set; + FD_ZERO(&set); + FD_SET(monitor->fd, &set); + FD_SET(monitor->sig[0], &set); + select(FD_SETSIZE, &set, NULL, NULL, NULL); + return read(monitor->fd, buffer, length); } -int add_dirmonitor_inotify(struct dirmonitor* monitor, const char* path) { + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int length, int (*change_callback)(int, const char*, void*), void* data) { + for (struct inotify_event* info = (struct inotify_event*)buffer; (char*)info < buffer + length; info = (struct inotify_event*)((char*)info + sizeof(struct inotify_event))) + change_callback(info->wd, NULL, data); + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); } -void remove_dirmonitor_inotify(struct dirmonitor* monitor, int fd) { + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { inotify_rm_watch(monitor->fd, fd); -} \ No newline at end of file +} diff --git a/src/api/dirmonitor/kqueue.c b/src/api/dirmonitor/kqueue.c index e7f041bd..7c6e89d8 100644 --- a/src/api/dirmonitor/kqueue.c +++ b/src/api/dirmonitor/kqueue.c @@ -5,40 +5,41 @@ #include #include -struct dirmonitor { +struct dirmonitor_internal { int fd; }; -struct dirmonitor* init_dirmonitor_kqueue() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + +struct dirmonitor_internal* init_dirmonitor() { + struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = kqueue(); return monitor; } -void deinit_dirmonitor_kqueue(struct dirmonitor* monitor) { + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { close(monitor->fd); - free(monitor); } -int check_dirmonitor_kqueue(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - struct kevent event; - while (1) { - struct timespec tm = {0}; - int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); - if (nev == -1) { - return errno; - } - - if (nev <= 0) { - return 0; - } - - change_callback(event.ident, NULL, data); - } +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) { + int nev = kevent(monitor->fd, NULL, 0, (struct kevent*)buffer, buffer_size / sizeof(kevent), NULL); + if (nev == -1) + return -1; + if (nev <= 0) + return 0; + return nev * sizeof(struct kevent); } -int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) { + for (struct kevent* info = (struct kevent*)buffer; (char*)info < buffer + buffer_size; info = (struct kevent*)(((char*)info) + sizeof(kevent))) + change_callback(info->ident, NULL, data); + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { int fd = open(path, O_RDONLY); struct kevent change; @@ -48,6 +49,7 @@ int add_dirmonitor_kqueue(struct dirmonitor* monitor, const char* path) { return fd; } -void remove_dirmonitor_kqueue(struct dirmonitor* monitor, int fd) { + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close(fd); -} \ No newline at end of file +} diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c index d5945856..34caa6de 100644 --- a/src/api/dirmonitor/win32.c +++ b/src/api/dirmonitor/win32.c @@ -1,77 +1,62 @@ -#include #include -struct dirmonitor { + +struct dirmonitor_internal { HANDLE handle; - char buffer[64512]; - OVERLAPPED overlapped; - bool running; }; -struct dirmonitor* init_dirmonitor_win32() { - struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); - return monitor; -} - -static void close_monitor_handle(struct dirmonitor* monitor) { - if (monitor->handle) { - if (monitor->running) { - BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); - DWORD error = GetLastError(); - if (result == TRUE || error != ERROR_NOT_FOUND) { - DWORD bytes_transferred; - GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); - } - monitor->running = false; - } - CloseHandle(monitor->handle); - } - monitor->handle = NULL; -} - -void deinit_dirmonitor_win32(struct dirmonitor* monitor) { - close_monitor_handle(monitor); - free(monitor); -} - -int check_dirmonitor_win32(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { - if (!monitor->running) { - if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) { - return GetLastError(); - } - monitor->running = true; +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) { + HANDLE handle = monitor->handle; + if (handle && handle != INVALID_HANDLE_VALUE) { + DWORD bytes_transferred; + if (ReadDirectoryChangesW(handle, buffer, buffer_size, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, &bytes_transferred, NULL, NULL) == 0) + return 0; + return bytes_transferred; } - - DWORD bytes_transferred; - - if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { - int error = GetLastError(); - return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; - } - - monitor->running = false; - - for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { - change_callback(info->FileNameLength / sizeof(WCHAR), (char*)info->FileName, data); - if (!info->NextEntryOffset) - break; - } - - monitor->running = false; return 0; } -int add_dirmonitor_win32(struct dirmonitor* monitor, const char* path) { - close_monitor_handle(monitor); - monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { - return 1; - } - monitor->handle = NULL; - return -1; + +struct dirmonitor* init_dirmonitor() { + return calloc(sizeof(struct dirmonitor_internal), 1); } -void remove_dirmonitor_win32(struct dirmonitor* monitor, int fd) { + +static void close_monitor_handle(struct dirmonitor_internal* monitor) { + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) { + HANDLE handle = monitor->handle; + monitor->handle = NULL; + CancelIoEx(handle, NULL); + CloseHandle(handle); + } +} + + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close_monitor_handle(monitor); +} + + +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) { + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)buffer; (char*)info < buffer + buffer_size; info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) { + char transform_buffer[PATH_MAX*4]; + int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength, transform_buffer, PATH_MAX*4 - 1, NULL, NULL); + change_callback(count, buffer, data); + if (!info->NextEntryOffset) + break; + } + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + return !monitor->handle || monitor->handle == INVALID_HANDLE_VALUE ? -1 : 1; +} + + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close_monitor_handle(monitor); } diff --git a/src/meson.build b/src/meson.build index 7229ac72..fa4a1390 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,8 +42,6 @@ lite_sources += [ 'api/dirmonitor.c', 'api/dirmonitor/' + dirmonitor_backend + '.c', ] -lite_cargs += '-DDIRMONITOR_BACKEND=' + dirmonitor_backend -lite_cargs += '-DDIRMONITOR_' + dirmonitor_backend.to_upper() lite_rc = [] From 9f7c6974ae0bb42e2abd4092fb8d632c2b6c9ef8 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 24 Apr 2022 21:13:18 -0400 Subject: [PATCH 239/409] Make sure pipes are closed on exec. --- src/api/dirmonitor/inotify.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index ca756d77..260b074b 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -2,6 +2,7 @@ #include #include #include +#include struct dirmonitor_internal { @@ -15,6 +16,8 @@ struct dirmonitor_internal* init_dirmonitor() { struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); monitor->fd = inotify_init(); pipe(monitor->sig); + fcntl(monitor->sig[0], F_SETFD, FD_CLOEXEC); + fcntl(monitor->sig[1], F_SETFD, FD_CLOEXEC); return monitor; } From a7ea84ae8feb1789d39d15580b128c46aa58d33c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 22:49:55 +0200 Subject: [PATCH 240/409] Clamp scroll position when dragging the scrollbar without animations --- data/core/view.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/core/view.lua b/data/core/view.lua index f9157c08..18693afa 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -135,6 +135,7 @@ function View:on_mouse_moved(x, y, dx, dy) local delta = self:get_scrollable_size() / self.size.y * dy self.scroll.to.y = self.scroll.to.y + delta if not config.animate_drag_scroll then + self:clamp_scroll_position() self.scroll.y = self.scroll.to.y end end From 4934e741b399a42001271a1d192fcd7bd231a22a Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 26 Apr 2022 18:29:05 -0400 Subject: [PATCH 241/409] TreeView Changes (#898) * Change to 1 click as per RFC on discord, with 100% in favour. * Added in the ability to specify as a view name, so it doesn't modify the title, and also fixed a bug where if you clicked *over* the amount of times your config says, it wouldn't regsiter. * Changed plugin to use keymap. --- data/core/init.lua | 7 +++-- data/plugins/treeview.lua | 62 +++++++++++---------------------------- 2 files changed, 21 insertions(+), 48 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 625fcd91..9d8e9425 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1170,12 +1170,13 @@ end local function get_title_filename(view) local doc_filename = view.get_filename and view:get_filename() or view:get_name() - return (doc_filename ~= "---") and doc_filename or "" + if doc_filename ~= "---" then return doc_filename end + return "" end function core.compose_window_title(title) - return title == "" and "Lite XL" or title .. " - Lite XL" + return (title == "" or title == nil) and "Lite XL" or title .. " - Lite XL" end @@ -1214,7 +1215,7 @@ function core.step() -- update window title local current_title = get_title_filename(core.active_view) - if current_title ~= core.window_title then + if current_title ~= nil and current_title ~= core.window_title then system.set_window_title(core.compose_window_title(current_title)) core.window_title = current_title end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 2357fb7d..fcb4f892 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -11,8 +11,6 @@ local RootView = require "core.rootview" local CommandView = require "core.commandview" config.plugins.treeview = common.merge({ - -- Amount of clicks to open a file - clicks_to_open = 2, -- Default treeview width size = 200 * SCALE }, config.plugins.treeview) @@ -111,7 +109,7 @@ end function TreeView:get_name() - return "Project" + return nil end @@ -237,46 +235,6 @@ function TreeView:on_mouse_moved(px, py, ...) end -local function create_directory_in(item) - local path = item.abs_filename - core.command_view:enter("Create directory in " .. path, function(text) - local dirname = path .. PATHSEP .. text - local success, err = system.mkdir(dirname) - if not success then - core.error("cannot create directory %q: %s", dirname, err) - end - item.expanded = true - end) -end - - -function TreeView:on_mouse_pressed(button, x, y, clicks) - if not self.visible then return end - local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks) - if caught or button ~= "left" then - return true - end - - if self.hovered_item then - self:set_selection(self.hovered_item) - - if keymap.modkeys["ctrl"] and button == "left" then - create_directory_in(self.selected_item) - elseif self.selected_item.type == "dir" - or (self.selected_item.type == "file" - and clicks == config.plugins.treeview.clicks_to_open - ) - then - command.perform "treeview:open" - end - else - return false - end - - return true -end - - function TreeView:update() -- update width local dest = self.visible and self.target_size or 0 @@ -643,6 +601,17 @@ command.add(TreeView, { ["treeview:deselect"] = function() view.selected_item = nil end, + + ["treeview:select"] = function() + view:set_selection(view.hovered_item) + end, + + ["treeview:select-and-open"] = function() + if view.hovered_item then + view:set_selection(view.hovered_item) + command.perform "treeview:open" + end + end, ["treeview:collapse"] = function() if view.selected_item then @@ -775,7 +744,7 @@ command.add(function() return treeitem() ~= nil end, { elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then system.exec(string.format("xdg-open %q", hovered_item.abs_filename)) end - end, + end }) keymap.add { @@ -787,7 +756,10 @@ keymap.add { ["return"] = "treeview:open", ["escape"] = "treeview:deselect", ["delete"] = "treeview:delete", - ["ctrl+return"] = "treeview:new-folder" + ["ctrl+return"] = "treeview:new-folder", + ["lclick"] = "treeview:select-and-open", + ["mclick"] = "treeview:select", + ["ctrl+lclick"] = "treeview:new-folder" } -- Return the treeview with toolbar and contextmenu to allow From 395040675064d830de1bedf8ffaf6f3dcbfa1df8 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 27 Apr 2022 17:55:46 +0200 Subject: [PATCH 242/409] Catch mouse clicks if `contextmenu` is open Also disallow re-opening the `contextmenu` if it's already visible. --- data/core/contextmenu.lua | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index 1c9839e7..b1a69d6d 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -171,19 +171,21 @@ function ContextMenu:call_selected_item() end function ContextMenu:on_mouse_pressed(button, px, py, clicks) - local selected = self:get_item_selected() local caught = false - self:hide() - if button == "left" then - if selected then - self:on_selected(selected) - caught = true + if self.show_context_menu then + if button == "left" then + local selected = self:get_item_selected() + if selected then + self:on_selected(selected) + end + end + self:hide() + caught = true + else + if button == "right" then + caught = self:show(px, py) end - end - - if button == "right" then - caught = self:show(px, py) end return caught end From f92f56d42ed7ae8bc7ac96d44b0f63661131c895 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 27 Apr 2022 21:53:35 +0200 Subject: [PATCH 243/409] Manage return values from "replacer" function in `Doc:replace` Before the addition of multi-cursor support, we just returned the second return value of the "replacer" function to the caller. With the introduction of multi-cursors, we naively summed the second return values for each cursor. In some cases the "replacer" function doesn't return any second value, so we tried to do math with `nil`, thus throwing errors. Now the second return value is added to a table which is then returned to the caller. --- data/core/doc/init.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index f9369a46..460e387d 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -448,7 +448,7 @@ end function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) local old_text = self:get_text(line1, col1, line2, col2) - local new_text, n = fn(old_text) + local new_text, res = fn(old_text) if old_text ~= new_text then self:insert(line2, col2, new_text) self:remove(line1, col1, line2, col2) @@ -457,22 +457,22 @@ function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) self:set_selections(idx, line1, col1, line2, col2) end end - return n + return res end function Doc:replace(fn) - local has_selection, n = false, 0 + local has_selection, results = false, { } for idx, line1, col1, line2, col2 in self:get_selections(true) do if line1 ~= line2 or col1 ~= col2 then - n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn) + results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn) has_selection = true end end if not has_selection then self:set_selection(table.unpack(self.selections)) - n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn) + results[1] = self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn) end - return n + return results end From ac42e6457a5f2bbc28143e3f40de222ff47da516 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 28 Apr 2022 01:54:43 +0200 Subject: [PATCH 244/409] Check if `USERDIR` doesn't exist in `core.delete_temp_files` --- data/core/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 9d8e9425..0fa260ed 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -815,7 +815,7 @@ local temp_file_counter = 0 function core.delete_temp_files(dir) dir = type(dir) == "string" and common.normalize_path(dir) or USERDIR - for _, filename in ipairs(system.list_dir(dir)) do + for _, filename in ipairs(system.list_dir(dir) or {}) do if filename:find(temp_file_prefix, 1, true) == 1 then os.remove(dir .. PATHSEP .. filename) end From b7db7cd533bd6e2242e84373746eccd4ff8db459 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 28 Apr 2022 21:41:55 -0400 Subject: [PATCH 245/409] Added plugin load-time log. --- data/core/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/core/init.lua b/data/core/init.lua index 0fa260ed..ad3f817c 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -914,8 +914,9 @@ function core.load_plugins() local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins table.insert(list, filename) elseif config.plugins[basename] ~= false then + local start = system.get_time() local ok = core.try(require, "plugins." .. basename) - if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end + if ok then core.log_quiet("Loaded plugin %q from %s in %.1fms", basename, plugin_dir, (system.get_time() - start)*1000) end if not ok then no_errors = false end From 9de75988ba6427891fb739b846dce6b8bc9ea30e Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 29 Apr 2022 03:50:34 +0200 Subject: [PATCH 246/409] Send `mouseleft` event when the mouse leaves the window (#928) * Send `mouseleft` event when the mouse leaves the window * Call `View:on_mouse_left` when the mouse leaves the `View` Previously `View:on_mouse_left` was called only when the mouse left the window, and it was called on every visible `View`. Now it gets also called when the mouse "changes" `View`, and only the last `View` the mouse was on will receive the event. --- data/core/init.lua | 2 ++ data/core/node.lua | 13 +++++++++++++ data/core/rootview.lua | 12 ++++++++++++ data/core/view.lua | 6 ++++++ src/api/system.c | 3 +++ 5 files changed, 36 insertions(+) diff --git a/data/core/init.lua b/data/core/init.lua index 0fa260ed..e5a81f1f 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1136,6 +1136,8 @@ function core.on_event(type, ...) end elseif type == "mousereleased" then core.root_view:on_mouse_released(...) + elseif type == "mouseleft" then + core.root_view:on_mouse_left() elseif type == "mousewheel" then if not core.root_view:on_mouse_wheel(...) then did_keymap = keymap.on_mouse_wheel(...) diff --git a/data/core/node.lua b/data/core/node.lua index 56e0889a..fee62ba6 100644 --- a/data/core/node.lua +++ b/data/core/node.lua @@ -51,6 +51,15 @@ function Node:on_mouse_released(...) end +function Node:on_mouse_left() + if self.type == "leaf" then + self.active_view:on_mouse_left() + else + self:propagate("on_mouse_left") + end +end + + function Node:consume(node) for k, _ in pairs(self) do self[k] = nil end for k, v in pairs(node) do self[k] = v end @@ -160,8 +169,12 @@ end function Node:set_active_view(view) assert(self.type == "leaf", "Tried to set active view on non-leaf node") + local last_active_view = self.active_view self.active_view = view core.set_active_view(view) + if last_active_view and last_active_view ~= view then + last_active_view:on_mouse_left() + end end diff --git a/data/core/rootview.lua b/data/core/rootview.lua index b6cd95bb..8e32c68c 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -256,8 +256,13 @@ function RootView:on_mouse_moved(x, y, dx, dy) self.root_node:on_mouse_moved(x, y, dx, dy) + local last_overlapping_node = self.overlapping_node self.overlapping_node = self.root_node:get_child_overlapping_point(x, y) + if last_overlapping_node and last_overlapping_node ~= self.overlapping_node then + last_overlapping_node:on_mouse_left() + end + local div = self.root_node:get_divider_overlapping_point(x, y) local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y) if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then @@ -272,6 +277,13 @@ function RootView:on_mouse_moved(x, y, dx, dy) end +function RootView:on_mouse_left() + if self.overlapping_node then + self.overlapping_node:on_mouse_left() + end +end + + function RootView:on_file_dropped(filename, x, y) local node = self.root_node:get_child_overlapping_point(x, y) return node and node.active_view:on_file_dropped(filename, x, y) diff --git a/data/core/view.lua b/data/core/view.lua index 18693afa..1f8a9208 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -144,6 +144,12 @@ function View:on_mouse_moved(x, y, dx, dy) end +function View:on_mouse_left() + self.hovered_scrollbar = false + self.hovered_scrollbar_track = false +end + + function View:on_file_dropped(filename, x, y) return false end diff --git a/src/api/system.c b/src/api/system.c index 45231d0b..ff99ce6f 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -161,6 +161,9 @@ top: } else if (e.window.event == SDL_WINDOWEVENT_RESTORED) { lua_pushstring(L, "restored"); return 1; + } else if (e.window.event == SDL_WINDOWEVENT_LEAVE) { + lua_pushstring(L, "mouseleft"); + return 1; } if (e.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { lua_pushstring(L, "focuslost"); From 2e0d0995d62c935f7e926020fb6e7101c3d9df4c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 30 Apr 2022 22:09:40 +0200 Subject: [PATCH 247/409] Add typeahead to `CommandView` (#963) --- data/core/commandview.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 2b91e30e..b102c43b 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -36,6 +36,7 @@ function CommandView:new() self.suggestions_height = 0 self.show_suggestions = true self.last_change_id = 0 + self.last_text = "" self.gutter_width = 0 self.gutter_text_brightness = 0 self.selection_offset = 0 @@ -80,6 +81,7 @@ end function CommandView:set_text(text, select) + self.last_text = text self.doc:remove(1, 1, math.huge, math.huge) self.doc:text_input(text) if select then @@ -161,6 +163,7 @@ function CommandView:exit(submitted, inexplicit) if not submitted then cancel(not inexplicit) end self.show_suggestions = true self.save_suggestion = nil + self.last_text = "" end @@ -198,6 +201,16 @@ function CommandView:update() -- update suggestions if text has changed if self.last_change_id ~= self.doc:get_change_id() then self:update_suggestions() + if self.suggestions[self.suggestion_idx] then + local current_text = self:get_text() + local suggested_text = self.suggestions[self.suggestion_idx].text or "" + if #self.last_text < #current_text and + string.find(suggested_text, current_text, 1, true) == 1 then + self:set_text(suggested_text) + self.doc:set_selection(1, #current_text + 1, 1, math.huge) + end + self.last_text = current_text + end self.last_change_id = self.doc:get_change_id() end From 548dbf67c2bedac2e7aca1ec687557a5bdda34e1 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 30 Apr 2022 16:23:05 -0400 Subject: [PATCH 248/409] Added in log to show total time. --- data/core/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/core/init.lua b/data/core/init.lua index ad3f817c..0b386804 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -904,7 +904,9 @@ function core.load_plugins() end end table.sort(ordered) + + local load_start = system.get_time() for _, filename in ipairs(ordered) do local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename) @@ -923,6 +925,7 @@ function core.load_plugins() end end end + core.log_quiet("Loaded all plugins in %.1fms", (system.get_time() - load_start)*1000) return no_errors, refused_list end From b5fe333345d3159436764d151e124472fdea0f0d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 2 May 2022 13:55:25 -0400 Subject: [PATCH 249/409] core: expose rescan_project_directories and configure_borderless_window --- data/core/init.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 2a0cbd74..74471006 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -294,7 +294,7 @@ end -- The function below is needed to reload the project directories -- when the project's module changes. -local function rescan_project_directories() +function core.rescan_project_directories() local save_project_dirs = {} local n = #core.project_directories for i = 1, n do @@ -574,7 +574,7 @@ function core.remove_project_directory(path) end -local function configure_borderless_window() +function core.configure_borderless_window() system.set_window_bordered(not config.borderless) core.title_view:configure_hit_test(config.borderless) core.title_view.visible = config.borderless @@ -590,8 +590,8 @@ local function add_config_files_hooks() doc_save(self, filename, abs_filename) if self.abs_filename == user_filename or self.abs_filename == module_filename then reload_customizations() - rescan_project_directories() - configure_borderless_window() + core.rescan_project_directories() + core.configure_borderless_window() end end end @@ -752,7 +752,7 @@ function core.init() command.perform("core:open-log") end - configure_borderless_window() + core.configure_borderless_window() if #plugins_refuse_list.userdir.plugins > 0 or #plugins_refuse_list.datadir.plugins > 0 then local opt = { @@ -904,7 +904,7 @@ function core.load_plugins() end end table.sort(ordered) - + local load_start = system.get_time() for _, filename in ipairs(ordered) do From 91797d65d19a185bbfa9dcb48834869666aba51a Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 2 May 2022 22:35:21 -0400 Subject: [PATCH 250/409] Fixed minor race condition. --- src/api/dirmonitor/inotify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index 260b074b..cb38c315 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -23,9 +23,9 @@ struct dirmonitor_internal* init_dirmonitor() { void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close(monitor->fd); close(monitor->sig[0]); close(monitor->sig[1]); - close(monitor->fd); } From 94abf66444550f323c849ff96b57933ce4a7166f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 2 May 2022 22:35:21 -0400 Subject: [PATCH 251/409] Fixed minor race condition. --- src/api/dirmonitor/inotify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index 260b074b..cb38c315 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -23,9 +23,9 @@ struct dirmonitor_internal* init_dirmonitor() { void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + close(monitor->fd); close(monitor->sig[0]); close(monitor->sig[1]); - close(monitor->fd); } From f8622efc015fef88e988b82af5151f90ad0ddcc0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 3 May 2022 06:28:34 +0200 Subject: [PATCH 252/409] Add pretty printing to `common.serialize` --- data/core/common.lua | 47 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index 6a25cae2..1aa2b86e 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -222,19 +222,58 @@ function common.bench(name, fn, ...) end -function common.serialize(val) +local function serialize(val, pretty, indent_str, escape, sort, limit, level) + local space = pretty and " " or "" + local indent = pretty and string.rep(indent_str, level) or "" + local newline = pretty and "\n" or "" if type(val) == "string" then - return string.format("%q", val) + local out = string.format("%q", val) + if escape then + out = string.gsub(out, "\\\n", "\\n") + out = string.gsub(out, "\\7", "\\a") + out = string.gsub(out, "\\8", "\\b") + out = string.gsub(out, "\\9", "\\t") + out = string.gsub(out, "\\11", "\\v") + out = string.gsub(out, "\\12", "\\f") + out = string.gsub(out, "\\13", "\\r") + end + return out elseif type(val) == "table" then + -- early exit + if level >= limit then return tostring(val) end + local next_indent = pretty and (indent .. indent_str) or "" local t = {} for k, v in pairs(val) do - table.insert(t, "[" .. common.serialize(k) .. "]=" .. common.serialize(v)) + table.insert(t, + next_indent .. "[" .. + serialize(k, pretty, indent_str, escape, sort, limit, level + 1) .. + "]" .. space .. "=" .. space .. serialize(v, pretty, indent_str, escape, sort, limit, level + 1)) end - return "{" .. table.concat(t, ",") .. "}" + if #t == 0 then return "{}" end + if sort then table.sort(t) end + return "{" .. newline .. table.concat(t, "," .. newline) .. newline .. indent .. "}" end return tostring(val) end +-- Serialize `val` into a parsable string. +-- Available options +-- * pretty: enable pretty printing +-- * indent_str: indent to use (" " by default) +-- * escape: use normal escape characters instead of the ones used by string.format("%q", ...) +-- * sort: sort the keys inside tables +-- * limit: limit how deep to serialize +-- * initial_indent: the initial indentation level +function common.serialize(val, opts) + opts = opts or {} + local indent_str = opts.indent_str or " " + local initial_indent = opts.initial_indent or 0 + local indent = opts.pretty and string.rep(indent_str, initial_indent) or "" + local limit = (opts.limit or math.huge) + initial_indent + return indent .. serialize(val, opts.pretty, indent_str, + opts.escape, opts.sort, limit, initial_indent) +end + function common.basename(path) -- a path should never end by / or \ except if it is '/' (unix root) or From 0ca0e3600983f843ff909956a65017d2f45ee9c4 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 4 May 2022 02:58:34 +0200 Subject: [PATCH 253/409] Open `LogView` in correct `Node` Using `get_active_node` might result in a locked `Node`; calling `add_view` on that `Node` throws an error. `get_active_node_default` always returns an unlocked `Node`. --- data/core/commands/core.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 927d01f6..3f466292 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -149,7 +149,7 @@ command.add(nil, { end, ["core:open-log"] = function() - local node = core.root_view:get_active_node() + local node = core.root_view:get_active_node_default() node:add_view(LogView()) end, From 8345a04d046e4aa7b44024d93b3eb88383f2dbb4 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 3 May 2022 23:13:49 -0400 Subject: [PATCH 254/409] Updated treeview to match convention. --- data/plugins/treeview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 444353f8..ef38105c 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -454,7 +454,7 @@ function TreeView:toggle_expand(toggle) end local hovered_dir = core.project_dir_by_name(item.dir_name) if hovered_dir and hovered_dir.files_limit then - core.update_project_subdir(hovered_dir, item.filename, item.expanded) + core.update_project_subdir(hovered_dir, item.depth == 0 and "" or item.filename, item.expanded) end end end From 815683612682b149c9d3ae285686bca3c0efceef Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 26 Apr 2022 04:33:46 +0200 Subject: [PATCH 255/409] Fix `ren_font_group_get_tab_size` returning unexpected values We were casting the `xadvance` to an int, so in some cases the resulting tab size was wrong. --- src/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer.c b/src/renderer.c index f5f08045..a2c2ddb9 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -240,7 +240,7 @@ void ren_font_group_set_tab_size(RenFont **fonts, int n) { } int ren_font_group_get_tab_size(RenFont **fonts) { - int advance = font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance; + float advance = font_get_glyphset(fonts[0], '\t', 0)->metrics['\t'].xadvance; if (fonts[0]->space_advance) { advance /= fonts[0]->space_advance; } From 09bfb8d869fb57d370d8fe3a4062062579242178 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 4 May 2022 20:05:41 -0400 Subject: [PATCH 256/409] Updated linewrap and autocomplete to use the new get_line_screen_position which takes a col. --- data/plugins/autocomplete.lua | 3 +-- data/plugins/linewrapping.lua | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index 8273f3e5..eeb430a5 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -228,8 +228,7 @@ local function get_suggestions_rect(av) end local line, col = av.doc:get_selection() - local x, y = av:get_line_screen_position(line) - x = x + av:get_col_x_offset(line, col - #partial) + local x, y = av:get_line_screen_position(line, col - #partial) y = y + av:get_line_height() + style.padding.y local font = av:get_font() local th = font:get_height() diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index c7ae6278..bcb237eb 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -380,7 +380,7 @@ function DocView:get_line_screen_position(line, col) local x, y = self:get_content_offset() local lh = self:get_line_height() local gw = self:get_gutter_width() - return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y + return x + gw + style.padding.x + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y end local old_resolve_screen_position = DocView.resolve_screen_position From 2eaba8ab92bc37473e94b131bdaeb1a15a40ec96 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 4 May 2022 20:08:08 -0400 Subject: [PATCH 257/409] Erroneously added padding. --- data/plugins/linewrapping.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index bcb237eb..c7ae6278 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -380,7 +380,7 @@ function DocView:get_line_screen_position(line, col) local x, y = self:get_content_offset() local lh = self:get_line_height() local gw = self:get_gutter_width() - return x + gw + style.padding.x + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y + return x + gw + (col and self:get_col_x_offset(line, col) or 0), y + (idx-1) * lh + style.padding.y end local old_resolve_screen_position = DocView.resolve_screen_position From 93be54e9c3c1c143e7a276347f6f4896cda30827 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 5 May 2022 12:35:21 -0400 Subject: [PATCH 258/409] meson: install docs/api to datadir for lsp support --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 0e5c62ac..f303049c 100644 --- a/meson.build +++ b/meson.build @@ -174,6 +174,7 @@ endif install_data('licenses/licenses.md', install_dir : lite_docdir) +install_subdir('docs/api' , install_dir : lite_datadir, strip_directory: true) install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua') foreach data_module : ['fonts', 'plugins', 'colors'] install_subdir(join_paths('data', data_module), install_dir : lite_datadir) From b3fea8f88025d9a21e77ed0bffc649adb1b1718b Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 5 May 2022 18:02:00 -0400 Subject: [PATCH 259/409] plugins: add load priority support with '--priority:###' --- data/core/init.lua | 102 +++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 74471006..e151b8be 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -859,7 +859,7 @@ function core.restart() end -local function check_plugin_version(filename) +local function get_plugin_details(filename) local info = system.get_file_info(filename) if info ~= nil and info.type == "dir" then filename = filename .. "/init.lua" @@ -868,24 +868,35 @@ local function check_plugin_version(filename) if not info or not filename:match("%.lua$") then return false end local f = io.open(filename, "r") if not f then return false end + local priority = false local version_match = false for line in f:lines() do - local mod_version = line:match('%-%-.*%f[%a]mod%-version%s*:%s*(%d+)') - if mod_version then - version_match = (mod_version == MOD_VERSION) - break + if not version_match then + local mod_version = line:match('%-%-.*%f[%a]mod%-version%s*:%s*(%d+)') + if mod_version then + version_match = (mod_version == MOD_VERSION) + end + -- The following pattern is used for backward compatibility only + -- Future versions will look only at the mod-version tag. + local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$') + if version then + -- we consider the version tag 2.0 equivalent to mod-version:2 + version_match = (version == '2.0' and MOD_VERSION == "2") + end end - -- The following pattern is used for backward compatibility only - -- Future versions will look only at the mod-version tag. - local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$') - if version then - -- we consider the version tag 2.0 equivalent to mod-version:2 - version_match = (version == '2.0' and MOD_VERSION == "2") + if not priority then + priority = line:match('%-%-.*%f[%a]priority%s*:%s*(%d+)') + if priority then priority = tonumber(priority) end + end + if version_match then break end end f:close() - return true, version_match + return true, { + version_match = version_match, + priority = priority or 100 + } end @@ -899,33 +910,70 @@ function core.load_plugins() for _, root_dir in ipairs {DATADIR, USERDIR} do local plugin_dir = root_dir .. "/plugins" for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do - if not files[filename] then table.insert(ordered, filename) end - files[filename] = plugin_dir -- user plugins will always replace system plugins + if not files[filename] then + table.insert( + ordered, {file = filename} + ) + end + -- user plugins will always replace system plugins + files[filename] = plugin_dir end end - table.sort(ordered) + for _, plugin in ipairs(ordered) do + local dir = files[plugin.file] + local name = plugin.file:match("(.-)%.lua$") or plugin.file + local is_lua_file, details = get_plugin_details(dir .. '/' .. plugin.file) + + plugin.valid = is_lua_file + plugin.name = name + plugin.dir = dir + plugin.priority = details and details.priority or 100 + plugin.version_match = details and details.version_match or false + end + + -- sort by priority or name for plugins that have same priority + table.sort(ordered, function(a, b) + if a.priority ~= b.priority then + return a.priority < b.priority + end + return a.name < b.name + end) local load_start = system.get_time() - for _, filename in ipairs(ordered) do - local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename - local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename) - if is_lua_file then - if not config.skip_plugins_version and not version_match then - core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir) - local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins - table.insert(list, filename) - elseif config.plugins[basename] ~= false then + for _, plugin in ipairs(ordered) do + if plugin.valid then + if not config.skip_plugins_version and not plugin.version_match then + core.log_quiet( + "Version mismatch for plugin %q from %s", + plugin.name, + plugin.dir + ) + local rlist = plugin.dir:find(USERDIR, 1, true) == 1 + and 'userdir' or 'datadir' + local list = refused_list[rlist].plugins + table.insert(list, plugin.file) + elseif config.plugins[plugin.name] ~= false then local start = system.get_time() - local ok = core.try(require, "plugins." .. basename) - if ok then core.log_quiet("Loaded plugin %q from %s in %.1fms", basename, plugin_dir, (system.get_time() - start)*1000) end + local ok = core.try(require, "plugins." .. plugin.name) + if ok then + core.log_quiet( + "Loaded plugin %q from %s in %.1fms", + plugin.name, + plugin.dir, + (system.get_time() - start) * 1000 + ) + end if not ok then no_errors = false end end end end - core.log_quiet("Loaded all plugins in %.1fms", (system.get_time() - load_start)*1000) + core.log_quiet( + "Loaded all plugins in %.1fms", + (system.get_time() - load_start) * 1000 + ) return no_errors, refused_list end From d8436d1e92b56fc84cb64056c7f7e8ab640ea196 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 6 May 2022 11:51:30 -0400 Subject: [PATCH 260/409] Bumped verison number in meson. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0e5c62ac..781931bd 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('lite-xl', ['c'], - version : '2.0.5', + version : '2.1.0', license : 'MIT', meson_version : '>= 0.47', default_options : [ From ddc3a8842bcf2277b0ae5437a2e125433127dd8d Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 9 May 2022 21:30:13 -0400 Subject: [PATCH 261/409] Fixed dirwatch dummy, and scanning. --- data/core/dirwatch.lua | 5 +++-- src/api/dirmonitor/dummy.c | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 314a33c0..68f62082 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -97,8 +97,9 @@ function dirwatch:check(change_callback, scan_time, wait_time) local start_time = system.get_time() for directory, old_modified in pairs(self.scanned) do if old_modified then - local new_modified = system.get_file_info(directory).modified - if old_modified < new_modified then + local info = system.get_file_info(directory) + local new_modified = info and info.modified + if old_modified ~= new_modified then change_callback(directory) had_change = true self.scanned[directory] = new_modified diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c index 9c19d27d..c71e849d 100644 --- a/src/api/dirmonitor/dummy.c +++ b/src/api/dirmonitor/dummy.c @@ -1,8 +1,8 @@ #include struct dirmonitor_internal* init_dirmonitor() { return NULL; } -void deinit_dirmonitor(struct dirmonitor_internal*) { } -int get_changes_dirmonitor(struct dirmonitor_internal*, char*, size_t) { return -1; } -int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*) { return -1; } -int add_dirmonitor(struct dirmonitor_internal*, const char*) { return -1; } -void remove_dirmonitor(struct dirmonitor_internal*, int) { } +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { } +int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, size_t len) { return -1; } +int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int size, int (*callback)(int, const char*, void*), void* data) { return -1; } +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return -1; } +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { } From f1f8a9b3f2d7f4d3cdb70c600692161dcb4808f1 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 26 Apr 2022 18:29:05 -0400 Subject: [PATCH 262/409] TreeView Changes (#898) * Change to 1 click as per RFC on discord, with 100% in favour. * Added in the ability to specify as a view name, so it doesn't modify the title, and also fixed a bug where if you clicked *over* the amount of times your config says, it wouldn't regsiter. * Changed plugin to use keymap. --- data/core/init.lua | 7 +++-- data/plugins/treeview.lua | 62 +++++++++++---------------------------- 2 files changed, 21 insertions(+), 48 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 625fcd91..9d8e9425 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1170,12 +1170,13 @@ end local function get_title_filename(view) local doc_filename = view.get_filename and view:get_filename() or view:get_name() - return (doc_filename ~= "---") and doc_filename or "" + if doc_filename ~= "---" then return doc_filename end + return "" end function core.compose_window_title(title) - return title == "" and "Lite XL" or title .. " - Lite XL" + return (title == "" or title == nil) and "Lite XL" or title .. " - Lite XL" end @@ -1214,7 +1215,7 @@ function core.step() -- update window title local current_title = get_title_filename(core.active_view) - if current_title ~= core.window_title then + if current_title ~= nil and current_title ~= core.window_title then system.set_window_title(core.compose_window_title(current_title)) core.window_title = current_title end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index ef38105c..cf7b0fb2 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -11,8 +11,6 @@ local RootView = require "core.rootview" local CommandView = require "core.commandview" config.plugins.treeview = common.merge({ - -- Amount of clicks to open a file - clicks_to_open = 2, -- Default treeview width size = 200 * SCALE }, config.plugins.treeview) @@ -111,7 +109,7 @@ end function TreeView:get_name() - return "Project" + return nil end @@ -237,46 +235,6 @@ function TreeView:on_mouse_moved(px, py, ...) end -local function create_directory_in(item) - local path = item.abs_filename - core.command_view:enter("Create directory in " .. path, function(text) - local dirname = path .. PATHSEP .. text - local success, err = system.mkdir(dirname) - if not success then - core.error("cannot create directory %q: %s", dirname, err) - end - item.expanded = true - end) -end - - -function TreeView:on_mouse_pressed(button, x, y, clicks) - if not self.visible then return end - local caught = TreeView.super.on_mouse_pressed(self, button, x, y, clicks) - if caught or button ~= "left" then - return true - end - - if self.hovered_item then - self:set_selection(self.hovered_item) - - if keymap.modkeys["ctrl"] and button == "left" then - create_directory_in(self.selected_item) - elseif self.selected_item.type == "dir" - or (self.selected_item.type == "file" - and clicks == config.plugins.treeview.clicks_to_open - ) - then - command.perform "treeview:open" - end - else - return false - end - - return true -end - - function TreeView:update() -- update width local dest = self.visible and self.target_size or 0 @@ -643,6 +601,17 @@ command.add(TreeView, { ["treeview:deselect"] = function() view.selected_item = nil end, + + ["treeview:select"] = function() + view:set_selection(view.hovered_item) + end, + + ["treeview:select-and-open"] = function() + if view.hovered_item then + view:set_selection(view.hovered_item) + command.perform "treeview:open" + end + end, ["treeview:collapse"] = function() if view.selected_item then @@ -775,7 +744,7 @@ command.add(function() return treeitem() ~= nil end, { elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then system.exec(string.format("xdg-open %q", hovered_item.abs_filename)) end - end, + end }) keymap.add { @@ -787,7 +756,10 @@ keymap.add { ["return"] = "treeview:open", ["escape"] = "treeview:deselect", ["delete"] = "treeview:delete", - ["ctrl+return"] = "treeview:new-folder" + ["ctrl+return"] = "treeview:new-folder", + ["lclick"] = "treeview:select-and-open", + ["mclick"] = "treeview:select", + ["ctrl+lclick"] = "treeview:new-folder" } -- Return the treeview with toolbar and contextmenu to allow From 0315d397bd5cbda6e0486ad8b1efffc775d36212 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 9 May 2022 23:12:43 -0400 Subject: [PATCH 263/409] Removed undefined behaviour by using `poll` over `select`. --- src/api/dirmonitor/inotify.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index cb38c315..70e536dd 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -1,8 +1,8 @@ #include -#include #include #include #include +#include struct dirmonitor_internal { @@ -30,11 +30,8 @@ void deinit_dirmonitor(struct dirmonitor_internal* monitor) { int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int length) { - fd_set set; - FD_ZERO(&set); - FD_SET(monitor->fd, &set); - FD_SET(monitor->sig[0], &set); - select(FD_SETSIZE, &set, NULL, NULL, NULL); + struct pollfd fds[2] = { { .fd = monitor->fd, .events = POLLIN | POLLERR, .revents = 0 }, { .fd = monitor->sig[0], .events = POLLIN | POLLERR, .revents = 0 } }; + poll(fds, 2, -1); return read(monitor->fd, buffer, length); } From 359880e963b64a0a6c348f0574e494872f9058e1 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 11 May 2022 00:20:13 -0400 Subject: [PATCH 264/409] c core: fix extra utf8 build conflict on windows --- data/core/utf8string.lua | 2 ++ docs/api/{utf8.lua => utf8extra.lua} | 46 +++++++++++++++------------- src/api/api.c | 4 +-- src/api/utf8.c | 2 +- 4 files changed, 30 insertions(+), 24 deletions(-) rename docs/api/{utf8.lua => utf8extra.lua} (84%) diff --git a/data/core/utf8string.lua b/data/core/utf8string.lua index 1a2da19b..a22a0ef6 100644 --- a/data/core/utf8string.lua +++ b/data/core/utf8string.lua @@ -2,6 +2,8 @@ -- inject utf8 functions to strings -------------------------------------------------------------------------------- +local utf8 = require "utf8extra" + string.ubyte = utf8.byte string.uchar = utf8.char string.ufind = utf8.find diff --git a/docs/api/utf8.lua b/docs/api/utf8extra.lua similarity index 84% rename from docs/api/utf8.lua rename to docs/api/utf8extra.lua index d4dff5a9..1ff4dcb6 100644 --- a/docs/api/utf8.lua +++ b/docs/api/utf8extra.lua @@ -1,19 +1,23 @@ ---@meta +---Additional utf8 support not provided by lua. +---@class utf8extra +utf8extra = {} + ---UTF-8 equivalent of string.byte ---@param s string ---@param i? integer ---@param j? integer ---@return integer ---@return ... -function utf8.byte(s, i, j) end +function utf8extra.byte(s, i, j) end ---UTF-8 equivalent of string.char ---@param byte integer ---@param ... integer ---@return string ---@return ... -function utf8.char(byte, ...) end +function utf8extra.char(byte, ...) end ---UTF-8 equivalent of string.find ---@param s string @@ -23,14 +27,14 @@ function utf8.char(byte, ...) end ---@return integer start ---@return integer end ---@return ... captured -function utf8.find(s, pattern, init, plain) end +function utf8extra.find(s, pattern, init, plain) end ---UTF-8 equivalent of string.gmatch ---@param s string ---@param pattern string ---@param init? integer ---@return fun():string, ... -function utf8.gmatch(s, pattern, init) end +function utf8extra.gmatch(s, pattern, init) end ---UTF-8 equivalent of string.gsub ---@param s string @@ -39,41 +43,41 @@ function utf8.gmatch(s, pattern, init) end ---@param n integer ---@return string ---@return integer count -function utf8.gsub(s, pattern, repl, n) end +function utf8extra.gsub(s, pattern, repl, n) end ---UTF-8 equivalent of string.len ---@param s string ---@return integer -function utf8.len(s) end +function utf8extra.len(s) end ---UTF-8 equivalent of string.lower ---@param s string ---@return string -function utf8.lower(s) end +function utf8extra.lower(s) end ---UTF-8 equivalent of string.match ---@param s string ---@param pattern string ---@param init? integer ---@return string | number captured -function utf8.match(s, pattern, init) end +function utf8extra.match(s, pattern, init) end ---UTF-8 equivalent of string.reverse ---@param s string ---@return string -function utf8.reverse(s) end +function utf8extra.reverse(s) end ---UTF-8 equivalent of string.sub ---@param s string ---@param i integer ---@param j? integer ---@return string -function utf8.sub(s, i, j) end +function utf8extra.sub(s, i, j) end ---UTF-8 equivalent of string.upper ---@param s string ---@return string -function utf8.upper(s) end +function utf8extra.upper(s) end ---Escape a str to UTF-8 format string. It support several escape format: ---* %ddd - which ddd is a decimal number at any length: change Unicode code point to UTF-8 format. @@ -91,7 +95,7 @@ function utf8.upper(s) end ---``` ---@param s string ---@return string utf8_string -function utf8.escape(s) end +function utf8extra.escape(s) end ---Convert UTF-8 position to byte offset. if only index is given, return byte ---offset of this UTF-8 char index. if both charpos and index is given, a new @@ -103,7 +107,7 @@ function utf8.escape(s) end ---@param index? integer ---@return integer charpos ---@return integer codepoint -function utf8.charpos(s, charpos, index) end +function utf8extra.charpos(s, charpos, index) end ---Iterate though the UTF-8 string s. If only s is given, it can used as a iterator: ---```lua @@ -120,7 +124,7 @@ function utf8.charpos(s, charpos, index) end ---@param index? integer ---@return integer charpos ---@return integer codepoint -function utf8.next(s, charpos, index) end +function utf8extra.next(s, charpos, index) end ---Insert a substring to s. If idx is given, insert substring before char at ---this index, otherwise substring will concat to s. idx can be negative. @@ -128,7 +132,7 @@ function utf8.next(s, charpos, index) end ---@param idx? integer ---@param substring string ---return string new_string -function utf8.insert(s, idx, substring) end +function utf8extra.insert(s, idx, substring) end ---Delete a substring in s. If neither start nor stop is given, delete the last ---UTF-8 char in s, otherwise delete char from start to end of s. if stop is @@ -138,7 +142,7 @@ function utf8.insert(s, idx, substring) end ---@param start? integer ---@param stop? integer ---return string new_string -function utf8.remove(s, start, stop) end +function utf8extra.remove(s, start, stop) end ---Calculate the width of UTF-8 string s. if ambi_is_double is given, the ---ambiguous width character's width is 2, otherwise it's 1. fullwidth/doublewidth @@ -150,7 +154,7 @@ function utf8.remove(s, start, stop) end ---@param ambi_is_double? boolean ---@param default_width? integer ---@return integer width -function utf8.width(s, ambi_is_double, default_width) end +function utf8extra.width(s, ambi_is_double, default_width) end ---Return the character index at given location in string s. this is a reverse ---operation of utf8.width(). this function returns a index of location, and a @@ -164,24 +168,24 @@ function utf8.width(s, ambi_is_double, default_width) end ---@return integer idx ---@return integer offset ---@return integer width -function utf8.widthindex(s, location, ambi_is_double, default_width) end +function utf8extra.widthindex(s, location, ambi_is_double, default_width) end ---Convert UTF-8 string s to title-case, used to compare by ignore case. if s ---is a number, it's treat as a code point and return a convert code point ---(number). utf8.lower/utf8.pper has the same extension. ---@param s string ---return string new_string -function utf8.title(s) end +function utf8extra.title(s) end ---Convert UTF-8 string s to folded case, used to compare by ignore case. if s ---is a number, it's treat as a code point and return a convert code point ---(number). utf8.lower/utf8.pper has the same extension. ---@param s string ---return string new_string -function utf8.fold(s) end +function utf8extra.fold(s) end ---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b. ---@param a string ---@param b string ---@return integer result -function utf8.ncasecmp(a, b) end +function utf8extra.ncasecmp(a, b) end diff --git a/src/api/api.c b/src/api/api.c index 67a05f19..acf6eec2 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -5,7 +5,7 @@ int luaopen_renderer(lua_State *L); int luaopen_regex(lua_State *L); int luaopen_process(lua_State *L); int luaopen_dirmonitor(lua_State* L); -int luaopen_utf8(lua_State* L); +int luaopen_utf8extra(lua_State* L); static const luaL_Reg libs[] = { { "system", luaopen_system }, @@ -13,7 +13,7 @@ static const luaL_Reg libs[] = { { "regex", luaopen_regex }, { "process", luaopen_process }, { "dirmonitor", luaopen_dirmonitor }, - { "utf8", luaopen_utf8 }, + { "utf8extra", luaopen_utf8extra }, { NULL, NULL } }; diff --git a/src/api/utf8.c b/src/api/utf8.c index e1a4ebfe..6f0d6c17 100644 --- a/src/api/utf8.c +++ b/src/api/utf8.c @@ -1264,7 +1264,7 @@ static const char UTF8PATT[] = "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"; static const char UTF8PATT[] = "[%z\1-\x7F\xC2-\xF4][\x80-\xBF]*"; #endif -int luaopen_utf8 (lua_State *L) { +int luaopen_utf8extra (lua_State *L) { luaL_Reg libs[] = { #define ENTRY(name) { #name, Lutf8_##name } ENTRY(offset), From 1d1b3e0a09d6da91edd609643887127a725240d7 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 12 May 2022 18:33:56 -0400 Subject: [PATCH 265/409] Add utf8 support on doc lower and upper commands --- data/core/commands/doc.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index ed35913a..87079180 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -180,7 +180,7 @@ local commands = { local line, col = doc():get_selection() doc():set_selection(line, col) end, - + ["doc:cut"] = function() cut_or_copy(true) end, @@ -402,11 +402,11 @@ local commands = { end, ["doc:upper-case"] = function() - doc():replace(string.upper) + doc():replace(string.uupper) end, ["doc:lower-case"] = function() - doc():replace(string.lower) + doc():replace(string.ulower) end, ["doc:go-to-line"] = function() From fd0a433f59ddcd6c6d1f8de83db2da1079b93868 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 12 May 2022 20:33:01 -0400 Subject: [PATCH 266/409] object: made is() stricter and added extends() Currently some plugins had/have issues with predicates that check if active view is a docview to perform certain operations like draw in the case of minimap or lineguide. Since is() was checking the entire inheritance tree it was returning true for views that inherit from the same parent, which caused CommandView to be matched along DocView, etc... This change does the following to solve the issue: * Make Object:is() only match the top level parent of the object which is more in line with what one would expect from a method named 'is'. * Introduces Object:extends() which keeps the same functionality that Object:is() offered before. --- data/core/object.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/core/object.lua b/data/core/object.lua index 0941ce5d..a57ac9e8 100644 --- a/data/core/object.lua +++ b/data/core/object.lua @@ -21,6 +21,14 @@ end function Object:is(T) + if getmetatable(self) == T then + return true + end + return false +end + + +function Object:extends(T) local mt = getmetatable(self) while mt do if mt == T then From b8ed4a43f62ec1ea857e3c005bf6c7a217d20f49 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 9 May 2022 20:02:32 -0400 Subject: [PATCH 267/409] keymap: changes and docs * Prevent adding duplicate bindings * Clean reverse_map on overwrite or add direct * Added get_bindings to complement get_binding * Added doc comments for easier comprehension * Check if command is function on add_direct --- data/core/keymap.lua | 144 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/data/core/keymap.lua b/data/core/keymap.lua index d24eb85a..139aeb79 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -3,33 +3,111 @@ local command = require "core.command" local config = require "core.config" local keymap = {} +---@alias keymap.shortcut string +---@alias keymap.command string +---@alias keymap.modkey string +---@alias keymap.pressed boolean +---@alias keymap.map table +---@alias keymap.rmap table + +---Pressed status of mod keys. +---@type table keymap.modkeys = {} + +---List of commands assigned to a shortcut been the key of the map the shortcut. +---@type keymap.map keymap.map = {} + +---List of shortcuts assigned to a command been the key of the map the command. +---@type keymap.rmap keymap.reverse_map = {} local macos = PLATFORM == "Mac OS X" -- Thanks to mathewmariani, taken from his lite-macos github repository. local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic")) + +---@type table local modkey_map = modkeys_os.map + +---@type keymap.modkey[] local modkeys = modkeys_os.keys -local function key_to_stroke(k) + +---Generates a stroke sequence including currently pressed mod keys. +---@param key string +---@return string +local function key_to_stroke(key) local stroke = "" for _, mk in ipairs(modkeys) do if keymap.modkeys[mk] then stroke = stroke .. mk .. "+" end end - return stroke .. k + return stroke .. key end +---Remove the given value from an array associated to a key in a table. +---@param tbl table The table containing the key +---@param k string The key containing the array +---@param v? string The value to remove from the array +local function remove_only(tbl, k, v) + if tbl[k] then + if v then + local j = 0 + for i=1, #tbl[k] do + while tbl[k][i + j] == v do + j = j + 1 + end + tbl[k][i] = tbl[k][i + j] + end + else + tbl[k] = nil + end + end +end + + +---Removes from a keymap.map the bindings that are already registered. +---@param map keymap.map +local function remove_duplicates(map) + for stroke, commands in pairs(map) do + if type(commands) == "string" or type(commands) == "function" then + commands = { commands } + end + if keymap.map[stroke] then + for _, registered_cmd in ipairs(keymap.map[stroke]) do + local j = 0 + for i=1, #commands do + while commands[i + j] == registered_cmd do + j = j + 1 + end + commands[i] = commands[i + j] + end + end + end + if #commands < 1 then + map[stroke] = nil + else + map[stroke] = commands + end + end +end + + +---Add bindings by replacing commands that were previously assigned to a shortcut. +---@param map keymap.map function keymap.add_direct(map) for stroke, commands in pairs(map) do - if type(commands) == "string" then + if type(commands) == "string" or type(commands) == "function" then commands = { commands } end + if keymap.map[stroke] then + for _, cmd in ipairs(keymap.map[stroke]) do + remove_only(keymap.reverse_map, cmd, stroke) + end + end keymap.map[stroke] = commands for _, cmd in ipairs(commands) do keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {} @@ -38,15 +116,23 @@ function keymap.add_direct(map) end end + +---Adds bindings by appending commands to already registered shortcut or by +---replacing currently assigned commands if overwrite is specified. +---@param map keymap.map +---@param overwrite? boolean function keymap.add(map, overwrite) + remove_duplicates(map) for stroke, commands in pairs(map) do if macos then stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd") end - if type(commands) == "string" or type(commands) == "function" then - commands = { commands } - end if overwrite then + if keymap.map[stroke] then + for _, cmd in ipairs(keymap.map[stroke]) do + remove_only(keymap.reverse_map, cmd, stroke) + end + end keymap.map[stroke] = commands else keymap.map[stroke] = keymap.map[stroke] or {} @@ -62,35 +148,34 @@ function keymap.add(map, overwrite) end -local function remove_only(tbl, k, v) - for key, values in pairs(tbl) do - if key == k then - if v then - for i, value in ipairs(values) do - if value == v then - table.remove(values, i) - end - end - else - tbl[key] = nil - end - break - end - end -end - - -function keymap.unbind(key, cmd) - remove_only(keymap.map, key, cmd) - remove_only(keymap.reverse_map, cmd, key) +---Unregisters the given shortcut and associated command. +---@param shortcut string +---@param cmd string +function keymap.unbind(shortcut, cmd) + remove_only(keymap.map, shortcut, cmd) + remove_only(keymap.reverse_map, cmd, shortcut) end +---Returns all the shortcuts associated to a command unpacked for easy assignment. +---@param cmd string +---@return ... function keymap.get_binding(cmd) return table.unpack(keymap.reverse_map[cmd] or {}) end +---Returns all the shortcuts associated to a command packed in a table. +---@param cmd string +---@return table | nil shortcuts +function keymap.get_bindings(cmd) + return keymap.reverse_map[cmd] +end + + +-------------------------------------------------------------------------------- +-- Events listening +-------------------------------------------------------------------------------- function keymap.on_key_pressed(k, ...) local mk = modkey_map[k] if mk then @@ -101,7 +186,7 @@ function keymap.on_key_pressed(k, ...) end else local stroke = key_to_stroke(k) - local commands, performed = keymap.map[stroke] + local commands, performed = keymap.map[stroke], false if commands then for _, cmd in ipairs(commands) do if type(cmd) == "function" then @@ -143,6 +228,9 @@ function keymap.on_key_released(k) end +-------------------------------------------------------------------------------- +-- Register default bindings +-------------------------------------------------------------------------------- if macos then local keymap_macos = require("core.keymap-macos") keymap_macos(keymap) From 59d91087e9b1ff3990f2d5543432531ee11ae5b0 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 12 May 2022 22:15:29 -0400 Subject: [PATCH 268/409] adjust and consolidate duplicated predicate code --- data/core/command.lua | 22 ++++++++++++++++++++-- data/core/contextmenu.lua | 9 +-------- data/core/statusview.lua | 17 +++-------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/data/core/command.lua b/data/core/command.lua index bdc1ed34..39582de3 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -6,15 +6,33 @@ command.map = {} local always_true = function() return true end -function command.add(predicate, map) +---Used iternally by command.add, statusview, and contextmenu to generate a +---function with a condition to evaluate returning the boolean result of this +---evaluation. +--- +---If a string predicate is given it is treated as a require import that should +---return a valid object which is checked against the current active view, the +---sames applies if a table is given. A function that returns a boolean can be +---used instead to perform a custom evaluation, setting to nil means always +---evaluates to true. +--- +---@param predicate string | table | function +---@return function +function command.generate_predicate(predicate) predicate = predicate or always_true if type(predicate) == "string" then predicate = require(predicate) end if type(predicate) == "table" then local class = predicate - predicate = function() return core.active_view:is(class) end + predicate = function() return core.active_view:extends(class) end end + return predicate +end + + +function command.add(predicate, map) + predicate = command.generate_predicate(predicate) for name, fn in pairs(map) do assert(not command.map[name], "command already exists: " .. name) command.map[name] = { predicate = predicate, perform = fn } diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index b1a69d6d..94ef61f8 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -39,14 +39,7 @@ local function get_item_size(item) end function ContextMenu:register(predicate, items) - if type(predicate) == "string" then - predicate = require(predicate) - end - if type(predicate) == "table" then - local class = predicate - predicate = function() return core.active_view:is(class) end - end - + predicate = command.generate_predicate(predicate) local width, height = 0, 0 --precalculate the size of context menu for i, item in ipairs(items) do if item ~= DIVIDER then diff --git a/data/core/statusview.lua b/data/core/statusview.lua index b1a45385..542522c7 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -113,9 +113,6 @@ function StatusView.Item:hide() self.visible = false end ---Show the item on the status bar. function StatusView.Item:show() self.visible = true end ----Function assiged by default when user provides a nil predicate. -local function predicate_always_true() return true end - ---A condition to evaluate if the item should be displayed. If a string ---is given it is treated as a require import that should return a valid object ---which is checked against the current active view, the sames applies if a @@ -123,15 +120,7 @@ local function predicate_always_true() return true end ---perform a custom evaluation, setting to nil means always evaluates to true. ---@param predicate string | table | StatusView.Item.predicate function StatusView.Item:set_predicate(predicate) - predicate = predicate or predicate_always_true - if type(predicate) == "string" then - predicate = require(predicate) - end - if type(predicate) == "table" then - local class = predicate - predicate = function() return core.active_view:is(class) end - end - self.predicate = predicate + self.predicate = command.generate_predicate(predicate) end @@ -655,14 +644,14 @@ local function merge_deprecated_items(destination, items, alignment) local position = alignment == StatusView.Item.LEFT and "left" or "right" local item_start = StatusView.Item( - predicate_always_true, + nil, "deprecated:"..position.."-start", alignment ) item_start.get_item = items_start local item_end = StatusView.Item( - predicate_always_true, + nil, "deprecated:"..position.."-end", alignment ) From 94430bcbd2937aa944be72c31163a4428c14a653 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 11 May 2022 01:05:36 -0400 Subject: [PATCH 269/409] tokenizer: fix next utf8 char retrieval bug --- data/core/tokenizer.lua | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index e0c630a4..ebe550ff 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -169,12 +169,12 @@ function tokenizer.tokenize(incoming_syntax, text, state) if p.whole_line[p_idx] and next > 1 then return end - -- go to the start of the next utf-8 character - while text:byte(next) and common.is_utf8_cont(text, next) do - next = next + 1 - end res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } - or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } + or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } + if p.regex and #res > 0 then -- set correct utf8 len for regex result + res[2] = res[1] + string.ulen(text:sub(res[1], res[2])) - 1 + res[1] = next + end if res[1] and close and target[3] then local count = 0 for i = res[1] - 1, 1, -1 do @@ -189,7 +189,8 @@ function tokenizer.tokenize(incoming_syntax, text, state) return table.unpack(res) end - while i <= #text do + local text_len = text:ulen() + while i <= text_len do -- continue trying to match the end pattern of a pair if we have a state set if current_pattern_idx > 0 then local p = current_syntax.patterns[current_pattern_idx] @@ -262,13 +263,8 @@ function tokenizer.tokenize(incoming_syntax, text, state) -- consume character if we didn't match if not matched then - local n = 0 - -- reach the next character - while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do - n = n + 1 - end - push_token(res, "normal", text:usub(i, i + n)) - i = i + n + 1 + push_token(res, "normal", text:usub(i, i)) + i = i + 1 end end From 8bda5d4198f750e237fc07d052030ed6f6df47a5 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 13 May 2022 12:18:32 -0400 Subject: [PATCH 270/409] docs: added font.group to renderer and other adjustments --- docs/api/renderer.lua | 57 +++++++++++++------------------------------ 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/docs/api/renderer.lua b/docs/api/renderer.lua index 7a9b636d..8738a7c1 100644 --- a/docs/api/renderer.lua +++ b/docs/api/renderer.lua @@ -6,7 +6,9 @@ renderer = {} --- ----Represents a color used by the rendering functions. +---Array of bytes that represents a color used by the rendering functions. +---Note: indexes for rgba are numerical 1 = r, 2 = g, 3 = b, 4 = a but for +---documentation purposes the letters r, g, b, a were used. ---@class renderer.color ---@field public r number Red ---@field public g number Green @@ -17,7 +19,7 @@ renderer.color = {} --- ---Represent options that affect a font's rendering. ---@class renderer.fontoptions ----@field public antialiasing "'grayscale'" | "'subpixel'" +---@field public antialiasing "'none'" | "'grayscale'" | "'subpixel'" ---@field public hinting "'slight'" | "'none'" | '"full"' -- @field public bold boolean -- @field public italic boolean @@ -38,6 +40,16 @@ renderer.font = {} ---@return renderer.font function renderer.font.load(path, size, options) end +--- +---Combines an array of fonts into a single one for broader charset support, +---the order of the list determines the fonts precedence when retrieving +---a symbol from it. +--- +---@param fonts renderer.font[] +--- +---@return renderer.font +function renderer.font.group(fonts) end + --- ---Clones a font object into a new one. --- @@ -80,25 +92,6 @@ function renderer.font:get_size() end ---@param size number function renderer.font:set_size(size) end ---- ----Assistive functionality to replace characters in a ----rendered text with other characters. ----@class renderer.replacements -renderer.replacements = {} - ---- ----Create a new character replacements object. ---- ----@return renderer.replacements -function renderer.replacements.new() end - ---- ----Add to internal map a character to character replacement. ---- ----@param original_char string Should be a single character like '\t' ----@param replacement_char string Should be a single character like '»' -function renderer.replacements:add(original_char, replacement_char) end - --- ---Toggles drawing debugging rectangles on the currently rendered sections ---of the window to help troubleshoot the renderer. @@ -141,29 +134,13 @@ function renderer.set_clip_rect(x, y, width, height) end function renderer.draw_rect(x, y, width, height, color) end --- ----Draw text. +---Draw text and return the x coordinate where the text finished drawing. --- ---@param font renderer.font ---@param text string ---@param x number ---@param y number ---@param color renderer.color ----@param replace renderer.replacements ----@param color_replace renderer.color --- ----@return number x_subpixel -function renderer.draw_text(font, text, x, y, color, replace, color_replace) end - ---- ----Draw text at subpixel level. ---- ----@param font renderer.font ----@param text string ----@param x number ----@param y number ----@param color renderer.color ----@param replace renderer.replacements ----@param color_replace renderer.color ---- ----@return number x_subpixel -function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end +---@return number x +function renderer.draw_text(font, text, x, y, color) end From 36c4d5d9ed0e0be4df4d0b3260fe164a1ad75eab Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 15 May 2022 15:21:26 -0400 Subject: [PATCH 271/409] Autoreload Nagview (#942) * Modified autoreload to use new dirwatch infrastructure, and added in nagview to verify that fs changes don't stomp on our changes, unless you want them to. * Split out reload functionality to actual document, and added in a thread to check the document, in the cases where it wouldn't be covered by dirwatch. * As per request from jgmdev, added in ability to show nagview always. * Changed things over to use dirwatch. * Made sure we redrew things, added in a contingency in 'save' for times when we load a non-existent file, and added some checks. --- data/core/commands/doc.lua | 4 ++ data/core/dirwatch.lua | 5 ++ data/core/doc/init.lua | 10 ++++ data/plugins/autoreload.lua | 104 +++++++++++++++++++++++++++-------- src/api/dirmonitor/inotify.c | 2 +- 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 597e3de4..cbecad23 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -466,6 +466,10 @@ local commands = { command.perform("doc:save-as") end end, + + ["doc:reload"] = function() + doc():reload() + end, ["file:rename"] = function() local old_filename = doc().filename diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 68f62082..fcbd019d 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -31,10 +31,15 @@ end -- In windows, this is a no-op for anything underneath a top-level directory, -- but code should be called anyway, so we can ensure that we have a proper -- experience across all platforms. Should be an absolute path. +-- Can also be called on individual files, though this should be used sparingly, +-- so as not to run into system limits (like in the autoreload plugin). function dirwatch:watch(directory, bool) if bool == false then return self:unwatch(directory) end + local info = system.get_file_info(directory) + if not info then return end if not self.watched[directory] and not self.scanned[directory] then if PLATFORM == "Windows" then + if info.type ~= "dir" then return self:scan(directory) end if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then -- Get the highest level of directory that is common to this directory, and the original. local target = directory diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index f9369a46..0683b8b0 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -80,6 +80,16 @@ function Doc:load(filename) end +function Doc:reload() + if self.filename then + local sel = { self:get_selection() } + self:load(self.filename) + self:clean() + self:set_selection(table.unpack(sel)) + end +end + + function Doc:save(filename, abs_filename) if not filename then assert(self.filename, "no filename set to default to") diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 596b94f7..172edea2 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -1,44 +1,100 @@ -- mod-version:3 -- lite-xl 2.1 local core = require "core" local config = require "core.config" +local style = require "core.style" local Doc = require "core.doc" +local Node = require "core.node" +local common = require "core.common" +local dirwatch = require "core.dirwatch" +config.plugins.autoreload = common.merge({ + always_show_nagview = false +}, config.plugins.autoreload) + +local watch = dirwatch.new() local times = setmetatable({}, { __mode = "k" }) +local visible = setmetatable({}, { __mode = "k" }) + +local function get_project_doc_watch(doc) + for i, v in ipairs(core.project_directories) do + if doc.abs_filename:find(v.name, 1, true) == 1 then return v.watch end + end + return watch +end local function update_time(doc) - local info = system.get_file_info(doc.filename) - times[doc] = info.modified + times[doc] = system.get_file_info(doc.filename).modified end local function reload_doc(doc) - local fp = io.open(doc.filename, "r") - local text = fp:read("*a") - fp:close() - - local sel = { doc:get_selection() } - doc:remove(1, 1, math.huge, math.huge) - doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", "")) - doc:set_selection(table.unpack(sel)) - + doc:reload() update_time(doc) - doc:clean() + core.redraw = true core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) end -local on_modify = core.on_dirmonitor_modify - -core.on_dirmonitor_modify = function(dir, filepath) - local abs_filename = dir.name .. PATHSEP .. filepath - for _, doc in ipairs(core.docs) do - local info = system.get_file_info(doc.filename or "") - if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then - reload_doc(doc) - break - end +local function check_prompt_reload(doc) + if doc and doc.deferred_reload then + core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", { + { font = style.font, text = "Yes", default_yes = true }, + { font = style.font, text = "No" , default_no = true } + }, function(item) + if item.text == "Yes" then reload_doc(doc) end + doc.deferred_reload = false + end) end - on_modify(dir, filepath) end +local function doc_changes_visiblity(doc, visibility) + if doc and visible[doc] ~= visibility and doc.abs_filename then + visible[doc] = visibility + if visibility then check_prompt_reload(doc) end + get_project_doc_watch(doc):watch(doc.abs_filename, visibility) + end +end + +local on_check = dirwatch.check +function dirwatch:check(change_callback, ...) + on_check(self, function(dir) + for _, doc in ipairs(core.docs) do + if dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename then + local info = system.get_file_info(doc.filename or "") + if info and times[doc] ~= info.modified then + if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then + reload_doc(doc) + else + doc.deferred_reload = true + if doc == core.active_view.doc then check_prompt_reload(doc) end + end + end + end + end + change_callback(dir) + end, ...) +end + +local core_set_active_view = core.set_active_view +function core.set_active_view(view) + core_set_active_view(view) + doc_changes_visiblity(view.doc, true) +end + +local node_set_active_view = Node.set_active_view +function Node:set_active_view(view) + if self.active_view then doc_changes_visiblity(self.active_view.doc, false) end + node_set_active_view(self, view) + doc_changes_visiblity(self.active_view.doc, true) +end + +core.add_thread(function() + while true do + -- because we already hook this function above; we only + -- need to check the file. + watch:check(function() end) + coroutine.yield(0.05) + end +end) + -- patch `Doc.save|load` to store modified time local load = Doc.load local save = Doc.save @@ -51,6 +107,8 @@ end Doc.save = function(self, ...) local res = save(self, ...) + -- if starting with an unsaved document with a filename. + if not times[self] then get_project_doc_watch(self):watch(self.abs_filename, true) end update_time(self) return res end diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index 70e536dd..697e1815 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -44,7 +44,7 @@ int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buff int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { - return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MODIFY | IN_MOVED_TO); } From d56f4e1ee553a1cb41ab333822903bcedcf9318a Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 21 Apr 2022 22:33:00 -0400 Subject: [PATCH 272/409] Modified autoreload to use new dirwatch infrastructure, and added in nagview to verify that fs changes don't stomp on our changes, unless you want them to. --- data/plugins/autoreload.lua | 51 +++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 9978092e..b65beaac 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -1,7 +1,10 @@ -- mod-version:2 -- lite-xl 2.0 local core = require "core" local config = require "core.config" +local style = require "core.style" local Doc = require "core.doc" +local common = require "core.common" +local dirwatch = require "core.dirwatch" local times = setmetatable({}, { __mode = "k" }) @@ -25,18 +28,44 @@ local function reload_doc(doc) core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) end -local on_modify = core.on_dirmonitor_modify - -core.on_dirmonitor_modify = function(dir, filepath) - local abs_filename = dir.name .. PATHSEP .. filepath - for _, doc in ipairs(core.docs) do - local info = system.get_file_info(doc.filename or "") - if doc.abs_filename == abs_filename and info and times[doc] ~= info.modified then - reload_doc(doc) - break - end +local function check_prompt_reload() + if core.active_view.doc and core.active_view.doc.deferred_reload then + local doc = core.active_view.doc + core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", { + { font = style.font, text = "Yes", default_yes = true }, + { font = style.font, text = "No" , default_no = true } + }, function(item) + if item.text == "Yes" then reload_doc(doc) end + doc.deferred_reload = false + end) end - on_modify(dir, filepath) +end + +local on_check = dirwatch.check +function dirwatch:check(change_callback, ...) + on_check(self, function(dir) + for _, doc in ipairs(core.docs) do + if dir == common.dirname(doc.abs_filename) then + local info = system.get_file_info(doc.filename or "") + if info and times[doc] ~= info.modified then + if not doc:is_dirty() then + reload_doc(doc) + else + doc.deferred_reload = true + end + break + end + end + end + change_callback(dir) + end, ...) + check_prompt_reload() +end + +local set_active_view = core.set_active_view +function core.set_active_view(view) + set_active_view(view) + check_prompt_reload() end -- patch `Doc.save|load` to store modified time From 173370694e73b6983808c73357e5bb4a3af3667f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 28 Apr 2022 21:30:55 -0400 Subject: [PATCH 273/409] Split out reload functionality to actual document, and added in a thread to check the document, in the cases where it wouldn't be covered by dirwatch. --- data/core/commands/doc.lua | 4 +++ data/core/doc/init.lua | 10 ++++++++ data/plugins/autoreload.lua | 50 +++++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 87079180..d470076f 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -466,6 +466,10 @@ local commands = { command.perform("doc:save-as") end end, + + ["doc:reload"] = function() + doc():reload() + end, ["file:rename"] = function() local old_filename = doc().filename diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 460e387d..a2546246 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -80,6 +80,16 @@ function Doc:load(filename) end +function Doc:reload() + if self.filename then + local sel = { self:get_selection() } + self:load(self.filename) + self:clean() + self:set_selection(table.unpack(sel)) + end +end + + function Doc:save(filename, abs_filename) if not filename then assert(self.filename, "no filename set to default to") diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index b65beaac..45d7217f 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -14,17 +14,8 @@ local function update_time(doc) end local function reload_doc(doc) - local fp = io.open(doc.filename, "r") - local text = fp:read("*a") - fp:close() - - local sel = { doc:get_selection() } - doc:remove(1, 1, math.huge, math.huge) - doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", "")) - doc:set_selection(table.unpack(sel)) - + doc:reload() update_time(doc) - doc:clean() core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) end @@ -41,20 +32,23 @@ local function check_prompt_reload() end end +local function check_if_modified(doc) + local info = system.get_file_info(doc.filename or "") + if info and times[doc] ~= info.modified then + if not doc:is_dirty() then + reload_doc(doc) + else + doc.deferred_reload = true + end + end +end + local on_check = dirwatch.check function dirwatch:check(change_callback, ...) on_check(self, function(dir) for _, doc in ipairs(core.docs) do if dir == common.dirname(doc.abs_filename) then - local info = system.get_file_info(doc.filename or "") - if info and times[doc] ~= info.modified then - if not doc:is_dirty() then - reload_doc(doc) - else - doc.deferred_reload = true - end - break - end + check_if_modified(doc) end end change_callback(dir) @@ -62,10 +56,28 @@ function dirwatch:check(change_callback, ...) check_prompt_reload() end + local set_active_view = core.set_active_view function core.set_active_view(view) set_active_view(view) check_prompt_reload() + if view.doc and view.doc.abs_filename then + local should_poll = true + for i,v in ipairs(core.project_directories) do + if view.doc.abs_filename:find(v.name, 1, true) == 1 and not v.files_limit then + should_poll = false + end + end + if should_poll then + local doc = core.active_view.doc + core.add_thread(function() + while core.active_view.doc == doc do + check_if_modified(doc) + coroutine.yield(0.25) + end + end) + end + end end -- patch `Doc.save|load` to store modified time From 20dc101229b969bb67b94c824b23f23818ee3104 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 3 May 2022 23:24:26 -0400 Subject: [PATCH 274/409] As per request from jgmdev, added in ability to show nagview always. --- data/plugins/autoreload.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 45d7217f..fba22a23 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -6,6 +6,10 @@ local Doc = require "core.doc" local common = require "core.common" local dirwatch = require "core.dirwatch" +config.plugins.autoreload = common.merge({ + always_show_nagview = false +}, config.plugins.autoreload) + local times = setmetatable({}, { __mode = "k" }) local function update_time(doc) @@ -35,7 +39,7 @@ end local function check_if_modified(doc) local info = system.get_file_info(doc.filename or "") if info and times[doc] ~= info.modified then - if not doc:is_dirty() then + if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then reload_doc(doc) else doc.deferred_reload = true From 5a0d213f3b420bb4ff6cf46edbd306d437026745 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 15 May 2022 13:25:03 -0400 Subject: [PATCH 275/409] Changed things over to use dirwatch. --- data/core/dirwatch.lua | 3 ++ data/plugins/autoreload.lua | 75 ++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 314a33c0..c13e91ea 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -31,10 +31,13 @@ end -- In windows, this is a no-op for anything underneath a top-level directory, -- but code should be called anyway, so we can ensure that we have a proper -- experience across all platforms. Should be an absolute path. +-- Can also be called on individual files, though this should be used sparingly, +-- so as not to run into system limits (like in the autoreload plugin). function dirwatch:watch(directory, bool) if bool == false then return self:unwatch(directory) end if not self.watched[directory] and not self.scanned[directory] then if PLATFORM == "Windows" then + if system.get_file_info(directory).type ~= "dir" then return self:scan(directory) end if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then -- Get the highest level of directory that is common to this directory, and the original. local target = directory diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index fba22a23..4e8f5513 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -10,7 +10,16 @@ config.plugins.autoreload = common.merge({ always_show_nagview = false }, config.plugins.autoreload) +local function get_project_doc(doc) + for i, v in ipairs(core.project_directories) do + if doc.abs_filename:find(v.abs_filename, 1, true) == 1 then return v end + end + return nil +end + +local watch = dirwatch.new() local times = setmetatable({}, { __mode = "k" }) +local visible = setmetatable({}, { __mode = "k" }) local function update_time(doc) local info = system.get_file_info(doc.filename) @@ -23,9 +32,9 @@ local function reload_doc(doc) core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) end -local function check_prompt_reload() - if core.active_view.doc and core.active_view.doc.deferred_reload then - local doc = core.active_view.doc +local function check_prompt_reload(doc) + if doc or (core.active_view.doc and core.active_view.doc.deferred_reload) then + doc = doc or core.active_view.doc core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", { { font = style.font, text = "Yes", default_yes = true }, { font = style.font, text = "No" , default_no = true } @@ -36,6 +45,24 @@ local function check_prompt_reload() end end +local function doc_becomes_visible(doc) + if doc and not visible[doc] and doc.abs_filename then + visible[doc] = true + check_prompt_reload(doc) + local dir = get_project_doc(doc) + (dir and dir.watch or watch):watch(doc.abs_filename) + end +end + +local function doc_becomes_invisible(doc) + if doc and visible[doc] then + visible[doc] = false + local dir = get_project_doc(doc) + (dir and dir.watch or watch):unwatch(doc.abs_filename) + end +end + +>>>>>>> Stashed changes local function check_if_modified(doc) local info = system.get_file_info(doc.filename or "") if info and times[doc] ~= info.modified then @@ -51,7 +78,7 @@ local on_check = dirwatch.check function dirwatch:check(change_callback, ...) on_check(self, function(dir) for _, doc in ipairs(core.docs) do - if dir == common.dirname(doc.abs_filename) then + if dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename then check_if_modified(doc) end end @@ -60,30 +87,28 @@ function dirwatch:check(change_callback, ...) check_prompt_reload() end - -local set_active_view = core.set_active_view +local core_set_active_view = core.set_active_view function core.set_active_view(view) - set_active_view(view) - check_prompt_reload() - if view.doc and view.doc.abs_filename then - local should_poll = true - for i,v in ipairs(core.project_directories) do - if view.doc.abs_filename:find(v.name, 1, true) == 1 and not v.files_limit then - should_poll = false - end - end - if should_poll then - local doc = core.active_view.doc - core.add_thread(function() - while core.active_view.doc == doc do - check_if_modified(doc) - coroutine.yield(0.25) - end - end) - end - end + core_set_active_view(view) + doc_becomes_visible(view.doc) end +local node_set_active_view = Node.set_active_view +function Node:set_active_view(view) + doc_becomes_invisible(self.active_view.doc) + node_set_active_view(self, view) + doc_becomes_visible(self.active_view.doc) +end + +core.add_thread(function() + while true do + -- because we already hook this function above; we only + -- need to check the file. + watch:check(function() end) + coroutine.yield(0.05) + end +end) + -- patch `Doc.save|load` to store modified time local load = Doc.load local save = Doc.save From daeb2a8e044aa8e26ad93e1e91e89a373c6508d2 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 15 May 2022 14:31:00 -0400 Subject: [PATCH 276/409] Made sure we redrew things, added in a contingency in 'save' for times when we load a non-existent file, and added some checks. --- data/core/dirwatch.lua | 4 +- data/plugins/autoreload.lua | 72 +++++++++++++++--------------------- src/api/dirmonitor/inotify.c | 2 +- 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index c13e91ea..093e541c 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -35,9 +35,11 @@ end -- so as not to run into system limits (like in the autoreload plugin). function dirwatch:watch(directory, bool) if bool == false then return self:unwatch(directory) end + local info = system.get_file_info(directory) + if not info then return end if not self.watched[directory] and not self.scanned[directory] then if PLATFORM == "Windows" then - if system.get_file_info(directory).type ~= "dir" then return self:scan(directory) end + if info.type ~= "dir" then return self:scan(directory) end if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then -- Get the highest level of directory that is common to this directory, and the original. local target = directory diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 4e8f5513..03243851 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -3,6 +3,7 @@ local core = require "core" local config = require "core.config" local style = require "core.style" local Doc = require "core.doc" +local Node = require "core.node" local common = require "core.common" local dirwatch = require "core.dirwatch" @@ -10,31 +11,30 @@ config.plugins.autoreload = common.merge({ always_show_nagview = false }, config.plugins.autoreload) -local function get_project_doc(doc) - for i, v in ipairs(core.project_directories) do - if doc.abs_filename:find(v.abs_filename, 1, true) == 1 then return v end - end - return nil -end - local watch = dirwatch.new() local times = setmetatable({}, { __mode = "k" }) local visible = setmetatable({}, { __mode = "k" }) +local function get_project_doc_watch(doc) + for i, v in ipairs(core.project_directories) do + if doc.abs_filename:find(v.name, 1, true) == 1 then return v.watch end + end + return watch +end + local function update_time(doc) - local info = system.get_file_info(doc.filename) - times[doc] = info.modified + times[doc] = system.get_file_info(doc.filename).modified end local function reload_doc(doc) doc:reload() update_time(doc) + core.redraw = true core.log_quiet("Auto-reloaded doc \"%s\"", doc.filename) end local function check_prompt_reload(doc) - if doc or (core.active_view.doc and core.active_view.doc.deferred_reload) then - doc = doc or core.active_view.doc + if doc and doc.deferred_reload then core.nag_view:show("File Changed", doc.filename .. " has changed. Reload this file?", { { font = style.font, text = "Yes", default_yes = true }, { font = style.font, text = "No" , default_no = true } @@ -45,32 +45,11 @@ local function check_prompt_reload(doc) end end -local function doc_becomes_visible(doc) - if doc and not visible[doc] and doc.abs_filename then - visible[doc] = true - check_prompt_reload(doc) - local dir = get_project_doc(doc) - (dir and dir.watch or watch):watch(doc.abs_filename) - end -end - -local function doc_becomes_invisible(doc) - if doc and visible[doc] then - visible[doc] = false - local dir = get_project_doc(doc) - (dir and dir.watch or watch):unwatch(doc.abs_filename) - end -end - ->>>>>>> Stashed changes -local function check_if_modified(doc) - local info = system.get_file_info(doc.filename or "") - if info and times[doc] ~= info.modified then - if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then - reload_doc(doc) - else - doc.deferred_reload = true - end +local function doc_changes_visiblity(doc, visibility) + if doc and visible[doc] ~= visibility and doc.abs_filename then + visible[doc] = visibility + if visibility then check_prompt_reload(doc) end + get_project_doc_watch(doc):watch(doc.abs_filename, visibility) end end @@ -79,25 +58,32 @@ function dirwatch:check(change_callback, ...) on_check(self, function(dir) for _, doc in ipairs(core.docs) do if dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename then - check_if_modified(doc) + local info = system.get_file_info(doc.filename or "") + if info and times[doc] ~= info.modified then + if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then + reload_doc(doc) + else + doc.deferred_reload = true + if doc == core.active_view.doc then check_prompt_reload(doc) end + end + end end end change_callback(dir) end, ...) - check_prompt_reload() end local core_set_active_view = core.set_active_view function core.set_active_view(view) core_set_active_view(view) - doc_becomes_visible(view.doc) + doc_changes_visiblity(view.doc, true) end local node_set_active_view = Node.set_active_view function Node:set_active_view(view) - doc_becomes_invisible(self.active_view.doc) + if self.active_view then doc_changes_visiblity(self.active_view.doc, false) end node_set_active_view(self, view) - doc_becomes_visible(self.active_view.doc) + doc_changes_visiblity(self.active_view.doc, true) end core.add_thread(function() @@ -121,6 +107,8 @@ end Doc.save = function(self, ...) local res = save(self, ...) + -- if starting with an unsaved document with a filename. + if not times[self] then get_project_doc_watch(self):watch(self.abs_filename, true) end update_time(self) return res end diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index cb38c315..63e501a7 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -47,7 +47,7 @@ int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buff int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { - return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MODIFY | IN_MOVED_TO); } From 4d3e8d8bd0a7de6cfcc478eb63dd9d5a5e2a17f9 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 15 May 2022 16:10:57 -0400 Subject: [PATCH 277/409] command predicates: added support for strict matching by appending '!' on string predicates --- data/core/command.lua | 21 ++++++++++++++++----- data/core/object.lua | 5 +---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/data/core/command.lua b/data/core/command.lua index 39582de3..f54854a4 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -11,21 +11,32 @@ local always_true = function() return true end ---evaluation. --- ---If a string predicate is given it is treated as a require import that should ----return a valid object which is checked against the current active view, the ----sames applies if a table is given. A function that returns a boolean can be ----used instead to perform a custom evaluation, setting to nil means always ----evaluates to true. +---return a valid object which is checked against the current active view, +---eg: "core.docview" will match any view that inherits from DocView. Appending +---a `!` at the end of the string means we want to match the given object +---from the import strcitly eg: "core.docview!" only DocView is matched. +---A function that returns a boolean can be used instead to perform a custom +---evaluation, setting to nil means always evaluates to true. --- ---@param predicate string | table | function ---@return function function command.generate_predicate(predicate) predicate = predicate or always_true + local strict = false if type(predicate) == "string" then + if predicate:match("!$") then + strict = true + predicate = predicate:gsub("!$", "") + end predicate = require(predicate) end if type(predicate) == "table" then local class = predicate - predicate = function() return core.active_view:extends(class) end + if not strict then + predicate = function() return core.active_view:extends(class) end + else + predicate = function() return core.active_view:is(class) end + end end return predicate end diff --git a/data/core/object.lua b/data/core/object.lua index a57ac9e8..6a6ea490 100644 --- a/data/core/object.lua +++ b/data/core/object.lua @@ -21,10 +21,7 @@ end function Object:is(T) - if getmetatable(self) == T then - return true - end - return false + return getmetatable(self) == T end From 26e47f7583dfb753f365005b67e381b0fba0f1ea Mon Sep 17 00:00:00 2001 From: jgmdev Date: Sun, 15 May 2022 17:17:28 -0400 Subject: [PATCH 278/409] plugin contextmenu: simplify predicate --- data/plugins/contextmenu.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua index ac811b2a..918c6417 100644 --- a/data/plugins/contextmenu.lua +++ b/data/plugins/contextmenu.lua @@ -3,7 +3,6 @@ local core = require "core" local command = require "core.command" local keymap = require "core.keymap" local ContextMenu = require "core.contextmenu" -local DocView = require "core.docview" local RootView = require "core.rootview" local menu = ContextMenu() @@ -33,7 +32,7 @@ function RootView:draw(...) menu:draw() end -command.add(function() return getmetatable(core.active_view) == DocView end, { +command.add("core.docview!", { ["context:show"] = function() menu:show(core.active_view.position.x, core.active_view.position.y) end From 3bd679da12c5c71aae316dbb2b2757c827a3a4a8 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Mon, 23 May 2022 13:10:11 +0800 Subject: [PATCH 279/409] make lite-xl respect the taskbar and allow aero-drop in borderless mode --- src/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.c b/src/main.c index 94d2674b..0ce74862 100644 --- a/src/main.c +++ b/src/main.c @@ -103,6 +103,18 @@ int main(int argc, char **argv) { SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); #endif +#if SDL_VERSION_ATLEAST(2, 0, 8) + /* This hint tells SDL to respect borderless window as a normal window. + ** For example, the window will sit right on top of the taskbar instead + ** of obscuring it. */ + SDL_SetHint("SDL_BORDERLESS_WINDOWED_STYLE", "1"); +#endif +#if SDL_VERSION_ATLEAST(2, 0, 12) + /* This hint tells SDL to allow the user to resize a borderless windoow. + ** It also enables aero-snap on Windows apparently. */ + SDL_SetHint("SDL_BORDERLESS_RESIZABLE_STYLE", "1"); +#endif + SDL_DisplayMode dm; SDL_GetCurrentDisplayMode(0, &dm); From 8bbca7c0b086032799aee328aa4581c11cccb314 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 23 May 2022 15:12:37 -0400 Subject: [PATCH 280/409] plugins: added settings gui support --- data/plugins/autocomplete.lua | 50 ++++++++++++++++++- data/plugins/autoreload.lua | 12 ++++- data/plugins/linewrapping.lua | 93 +++++++++++++++++++++++++---------- data/plugins/scale.lua | 48 ++++++++++++++++-- data/plugins/treeview.lua | 38 ++++++++++++-- 5 files changed, 207 insertions(+), 34 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index eeb430a5..e98c603b 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -20,7 +20,55 @@ config.plugins.autocomplete = common.merge({ -- Maximum amount of symbols to cache per document max_symbols = 4000, -- Font size of the description box - desc_font_size = 12 + desc_font_size = 12, + -- The config specification used by gui generators + config_spec = { + name = "Autocomplete", + { + label = "Minimum Length", + description = "Amount of characters that need to be written for autocomplete to popup.", + path = "min_len", + type = "number", + default = 3, + min = 1, + max = 5 + }, + { + label = "Maximum Height", + description = "The maximum amount of visible items.", + path = "max_height", + type = "number", + default = 6, + min = 1, + max = 20 + }, + { + label = "Maximum Suggestions", + description = "The maximum amount of scrollable items.", + path = "max_suggestions", + type = "number", + default = 100, + min = 10, + max = 10000 + }, + { + label = "Maximum Symbols", + description = "Maximum amount of symbols to cache per document.", + path = "max_symbols", + type = "number", + default = 4000, + min = 1000, + max = 10000 + }, + { + label = "Description Font Size", + description = "Font size of the description box.", + path = "desc_font_size", + type = "number", + default = 12, + min = 8 + } + } }, config.plugins.autocomplete) local autocomplete = {} diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 172edea2..8a45a2ca 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -8,7 +8,17 @@ local common = require "core.common" local dirwatch = require "core.dirwatch" config.plugins.autoreload = common.merge({ - always_show_nagview = false + always_show_nagview = false, + config_spec = { + name = "Autoreload", + { + label = "Always Show Nagview", + description = "Alerts you if an opened file changes externally even if you haven't modified it.", + path = "always_show_nagview", + type = "toggle", + default = false + } + } }, config.plugins.autoreload) local watch = dirwatch.new() diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index c7ae6278..f42e45b7 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -22,7 +22,50 @@ config.plugins.linewrapping = common.merge({ -- Whether or not to enable wrapping by default when opening files. enable_by_default = false, -- Requires tokenization - require_tokenization = false + require_tokenization = false, + -- The config specification used by gui generators + config_spec = { + name = "Line Wrapping", + { + label = "Mode", + description = "The type of wrapping to perform.", + path = "mode", + type = "selection", + default = "letter", + values = { + {"Letters", "letter"}, + {"Words", "word"} + } + }, + { + label = "Guide", + description = "Whether or not to draw a guide.", + path = "guide", + type = "toggle", + default = true + }, + { + label = "Indent", + description = "Whether or not to follow the indentation of wrapped line.", + path = "indent", + type = "toggle", + default = true + }, + { + label = "Enable by Default", + description = "Whether or not to enable wrapping by default when opening files.", + path = "enable_by_default", + type = "toggle", + default = false + }, + { + label = "Require Tokenization", + description = "Use tokenization when applying wrapping.", + path = "require_tokenization", + type = "toggle", + default = false + } + } }, config.plugins.linewrapping) local LineWrapping = {} @@ -54,10 +97,10 @@ function LineWrapping.compute_line_breaks(doc, default_font, line, width, mode) w = font:get_width(char) xoffset = xoffset + w if xoffset > width then - if mode == "word" and last_space then + if mode == "word" and last_space then table.insert(splits, last_space + 1) xoffset = w + begin_width + (xoffset - last_width) - else + else table.insert(splits, i) xoffset = w + begin_width end @@ -113,11 +156,11 @@ function LineWrapping.reconstruct_breaks(docview, default_font, width, line_offs docview.wrapped_settings = nil end end - + -- When we have an insertion or deletion, we have four sections of text. -- 1. The unaffected section, located prior to the cursor. This is completely ignored. -- 2. The beginning of the affected line prior to the insertion or deletion. Begins on column 1 of the selection. --- 3. The removed/pasted lines. +-- 3. The removed/pasted lines. -- 4. Every line after the modification, begins one line after the selection in the initial document. function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines) -- Step 1: Determine the index for the line for #2. @@ -134,16 +177,16 @@ function LineWrapping.update_breaks(docview, old_line1, old_line2, net_lines) table.remove(docview.wrapped_line_offsets, old_line1) end -- Step 4: Shift the line number of wrapped_lines past #4 by the amount of inserted/deleted lines. - if net_lines ~= 0 then + if net_lines ~= 0 then for i = offset, #docview.wrapped_lines, 2 do docview.wrapped_lines[i] = docview.wrapped_lines[i] + net_lines end - end + end -- Step 5: Compute the breaks and offsets for the lines for #2 and #3. Insert them into the table. local new_line1 = old_line1 local new_line2 = old_line2 + net_lines for line = new_line1, new_line2 do - local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode) + local breaks, begin_width = LineWrapping.compute_line_breaks(docview.doc, docview.wrapped_settings.font, line, docview.wrapped_settings.width, config.plugins.linewrapping.mode) table.insert(docview.wrapped_line_offsets, line, begin_width) for i,b in ipairs(breaks) do table.insert(docview.wrapped_lines, offset, b) @@ -177,7 +220,7 @@ end function LineWrapping.update_docview_breaks(docview) local x,y,w,h = docview:get_scrollbar_rect() - local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview)) + local width = (type(config.plugins.linewrapping.width_override) == "function" and config.plugins.linewrapping.width_override(docview)) or config.plugins.linewrapping.width_override or (docview.size.x - docview:get_gutter_width() - w) if (not docview.wrapped_settings or docview.wrapped_settings.width == nil or width ~= docview.wrapped_settings.width) then docview.scroll.to.x = 0 @@ -187,9 +230,9 @@ end local function get_idx_line_col(docview, idx) local doc = docview.doc - if not docview.wrapped_settings then + if not docview.wrapped_settings then if idx > #doc.lines then return #doc.lines, #doc.lines[#doc.lines] + 1 end - return idx, 1 + return idx, 1 end if idx < 1 then return 1, 1 end local offset = (idx - 1) * 2 + 1 @@ -199,7 +242,7 @@ end local function get_idx_line_length(docview, idx) local doc = docview.doc - if not docview.wrapped_settings then + if not docview.wrapped_settings then if idx > #doc.lines then return #doc.lines[#doc.lines] + 1 end return #doc.lines[idx] end @@ -353,8 +396,8 @@ function DocView:get_col_x_offset(line, col, line_end) local xoffset, i = (scol ~= 1 and self.wrapped_line_offsets[line] or 0), 1 local default_font = self:get_font() for _, type, text in self.doc.highlighter:each_token(line) do - if i + #text >= scol then - if i < scol then + if i + #text >= scol then + if i < scol then text = text:sub(scol - i + 1) i = scol end @@ -407,7 +450,7 @@ function DocView:draw_line_text(line, x, y) while text ~= nil and token_offset <= #text do local next_line, next_line_start_col = get_idx_line_col(self, idx + 1) if next_line ~= line then - next_line_start_col = #self.doc.lines[line] + next_line_start_col = #self.doc.lines[line] end local max_length = next_line_start_col - total_offset local rendered_text = text:sub(token_offset, token_offset + max_length - 1) @@ -454,9 +497,9 @@ function DocView:draw_line_body(line, x, y) draw_highlight = (line1 == line2 and col1 == col2) end end - if draw_highlight then + if draw_highlight then local _, _, count = get_line_idx_col_count(self, line) - for i=1,count do + for i=1,count do self:draw_line_highlight(x + self.scroll.x, y + lh * (i - 1)) end end @@ -468,9 +511,9 @@ local old_draw = DocView.draw function DocView:draw() old_draw(self) if self.wrapped_settings then - LineWrapping.draw_guide(self) + LineWrapping.draw_guide(self) end -end +end local old_draw_line_gutter = DocView.draw_line_gutter function DocView:draw_line_gutter(line, x, y, width) @@ -512,18 +555,18 @@ function DocView.translate.next_line(doc, line, col, dv) end command.add(nil, { - ["line-wrapping:enable"] = function() - if core.active_view and core.active_view.doc then + ["line-wrapping:enable"] = function() + if core.active_view and core.active_view.doc then LineWrapping.update_docview_breaks(core.active_view) end end, - ["line-wrapping:disable"] = function() - if core.active_view and core.active_view.doc then + ["line-wrapping:disable"] = function() + if core.active_view and core.active_view.doc then LineWrapping.reconstruct_breaks(core.active_view, core.active_view:get_font(), math.huge) end end, - ["line-wrapping:toggle"] = function() - if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then + ["line-wrapping:toggle"] = function() + if core.active_view and core.active_view.doc and core.active_view.wrapped_settings then command.perform("line-wrapping:disable") else command.perform("line-wrapping:enable") diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index cc9db5bf..a09fde77 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -9,8 +9,43 @@ local RootView = require "core.rootview" local CommandView = require "core.commandview" config.plugins.scale = common.merge({ + -- The method used to apply the scaling: "code", "ui" mode = "code", - use_mousewheel = true + -- Allow using CTRL + MouseWheel for changing the scale. + use_mousewheel = true, + -- The config specification used by gui generators + config_spec = { + name = "Scale", + { + label = "Mode", + description = "The method used to apply the scaling.", + path = "mode", + type = "selection", + default = "code", + values = { + {"Code Only", "code"}, + {"Everything", "ui"} + } + }, + { + label = "Use MouseWheel", + description = "Allow using CTRL + MouseWheel for changing the scale.", + path = "use_mousewheel", + type = "toggle", + default = true, + on_apply = function(enabled) + if enabled then + keymap.add { + ["ctrl+wheelup"] = "scale:increase", + ["ctrl+wheeldown"] = "scale:decrease" + } + else + keymap.unbind("ctrl+wheelup", "scale:increase") + keymap.unbind("ctrl+wheeldown", "scale:decrease") + end + end + } + } }, config.plugins.scale) local scale_steps = 0.05 @@ -89,11 +124,16 @@ command.add(nil, { keymap.add { ["ctrl+0"] = "scale:reset", ["ctrl+-"] = "scale:decrease", - ["ctrl+="] = "scale:increase", - ["ctrl+wheelup"] = "scale:increase", - ["ctrl+wheeldown"] = "scale:decrease" + ["ctrl+="] = "scale:increase" } +if config.plugins.scale.use_mousewheel then + keymap.add { + ["ctrl+wheelup"] = "scale:increase", + ["ctrl+wheeldown"] = "scale:decrease" + } +end + return { ["set"] = set_scale, ["get"] = get_scale, diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 16f369b7..a812c928 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -601,13 +601,13 @@ command.add(TreeView, { ["treeview:deselect"] = function() view.selected_item = nil end, - + ["treeview:select"] = function() view:set_selection(view.hovered_item) end, - + ["treeview:select-and-open"] = function() - if view.hovered_item then + if view.hovered_item then view:set_selection(view.hovered_item) command.perform "treeview:open" end @@ -762,6 +762,38 @@ keymap.add { ["ctrl+lclick"] = "treeview:new-folder" } +-- The config specification used by gui generators +config.plugins.treeview.config_spec = { + name = "Treeview", + { + label = "Size", + description = "Default treeview width.", + path = "size", + type = "number", + default = math.ceil(toolbar_view:get_min_width() / SCALE), + min = toolbar_view:get_min_width() / SCALE, + get_value = function(value) + return value / SCALE + end, + set_value = function(value) + return value * SCALE + end, + on_apply = function(value) + view:set_target_size("x", math.max(value, toolbar_view:get_min_width())) + end + }, + { + label = "Hide on Startup", + description = "Show or hide the treeview on startup.", + path = "visible", + type = "toggle", + default = false, + on_apply = function(value) + view.visible = not value + end + } +} + -- Return the treeview with toolbar and contextmenu to allow -- user or plugin modifications view.toolbar = toolbar_view From 74f7389caca79b78cd392931069bd998e3b791ea Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 28 May 2022 01:20:41 +0200 Subject: [PATCH 281/409] Make regex API return integers --- src/api/regex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/regex.c b/src/api/regex.c index 6a0aac7a..d23eaf71 100644 --- a/src/api/regex.c +++ b/src/api/regex.c @@ -88,7 +88,7 @@ static int f_pcre_match(lua_State *L) { return 0; } for (int i = 0; i < rc*2; i++) - lua_pushnumber(L, ovector[i]+offset+1); + lua_pushinteger(L, ovector[i]+offset+1); pcre2_match_data_free(md); return rc*2; } From 14be51b1eca87dc1a97249d04984c2ce38926fa1 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 28 May 2022 01:21:41 +0200 Subject: [PATCH 282/409] Make `regex.match` return all the results --- data/core/regex.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/core/regex.lua b/data/core/regex.lua index 637d23fd..fa85d56c 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -5,8 +5,9 @@ regex.__index = function(table, key) return regex[key]; end regex.match = function(pattern_string, string, offset, options) local pattern = type(pattern_string) == "table" and pattern_string or regex.compile(pattern_string) - local s, e = regex.cmatch(pattern, string, offset or 1, options or 0) - return s, e and e - 1 + local res = { regex.cmatch(pattern, string, offset or 1, options or 0) } + res[2] = res[2] and res[2] - 1 + return table.unpack(res) end -- Will iterate back through any UTF-8 bytes so that we don't replace bits From 2a41002355181399e993a5395c28d8db024cb161 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 28 May 2022 01:38:22 +0200 Subject: [PATCH 283/409] Allow using regex groups to split tokens Before, this was only supported by Lua patterns. This expects the regex to use the same syntax used for patterns. That is, the token should be split by empty groups. --- data/core/tokenizer.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index ebe550ff..3d935cae 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -174,6 +174,17 @@ function tokenizer.tokenize(incoming_syntax, text, state) if p.regex and #res > 0 then -- set correct utf8 len for regex result res[2] = res[1] + string.ulen(text:sub(res[1], res[2])) - 1 res[1] = next + -- `regex.match` returns group results as a series of `begin, end` + -- we only want `begin`s + for i=1,(#res-3) do + local curr = i + 3 + local from = i * 2 + 3 + if from < #res then + res[curr] = string.uoffset(text, res[from]) + else + res[curr] = nil + end + end end if res[1] and close and target[3] then local count = 0 From b3278f6360a2342883fa641ccfcc67159923bac9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 30 May 2022 15:40:46 +0100 Subject: [PATCH 284/409] Made moving to a line beyond the end of a document move the caret column to the end of the line --- data/core/docview.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/core/docview.lua b/data/core/docview.lua index 2c624d80..f5440262 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -29,6 +29,9 @@ DocView.translate = { end, ["next_page"] = function(doc, line, col, dv) + if line == #doc.lines then + return #doc.lines, #doc.lines[line] + end local min, max = dv:get_visible_line_range() return line + (max - min), 1 end, From 86e3f4a690bb059e6a46590447f73a1602541fce Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 30 May 2022 17:13:21 +0100 Subject: [PATCH 285/409] Made wrapping overflow optional --- data/core/commands/core.lua | 2 +- data/core/commandview.lua | 17 +++++++++++++---- data/plugins/lineguide.lua | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 3f466292..9ba809c4 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -145,7 +145,7 @@ command.add(nil, { else return true end - end) + end, true) end, ["core:open-log"] = function() diff --git a/data/core/commandview.lua b/data/core/commandview.lua index e3eb3dc9..0d19f82d 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -44,6 +44,7 @@ function CommandView:new() self.font = "font" self.size.y = 0 self.label = "" + self.wrap_on_overflow = false end @@ -89,11 +90,18 @@ function CommandView:set_text(text, select) end end - function CommandView:move_suggestion_idx(dir) + local function overflow_suggestion_idx(n, count) + if self.wrap_on_overflow then + return (n - 1) % count + 1 + else + return common.clamp(n, 1, count) + end + end + if self.show_suggestions then local n = self.suggestion_idx + dir - self.suggestion_idx = common.clamp(n, 1, #self.suggestions) + self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions) self:complete() self.last_change_id = self.doc:get_change_id() else @@ -104,7 +112,7 @@ function CommandView:move_suggestion_idx(dir) if n == 0 and self.save_suggestion then self:set_text(self.save_suggestion) else - self.suggestion_idx = common.clamp(n, 1, #self.suggestions) + self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions) self:complete() end else @@ -135,10 +143,11 @@ function CommandView:submit() end -function CommandView:enter(text, submit, suggest, cancel, validate) +function CommandView:enter(text, submit, suggest, cancel, validate, wrap_on_overflow) if self.state ~= default_state then return end + self.wrap_on_overflow = wrap_on_overflow or false; self.state = { submit = submit or noop, suggest = suggest or noop, diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 83c35a8d..61f817f1 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -13,7 +13,7 @@ function DocView:draw_overlay(...) local y = self.position.y local w = math.ceil(SCALE * 1) local h = self.size.y - + local color = style.guide or style.selection renderer.draw_rect(x, y, w, h, color) end From e7d2b03e629dad92dd293c0f80dfdf1e8b51bc3a Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 30 May 2022 13:24:53 -0400 Subject: [PATCH 286/409] meson: fix not obeyed forcefallback for lua --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1cc54cbc..11d81917 100644 --- a/meson.build +++ b/meson.build @@ -81,7 +81,7 @@ if not get_option('source-only') ] foreach lua : lua_names - last_lua = (lua == lua_names[-1]) + last_lua = (lua == lua_names[-1] or get_option('wrap_mode') == 'forcefallback') lua_dep = dependency(lua, fallback: last_lua ? ['lua', 'lua_dep'] : [], required : last_lua, version: '>= 5.4', default_options: default_fallback_options + ['default_library=static', 'line_editing=false', 'interpreter=false'] From e4a806a9d0aa20318b6a2d31c6ea0fba1433a831 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 30 May 2022 21:12:16 +0200 Subject: [PATCH 287/409] Add options to `CommandView:enter` Now `CommandView:enter` can accept a table that specifies its behavior. The old behavior is kept for compatibility. --- data/core/commandview.lua | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 0d19f82d..101cbd98 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -25,7 +25,9 @@ local default_state = { submit = noop, suggest = noop, cancel = noop, - validate = function() return true end + validate = function() return true end, + typeahead = true, + wrap = true, } @@ -44,7 +46,6 @@ function CommandView:new() self.font = "font" self.size.y = 0 self.label = "" - self.wrap_on_overflow = false end @@ -90,9 +91,10 @@ function CommandView:set_text(text, select) end end + function CommandView:move_suggestion_idx(dir) local function overflow_suggestion_idx(n, count) - if self.wrap_on_overflow then + if self.state.wrap then return (n - 1) % count + 1 else return common.clamp(n, 1, count) @@ -143,17 +145,25 @@ function CommandView:submit() end -function CommandView:enter(text, submit, suggest, cancel, validate, wrap_on_overflow) +function CommandView:enter(text, ...) if self.state ~= default_state then return end - self.wrap_on_overflow = wrap_on_overflow or false; - self.state = { - submit = submit or noop, - suggest = suggest or noop, - cancel = cancel or noop, - validate = validate or function() return true end - } + local options = select(1, ...) + + if type(options) ~= "table" then + core.info("Warning: deprecated CommandView:enter usage") + local submit, suggest, cancel, validate = ... + options = { + submit = submit, + suggest = suggest, + cancel = cancel, + validate = validate, + } + end + + self.state = common.merge(default_state, options) + core.set_active_view(self) self:update_suggestions() self.gutter_text_brightness = 100 @@ -210,7 +220,7 @@ function CommandView:update() -- update suggestions if text has changed if self.last_change_id ~= self.doc:get_change_id() then self:update_suggestions() - if self.suggestions[self.suggestion_idx] then + if self.state.typeahead and self.suggestions[self.suggestion_idx] then local current_text = self:get_text() local suggested_text = self.suggestions[self.suggestion_idx].text or "" if #self.last_text < #current_text and From 11e27c6fda10b1223b572c187ce724a4e03dce5a Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 30 May 2022 22:06:47 +0200 Subject: [PATCH 288/409] Use new `CommandView:enter` options table --- data/core/commands/core.lua | 226 ++++++++++++++++------------- data/core/commands/doc.lua | 62 ++++---- data/core/commands/files.lua | 12 +- data/core/commands/findreplace.lua | 82 ++++++----- data/core/commands/statusbar.lua | 16 +- data/core/docview.lua | 27 ++-- data/plugins/detectindent.lua | 23 ++- data/plugins/projectsearch.lua | 40 ++--- data/plugins/tabularize.lua | 30 ++-- data/plugins/treeview.lua | 65 +++++---- 10 files changed, 321 insertions(+), 262 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 9ba809c4..40be12a0 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -48,36 +48,42 @@ command.add(nil, { end, ["core:reload-module"] = function() - core.command_view:enter("Reload Module", function(text, item) - local text = item and item.text or text - core.reload_module(text) - core.log("Reloaded module %q", text) - end, function(text) - local items = {} - for name in pairs(package.loaded) do - table.insert(items, name) + core.command_view:enter("Reload Module", { + submit = function(text, item) + local text = item and item.text or text + core.reload_module(text) + core.log("Reloaded module %q", text) + end, + suggest = function(text) + local items = {} + for name in pairs(package.loaded) do + table.insert(items, name) + end + return common.fuzzy_match(items, text) end - return common.fuzzy_match(items, text) - end) + }) end, ["core:find-command"] = function() local commands = command.get_all_valid() - core.command_view:enter("Do Command", function(text, item) - if item then - command.perform(item.command) + core.command_view:enter("Do Command", { + submit = function(text, item) + if item then + command.perform(item.command) + end + end, + suggest = function(text) + local res = common.fuzzy_match(commands, text) + for i, name in ipairs(res) do + res[i] = { + text = command.prettify_name(name), + info = keymap.get_binding(name), + command = name, + } + end + return res end - end, function(text) - local res = common.fuzzy_match(commands, text) - for i, name in ipairs(res) do - res[i] = { - text = command.prettify_name(name), - info = keymap.get_binding(name), - command = name, - } - end - return res - end) + }) end, ["core:find-file"] = function() @@ -91,12 +97,15 @@ command.add(nil, { table.insert(files, common.home_encode(path .. item.filename)) end end - core.command_view:enter("Open File From Project", function(text, item) - text = item and item.text or text - core.root_view:open_doc(core.open_doc(common.home_expand(text))) - end, function(text) - return common.fuzzy_match_with_recents(files, core.visited_files, text) - end) + core.command_view:enter("Open File From Project", { + submit = function(text, item) + text = item and item.text or text + core.root_view:open_doc(core.open_doc(common.home_expand(text))) + end, + suggest = function(text) + return common.fuzzy_match_with_recents(files, core.visited_files, text) + end + }) end, ["core:new-doc"] = function() @@ -104,12 +113,11 @@ command.add(nil, { end, ["core:new-named-doc"] = function() - core.command_view:enter( - "File name", - function(text) + core.command_view:enter("File name", { + submit = function(text) core.root_view:open_doc(core.open_doc(text)) end - ) + }) end, ["core:open-file"] = function() @@ -122,30 +130,34 @@ command.add(nil, { core.command_view:set_text(text) end end - core.command_view:enter("Open File", function(text) - local filename = system.absolute_path(common.home_expand(text)) - core.root_view:open_doc(core.open_doc(filename)) - end, function (text) - return common.home_encode_list(common.path_suggest(common.home_expand(text))) - end, nil, function(text) - local filename = common.home_expand(text) - local path_stat, err = system.get_file_info(filename) - if err then - if err:find("No such file", 1, true) then - -- check if the containing directory exists - local dirname = common.dirname(filename) - local dir_stat = dirname and system.get_file_info(dirname) - if not dirname or (dir_stat and dir_stat.type == 'dir') then + core.command_view:enter("Open File", { + submit = function(text) + local filename = system.absolute_path(common.home_expand(text)) + core.root_view:open_doc(core.open_doc(filename)) + end, + suggest = function (text) + return common.home_encode_list(common.path_suggest(common.home_expand(text))) + end, + validate = function(text) + local filename = common.home_expand(text) + local path_stat, err = system.get_file_info(filename) + if err then + if err:find("No such file", 1, true) then + -- check if the containing directory exists + local dirname = common.dirname(filename) + local dir_stat = dirname and system.get_file_info(dirname) + if not dirname or (dir_stat and dir_stat.type == 'dir') then + return true + end + end + core.error("Cannot open file %s: %s", text, err) + elseif path_stat.type == 'dir' then + core.error("Cannot open %s, is a folder", text) + else return true end - end - core.error("Cannot open file %s: %s", text, err) - elseif path_stat.type == 'dir' then - core.error("Cannot open %s, is a folder", text) - else - return true - end - end, true) + end, + }) end, ["core:open-log"] = function() @@ -173,18 +185,21 @@ command.add(nil, { if dirname then core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end - core.command_view:enter("Change Project Folder", function(text) - local path = common.home_expand(text) - local abs_path = check_directory_path(path) - if not abs_path then - core.error("Cannot open directory %q", path) - return - end - if abs_path == core.project_dir then return end - core.confirm_close_docs(core.docs, function(dirpath) - core.open_folder_project(dirpath) - end, abs_path) - end, suggest_directory) + core.command_view:enter("Change Project Folder", { + submit = function(text) + local path = common.home_expand(text) + local abs_path = check_directory_path(path) + if not abs_path then + core.error("Cannot open directory %q", path) + return + end + if abs_path == core.project_dir then return end + core.confirm_close_docs(core.docs, function(dirpath) + core.open_folder_project(dirpath) + end, abs_path) + end, + suggest = suggest_directory + }) end, ["core:open-project-folder"] = function() @@ -192,34 +207,40 @@ command.add(nil, { if dirname then core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) end - core.command_view:enter("Open Project", function(text) - local path = common.home_expand(text) - local abs_path = check_directory_path(path) - if not abs_path then - core.error("Cannot open directory %q", path) - return - end - if abs_path == core.project_dir then - core.error("Directory %q is currently opened", abs_path) - return - end - system.exec(string.format("%q %q", EXEFILE, abs_path)) - end, suggest_directory) + core.command_view:enter("Open Project", { + submit = function(text) + local path = common.home_expand(text) + local abs_path = check_directory_path(path) + if not abs_path then + core.error("Cannot open directory %q", path) + return + end + if abs_path == core.project_dir then + core.error("Directory %q is currently opened", abs_path) + return + end + system.exec(string.format("%q %q", EXEFILE, abs_path)) + end, + suggest = suggest_directory + }) end, ["core:add-directory"] = function() - core.command_view:enter("Add Directory", function(text) - text = common.home_expand(text) - local path_stat, err = system.get_file_info(text) - if not path_stat then - core.error("cannot open %q: %s", text, err) - return - elseif path_stat.type ~= 'dir' then - core.error("%q is not a directory", text) - return - end - core.add_project_directory(system.absolute_path(text)) - end, suggest_directory) + core.command_view:enter("Add Directory", { + submit = function(text) + text = common.home_expand(text) + local path_stat, err = system.get_file_info(text) + if not path_stat then + core.error("cannot open %q: %s", text, err) + return + elseif path_stat.type ~= 'dir' then + core.error("%q is not a directory", text) + return + end + core.add_project_directory(system.absolute_path(text)) + end, + suggest = suggest_directory + }) end, ["core:remove-directory"] = function() @@ -228,14 +249,17 @@ command.add(nil, { for i = n, 2, -1 do dir_list[n - i + 1] = core.project_directories[i].name end - core.command_view:enter("Remove Directory", function(text, item) - text = common.home_expand(item and item.text or text) - if not core.remove_project_directory(text) then - core.error("No directory %q to be removed", text) + core.command_view:enter("Remove Directory", { + submit = function(text, item) + text = common.home_expand(item and item.text or text) + if not core.remove_project_directory(text) then + core.error("No directory %q to be removed", text) + end + end, + suggest = function(text) + text = common.home_expand(text) + return common.home_encode_list(common.dir_list_suggest(text, dir_list)) end - end, function(text) - text = common.home_expand(text) - return common.home_encode_list(common.dir_list_suggest(text, dir_list)) - end) + }) end, }) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 5d953805..f572ec26 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -423,21 +423,23 @@ local commands = { end end - core.command_view:enter("Go To Line", function(text, item) - local line = item and item.line or tonumber(text) - if not line then - core.error("Invalid line number or unmatched string") - return + core.command_view:enter("Go To Line", { + submit = function(text, item) + local line = item and item.line or tonumber(text) + if not line then + core.error("Invalid line number or unmatched string") + return + end + dv.doc:set_selection(line, 1 ) + dv:scroll_to_line(line, true) + end, + suggest = function(text) + if not text:find("^%d*$") then + init_items() + return common.fuzzy_match(items, text) + end end - dv.doc:set_selection(line, 1 ) - dv:scroll_to_line(line, true) - - end, function(text) - if not text:find("^%d*$") then - init_items() - return common.fuzzy_match(items, text) - end - end) + }) end, ["doc:toggle-line-ending"] = function() @@ -452,11 +454,14 @@ local commands = { local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$") core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP) end - core.command_view:enter("Save As", function(filename) - save(common.home_expand(filename)) - end, function (text) - return common.home_encode_list(common.path_suggest(common.home_expand(text))) - end) + core.command_view:enter("Save As", { + submit = function(filename) + save(common.home_expand(filename)) + end, + suggest = function (text) + return common.home_encode_list(common.path_suggest(common.home_expand(text))) + end + }) end, ["doc:save"] = function() @@ -478,15 +483,18 @@ local commands = { return end core.command_view:set_text(old_filename) - core.command_view:enter("Rename", function(filename) - save(common.home_expand(filename)) - core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) - if filename ~= old_filename then - os.remove(old_filename) + core.command_view:enter("Rename", { + submit = function(filename) + save(common.home_expand(filename)) + core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) + if filename ~= old_filename then + os.remove(old_filename) + end + end, + suggest = function (text) + return common.home_encode_list(common.path_suggest(common.home_expand(text))) end - end, function (text) - return common.home_encode_list(common.path_suggest(common.home_expand(text))) - end) + }) end, diff --git a/data/core/commands/files.lua b/data/core/commands/files.lua index b2fdb336..a13db5df 100644 --- a/data/core/commands/files.lua +++ b/data/core/commands/files.lua @@ -4,11 +4,13 @@ local common = require "core.common" command.add(nil, { ["files:create-directory"] = function() - core.command_view:enter("New directory name", function(text) - local success, err, path = common.mkdirp(text) - if not success then - core.error("cannot create directory %q: %s", path, err) + core.command_view:enter("New directory name", { + submit = function(text) + local success, err, path = common.mkdirp(text) + if not success then + core.error("cannot create directory %q: %s", path, err) + end end - end) + }) end, }) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index db6a2dd6..ccfbf118 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -62,27 +62,31 @@ local function find(label, search_fn) core.status_view:show_tooltip(get_find_tooltip()) core.command_view:set_hidden_suggestions() - core.command_view:enter(label, function(text, item) - insert_unique(core.previous_find, text) - core.status_view:remove_tooltip() - if found_expression then + core.command_view:enter(label, { + submit = function(text, item) + insert_unique(core.previous_find, text) + core.status_view:remove_tooltip() + if found_expression then + last_fn, last_text = search_fn, text + else + core.error("Couldn't find %q", text) + last_view.doc:set_selection(table.unpack(last_sel)) + last_view:scroll_to_make_visible(table.unpack(last_sel)) + end + end, + suggest = function(text) + update_preview(last_sel, search_fn, text) last_fn, last_text = search_fn, text - else - core.error("Couldn't find %q", text) - last_view.doc:set_selection(table.unpack(last_sel)) - last_view:scroll_to_make_visible(table.unpack(last_sel)) + return core.previous_find + end, + cancel = function(explicit) + core.status_view:remove_tooltip() + if explicit then + last_view.doc:set_selection(table.unpack(last_sel)) + last_view:scroll_to_make_visible(table.unpack(last_sel)) + end end - end, function(text) - update_preview(last_sel, search_fn, text) - last_fn, last_text = search_fn, text - return core.previous_find - end, function(explicit) - core.status_view:remove_tooltip() - if explicit then - last_view.doc:set_selection(table.unpack(last_sel)) - last_view:scroll_to_make_visible(table.unpack(last_sel)) - end - end) + }) end @@ -91,25 +95,31 @@ local function replace(kind, default, fn) core.status_view:show_tooltip(get_find_tooltip()) core.command_view:set_hidden_suggestions() - core.command_view:enter("Find To Replace " .. kind, function(old) - insert_unique(core.previous_find, old) - core.command_view:set_text(old, true) + core.command_view:enter("Find To Replace " .. kind, { + submit = function(old) + insert_unique(core.previous_find, old) + core.command_view:set_text(old, true) - local s = string.format("Replace %s %q With", kind, old) - core.command_view:set_hidden_suggestions() - core.command_view:enter(s, function(new) + local s = string.format("Replace %s %q With", kind, old) + core.command_view:set_hidden_suggestions() + core.command_view:enter(s, { + submit = function(new) + core.status_view:remove_tooltip() + insert_unique(core.previous_replace, new) + local n = doc():replace(function(text) + return fn(text, old, new) + end) + core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new) + end, + suggest = function() return core.previous_replace end, function() + core.status_view:remove_tooltip() + end + }) + end, + suggest = function() return core.previous_find end, function() core.status_view:remove_tooltip() - insert_unique(core.previous_replace, new) - local n = doc():replace(function(text) - return fn(text, old, new) - end) - core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new) - end, function() return core.previous_replace end, function() - core.status_view:remove_tooltip() - end) - end, function() return core.previous_find end, function() - core.status_view:remove_tooltip() - end) + end + }) end local function has_selection() diff --git a/data/core/commands/statusbar.lua b/data/core/commands/statusbar.lua index ac8837ed..5676ef52 100644 --- a/data/core/commands/statusbar.lua +++ b/data/core/commands/statusbar.lua @@ -50,20 +50,20 @@ command.add(nil, { core.status_view:display_messages(true) end, ["status-bar:hide-item"] = function() - core.command_view:enter("Status bar item to hide", - function(text, item) + core.command_view:enter("Status bar item to hide", { + submit = function(text, item) core.status_view:hide_items(item.name) end, - status_view_get_items - ) + suggest = status_view_get_items + }) end, ["status-bar:show-item"] = function() - core.command_view:enter("Status bar item to show", - function(text, item) + core.command_view:enter("Status bar item to show", { + submit = function(text, item) core.status_view:show_items(item.name) end, - status_view_get_items - ) + suggest = status_view_get_items + }) end, ["status-bar:reset-items"] = function() core.status_view:show_items() diff --git a/data/core/docview.lua b/data/core/docview.lua index f5440262..bf76db38 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -65,19 +65,22 @@ end function DocView:try_close(do_close) if self.doc:is_dirty() and #core.get_views_referencing_doc(self.doc) == 1 then - core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item) - if item.text:match("^[cC]") then - do_close() - elseif item.text:match("^[sS]") then - self.doc:save() - do_close() + core.command_view:enter("Unsaved Changes; Confirm Close", { + submit = function(_, item) + if item.text:match("^[cC]") then + do_close() + elseif item.text:match("^[sS]") then + self.doc:save() + do_close() + end + end, + suggest = function(text) + local items = {} + if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end + if not text:find("^[^sS]") then table.insert(items, "Save And Close") end + return items end - end, function(text) - local items = {} - if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end - if not text:find("^[^sS]") then table.insert(items, "Save And Close") end - return items - end) + }) else do_close() end diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 3501f472..29204760 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -300,22 +300,20 @@ local function set_indent_type(doc, type) end local function set_indent_type_command() - core.command_view:enter( - "Specify indent style for this file", - function(value) -- submit + core.command_view:enter("Specify indent style for this file", { + submit = function(value) local doc = core.active_view.doc value = value:lower() set_indent_type(doc, value == "tabs" and "hard" or "soft") end, - function(text) -- suggest + suggest = function(text) return common.fuzzy_match({"tabs", "spaces"}, text) end, - nil, -- cancel - function(text) -- validate + validate = function(text) local t = text:lower() return t == "tabs" or t == "spaces" end - ) + }) end @@ -330,20 +328,17 @@ local function set_indent_size(doc, size) end local function set_indent_size_command() - core.command_view:enter( - "Specify indent size for current file", - function(value) -- submit + core.command_view:enter("Specify indent size for current file", { + submit = function(value) value = math.floor(tonumber(value)) local doc = core.active_view.doc set_indent_size(doc, value) end, - nil, -- suggest - nil, -- cancel - function(value) -- validate + validate = function(value) value = tonumber(value) return value ~= nil and value >= 1 end - ) + }) end diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index d7a79120..6ac51272 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -244,30 +244,36 @@ end command.add(nil, { ["project-search:find"] = function() set_command_view_text() - core.command_view:enter("Find Text In Project", function(text) - text = text:lower() - begin_search(text, function(line_text) - return line_text:lower():find(text, nil, true) - end) - end) + core.command_view:enter("Find Text In Project", { + submit = function(text) + text = text:lower() + begin_search(text, function(line_text) + return line_text:lower():find(text, nil, true) + end) + end + }) end, ["project-search:find-regex"] = function() - core.command_view:enter("Find Regex In Project", function(text) - local re = regex.compile(text, "i") - begin_search(text, function(line_text) - return regex.cmatch(re, line_text) - end) - end) + core.command_view:enter("Find Regex In Project", { + submit = function(text) + local re = regex.compile(text, "i") + begin_search(text, function(line_text) + return regex.cmatch(re, line_text) + end) + end + }) end, ["project-search:fuzzy-find"] = function() set_command_view_text() - core.command_view:enter("Fuzzy Find Text In Project", function(text) - begin_search(text, function(line_text) - return common.fuzzy_match(line_text, text) and 1 - end) - end) + core.command_view:enter("Fuzzy Find Text In Project", { + submit = function(text) + begin_search(text, function(line_text) + return common.fuzzy_match(line_text, text) and 1 + end) + end + }) end, }) diff --git a/data/plugins/tabularize.lua b/data/plugins/tabularize.lua index 7519a387..7d69a4a4 100644 --- a/data/plugins/tabularize.lua +++ b/data/plugins/tabularize.lua @@ -42,20 +42,22 @@ end command.add("core.docview", { ["tabularize:tabularize"] = function() - core.command_view:enter("Tabularize On Delimiter", function(delim) - if delim == "" then delim = " " end + core.command_view:enter("Tabularize On Delimiter", { + submit = function(delim) + if delim == "" then delim = " " end - local doc = core.active_view.doc - local line1, col1, line2, col2, swap = doc:get_selection(true) - line1, col1 = doc:position_offset(line1, col1, translate.start_of_line) - line2, col2 = doc:position_offset(line2, col2, translate.end_of_line) - doc:set_selection(line1, col1, line2, col2, swap) - - doc:replace(function(text) - local lines = gmatch_to_array(text, "[^\n]*\n?") - tabularize_lines(lines, delim) - return table.concat(lines) - end) - end) + local doc = core.active_view.doc + local line1, col1, line2, col2, swap = doc:get_selection(true) + line1, col1 = doc:position_offset(line1, col1, translate.start_of_line) + line2, col2 = doc:position_offset(line2, col2, translate.end_of_line) + doc:set_selection(line1, col1, line2, col2, swap) + + doc:replace(function(text) + local lines = gmatch_to_array(text, "[^\n]*\n?") + tabularize_lines(lines, delim) + return table.concat(lines) + end) + end + }) end, }) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 16f369b7..419878ee 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -690,48 +690,57 @@ command.add(function() return treeitem() ~= nil end, { local old_filename = treeitem().filename local old_abs_filename = treeitem().abs_filename core.command_view:set_text(old_filename) - core.command_view:enter("Rename", function(filename) - filename = core.normalize_to_project_dir(filename) - local abs_filename = core.project_absolute_path(filename) - local res, err = os.rename(old_abs_filename, abs_filename) - if res then -- successfully renamed - for _, doc in ipairs(core.docs) do - if doc.abs_filename and old_abs_filename == doc.abs_filename then - doc:set_filename(filename, abs_filename) -- make doc point to the new filename - doc:reset_syntax() - break -- only first needed + core.command_view:enter("Rename", { + submit = function(filename) + filename = core.normalize_to_project_dir(filename) + local abs_filename = core.project_absolute_path(filename) + local res, err = os.rename(old_abs_filename, abs_filename) + if res then -- successfully renamed + for _, doc in ipairs(core.docs) do + if doc.abs_filename and old_abs_filename == doc.abs_filename then + doc:set_filename(filename, abs_filename) -- make doc point to the new filename + doc:reset_syntax() + break -- only first needed + end end + core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) + else + core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err) end - core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) - else - core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err) - end - end, common.path_suggest) + end, + suggest = common.path_suggest + }) end, ["treeview:new-file"] = function() if not is_project_folder(treeitem().abs_filename) then core.command_view:set_text(treeitem().filename .. "/") end - core.command_view:enter("Filename", function(filename) - local doc_filename = core.project_dir .. PATHSEP .. filename - local file = io.open(doc_filename, "a+") - file:write("") - file:close() - core.root_view:open_doc(core.open_doc(doc_filename)) - core.log("Created %s", doc_filename) - end, common.path_suggest) + core.command_view:enter("Filename", { + submit = function(filename) + local doc_filename = core.project_dir .. PATHSEP .. filename + local file = io.open(doc_filename, "a+") + file:write("") + file:close() + core.root_view:open_doc(core.open_doc(doc_filename)) + core.log("Created %s", doc_filename) + end, + suggest = common.path_suggest + }) end, ["treeview:new-folder"] = function() if not is_project_folder(treeitem().abs_filename) then core.command_view:set_text(treeitem().filename .. "/") end - core.command_view:enter("Folder Name", function(filename) - local dir_path = core.project_dir .. PATHSEP .. filename - common.mkdirp(dir_path) - core.log("Created %s", dir_path) - end, common.path_suggest) + core.command_view:enter("Folder Name", { + submit = function(filename) + local dir_path = core.project_dir .. PATHSEP .. filename + common.mkdirp(dir_path) + core.log("Created %s", dir_path) + end, + suggest = common.path_suggest + }) end, ["treeview:open-in-system"] = function() From 4f0d45d6ab10cf86ff711fb2e2a66145e9021055 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 01:03:29 +0200 Subject: [PATCH 289/409] Don't check unnamed files in `autoreload` plugin --- data/plugins/autoreload.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 8a45a2ca..6ef08a76 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -67,7 +67,7 @@ local on_check = dirwatch.check function dirwatch:check(change_callback, ...) on_check(self, function(dir) for _, doc in ipairs(core.docs) do - if dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename then + if doc.abs_filename and (dir == common.dirname(doc.abs_filename) or dir == doc.abs_filename) then local info = system.get_file_info(doc.filename or "") if info and times[doc] ~= info.modified then if not doc:is_dirty() and not config.plugins.autoreload.always_show_nagview then From 7ac776bef66de7732d1b3075b5d21198a3f7e353 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 01:59:14 +0200 Subject: [PATCH 290/409] Fix UTF-8 matches in regex group `tokenizer` --- data/core/tokenizer.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 3d935cae..6f3515b4 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -173,18 +173,21 @@ function tokenizer.tokenize(incoming_syntax, text, state) or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } if p.regex and #res > 0 then -- set correct utf8 len for regex result res[2] = res[1] + string.ulen(text:sub(res[1], res[2])) - 1 - res[1] = next -- `regex.match` returns group results as a series of `begin, end` -- we only want `begin`s + if #res >= 3 then + res[3] = res[1] + string.ulen(text:sub(res[1], res[3])) - 1 + end for i=1,(#res-3) do local curr = i + 3 local from = i * 2 + 3 if from < #res then - res[curr] = string.uoffset(text, res[from]) + res[curr] = res[1] + string.ulen(text:sub(res[1], res[from])) - 1 else res[curr] = nil end end + res[1] = next end if res[1] and close and target[3] then local count = 0 From d8efb1ab53c7e6414d78230219f5ae6655b8b9b0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 02:03:42 +0200 Subject: [PATCH 291/409] Show error if language plugin pattern has mismatching number of groups The number of results from a pattern with groups must never be greater than the number of token types for that pattern. Also if a token type was undefined, it's now pushed as a `normal` one. --- data/core/tokenizer.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 6f3515b4..555d60b5 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -1,9 +1,12 @@ +local core = require "core" local syntax = require "core.syntax" local common = require "core.common" local tokenizer = {} +local bad_patterns = {} local function push_token(t, type, text) + type = type or "normal" local prev_type = t[#t-1] local prev_text = t[#t] if prev_type and (prev_type == type or prev_text:ufind("^%s*$")) then @@ -256,6 +259,15 @@ function tokenizer.tokenize(incoming_syntax, text, state) local matched = false for n, p in ipairs(current_syntax.patterns) do local find_results = { find_text(text, p, i, true, false) } + if #find_results - 1 > #p.type then + if not bad_patterns[current_syntax] then + bad_patterns[current_syntax] = { } + end + if not bad_patterns[current_syntax][n] then + bad_patterns[current_syntax][n] = true + core.error("Malformed pattern #%d in %s language plugin", n, current_syntax.name or "unnamed") + end + end if find_results[1] then -- matched pattern; make and add tokens push_tokens(res, current_syntax, p, text, find_results) From c92f6a7b7f26d478c7d7d9ef4c4e93ee8fb79b5a Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 02:26:18 +0200 Subject: [PATCH 292/409] Always show backtrace for `error` log entries --- data/core/init.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 74471006..884c77bf 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1056,7 +1056,7 @@ function core.get_views_referencing_doc(doc) end -local function log(level, show, fmt, ...) +function log(level, show, backtrace, fmt, ...) local text = string.format(fmt, ...) if show then local s = style.log[level] @@ -1069,7 +1069,8 @@ local function log(level, show, fmt, ...) level = level, text = text, time = os.time(), - at = at + at = at, + info = backtrace and debug.traceback(nil, 2):gsub("\t", "") } table.insert(core.log_items, item) if #core.log_items > config.max_log_items then @@ -1080,17 +1081,17 @@ end function core.log(...) - return log("INFO", true, ...) + return log("INFO", true, false, ...) end function core.log_quiet(...) - return log("INFO", false, ...) + return log("INFO", false, false, ...) end function core.error(...) - return log("ERROR", true, ...) + return log("ERROR", true, true, ...) end From db2d30caaff3864b20f497c7bc72823ba962b6da Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 02:26:42 +0200 Subject: [PATCH 293/409] Add `warn` log level --- data/core/init.lua | 3 +++ data/core/style.lua | 1 + 2 files changed, 4 insertions(+) diff --git a/data/core/init.lua b/data/core/init.lua index 884c77bf..332ac915 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1089,6 +1089,9 @@ function core.log_quiet(...) return log("INFO", false, false, ...) end +function core.warn(...) + return log("WARN", true, true, ...) +end function core.error(...) return log("ERROR", true, true, ...) diff --git a/data/core/style.lua b/data/core/style.lua index 70ad502c..7258eb16 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -76,6 +76,7 @@ style.syntax_fonts = {} style.log = { INFO = { icon = "i", color = style.text }, + WARN = { icon = "!", color = style.warn }, ERROR = { icon = "!", color = style.error } } From fae9af96bf1cd039acb89d8c28a2cecaa8aa20f7 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Tue, 31 May 2022 02:35:56 +0200 Subject: [PATCH 294/409] Expose function to create custom log entries --- data/core/init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index 332ac915..c0e2d477 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -1056,7 +1056,7 @@ function core.get_views_referencing_doc(doc) end -function log(level, show, backtrace, fmt, ...) +function core.custom_log(level, show, backtrace, fmt, ...) local text = string.format(fmt, ...) if show then local s = style.log[level] @@ -1081,20 +1081,20 @@ end function core.log(...) - return log("INFO", true, false, ...) + return core.custom_log("INFO", true, false, ...) end function core.log_quiet(...) - return log("INFO", false, false, ...) + return core.custom_log("INFO", false, false, ...) end function core.warn(...) - return log("WARN", true, true, ...) + return core.custom_log("WARN", true, true, ...) end function core.error(...) - return log("ERROR", true, true, ...) + return core.custom_log("ERROR", true, true, ...) end From 83f368cdbece81af6ccb02baf8c35489679db9db Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 31 May 2022 14:57:26 -0400 Subject: [PATCH 295/409] core: check for sdl initialization errors --- src/main.c | 9 ++++++++- src/renwindow.c | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index 0ce74862..2f44d7c9 100644 --- a/src/main.c +++ b/src/main.c @@ -91,7 +91,10 @@ int main(int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + fprintf(stderr, "Error initializing sdl: %s", SDL_GetError()); + exit(1); + } SDL_EnableScreenSaver(); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); atexit(SDL_Quit); @@ -122,6 +125,10 @@ int main(int argc, char **argv) { "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); init_window_icon(); + if (!window) { + fprintf(stderr, "Error creating lite-xl window: %s", SDL_GetError()); + exit(1); + } ren_init(window); lua_State *L; diff --git a/src/renwindow.c b/src/renwindow.c index c2aa0096..3f83f294 100644 --- a/src/renwindow.c +++ b/src/renwindow.c @@ -35,6 +35,10 @@ void renwin_init_surface(UNUSED RenWindow *ren) { int w, h; SDL_GL_GetDrawableSize(ren->window, &w, &h); ren->surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_BGRA32); + if (!ren->surface) { + fprintf(stderr, "Error creating surface: %s", SDL_GetError()); + exit(1); + } setup_renderer(ren, w, h); #endif } @@ -68,7 +72,12 @@ SDL_Surface *renwin_get_surface(RenWindow *ren) { #ifdef LITE_USE_SDL_RENDERER return ren->surface; #else - return SDL_GetWindowSurface(ren->window); + SDL_Surface *surface = SDL_GetWindowSurface(ren->window); + if (!surface) { + fprintf(stderr, "Error getting window surface: %s", SDL_GetError()); + exit(1); + } + return surface; #endif } From 214f36157a29d88feb11a8786e7606c0b4809992 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 31 May 2022 16:29:14 -0400 Subject: [PATCH 296/409] plugins: only check mod version --- data/core/init.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index e151b8be..5b75badb 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -876,13 +876,6 @@ local function get_plugin_details(filename) if mod_version then version_match = (mod_version == MOD_VERSION) end - -- The following pattern is used for backward compatibility only - -- Future versions will look only at the mod-version tag. - local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$') - if version then - -- we consider the version tag 2.0 equivalent to mod-version:2 - version_match = (version == '2.0' and MOD_VERSION == "2") - end end if not priority then priority = line:match('%-%-.*%f[%a]priority%s*:%s*(%d+)') From 2d8a15f3ab6ed9d471c89e5c69fc3ed438903b0e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 31 May 2022 16:34:14 -0400 Subject: [PATCH 297/409] plugins: dropped --lite-xl version tag --- data/plugins/autocomplete.lua | 2 +- data/plugins/autoreload.lua | 2 +- data/plugins/contextmenu.lua | 2 +- data/plugins/detectindent.lua | 2 +- data/plugins/drawwhitespace.lua | 2 +- data/plugins/language_c.lua | 2 +- data/plugins/language_cpp.lua | 2 +- data/plugins/language_css.lua | 2 +- data/plugins/language_html.lua | 2 +- data/plugins/language_js.lua | 2 +- data/plugins/language_lua.lua | 2 +- data/plugins/language_md.lua | 2 +- data/plugins/language_python.lua | 2 +- data/plugins/language_xml.lua | 2 +- data/plugins/lineguide.lua | 2 +- data/plugins/linewrapping.lua | 2 +- data/plugins/macro.lua | 2 +- data/plugins/projectsearch.lua | 2 +- data/plugins/quote.lua | 2 +- data/plugins/reflow.lua | 2 +- data/plugins/scale.lua | 2 +- data/plugins/tabularize.lua | 2 +- data/plugins/toolbarview.lua | 2 +- data/plugins/treeview.lua | 2 +- data/plugins/trimwhitespace.lua | 2 +- data/plugins/workspace.lua | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index e98c603b..56b19a45 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local config = require "core.config" diff --git a/data/plugins/autoreload.lua b/data/plugins/autoreload.lua index 6ef08a76..cde1c085 100644 --- a/data/plugins/autoreload.lua +++ b/data/plugins/autoreload.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local config = require "core.config" local style = require "core.style" diff --git a/data/plugins/contextmenu.lua b/data/plugins/contextmenu.lua index a5450056..03fb83dc 100644 --- a/data/plugins/contextmenu.lua +++ b/data/plugins/contextmenu.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 3501f472..e481fa72 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local common = require "core.common" diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index d5deb9e8..3efab0ab 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local style = require "core.style" local DocView = require "core.docview" diff --git a/data/plugins/language_c.lua b/data/plugins/language_c.lua index 146a952d..f3154ed3 100644 --- a/data/plugins/language_c.lua +++ b/data/plugins/language_c.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_cpp.lua b/data/plugins/language_cpp.lua index e2857d9a..c10f593a 100644 --- a/data/plugins/language_cpp.lua +++ b/data/plugins/language_cpp.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_css.lua b/data/plugins/language_css.lua index 0dff28ac..722b9adc 100644 --- a/data/plugins/language_css.lua +++ b/data/plugins/language_css.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_html.lua b/data/plugins/language_html.lua index 2019f0dc..0e714f6c 100644 --- a/data/plugins/language_html.lua +++ b/data/plugins/language_html.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_js.lua b/data/plugins/language_js.lua index 0e350e7a..40aba817 100644 --- a/data/plugins/language_js.lua +++ b/data/plugins/language_js.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_lua.lua b/data/plugins/language_lua.lua index 07545c1f..01403ad3 100644 --- a/data/plugins/language_lua.lua +++ b/data/plugins/language_lua.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index c68acddc..c469855c 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" local style = require "core.style" local core = require "core" diff --git a/data/plugins/language_python.lua b/data/plugins/language_python.lua index dbb6f77c..9617c6d1 100644 --- a/data/plugins/language_python.lua +++ b/data/plugins/language_python.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/language_xml.lua b/data/plugins/language_xml.lua index bf75a79a..125c260e 100644 --- a/data/plugins/language_xml.lua +++ b/data/plugins/language_xml.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local syntax = require "core.syntax" syntax.add { diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 61f817f1..7dd645a1 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local config = require "core.config" local style = require "core.style" local DocView = require "core.docview" diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index f42e45b7..885474fb 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local DocView = require "core.docview" diff --git a/data/plugins/macro.lua b/data/plugins/macro.lua index bd51c17a..9f3b8482 100644 --- a/data/plugins/macro.lua +++ b/data/plugins/macro.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index d7a79120..648a2bad 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local keymap = require "core.keymap" diff --git a/data/plugins/quote.lua b/data/plugins/quote.lua index d651e463..2f5a9b2b 100644 --- a/data/plugins/quote.lua +++ b/data/plugins/quote.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local keymap = require "core.keymap" diff --git a/data/plugins/reflow.lua b/data/plugins/reflow.lua index 58deabe3..be6e2562 100644 --- a/data/plugins/reflow.lua +++ b/data/plugins/reflow.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local config = require "core.config" local command = require "core.command" diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index a09fde77..cb331f7a 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/tabularize.lua b/data/plugins/tabularize.lua index 7519a387..ac5766f9 100644 --- a/data/plugins/tabularize.lua +++ b/data/plugins/tabularize.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local translate = require "core.doc.translate" diff --git a/data/plugins/toolbarview.lua b/data/plugins/toolbarview.lua index 241fc430..37950bee 100644 --- a/data/plugins/toolbarview.lua +++ b/data/plugins/toolbarview.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index a812c928..f614e0c4 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local command = require "core.command" diff --git a/data/plugins/trimwhitespace.lua b/data/plugins/trimwhitespace.lua index 15d587f5..93db502a 100644 --- a/data/plugins/trimwhitespace.lua +++ b/data/plugins/trimwhitespace.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local command = require "core.command" local Doc = require "core.doc" diff --git a/data/plugins/workspace.lua b/data/plugins/workspace.lua index 0dc40cb2..788a753a 100644 --- a/data/plugins/workspace.lua +++ b/data/plugins/workspace.lua @@ -1,4 +1,4 @@ --- mod-version:3 -- lite-xl 2.1 +-- mod-version:3 local core = require "core" local common = require "core.common" local DocView = require "core.docview" From 9a428648a9bf3506713108aec95b9631f1cf9407 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 03:48:47 +0200 Subject: [PATCH 298/409] Add `common.is_absolute_path` --- data/core/common.lua | 5 +++++ data/core/init.lua | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/data/core/common.lua b/data/core/common.lua index 1aa2b86e..7ed87456 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -385,6 +385,11 @@ function common.normalize_path(filename) end +function common.is_absolute_path(path) + return path:sub(1, 1) == PATHSEP or path:match("^(%a):\\") +end + + function common.path_belongs_to(filename, path) return string.find(filename, path .. PATHSEP, 1, true) == 1 end diff --git a/data/core/init.lua b/data/core/init.lua index 59b293c5..4ed83716 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -605,7 +605,7 @@ end -- This function should get only filenames normalized using -- common.normalize_path function. function core.project_absolute_path(filename) - if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then + if common.is_absolute_path(filename) then return common.normalize_path(filename) elseif not core.project_dir then local cwd = system.absolute_path(".") From 295e6b7e5ac19348fa4052beec376bf2573122f1 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 03:51:29 +0200 Subject: [PATCH 299/409] Allow `common.path_suggest` to specify a root directory This will make relative paths start from `root`. --- data/core/common.lua | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index 7ed87456..c123bb4b 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -140,9 +140,25 @@ function common.fuzzy_match_with_recents(haystack, recents, needle) end -function common.path_suggest(text) +function common.path_suggest(text, root) + if root and root:sub(-1) ~= PATHSEP then + root = root .. PATHSEP + end local path, name = text:match("^(.-)([^/\\]*)$") - local files = system.list_dir(path == "" and "." or path) or {} + -- ignore root if path is absolute + local is_absolute = common.is_absolute_path(text) + if not is_absolute then + if path == "" then + path = root or "." + else + path = (root or "") .. path + end + end + + local files = system.list_dir(path) or {} + if path:sub(-1) ~= PATHSEP then + path = path .. PATHSEP + end local res = {} for _, file in ipairs(files) do file = path .. file @@ -151,6 +167,13 @@ function common.path_suggest(text) if info.type == "dir" then file = file .. PATHSEP end + if root then + -- remove root part from file path + local s, e = file:find(root, nil, true) + if s == 1 then + file = file:sub(e + 1) + end + end if file:lower():find(text:lower(), nil, true) == 1 then table.insert(res, file) end From e94c996a26f76aba6b1200412071f291e06ea14a Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 03:55:55 +0200 Subject: [PATCH 300/409] Add `TreeView` helper functions to get previous/next item --- data/plugins/treeview.lua | 66 ++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index ecb3a76d..a0dc4a4f 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -389,6 +389,7 @@ function TreeView:draw() end end + function TreeView:get_parent(item) local parent_path = common.dirname(item.abs_filename) if not parent_path then return end @@ -399,6 +400,40 @@ function TreeView:get_parent(item) end end + +function TreeView:get_item(item, where) + local last_item, last_x, last_y, last_w, last_h + local stop = false + + for it, x, y, w, h in self:each_item() do + if not item and where >= 0 then + return it, x, y, w, h + end + if item == it then + if where < 0 and last_item then + break + elseif where == 0 or (where < 0 and not last_item) then + return it, x, y, w, h + end + stop = true + elseif stop then + item = it + return it, x, y, w, h + end + last_item, last_x, last_y, last_w, last_h = it, x, y, w, h + end + return last_item, last_x, last_y, last_w, last_h +end + +function TreeView:get_next(item) + return self:get_item(item, 1) +end + +function TreeView:get_previous(item) + return self:get_item(item, -1) +end + + function TreeView:toggle_expand(toggle) local item = self.selected_item @@ -548,38 +583,13 @@ command.add(nil, { command.add(TreeView, { ["treeview:next"] = function() - local item = view.selected_item - local item_y - local stop = false - for it, _, y in view:each_item() do - if item == it then - stop = true - elseif stop then - item = it - item_y = y - break - end - end - + local item, _, item_y = view:get_next(view.selected_item) view:set_selection(item, item_y) end, ["treeview:previous"] = function() - local last_item - local last_item_y - for it, _, y in view:each_item() do - if it == view.selected_item then - if not last_item then - last_item = it - last_item_y = y - end - break - end - last_item = it - last_item_y = y - end - - view:set_selection(last_item, last_item_y) + local item, _, item_y = view:get_previous(view.selected_item) + view:set_selection(item, item_y) end, ["treeview:open"] = function() From 730ea0c91bf42b71f7be2ce641af521ca226ba81 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 03:57:14 +0200 Subject: [PATCH 301/409] Make `TreeView` more multi-project-dir aware --- data/plugins/treeview.lua | 93 ++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index a0dc4a4f..0f818e0c 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -404,7 +404,7 @@ end function TreeView:get_item(item, where) local last_item, last_x, last_y, last_w, last_h local stop = false - + for it, x, y, w, h in self:each_item() do if not item and where >= 0 then return it, x, y, w, h @@ -518,6 +518,15 @@ function core.on_quit_project() end local function is_project_folder(path) + for _,dir in pairs(core.project_directories) do + if dir.name == path then + return true + end + end + return false +end + +local function is_primary_project_folder(path) return core.project_dir == path end @@ -547,6 +556,17 @@ menu:register( } ) +menu:register( + function() + return view.hovered_item + and not is_primary_project_folder(view.hovered_item.abs_filename) + and is_project_folder(view.hovered_item.abs_filename) + end, + { + { text = "Remove directory", command = "treeview:remove-project-directory" }, + } +) + local previous_view = nil -- Register the TreeView commands and keymap @@ -637,7 +657,17 @@ command.add(TreeView, { end, ["treeview:expand"] = function() - view:toggle_expand(true) + local item = view.selected_item + if not item or item.type ~= "dir" then return end + + if item.expanded then + local next_item, _, next_y = view:get_next(item) + if next_item.depth > item.depth then + view:set_selection(next_item, next_y) + end + else + view:toggle_expand(true) + end end, }) @@ -656,8 +686,13 @@ command.add( ) end, { ["treeview:delete"] = function() - local filename = treeitem().abs_filename - local relfilename = treeitem().filename + local item = treeitem() + local filename = item.abs_filename + local relfilename = item.filename + if item.dir_name ~= core.project_dir then + -- add secondary project dirs names to the file path to show + relfilename = common.basename(item.dir_name) .. PATHSEP .. relfilename + end local file_info = system.get_file_info(filename) local file_type = file_info.type == "dir" and "Directory" or "File" -- Ask before deleting @@ -697,13 +732,16 @@ command.add( command.add(function() return treeitem() ~= nil end, { ["treeview:rename"] = function() - local old_filename = treeitem().filename - local old_abs_filename = treeitem().abs_filename + local item = treeitem() + local old_filename = item.filename + local old_abs_filename = item.abs_filename core.command_view:set_text(old_filename) core.command_view:enter("Rename", { submit = function(filename) - filename = core.normalize_to_project_dir(filename) - local abs_filename = core.project_absolute_path(filename) + local abs_filename = filename + if not common.is_absolute_path(filename) then + abs_filename = item.dir_name .. PATHSEP .. filename + end local res, err = os.rename(old_abs_filename, abs_filename) if res then -- successfully renamed for _, doc in ipairs(core.docs) do @@ -718,38 +756,47 @@ command.add(function() return treeitem() ~= nil end, { core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err) end end, - suggest = common.path_suggest + suggest = function(text) + return common.path_suggest(text, item.dir_name) + end }) end, ["treeview:new-file"] = function() - if not is_project_folder(treeitem().abs_filename) then - core.command_view:set_text(treeitem().filename .. "/") + local item = treeitem() + if not is_project_folder(item.abs_filename) then + core.command_view:set_text(item.filename .. PATHSEP) end core.command_view:enter("Filename", { submit = function(filename) - local doc_filename = core.project_dir .. PATHSEP .. filename + local doc_filename = item.dir_name .. PATHSEP .. filename + core.log(doc_filename) local file = io.open(doc_filename, "a+") file:write("") file:close() core.root_view:open_doc(core.open_doc(doc_filename)) core.log("Created %s", doc_filename) end, - suggest = common.path_suggest + suggest = function(text) + return common.path_suggest(text, item.dir_name) + end }) end, ["treeview:new-folder"] = function() - if not is_project_folder(treeitem().abs_filename) then - core.command_view:set_text(treeitem().filename .. "/") + local item = treeitem() + if not is_project_folder(item.abs_filename) then + core.command_view:set_text(item.filename .. "/") end core.command_view:enter("Folder Name", { submit = function(filename) - local dir_path = core.project_dir .. PATHSEP .. filename + local dir_path = item.dir_name .. PATHSEP .. filename common.mkdirp(dir_path) core.log("Created %s", dir_path) end, - suggest = common.path_suggest + suggest = function(text) + return common.path_suggest(text, item.dir_name) + end }) end, @@ -766,6 +813,18 @@ command.add(function() return treeitem() ~= nil end, { end }) +command.add(function() + local item = treeitem() + return item + and not is_primary_project_folder(item.abs_filename) + and is_project_folder(item.abs_filename) + end, { + ["treeview:remove-project-directory"] = function() + core.remove_project_directory(treeitem().dir_name) + end, +}) + + keymap.add { ["ctrl+\\"] = "treeview:toggle", ["up"] = "treeview:previous", From d390eb248e058c243d11ed4a4cd11df5e1f1d21d Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 18:19:33 +0200 Subject: [PATCH 302/409] Fix tooltip not getting removed after `find-replace:replace` --- data/core/commands/findreplace.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index ccfbf118..c999103f 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -111,12 +111,14 @@ local function replace(kind, default, fn) end) core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new) end, - suggest = function() return core.previous_replace end, function() + suggest = function() return core.previous_replace end, + cancel = function() core.status_view:remove_tooltip() end }) end, - suggest = function() return core.previous_find end, function() + suggest = function() return core.previous_find end, + cancel = function() core.status_view:remove_tooltip() end }) From 67066fc93a8cd6035ca0183ce7d7962090958db8 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 1 Jun 2022 18:36:00 -0400 Subject: [PATCH 303/409] Added in defaults for dirwatch. --- data/core/dirwatch.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index fcbd019d..5553047d 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -110,8 +110,8 @@ function dirwatch:check(change_callback, scan_time, wait_time) self.scanned[directory] = new_modified end end - if system.get_time() - start_time > scan_time then - coroutine.yield(wait_time) + if system.get_time() - start_time > (scan_time or 0.01) then + coroutine.yield(wait_time or 0.01) start_time = system.get_time() end end From 4157dd867c9ef153a2294c80a6a832a027d833b1 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 1 Jun 2022 23:07:31 -0400 Subject: [PATCH 304/409] plugin drawwhitespace: added config spec --- data/plugins/drawwhitespace.lua | 71 ++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 720b20ed..efa2be3a 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -6,6 +6,7 @@ local common = require "core.common" local config = require "core.config" config.plugins.drawwhitespace = common.merge({ + enabled = true, show_leading = true, show_trailing = true, show_middle = true, @@ -31,6 +32,67 @@ config.plugins.drawwhitespace = common.merge({ sub = "»", }, }, + + config_spec = { + name = "Draw Whitespace", + { + label = "Enabled", + description = "Disable or enable the drawing of white spaces.", + path = "enabled", + type = "toggle", + default = true + }, + { + label = "Show Leading", + description = "Draw whitespaces starting at the beginning of a line.", + path = "show_leading", + type = "toggle", + default = true, + }, + { + label = "Show Middle", + description = "Draw whitespaces on the middle of a line.", + path = "show_middle", + type = "toggle", + default = true, + }, + { + label = "Show Trailing", + description = "Draw whitespaces on the end of a line.", + path = "show_trailing", + type = "toggle", + default = true, + }, + { + label = "Show Trailing as Error", + description = "Uses an error square to spot them easily, requires 'Show Trailing' enabled.", + path = "show_trailing_error", + type = "toggle", + default = false, + on_apply = function(enabled) + local found = nil + local substitutions = config.plugins.drawwhitespace.substitutions + for i, sub in ipairs(substitutions) do + if sub.trailing_error then + found = i + end + end + if found == nil and enabled then + table.insert(substitutions, { + char = " ", + sub = "█", + show_leading = false, + show_middle = false, + show_trailing = true, + trailing_color = style.error, + trailing_error = true + }) + elseif found ~= nil and not enabled then + table.remove(substitutions, found) + end + end + } + } }, config.plugins.drawwhitespace) local function get_option(substitution, option) @@ -42,7 +104,14 @@ end local draw_line_text = DocView.draw_line_text function DocView:draw_line_text(idx, x, y) - if getmetatable(self) ~= DocView then return draw_line_text(self, idx, x, y) end + if + not config.plugins.drawwhitespace.enabled + or + getmetatable(self) ~= DocView + then + return draw_line_text(self, idx, x, y) + end + local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) local ty = y + self:get_line_text_y_offset() local tx From 8ce1ecb8973ae067adc4f3d35bb0d9aff0669d97 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 2 Jun 2022 06:42:19 +0200 Subject: [PATCH 305/409] Fix `CommandView:enter` deprecation log --- data/core/commandview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 101cbd98..401164d2 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -152,7 +152,7 @@ function CommandView:enter(text, ...) local options = select(1, ...) if type(options) ~= "table" then - core.info("Warning: deprecated CommandView:enter usage") + core.log("Warning: deprecated CommandView:enter usage") local submit, suggest, cancel, validate = ... options = { submit = submit, From 6c89a3e5752d71e52bdeed2619a8990628f50784 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 18:34:11 +0200 Subject: [PATCH 306/409] Add `show_suggestions` to `CommandView` options --- data/core/commands/findreplace.lua | 6 +++--- data/core/commandview.lua | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index c999103f..cd3d4b44 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -61,8 +61,8 @@ local function find(label, search_fn) core.command_view:set_text(text, true) core.status_view:show_tooltip(get_find_tooltip()) - core.command_view:set_hidden_suggestions() core.command_view:enter(label, { + show_suggestions = false, submit = function(text, item) insert_unique(core.previous_find, text) core.status_view:remove_tooltip() @@ -94,15 +94,15 @@ local function replace(kind, default, fn) core.command_view:set_text(default, true) core.status_view:show_tooltip(get_find_tooltip()) - core.command_view:set_hidden_suggestions() core.command_view:enter("Find To Replace " .. kind, { + show_suggestions = false, submit = function(old) insert_unique(core.previous_find, old) core.command_view:set_text(old, true) local s = string.format("Replace %s %q With", kind, old) - core.command_view:set_hidden_suggestions() core.command_view:enter(s, { + show_suggestions = false, submit = function(new) core.status_view:remove_tooltip() insert_unique(core.previous_replace, new) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 401164d2..abba458e 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -26,6 +26,7 @@ local default_state = { suggest = noop, cancel = noop, validate = function() return true end, + show_suggestions = true, typeahead = true, wrap = true, } @@ -36,7 +37,6 @@ function CommandView:new() self.suggestion_idx = 1 self.suggestions = {} self.suggestions_height = 0 - self.show_suggestions = true self.last_change_id = 0 self.last_text = "" self.gutter_width = 0 @@ -50,7 +50,8 @@ end function CommandView:set_hidden_suggestions() - self.show_suggestions = false + core.warn("Using deprecated function CommandView:set_hidden_suggestions") + self.state.show_suggestions = false end @@ -101,7 +102,7 @@ function CommandView:move_suggestion_idx(dir) end end - if self.show_suggestions then + if self.state.show_suggestions then local n = self.suggestion_idx + dir self.suggestion_idx = overflow_suggestion_idx(n, #self.suggestions) self:complete() @@ -162,6 +163,12 @@ function CommandView:enter(text, ...) } end + -- Support deprecated CommandView:set_hidden_suggestions + -- Remove this when set_hidden_suggestions is not supported anymore + if options.show_suggestions == nil then + options.show_suggestions = self.state.show_suggestions + end + self.state = common.merge(default_state, options) core.set_active_view(self) @@ -180,7 +187,6 @@ function CommandView:exit(submitted, inexplicit) self.doc:reset() self.suggestions = {} if not submitted then cancel(not inexplicit) end - self.show_suggestions = true self.save_suggestion = nil self.last_text = "" end @@ -246,7 +252,7 @@ function CommandView:update() -- update suggestions box height local lh = self:get_suggestion_line_height() - local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0 + local dest = self.state.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0 self:move_towards("suggestions_height", dest, nil, "commandview") -- update suggestion cursor offset @@ -316,7 +322,7 @@ end function CommandView:draw() CommandView.super.draw(self) - if self.show_suggestions then + if self.state.show_suggestions then core.root_view:defer_draw(draw_suggestions_box, self) end end From ec58b1f0bdaafd8791743c7f04e99b0cf0733976 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 1 Jun 2022 19:34:46 +0200 Subject: [PATCH 307/409] Add `text` and `select_text` to `CommandView` options --- data/core/commands/core.lua | 13 +++++++++---- data/core/commands/doc.lua | 8 +++++--- data/core/commands/findreplace.lua | 10 ++++++---- data/core/commandview.lua | 23 ++++++++++++++++++++--- data/plugins/projectsearch.lua | 13 ++++++------- data/plugins/treeview.lua | 10 +++++++--- 6 files changed, 53 insertions(+), 24 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 40be12a0..cdf8d421 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -122,15 +122,16 @@ command.add(nil, { ["core:open-file"] = function() local view = core.active_view + local text if view.doc and view.doc.abs_filename then local dirname, filename = view.doc.abs_filename:match("(.*)[/\\](.+)$") if dirname then dirname = core.normalize_to_project_dir(dirname) - local text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP - core.command_view:set_text(text) + text = dirname == core.project_dir and "" or common.home_encode(dirname) .. PATHSEP end end core.command_view:enter("Open File", { + text = text, submit = function(text) local filename = system.absolute_path(common.home_expand(text)) core.root_view:open_doc(core.open_doc(filename)) @@ -182,10 +183,12 @@ command.add(nil, { ["core:change-project-folder"] = function() local dirname = common.dirname(core.project_dir) + local text if dirname then - core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) + text = common.home_encode(dirname) .. PATHSEP end core.command_view:enter("Change Project Folder", { + text = text, submit = function(text) local path = common.home_expand(text) local abs_path = check_directory_path(path) @@ -204,10 +207,12 @@ command.add(nil, { ["core:open-project-folder"] = function() local dirname = common.dirname(core.project_dir) + local text if dirname then - core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) + text = common.home_encode(dirname) .. PATHSEP end core.command_view:enter("Open Project", { + text = text, submit = function(text) local path = common.home_expand(text) local abs_path = check_directory_path(path) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index f572ec26..1b39a9d9 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -448,13 +448,15 @@ local commands = { ["doc:save-as"] = function() local last_doc = core.last_active_view and core.last_active_view.doc + local text if doc().filename then - core.command_view:set_text(doc().filename) + text = doc().filename elseif last_doc and last_doc.filename then local dirname, filename = core.last_active_view.doc.abs_filename:match("(.*)[/\\](.+)$") - core.command_view:set_text(core.normalize_to_project_dir(dirname) .. PATHSEP) + text = core.normalize_to_project_dir(dirname) .. PATHSEP end core.command_view:enter("Save As", { + text = text, submit = function(filename) save(common.home_expand(filename)) end, @@ -482,8 +484,8 @@ local commands = { core.error("Cannot rename unsaved doc") return end - core.command_view:set_text(old_filename) core.command_view:enter("Rename", { + text = old_filename, submit = function(filename) save(common.home_expand(filename)) core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index cd3d4b44..584d4f67 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -58,10 +58,11 @@ local function find(label, search_fn) local text = last_view.doc:get_text(table.unpack(last_sel)) found_expression = false - core.command_view:set_text(text, true) core.status_view:show_tooltip(get_find_tooltip()) core.command_view:enter(label, { + text = text, + select_text = true, show_suggestions = false, submit = function(text, item) insert_unique(core.previous_find, text) @@ -91,17 +92,18 @@ end local function replace(kind, default, fn) - core.command_view:set_text(default, true) - core.status_view:show_tooltip(get_find_tooltip()) core.command_view:enter("Find To Replace " .. kind, { + text = default, + select_text = true, show_suggestions = false, submit = function(old) insert_unique(core.previous_find, old) - core.command_view:set_text(old, true) local s = string.format("Replace %s %q With", kind, old) core.command_view:enter(s, { + text = old, + select_text = true, show_suggestions = false, submit = function(new) core.status_view:remove_tooltip() diff --git a/data/core/commandview.lua b/data/core/commandview.lua index abba458e..8017fcfc 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -26,6 +26,8 @@ local default_state = { suggest = noop, cancel = noop, validate = function() return true end, + text = "", + select_text = false, show_suggestions = true, typeahead = true, wrap = true, @@ -146,14 +148,14 @@ function CommandView:submit() end -function CommandView:enter(text, ...) +function CommandView:enter(label, ...) if self.state ~= default_state then return end local options = select(1, ...) if type(options) ~= "table" then - core.log("Warning: deprecated CommandView:enter usage") + core.warn("Using CommandView:enter in a deprecated way") local submit, suggest, cancel, validate = ... options = { submit = submit, @@ -171,10 +173,25 @@ function CommandView:enter(text, ...) self.state = common.merge(default_state, options) + -- We need to keep the text entered with CommandView:set_text to + -- maintain compatibility with deprecated usage, but still allow + -- overwriting with options.text + local old_text = self:get_text() + if old_text ~= "" then + core.warn("Using deprecated function CommandView:set_text") + end + if options.text or options.select_text then + local text = options.text or old_text + self:set_text(text, self.state.select_text) + end + -- Replace with a simple + -- self:set_text(self.state.text, self.state.select_text) + -- once old usage is removed + core.set_active_view(self) self:update_suggestions() self.gutter_text_brightness = 100 - self.label = text .. ": " + self.label = label .. ": " end diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index c43e3147..2145199a 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -229,22 +229,20 @@ local function begin_search(text, fn) end -local function set_command_view_text() +local function get_selected_text() local view = core.active_view local doc = (view and view.doc) and view.doc or nil if doc then - core.command_view:set_text( - doc:get_text(table.unpack({ doc:get_selection() })), - true - ) + return doc:get_text(table.unpack({ doc:get_selection() })) end end command.add(nil, { ["project-search:find"] = function() - set_command_view_text() core.command_view:enter("Find Text In Project", { + text = get_selected_text(), + select_text = true, submit = function(text) text = text:lower() begin_search(text, function(line_text) @@ -266,8 +264,9 @@ command.add(nil, { end, ["project-search:fuzzy-find"] = function() - set_command_view_text() core.command_view:enter("Fuzzy Find Text In Project", { + text = get_selected_text(), + select_text = true, submit = function(text) begin_search(text, function(line_text) return common.fuzzy_match(line_text, text) and 1 diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index ecb3a76d..7d53e790 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -689,8 +689,8 @@ command.add(function() return treeitem() ~= nil end, { ["treeview:rename"] = function() local old_filename = treeitem().filename local old_abs_filename = treeitem().abs_filename - core.command_view:set_text(old_filename) core.command_view:enter("Rename", { + text = old_filename, submit = function(filename) filename = core.normalize_to_project_dir(filename) local abs_filename = core.project_absolute_path(filename) @@ -713,10 +713,12 @@ command.add(function() return treeitem() ~= nil end, { end, ["treeview:new-file"] = function() + local text if not is_project_folder(treeitem().abs_filename) then - core.command_view:set_text(treeitem().filename .. "/") + text = treeitem().filename .. "/" end core.command_view:enter("Filename", { + text = text, submit = function(filename) local doc_filename = core.project_dir .. PATHSEP .. filename local file = io.open(doc_filename, "a+") @@ -730,10 +732,12 @@ command.add(function() return treeitem() ~= nil end, { end, ["treeview:new-folder"] = function() + local text if not is_project_folder(treeitem().abs_filename) then - core.command_view:set_text(treeitem().filename .. "/") + text = treeitem().filename .. "/" end core.command_view:enter("Folder Name", { + text = text, submit = function(filename) local dir_path = core.project_dir .. PATHSEP .. filename common.mkdirp(dir_path) From ca46d8e261476d18b99fd82759c0a6a10e25a0f0 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Thu, 2 Jun 2022 19:20:54 -0400 Subject: [PATCH 308/409] style: move default colors to its own style file --- data/colors/default.lua | 46 +++++++++++++++++++++++++++++++++++++++++ data/core/init.lua | 2 +- data/core/style.lua | 43 +------------------------------------- 3 files changed, 48 insertions(+), 43 deletions(-) create mode 100644 data/colors/default.lua diff --git a/data/colors/default.lua b/data/colors/default.lua new file mode 100644 index 00000000..8f60deee --- /dev/null +++ b/data/colors/default.lua @@ -0,0 +1,46 @@ +local style = require "core.style" +local common = require "core.common" + +style.background = { common.color "#2e2e32" } -- Docview +style.background2 = { common.color "#252529" } -- Treeview +style.background3 = { common.color "#252529" } -- Command view +style.text = { common.color "#97979c" } +style.caret = { common.color "#93DDFA" } +style.accent = { common.color "#e1e1e6" } +-- style.dim - text color for nonactive tabs, tabs divider, prefix in log and +-- search result, hotkeys for context menu and command view +style.dim = { common.color "#525257" } +style.divider = { common.color "#202024" } -- Line between nodes +style.selection = { common.color "#48484f" } +style.line_number = { common.color "#525259" } +style.line_number2 = { common.color "#83838f" } -- With cursor +style.line_highlight = { common.color "#343438" } +style.scrollbar = { common.color "#414146" } +style.scrollbar2 = { common.color "#4b4b52" } -- Hovered +style.scrollbar_track = { common.color "#252529" } +style.nagbar = { common.color "#FF0000" } +style.nagbar_text = { common.color "#FFFFFF" } +style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" } +style.drag_overlay = { common.color "rgba(255,255,255,0.1)" } +style.drag_overlay_tab = { common.color "#93DDFA" } +style.good = { common.color "#72b886" } +style.warn = { common.color "#FFA94D" } +style.error = { common.color "#FF3333" } +style.modified = { common.color "#1c7c9c" } + +style.syntax["normal"] = { common.color "#e1e1e6" } +style.syntax["symbol"] = { common.color "#e1e1e6" } +style.syntax["comment"] = { common.color "#676b6f" } +style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case +style.syntax["keyword2"] = { common.color "#F77483" } -- self int float +style.syntax["number"] = { common.color "#FFA94D" } +style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil +style.syntax["string"] = { common.color "#f7c95c" } +style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < > +style.syntax["function"] = { common.color "#93DDFA" } + +style.log["INFO"] = { icon = "i", color = style.text } +style.log["WARN"] = { icon = "!", color = style.warn } +style.log["ERROR"] = { icon = "!", color = style.error } + +return style diff --git a/data/core/init.lua b/data/core/init.lua index 59b293c5..afde6fea 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -2,7 +2,7 @@ require "core.strict" require "core.regex" local common = require "core.common" local config = require "core.config" -local style = require "core.style" +local style = require "colors.default" local command local keymap local dirwatch diff --git a/data/core/style.lua b/data/core/style.lua index 7258eb16..3e340c57 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -28,44 +28,7 @@ style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, style.icon_big_font = style.icon_font:copy(23 * SCALE) style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE) -style.background = { common.color "#2e2e32" } -- Docview -style.background2 = { common.color "#252529" } -- Treeview -style.background3 = { common.color "#252529" } -- Command view -style.text = { common.color "#97979c" } -style.caret = { common.color "#93DDFA" } -style.accent = { common.color "#e1e1e6" } --- style.dim - text color for nonactive tabs, tabs divider, prefix in log and --- search result, hotkeys for context menu and command view -style.dim = { common.color "#525257" } -style.divider = { common.color "#202024" } -- Line between nodes -style.selection = { common.color "#48484f" } -style.line_number = { common.color "#525259" } -style.line_number2 = { common.color "#83838f" } -- With cursor -style.line_highlight = { common.color "#343438" } -style.scrollbar = { common.color "#414146" } -style.scrollbar2 = { common.color "#4b4b52" } -- Hovered -style.scrollbar_track = { common.color "#252529" } -style.nagbar = { common.color "#FF0000" } -style.nagbar_text = { common.color "#FFFFFF" } -style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" } -style.drag_overlay = { common.color "rgba(255,255,255,0.1)" } -style.drag_overlay_tab = { common.color "#93DDFA" } -style.good = { common.color "#72b886" } -style.warn = { common.color "#FFA94D" } -style.error = { common.color "#FF3333" } -style.modified = { common.color "#1c7c9c" } - style.syntax = {} -style.syntax["normal"] = { common.color "#e1e1e6" } -style.syntax["symbol"] = { common.color "#e1e1e6" } -style.syntax["comment"] = { common.color "#676b6f" } -style.syntax["keyword"] = { common.color "#E58AC9" } -- local function end if case -style.syntax["keyword2"] = { common.color "#F77483" } -- self int float -style.syntax["number"] = { common.color "#FFA94D" } -style.syntax["literal"] = { common.color "#FFA94D" } -- true false nil -style.syntax["string"] = { common.color "#f7c95c" } -style.syntax["operator"] = { common.color "#93DDFA" } -- = + - / < > -style.syntax["function"] = { common.color "#93DDFA" } -- This can be used to override fonts per syntax group. -- The syntax highlighter will take existing values from this table and @@ -74,10 +37,6 @@ style.syntax["function"] = { common.color "#93DDFA" } style.syntax_fonts = {} -- style.syntax_fonts["comment"] = renderer.font.load(path_to_font, size_of_font, rendering_options) -style.log = { - INFO = { icon = "i", color = style.text }, - WARN = { icon = "!", color = style.warn }, - ERROR = { icon = "!", color = style.error } -} +style.log = {} return style From 5da7467a5c5b7cd221bd4532d97a2da7dd263fd4 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 3 Jun 2022 03:16:18 -0400 Subject: [PATCH 309/409] plugin drawwhitespace: return line height on draw_line_text --- data/plugins/drawwhitespace.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index efa2be3a..6186eac6 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -176,5 +176,5 @@ function DocView:draw_line_text(idx, x, y) end end - draw_line_text(self, idx, x, y) + return draw_line_text(self, idx, x, y) end From de63574b534cf838be090702b5c78feca9f031d2 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 3 Jun 2022 04:13:54 -0400 Subject: [PATCH 310/409] plugin linewrapping: added priority Since the linewrapping plugin modifies some of the DocView line calculation and positioning functions we need to make sure of loading it before other plugins. This way we make sure that plugins that also overwrite and depend on DocView functionality aren't using the original methods without the linewrapping changes, which leads to wrong line and column calculations. --- data/plugins/linewrapping.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/linewrapping.lua b/data/plugins/linewrapping.lua index 885474fb..66b303ee 100644 --- a/data/plugins/linewrapping.lua +++ b/data/plugins/linewrapping.lua @@ -1,4 +1,4 @@ --- mod-version:3 +-- mod-version:3 --priority:10 local core = require "core" local common = require "core.common" local DocView = require "core.docview" From 0b96be7af2d763cb324c392fbd6cfca5f1f3397b Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 5 Jun 2022 04:10:51 +0200 Subject: [PATCH 311/409] Make `common.merge` work with invalid arguments This is needed because users could try to enable plugins with `config.plugins.plugin_name = true`. Before, this would result in `common.merge` throwing an error; now it just returns a copy of the "base" table. --- data/core/common.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data/core/common.lua b/data/core/common.lua index 1aa2b86e..6b4d9782 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -18,9 +18,16 @@ end function common.merge(a, b) + a = type(a) == "table" and a or {} local t = {} - for k, v in pairs(a) do t[k] = v end - if b then for k, v in pairs(b) do t[k] = v end end + for k, v in pairs(a) do + t[k] = v + end + if b and type(b) == "table" then + for k, v in pairs(b) do + t[k] = v + end + end return t end From 237f0c91cb4cf50ccc3c31439ead24aeda7ae510 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 5 Jun 2022 04:19:31 +0200 Subject: [PATCH 312/409] Open `LogView` on user/project module reload error --- data/core/init.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/data/core/init.lua b/data/core/init.lua index afde6fea..bee6f4e0 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -65,8 +65,19 @@ end local function reload_customizations() - core.load_user_directory() - core.load_project_module() + local user_error = not core.load_user_directory() + local project_error = not core.load_project_module() + if user_error or project_error then + local LogView = require "core.logview" + local rn = core.root_view.root_node + for _,v in pairs(core.root_view.root_node:get_children()) do + if v:is(LogView) then + rn:get_node_for_view(v):set_active_view(v) + return + end + end + command.perform("core:open-log") + end end From 2caa96e9b95ffcd0a73bff5ddb9d062e650ab404 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 7 Jun 2022 03:00:44 -0400 Subject: [PATCH 313/409] ChangesLog: more details to 2.1.0 release --- changelog.md | 606 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 427 insertions(+), 179 deletions(-) diff --git a/changelog.md b/changelog.md index b27f5f4f..0ae2953c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,74 +1,288 @@ -This files document the changes done in Lite XL for each release. +# Changes Log -### 2.1 +## 2.1 - 2022-06-07 -Upgraded Lua to 5.4, which should improve performance, and provide useful extra functionality. -It should also be more available out of the box with most modern linux/unix-based package -managers. +### New Features +* Make distinction between + [line and block comments](https://github.com/lite-xl/lite-xl/pull/771), + and added all appropriate functionality to the commenting/uncommenting lines. -Removed `dmon`, and implemented independent backends for dirmonitoring. Also more cleanly -split out dirmonitoring into its own class in lua, from core.init. We should now support -FreeBSD; and any other system that uses `kqueue` as their dirmonitoring library. We also -have a dummy-backend, which reverts transparnetly to scanning if there is some issue with -applying OS-level watches (such as system limits). +* [Added in line paste mode](https://github.com/lite-xl/lite-xl/pull/713), + if you copy without a selection. -Removed `libagg` and the font renderer; compacted all font rendering into a single renderer.c -file which uses `libfreetype` directly. Now allows for ad-hoc bolding, italics, and underlining -of fonts. +* Many [improvements to treeview](https://github.com/lite-xl/lite-xl/pull/732), + including keyboard navigation of treeview, and ability to specify single vs. + double-click behavior. -Removed `reproc` and replaced this with a simple POSIX/Windows implementation in `process.c`. -This allows for greater tweakability (i.e. we can now `break` for debugging purposes), -performance (startup time of subprocesses is noticeably shorter), and simplicity -(we no longer have to link reproc, or winsock, on windows). +* Added in [soft line wrapping](https://github.com/lite-xl/lite-xl/pull/636) + as core plugin, under `linewrapping.lua`, use `F10` to activate. -Split out `Node` and `EmptyView` into their own lua files, for plugin extensibility reasons. +* Revamped [StatusView](https://github.com/lite-xl/lite-xl/pull/852) API with + new features that include: -Revamped StatusView API, so that plugins can more easily coexist with each other. + * Support for predicates, click actions, tooltips on item hover + and custom drawing of added items. + * Hide items that are too huge by rendering with clip_rect. + * Ability to drag or scroll the left or right if too many items to display. + * New status bar commands accessible from the command palette that + include: toggling status bar visibility, toggling specific item visibility, + enable/disable status messages, etc... -Removed `cp_replace`, and replaced this with a core plugin, `drawwhitespace.lua`. +* Added `renderer.font.group` interface to set up + [font fallback groups](https://github.com/lite-xl/lite-xl/pull/616) in + the font renderer, if a token doesn't have a corresponding glyph. -Made distinction between line and block comments, and added all appropriate functionality -to the commenting/uncommenting lines. + **Example:** + ```lua + local emoji_font = renderer.font.load(USERDIR .. "/fonts/NotoEmoji-Regular.ttf", 15 * SCALE) + local nonicons = renderer.font.load(USERDIR .. "/fonts/nonicons.ttf", 15 * SCALE) + style.code_font = renderer.font.group({style.code_font, nonicons, emoji_font}) + ``` -Added in line paste mode, if you copy without a selection. +* Added in the ability to specify + [mouse clicks](https://github.com/lite-xl/lite-xl/pull/589) in the + keymap, allowing for easy binds of `ctrl+lclick`, and the like. -May improvements to treeview, including keyboard navigation of treeview, and ability to -specify single vs. double-click behavior. + **Example:** + ```lua + keymap.add { ["ctrl+shift+3lclick"] = "core:open-log" } + ``` -Added in soft line wrapping as core plugin, under `linewrapping.lua`, with an -F10 to activate. +* Improved ability for plugins to be loaded at a given time, by making the + convention of defining a config for the plugin using `common.merge` to merge + existing hashes together, rather than overwriting. -Bumped plugin mod-version number, as the rendering interface for docviews has changed. +* Releases will now include all language plugins and the + [settings gui](https://github.com/lite-xl/lite-xl-plugins/pull/65) plugin. -Added in meson wraps for freetype, pcre2, and SDL2 which target public, rather than -lite-xl maintained repos. +* New [core.warn](https://github.com/lite-xl/lite-xl/pull/1005) was introduced. -Added in the ability to set up font fallback groups in the font renderer, if a token -doesn't have a corresponding glyph. +* Added [suggestions warping](https://github.com/lite-xl/lite-xl/pull/1003) + for `CommandView`. -Added in a native plugin interface that allows for C-level interfacing with a -statically-linked lite-xl. The implementation of this may change in future. +* Allow regexes in tokenizer to + [split tokens with group](https://github.com/lite-xl/lite-xl/pull/999). -Improved fuzzy_matching to probably give you something closer to what you're -looking for. +* Added [settings gui support](https://github.com/lite-xl/lite-xl/pull/995) + to core plugins. -Improved handling of alternate keyboard layouts. +* Support for [stricter predicates](https://github.com/lite-xl/lite-xl/pull/990) + by appending a `!`, eg: `"core.docview!"`. -Improved ability for plugins to be loaded at a given time, by making the convention -of defining a config for the plugin use `common.merge` to merge existing hashes -together, rather than overwriting. +* [UTF8 support in tokenizer](https://github.com/lite-xl/lite-xl/pull/945) + and new utf8 counter parts of string functions, + eg: `string.ulen`, `string.ulower`, etc... -Added in the ability to specify mouseclicks in the keymap, allowing for easy binds of -`ctrl+lclick`, and the like. +* Added [utf8 support](https://github.com/lite-xl/lite-xl/pull/986) on doc + lower and upper commands. -Changed interface for keyhandling; now, all components should return true if they've -handled the event. +* Allow syntax patterns to match with the + [beginning of the line](https://github.com/lite-xl/lite-xl/pull/860). -Added in a default keymap for `core:restart`, `ctrl+shift+r`. + **Example:** + ```lua + { pattern = "^my_pattern_starting_at_beginning", type="symbol" } + ``` -Many, many, many more changes that are too numerous to list. +* [Add View:on_file_dropped](https://github.com/lite-xl/lite-xl/pull/845). -### 2.0.5 +* Implemented new function to retrieve current process id of lite-xl + [system.get_process_id()](https://github.com/lite-xl/lite-xl/pull/833). + +* [Allow functions in keymap](https://github.com/lite-xl/lite-xl/pull/948). + +* [Add type ahead to CommandView](https://github.com/lite-xl/lite-xl/pull/963). + +* Add syntax symbols to + [auto-complete](https://github.com/lite-xl/lite-xl/pull/913). + +* Add [animation categories](https://github.com/lite-xl/lite-xl/pull/941) + to enable finer transitions control. + +* Added in a [native plugin](https://github.com/lite-xl/lite-xl/pull/527) + interface that allows for C-level interfacing with a statically-linked + lite-xl. The implementation of this may change in future. + +* Config: added new development option to prevent plugin version checking at + startup named [skip_plugins_version](https://github.com/lite-xl/lite-xl/pull/879) + +### Backward Incompatible Changes +* Bumped plugin mod-version number as various interfaces like: `DocView`, + `StatusView` and `CommandView` have changed which should require a revision + from plugin developers to make sure their plugins work with this new release. + +* Changed interface for key handling; now, all components should return true if + they've handled the event. + +* For plugin developers, declaring config options by directly assigning + to the plugin table (eg: `config.plugins.plugin_name.myvalue = 10`) was + deprecated in favor of using `common.merge` + + **Example:** + ```lua + config.plugins.autowrap = common.merge({ + enabled = false, + files = { "%.md$", "%.txt$" } + }, config.plugins.autowrap) + ``` + +* The `font.set_size` function was dropped in favor of `font.copy`. + +* `DocView:draw_text_line` and related functions been used by plugin developers + require a revision, since some of this interfaces were updated to support + line wrapping. + +* Removed `cp_replace`, and replaced this with a core plugin, + [drawwhitespace.lua](https://github.com/lite-xl/lite-xl/pull/908). + +### Deprecated Features +* For plugins the usage of the `--lite-xl` version tag was dropped + in favor of `--mod-version`. + +* Overriding `StatusView:get_items()` has been deprecated in favor of + the new dedicated interface to insert status bar items: + + **New Interface:** + ```lua + ------@return StatusView.Item + function StatusView:add_item( + predicate, name, alignment, getitem, command, pos, tooltip + ) end + ``` + + **Example:** + ```lua + core.status_view:add_item( + nil, + "status:memory-usage", + StatusView.Item.RIGHT, + function() + return { + style.text, + string.format( + "%.2f MB", + (math.floor(collectgarbage("count") / 10.24) / 100) + ) + } + end, + nil, + 1, + "lua memory usage" + ).separator = core.status_view.separator2 + ``` + +* [CommandView:enter](https://github.com/lite-xl/lite-xl/pull/1004) now accepts + a single options table as a parameter, meaning that the old way of calling + this function will now show a deprecation message. + + **Example:** + ```lua + core.command_view:enter("Title", { + submit = function() end, + suggest = function() return end, + cancel = function() end, + validate = function() return true end, + typeahead = true, + wrap = true + }) + ``` + +### Other Changes +* [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which + should improve performance, and provide useful extra functionality. It should + also be more available out of the box with most modern + linux/unix-based package managers. + +* Removed `dmon`, and implemented independent backends for dirmonitoring. Also + more cleanly split out dirmonitoring into its own class in lua, from core.init. + We should now support FreeBSD; and any other system that uses `kqueue` as + their dir monitoring library. We also have a dummy-backend, which reverts + transparently to scanning if there is some issue with applying OS-level + watches (such as system limits). + +* Removed `libagg` and the font renderer; compacted all font rendering into a + single renderer.c file which uses `libfreetype` directly. Now allows for ad-hoc + bolding, italics, and underlining of fonts. + +* Removed `reproc` and replaced this with a simple POSIX/Windows implementation + in `process.c`. This allows for greater tweakability (i.e. we can now `break` + for debugging purposes), performance (startup time of subprocesses is + noticeably shorter), and simplicity (we no longer have to link reproc, or + winsock, on windows). + +* [Split out `Node` and `EmptyView`](https://github.com/lite-xl/lite-xl/pull/715) + into their own lua files, for plugin extensibility reasons. + +* Improved fuzzy_matching to probably give you something closer to what you're + looking for. + +* Improved handling of alternate keyboard layouts. + +* Added in a default keymap for `core:restart`, `ctrl+shift+r`. + +* Improvements to the [C and C++](https://github.com/lite-xl/lite-xl/pull/875) + syntax files. + +* Improvements to [markdown](https://github.com/lite-xl/lite-xl/pull/862) + syntax file. + +* [Improvements to borderless](https://github.com/lite-xl/lite-xl/pull/994) + mode on Windows + +* Fixed a bunch of problems relating to + [multi-cursor](https://github.com/lite-xl/lite-xl/pull/886). + +* [Performance improvement](https://github.com/lite-xl/lite-xl/pull/883) + of detect indent plugin. + +* NagView: [support vscroll](https://github.com/lite-xl/lite-xl/pull/876) when + message is too long. + +* Meson improvements which include: + * Added in meson wraps for freetype, pcre2, and SDL2 which target public, + rather than lite-xl maintained repos. + * [Seperate dirmonitor logic](https://github.com/lite-xl/lite-xl/pull/866), + add build time detection of features. + * Add [fallbacks](https://github.com/lite-xl/lite-xl/pull/798) to all + common dependencies. + * [Update SDL to 2.0.20](https://github.com/lite-xl/lite-xl/pull/884). + * install [docs/api](https://github.com/lite-xl/lite-xl/pull/979) to datadir + for lsp support. + +* Always check if the beginning of the + [text needs to be clipped](https://github.com/lite-xl/lite-xl/pull/871). + +* Added [git commit](https://github.com/lite-xl/lite-xl/pull/859) + on development builds. + +* Update [autocomplete](https://github.com/lite-xl/lite-xl/pull/832) + with changes needed for latest LSP plugin. + +* Use SDL to manage color format mapping in + [ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/829). + +* Various code [clean ups](https://github.com/lite-xl/lite-xl/pull/826). + +* [Autoreload Nagview](https://github.com/lite-xl/lite-xl/pull/942). + +* Improve performance of + [ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/935). + +* [Enhancements to scrollbar](https://github.com/lite-xl/lite-xl/pull/916). + +* Set the correct working directory for the + [AppImage version](https://github.com/lite-xl/lite-xl/pull/937). + +* Core: fixes and changes to + [temp file](https://github.com/lite-xl/lite-xl/pull/906) functions. + +* [Added plugin load-time log](https://github.com/lite-xl/lite-xl/pull/966). + +* Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896). + +* Many, many, many more changes that are too numerous to list. + +## 2.0.5 Revamp the project's user module so that modifications are immediately applied. @@ -77,9 +291,10 @@ The new mechanism is backward compatible.* Essentially there are two mechanisms: -- if a '/' or a '/$' appear at the end of the pattern it will match only directories -- if a '/' appears anywhere in the pattern except at the end the pattern will be - applied to the path +- if a '/' or a '/$' appear at the end of the pattern it will match only + directories +- if a '/' appears anywhere in the pattern except at the end the pattern will + be applied to the path In the first case, when the pattern corresponds to a directory, a '/' will be appended to the name of each directory before checking the pattern. @@ -96,43 +311,45 @@ Directory monitoring is now aware of symlinks and treat them appropriately. Fix problem when encountering special files type on linux. -Improve directory monitoring so that the related thread actually waits without using -any CPU time when there are no events. +Improve directory monitoring so that the related thread actually waits without +using any CPU time when there are no events. Improve the suggestion when changing project folder or opening a new one. Now the previously used directory are suggested but if the path is changed the actual existing directories that match the pattern are suggested. -In addition always use the text entered in the command view even if a suggested entry -is highlighted. +In addition always use the text entered in the command view even if a suggested +entry is highlighted. The NagView warning window now no longer moves the document content. -### 2.0.4 +## 2.0.4 -Fix some bugs related to newly introduced directory monitoring using the dmon library. +Fix some bugs related to newly introduced directory monitoring using the +dmon library. Fix a problem with plain text search using Lua patterns by error. -Fix a problem with visualization of UTF-8 characters that caused garbage characters -visualization. +Fix a problem with visualization of UTF-8 characters that caused garbage +characters visualization. Other fixes and improvements contributed by @Guldoman. -### 2.0.3 +## 2.0.3 -Replace periodic rescan of project folder with a notification based system using the -[dmon library](https://github.com/septag/dmon). Improves performance especially for -large project folders since the application no longer needs to rescan. -The application also reports immediatly any change in the project directory even -when the application is unfocused. +Replace periodic rescan of project folder with a notification based system +using the [dmon library](https://github.com/septag/dmon). Improves performance +especially for large project folders since the application no longer needs to +rescan. The application also reports immediately any change in the project +directory even when the application is unfocused. Improved find-replace reverse and forward search. -Fixed a bug in incremental syntax highlighting affecting documents with multiple-lines -comments or strings. +Fixed a bug in incremental syntax highlighting affecting documents with +multiple-lines comments or strings. -The application now always shows the tabs in the documents' view even when a single -document is opened. Can be changed with the option `config.always_show_tabs`. +The application now always shows the tabs in the documents' view even when +a single document is opened. Can be changed with the option +`config.always_show_tabs`. Fix problem with numeric keypad function keys not properly working. @@ -140,32 +357,36 @@ Fix problem with pixel not correctly drawn at the window's right edge. Treat correctly and open network paths on Windows. -Add some improvements for very slow network filesystems. +Add some improvements for very slow network file systems. -Fix problem with python syntax highliting, contributed by @dflock. +Fix problem with python syntax highlighting, contributed by @dflock. -### 2.0.2 +## 2.0.2 -Fix problem project directory when starting the application from Launcher on macOS. +Fix problem project directory when starting the application from Launcher on +macOS. -Improved LogView. Entries can now be expanded and there is a context menu to copy the item's content. +Improved LogView. Entries can now be expanded and there is a context menu to +copy the item's content. -Change the behavior of `ctrl+d` to add a multi-cursor selection to the next occurrence. -The old behavior to move the selection to the next occurrence is now done using the shortcut `ctrl+f3`. +Change the behavior of `ctrl+d` to add a multi-cursor selection to the next +occurrence. The old behavior to move the selection to the next occurrence is +now done using the shortcut `ctrl+f3`. -Added a command to create a multi-cursor with all the occurrences of the current selection. -Activated with the shortcut `ctrl+shift+l`. +Added a command to create a multi-cursor with all the occurrences of the +current selection. Activated with the shortcut `ctrl+shift+l`. Fix problem when trying to close an unsaved new document. -No longer shows an error for the `-psn` argument passed to the application on macOS. +No longer shows an error for the `-psn` argument passed to the application on +macOS. Fix `treeview:open-in-system` command on Windows. Fix rename command to update name of document if opened. -Improve the find and replace dialog so that previously used expressions can be recalled -using "up" and "down" keys. +Improve the find and replace dialog so that previously used expressions can be +recalled using "up" and "down" keys. Build package script rewrite with many improvements. @@ -173,63 +394,76 @@ Use bigger fonts by default. Other minor improvements and fixes. -With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101. +With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, +@redtide, @Timofffee, @boppyt, @Jan200101. -### 2.0.1 +## 2.0.1 Fix a few bugs and we mandate the mod-version 2 for plugins. This means that users should ensure they have up-to-date plugins for Lite XL 2.0. Here some details about the bug fixes: -- fix a bug that created a fatal error when using the command to change project folder or when closing all the active documents -- add a limit to avoid scaling fonts too much and fix a related invalid memory access for very small fonts +- fix a bug that created a fatal error when using the command to change project + folder or when closing all the active documents +- add a limit to avoid scaling fonts too much and fix a related invalid memory + access for very small fonts - fix focus problem with NagView when switching project directory - fix error that prevented the verification of plugins versions - fix error on X11 that caused a bug window event on exit -### 2.0 +## 2.0 -The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured; -any custom plugins may need to be adjusted accordingly (see note below about plugin namespacing). +The 2.0 version of lite contains *breaking changes* to lite, in terms of how +plugin settings are structured; any custom plugins may need to be adjusted +accordingly (see note below about plugin namespacing). Contains the following new features: -Full PCRE (regex) support for find and replace, as well as in language syntax definitions. Can be accessed -programatically via the lua `regex` module. +Full PCRE (regex) support for find and replace, as well as in language syntax +definitions. Can be accessed programatically via the lua `regex` module. -A full, finalized subprocess API, using libreproc. Subprocess can be started and interacted with using -`Process.new`. +A full, finalized subprocess API, using libreproc. Subprocess can be started +and interacted with using `Process.new`. -Support for multi-cursor editing. Cursors can be created by either ctrl+clicking on the screen, or by using -the keyboard shortcuts ctrl+shift+up/down to create an additional cursor on the previous/next line. +Support for multi-cursor editing. Cursors can be created by either ctrl+clicking +on the screen, or by using the keyboard shortcuts ctrl+shift+up/down to create +an additional cursor on the previous/next line. All build systems other than meson removed. -A more organized directory structure has been implemented; in particular a docs folder which contains C api -documentation, and a resource folder which houses all build resources. +A more organized directory structure has been implemented; in particular a docs +folder which contains C api documentation, and a resource folder which houses +all build resources. -Plugin config namespacing has been implemented. This means that instead of using `config.myplugin.a`, -to read settings, and `config.myplugin = false` to disable plugins, this has been changed to -`config.plugins.myplugin.a`, and `config.plugins.myplugin = false` repsectively. This may require changes to +Plugin config namespacing has been implemented. This means that instead of +using `config.myplugin.a`, to read settings, and `config.myplugin = false` to +disable plugins, this has been changed to `config.plugins.myplugin.a`, and +`config.plugins.myplugin = false` respectively. This may require changes to your user plugin, or to any custom plugins you have. A context menu on right click has been added. -Changes to how we deal with indentation have been implemented; in particular, hitting home no longer brings you -to the start of a line, it'll bring you to the start of indentation, which is more in line with other editors. +Changes to how we deal with indentation have been implemented; in particular, +hitting home no longer brings you to the start of a line, it'll bring you to +the start of indentation, which is more in line with other editors. -Lineguide, and scale plugins moved into the core, and removed from `lite-plugins`. This may also require you to -adjust your personal plugin folder to remove these if they're present. +Lineguide, and scale plugins moved into the core, and removed from +`lite-plugins`. This may also require you to adjust your personal plugin +folder to remove these if they're present. -In addition, there have been many other small fixes and improvements, too numerous to list here. +In addition, there have been many other small fixes and improvements, too +numerous to list here. -### 1.16.11 +## 1.16.11 -When opening directories with too many files lite-xl now keep diplaying files and directories in the treeview. -The application remains functional and the directories can be explored without using too much memory. -In this operating mode the files of the project are not indexed so the command "Core: Find File" will act as the "Core: Open File" command. -The "Project Search: Find" will work by searching all the files present in the project directory even if they are not indexed. +When opening directories with too many files lite-xl now keep displaying files +and directories in the treeview. The application remains functional and the +directories can be explored without using too much memory. In this operating +mode the files of the project are not indexed so the command "Core: Find File" +will act as the "Core: Open File" command.The "Project Search: Find" will work +by searching all the files present in the project directory even if they are +not indexed. Implemented changing fonts per syntax group by @liquidev. @@ -249,30 +483,30 @@ Fix bug with close button not working in borderless window mode. Fix problem with normalization of filename for opened documents. -### 1.16.10 +## 1.16.10 Improved syntax highlight system thanks to @liquidev and @adamharrison. -Thanks to the new system we provide more a accurate syntax highlighting for Lua, C and C++. -Other syntax improvements contributed by @vincens2005. +Thanks to the new system we provide more a accurate syntax highlighting for +Lua, C and C++. Other syntax improvements contributed by @vincens2005. Move to JetBrains Mono and Fira Sans fonts for code and UI respectively. -Thet are provided under the SIL Open Font License, Version 1.1. +They are provided under the SIL Open Font License, Version 1.1. See `doc/licenses.md` for license details. -Fixed bug with fonts and rencache module. -Under very specific situations the application was crashing due to invalid memory access. +Fixed bug with fonts and rencache module. Under very specific situations the +application was crashing due to invalid memory access. Add documentation for keymap binding, thanks to @Janis-Leuenberger. Added a contributors page in `doc/contributors.md`. -### 1.16.9 +## 1.16.9 Fix a bug related to nested panes resizing. Fix problem preventing creating a new file. -### 1.16.8 +## 1.16.8 Fix application crash when using the command `core:restart`. @@ -294,27 +528,28 @@ Both kind of tags can appear in new plugins in the form: where the old tag needs to appear at the end for compatibility. -### 1.16.7 +## 1.16.7 Add support for retina displays on Mac OS X. Fix a few problems related to file paths. -### 1.16.6 +## 1.16.6 -Implement a system to check the compatibility of plugins by checking a release tag. -Plugins that don't have the release tag will not be loaded. +Implement a system to check the compatibility of plugins by checking a release +tag. Plugins that don't have the release tag will not be loaded. Improve and extend the NagView with keyboard commands. -Special thanks to @takase1121 for the implementation and @liquidev for proposing and -discussing the enhancements. +Special thanks to @takase1121 for the implementation and @liquidev for proposing +and discussing the enhancements. Add support to build on Mac OS X and create an application bundle. Special thanks to @mathewmariani for his lite-macos fork, the Mac OS specific resources and his support. -Add hook function `DocView.on_text_change` so that plugin can accurately react on document changes. -Thanks to @vincens2005 for the suggestion and testing the implementation. +Add hook function `DocView.on_text_change` so that plugin can accurately react +on document changes. Thanks to @vincens2005 for the suggestion and testing the +implementation. Enable borderless window mode using the `config.borderless` variable. If enable the system window's bar will be replaced by a title bar provided @@ -332,13 +567,14 @@ commands `draw-whitespace:toggle`, `draw-whitespace:enable`, Improve the NagView to accept keyboard commands and introduce dialog commands. -Add hook function `Doc:on_text_change` called on document changes, to be used by plugins. +Add hook function `Doc:on_text_change` called on document changes, to be +used by plugins. -### 1.16.5 +## 1.16.5 Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122 -### 1.16.4 +## 1.16.4 Add tooltips to show full file names from the tree-view. @@ -353,7 +589,7 @@ Made borders between tabs look cleaner. Fix problem with files using hard tabs. -### 1.16.2 +## 1.16.2 Implement close button for tabs. @@ -361,12 +597,12 @@ Make the command view list of suggestion scrollable to see all the items. Improve update/resize behavior of treeview and toolbar. -### 1.16.1 +## 1.16.1 Improve behavior of commands to move, delete and duplicate multiple lines: no longer include the last line if it does not contain any selection. -Fix graphical artefacts when rendering some fonts like FiraSans. +Fix graphical artifacts when rendering some fonts like FiraSans. Introduce the `config.transitions` boolean variable. When false the transitions will be disabled and changes will be done immediately. @@ -375,7 +611,7 @@ Very useful for remote sessions where visual transitions doesn't work well. Fix many small problems related to the new toolbar and the tooptips. Fix problem with spacing in treeview when using monospace fonts. -### 1.16 +## 1.16 Implement a toolbar shown in the bottom part of the tree-view. The toolbar is especially meant for new users to give an easy, visual, access @@ -387,8 +623,8 @@ are actually resizable. Add config mechanism to disable a plugin by setting `config. = false`. -Improve the "detect indent" plugin to take into account the syntax and exclude comments -for much accurate results. +Improve the "detect indent" plugin to take into account the syntax and exclude +comments for much accurate results. Add command `root:close-all` to close all the documents currently opened. @@ -396,21 +632,24 @@ Show the full path filename of the active document in the window's title. Fix problem with user's module reload not always enabled. -### 1.15 +## 1.15 **Project directories** -Extend your project by adding more directories using the command `core:add-directory`. -To remove them use the corresponding command `core:remove-directory`. +Extend your project by adding more directories using the command +`core:add-directory`. To remove them use the corresponding command +`core:remove-directory`. **Workspaces** The workspace plugin from rxi/lite-plugins is now part of Lite XL. -In addition to the functionalities of the original plugin the extended version will -also remember the window size and position and the additonal project directories. -To not interfere with the project's files the workspace file is saved in the personal -Lite's configuration folder. -On unix-like systems it will be in: `$HOME/.config/lite-xl/ws`. +In addition to the functionalities of the original plugin the extended version +will also remember the window size and position and the additional project +directories. + +To not interfere with the project's files the workspace file is saved in the +personal Lite's configuration folder. On unix-like systems it will be in: +`$HOME/.config/lite-xl/ws`. **Scrolling the Tree View** @@ -422,10 +661,11 @@ As in the unix shell `~` is now used to identify the home directory. **Files and Directories** -Add command to create a new empty directory within the project using the command -`files:create-directory`. -In addition a control-click on a project directory will prompt the user to create -a new directory inside the directory pointed. +Add command to create a new empty directory within the project using the +command `files:create-directory`. + +In addition a control-click on a project directory will prompt the user to +create a new directory inside the directory pointed. **New welcome screen** @@ -433,51 +673,56 @@ Show 'Lite XL' instead of 'lite' and the version number. **Various fixes and improvements** -A few quirks previously with some of the new features have been fixed for a better user experience. +A few quirks previously with some of the new features have been fixed for a +better user experience. -### 1.14 +## 1.14 **Project Management** -Add a new command, Core: Change Project Folder, to change project directory by staying on the same window. -All the current opened documents will be closed. +Add a new command, Core: Change Project Folder, to change project directory by +staying on the same window. All the current opened documents will be closed. The new command is associated with the keyboard combination ctrl+shit+c. -A similar command is also added, Core: Open Project Folder, with key binding ctrl+shift+o. -It will open the chosen folder in a new window. +A similar command is also added, Core: Open Project Folder, with key binding +ctrl+shift+o. It will open the chosen folder in a new window. -In addition Lite XL will now remember the recently used projects across different sessions. -When invoked without arguments it will now open the project more recently used. -If a directory is specified it will behave like before and open the directory indicated as an argument. +In addition Lite XL will now remember the recently used projects across +different sessions. When invoked without arguments it will now open the project +more recently used. If a directory is specified it will behave like before and +open the directory indicated as an argument. **Restart command** -A Core: Restart command is added to restart the editor without leaving the current window. -Very convenient when modifying the Lua code for the editor itself. +A Core: Restart command is added to restart the editor without leaving the +current window. Very convenient when modifying the Lua code for the editor +itself. **User's setting auto-reload** -When saving the user configuration, the user's module, the changes will be automatically applied to the -current instance. +When saving the user configuration, the user's module, the changes will be +automatically applied to the current instance. **Bundle community provided colors schemes** -Included now in the release files the colors schemes from github.com/rxi/lite-colors. +Included now in the release files the colors schemes from +github.com/rxi/lite-colors. **Usability improvements** -Improve left and right scrolling of text to behave like other editors and improves text selection with mouse. +Improve left and right scrolling of text to behave like other editors and +improves text selection with mouse. **Fixes** Correct font's rendering for full hinting mode when using subpixel antialiasing. -### 1.13 +## 1.13 **Rendering options for fonts** -When loading fonts with the function renderer.font.load some rendering options can -be optionally specified: +When loading fonts with the function renderer.font.load some rendering options +can be optionally specified: - antialiasing: grayscale or subpixel - hinting: none, slight or full @@ -486,36 +731,39 @@ See data/core/style.lua for the details about its utilisation. The default remains antialiasing subpixel and hinting slight to reproduce the behavior of previous versions. -The option grayscale with full hinting is specially interesting for crisp font rendering -without color artifacts. +The option grayscale with full hinting is specially interesting for crisp font +rendering without color artifacts. **Unix-like install directories** Use unix-like install directories for the executable and for the data directory. The executable will be placed under $prefix/bin and the data folder will be $prefix/share/lite-xl. + The folder $prefix is not hard-coded in the binary but is determined at runtime as the directory such as the executable is inside $prefix/bin. -If no such $prefix exist it will fall back to the old behavior and use the "data" -folder from the executable directory. -In addtion to the `EXEDIR` global variable an additional variable is exposed, `DATADIR`, -to point to the data directory. +If no such $prefix exist it will fall back to the old behavior and use the +"data" folder from the executable directory. -The old behavior using the "data" directory can be still selected at compile time -using the "portable" option. The released Windows package will use the "data" -directory as before. +In addtion to the `EXEDIR` global variable an additional variable is exposed, +`DATADIR`, to point to the data directory. + +The old behavior using the "data" directory can be still selected at compile +time using the "portable" option. The released Windows package will use the +"data" directory as before. **Configuration stored into the user's home directory** -Now the Lite XL user's configuration will be stored in the user's home directory under -".config/lite-xl". -The home directory is determined using the "HOME" environment variable except on Windows -wher "USERPROFILE" is used instead. +Now the Lite XL user's configuration will be stored in the user's home directory +under .config/lite-xl". + +The home directory is determined using the "HOME" environment variable except +on Windows wher "USERPROFILE" is used instead. A new global variable `USERDIR` is exposed to point to the user's directory. -### 1.11 +## 1.11 - include changes from rxi's Lite 1.11 - fix behavior of tab to indent multiple lines @@ -523,11 +771,11 @@ A new global variable `USERDIR` is exposed to point to the user's directory. - limit project scan to a maximum number of files to limit memory usage - list recently visited files when using "Find File" command -### 1.08 +## 1.08 - Subpixel font rendering, removed gamma correction - Avoid using CPU when the editor is idle -### 1.06 +## 1.06 - subpixel font rendering with gamma correction From 1c6573aa1b0a07167693de89ed893ef4588575a1 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 7 Jun 2022 18:47:19 -0400 Subject: [PATCH 314/409] changelog: include treeview fixes --- changelog.md | 65 +++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/changelog.md b/changelog.md index 0ae2953c..8241ceeb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ # Changes Log -## 2.1 - 2022-06-07 +## [2.1.0] - 2022-06-07 ### New Features * Make distinction between @@ -107,6 +107,11 @@ startup named [skip_plugins_version](https://github.com/lite-xl/lite-xl/pull/879) ### Backward Incompatible Changes +* [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which + should improve performance, and provide useful extra functionality. It should + also be more available out of the box with most modern + linux/unix-based package managers. + * Bumped plugin mod-version number as various interfaces like: `DocView`, `StatusView` and `CommandView` have changed which should require a revision from plugin developers to make sure their plugins work with this new release. @@ -116,7 +121,7 @@ * For plugin developers, declaring config options by directly assigning to the plugin table (eg: `config.plugins.plugin_name.myvalue = 10`) was - deprecated in favor of using `common.merge` + deprecated in favor of using `common.merge`. **Example:** ```lua @@ -188,11 +193,6 @@ ``` ### Other Changes -* [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which - should improve performance, and provide useful extra functionality. It should - also be more available out of the box with most modern - linux/unix-based package managers. - * Removed `dmon`, and implemented independent backends for dirmonitoring. Also more cleanly split out dirmonitoring into its own class in lua, from core.init. We should now support FreeBSD; and any other system that uses `kqueue` as @@ -227,7 +227,7 @@ syntax file. * [Improvements to borderless](https://github.com/lite-xl/lite-xl/pull/994) - mode on Windows + mode on Windows. * Fixed a bunch of problems relating to [multi-cursor](https://github.com/lite-xl/lite-xl/pull/886). @@ -280,9 +280,12 @@ * Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896). +* TreeView improvements for + [multi-project](https://github.com/lite-xl/lite-xl/pull/1010). + * Many, many, many more changes that are too numerous to list. -## 2.0.5 +## [2.0.5] - 2022-01-29 Revamp the project's user module so that modifications are immediately applied. @@ -322,7 +325,7 @@ entry is highlighted. The NagView warning window now no longer moves the document content. -## 2.0.4 +## [2.0.4] - 2021-12-20 Fix some bugs related to newly introduced directory monitoring using the dmon library. @@ -334,7 +337,7 @@ characters visualization. Other fixes and improvements contributed by @Guldoman. -## 2.0.3 +## [2.0.3] - 2021-10-23 Replace periodic rescan of project folder with a notification based system using the [dmon library](https://github.com/septag/dmon). Improves performance @@ -361,7 +364,7 @@ Add some improvements for very slow network file systems. Fix problem with python syntax highlighting, contributed by @dflock. -## 2.0.2 +## [2.0.2] - 2021-09-10 Fix problem project directory when starting the application from Launcher on macOS. @@ -397,7 +400,7 @@ Other minor improvements and fixes. With many thanks to the contributors: @adamharrison, @takase1121, @Guldoman, @redtide, @Timofffee, @boppyt, @Jan200101. -## 2.0.1 +## [2.0.1] - 2021-08-28 Fix a few bugs and we mandate the mod-version 2 for plugins. This means that users should ensure they have up-to-date plugins for Lite XL 2.0. @@ -412,7 +415,7 @@ Here some details about the bug fixes: - fix error that prevented the verification of plugins versions - fix error on X11 that caused a bug window event on exit -## 2.0 +## [2.0] - 2021-08-16 The 2.0 version of lite contains *breaking changes* to lite, in terms of how plugin settings are structured; any custom plugins may need to be adjusted @@ -455,7 +458,7 @@ folder to remove these if they're present. In addition, there have been many other small fixes and improvements, too numerous to list here. -## 1.16.11 +## [1.16.11] - 2021-05-28 When opening directories with too many files lite-xl now keep displaying files and directories in the treeview. The application remains functional and the @@ -483,7 +486,7 @@ Fix bug with close button not working in borderless window mode. Fix problem with normalization of filename for opened documents. -## 1.16.10 +## [1.16.10] - 2021-05-22 Improved syntax highlight system thanks to @liquidev and @adamharrison. Thanks to the new system we provide more a accurate syntax highlighting for @@ -500,13 +503,13 @@ Add documentation for keymap binding, thanks to @Janis-Leuenberger. Added a contributors page in `doc/contributors.md`. -## 1.16.9 +## [1.16.9] - 2021-05-06 Fix a bug related to nested panes resizing. Fix problem preventing creating a new file. -## 1.16.8 +## [1.16.8] - 2021-05-06 Fix application crash when using the command `core:restart`. @@ -528,13 +531,13 @@ Both kind of tags can appear in new plugins in the form: where the old tag needs to appear at the end for compatibility. -## 1.16.7 +## [1.16.7] - 2021-05-01 Add support for retina displays on Mac OS X. Fix a few problems related to file paths. -## 1.16.6 +## [1.16.6] - 2021-04-21 Implement a system to check the compatibility of plugins by checking a release tag. Plugins that don't have the release tag will not be loaded. @@ -570,11 +573,11 @@ Improve the NagView to accept keyboard commands and introduce dialog commands. Add hook function `Doc:on_text_change` called on document changes, to be used by plugins. -## 1.16.5 +## [1.16.5] - 2021-03-20 Hotfix for Github's issue https://github.com/franko/lite-xl/issues/122 -## 1.16.4 +## [1.16.4] - 2021-03-20 Add tooltips to show full file names from the tree-view. @@ -589,7 +592,7 @@ Made borders between tabs look cleaner. Fix problem with files using hard tabs. -## 1.16.2 +## [1.16.2] - 2021-03-05 Implement close button for tabs. @@ -597,7 +600,7 @@ Make the command view list of suggestion scrollable to see all the items. Improve update/resize behavior of treeview and toolbar. -## 1.16.1 +## [1.16.1] - 2021-02-25 Improve behavior of commands to move, delete and duplicate multiple lines: no longer include the last line if it does not contain any selection. @@ -611,7 +614,7 @@ Very useful for remote sessions where visual transitions doesn't work well. Fix many small problems related to the new toolbar and the tooptips. Fix problem with spacing in treeview when using monospace fonts. -## 1.16 +## [1.16] - 2021-02-19 Implement a toolbar shown in the bottom part of the tree-view. The toolbar is especially meant for new users to give an easy, visual, access @@ -632,7 +635,7 @@ Show the full path filename of the active document in the window's title. Fix problem with user's module reload not always enabled. -## 1.15 +## [1.15] - 2021-01-04 **Project directories** @@ -676,7 +679,7 @@ Show 'Lite XL' instead of 'lite' and the version number. A few quirks previously with some of the new features have been fixed for a better user experience. -## 1.14 +## [1.14] - 2020-12-13 **Project Management** @@ -717,7 +720,7 @@ improves text selection with mouse. Correct font's rendering for full hinting mode when using subpixel antialiasing. -## 1.13 +## [1.13] - 2020-12-06 **Rendering options for fonts** @@ -763,7 +766,7 @@ on Windows wher "USERPROFILE" is used instead. A new global variable `USERDIR` is exposed to point to the user's directory. -## 1.11 +## [1.11] - 2020-07-05 - include changes from rxi's Lite 1.11 - fix behavior of tab to indent multiple lines @@ -771,11 +774,11 @@ A new global variable `USERDIR` is exposed to point to the user's directory. - limit project scan to a maximum number of files to limit memory usage - list recently visited files when using "Find File" command -## 1.08 +## [1.08] - 2020-06-14 - Subpixel font rendering, removed gamma correction - Avoid using CPU when the editor is idle -## 1.06 +## [1.06] - 2020-05-31 - subpixel font rendering with gamma correction From 3f206db69ac288723a1f4a7ed87b81f830998317 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 7 Jun 2022 20:09:48 -0400 Subject: [PATCH 315/409] initial documentation for better code completion --- changelog.md | 10 ++- data/core/commandview.lua | 20 ++++- data/core/contextmenu.lua | 1 + data/core/doc/init.lua | 2 +- data/core/docview.lua | 3 +- data/core/emptyview.lua | 2 + data/core/init.lua | 5 ++ data/core/nagview.lua | 2 + data/core/node.lua | 1 + data/core/object.lua | 23 ++++-- data/core/rootview.lua | 39 +++++++++- data/core/statusview.lua | 156 +++++++++++++++++++------------------- data/core/titleview.lua | 2 + data/core/view.lua | 92 ++++++++++++++++++++-- data/plugins/treeview.lua | 4 +- 15 files changed, 262 insertions(+), 100 deletions(-) diff --git a/changelog.md b/changelog.md index 8241ceeb..bf61310d 100644 --- a/changelog.md +++ b/changelog.md @@ -178,7 +178,9 @@ * [CommandView:enter](https://github.com/lite-xl/lite-xl/pull/1004) now accepts a single options table as a parameter, meaning that the old way of calling - this function will now show a deprecation message. + this function will now show a deprecation message. Also `CommandView:set_text` + and `CommandView:set_hidden_suggestions` has been + [deprecated](https://github.com/lite-xl/lite-xl/pull/1014). **Example:** ```lua @@ -187,6 +189,9 @@ suggest = function() return end, cancel = function() end, validate = function() return true end, + text = "", + select_text = false, + show_suggestions = true, typeahead = true, wrap = true }) @@ -283,6 +288,9 @@ * TreeView improvements for [multi-project](https://github.com/lite-xl/lite-xl/pull/1010). +* Open LogView on user/project + [module reload error](https://github.com/lite-xl/lite-xl/pull/1022). + * Many, many, many more changes that are too numerous to list. ## [2.0.5] - 2022-01-29 diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 8017fcfc..545455e6 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -6,13 +6,16 @@ local DocView = require "core.docview" local View = require "core.view" +---@class core.commandview.input : core.doc +---@field super core.doc local SingleLineDoc = Doc:extend() function SingleLineDoc:insert(line, col, text) SingleLineDoc.super.insert(self, line, col, text:gsub("\n", "")) end - +---@class core.commandview : core.docview +---@field super core.docview local CommandView = DocView:extend() CommandView.context = "application" @@ -21,6 +24,16 @@ local max_suggestions = 10 local noop = function() end +---@class core.commandview.state +---@field submit function +---@field suggest function +---@field cancel function +---@field validate function +---@field text string +---@field select_text boolean +---@field show_suggestions boolean +---@field typeahead boolean +---@field wrap boolean local default_state = { submit = noop, suggest = noop, @@ -51,6 +64,7 @@ function CommandView:new() end +---@deprecated function CommandView:set_hidden_suggestions() core.warn("Using deprecated function CommandView:set_hidden_suggestions") self.state.show_suggestions = false @@ -147,7 +161,9 @@ function CommandView:submit() end end - +---@param label string +---@varargs any +---@overload fun(label:string, options: core.commandview.state) function CommandView:enter(label, ...) if self.state ~= default_state then return diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index 94ef61f8..040fe6c3 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -11,6 +11,7 @@ local border_width = 1 local divider_width = 1 local DIVIDER = {} +---@class core.contextmenu : core.object local ContextMenu = Object:extend() ContextMenu.DIVIDER = DIVIDER diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index a2546246..fc2226cb 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -5,7 +5,7 @@ local syntax = require "core.syntax" local config = require "core.config" local common = require "core.common" - +---@class core.doc : core.object local Doc = Object:extend() diff --git a/data/core/docview.lua b/data/core/docview.lua index bf76db38..f4270e9f 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -6,7 +6,8 @@ local keymap = require "core.keymap" local translate = require "core.doc.translate" local View = require "core.view" - +---@class core.docview : core.view +---@field super core.view local DocView = View:extend() DocView.context = "session" diff --git a/data/core/emptyview.lua b/data/core/emptyview.lua index 36ba8fb7..0d0a929e 100644 --- a/data/core/emptyview.lua +++ b/data/core/emptyview.lua @@ -2,6 +2,8 @@ local style = require "core.style" local keymap = require "core.keymap" local View = require "core.view" +---@class core.emptyview : core.view +---@field super core.view local EmptyView = View:extend() local function draw_text(x, y, color) diff --git a/data/core/init.lua b/data/core/init.lua index 2c487dff..f8459e3f 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -694,10 +694,15 @@ function core.init() core.quit_request = false -- We load core views before plugins that may need them. + ---@type core.rootview core.root_view = RootView() + ---@type core.commandview core.command_view = CommandView() + ---@type core.statusview core.status_view = StatusView() + ---@type core.nagview core.nag_view = NagView() + ---@type core.titleview core.title_view = TitleView() -- Some plugins (eg: console) require the nodes to be initialized to defaults diff --git a/data/core/nagview.lua b/data/core/nagview.lua index c34d2a64..9c373f58 100644 --- a/data/core/nagview.lua +++ b/data/core/nagview.lua @@ -11,6 +11,8 @@ local UNDERLINE_MARGIN = common.round(1 * SCALE) local noop = function() end +---@class core.nagview : core.view +---@field super core.view local NagView = View:extend() function NagView:new() diff --git a/data/core/node.lua b/data/core/node.lua index fee62ba6..087610a6 100644 --- a/data/core/node.lua +++ b/data/core/node.lua @@ -6,6 +6,7 @@ local Object = require "core.object" local EmptyView = require "core.emptyview" local View = require "core.view" +---@class core.node : core.object local Node = Object:extend() function Node:new(type) diff --git a/data/core/object.lua b/data/core/object.lua index 6a6ea490..afd13cdf 100644 --- a/data/core/object.lua +++ b/data/core/object.lua @@ -1,11 +1,12 @@ +---@class core.object +---@field super core.object local Object = {} Object.__index = Object +---Can be overrided by child objects to implement a constructor. +function Object:new() end -function Object:new() -end - - +---@return core.object function Object:extend() local cls = {} for k, v in pairs(self) do @@ -19,12 +20,16 @@ function Object:extend() return cls end - +---Check if the object is strictly of the given type. +---@param T any +---@return boolean function Object:is(T) return getmetatable(self) == T end - +---Check if the object inherits from the given type. +---@param T any +---@return boolean function Object:extends(T) local mt = getmetatable(self) while mt do @@ -36,12 +41,14 @@ function Object:extends(T) return false end - +---Metamethod to get a string representation of an object. +---@return string function Object:__tostring() return "Object" end - +---Methamethod to allow using the object call as a constructor. +---@return core.object function Object:__call(...) local obj = setmetatable({}, self) obj:new(...) diff --git a/data/core/rootview.lua b/data/core/rootview.lua index 8e32c68c..c4eb656f 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -5,7 +5,10 @@ local Node = require "core.node" local View = require "core.view" local DocView = require "core.docview" - +---@class core.rootview : core.view +---@field super core.view +---@field root_node core.node +---@field mouse core.view.position local RootView = View:extend() function RootView:new() @@ -29,6 +32,7 @@ function RootView:defer_draw(fn, ...) end +---@return core.node function RootView:get_active_node() local node = self.root_node:get_node_for_view(core.active_view) if not node then node = self:get_primary_node() end @@ -36,6 +40,7 @@ function RootView:get_active_node() end +---@return core.node local function get_primary_node(node) if node.is_primary_node then return node @@ -46,6 +51,7 @@ local function get_primary_node(node) end +---@return core.node function RootView:get_active_node_default() local node = self.root_node:get_node_for_view(core.active_view) if not node then node = self:get_primary_node() end @@ -59,11 +65,14 @@ function RootView:get_active_node_default() end +---@return core.node function RootView:get_primary_node() return get_primary_node(self.root_node) end +---@param node core.node +---@return core.node local function select_next_primary_node(node) if node.is_primary_node then return end if node.type ~= "leaf" then @@ -77,11 +86,14 @@ local function select_next_primary_node(node) end +---@return core.node function RootView:select_next_primary_node() return select_next_primary_node(self.root_node) end +---@param doc core.doc +---@return core.docview function RootView:open_doc(doc) local node = self:get_active_node_default() for i, view in ipairs(node.views) do @@ -98,17 +110,27 @@ function RootView:open_doc(doc) end +---@param keep_active boolean function RootView:close_all_docviews(keep_active) self.root_node:close_all_docviews(keep_active) end --- Function to intercept mouse pressed events on the active view. --- Do nothing by default. +---Function to intercept mouse pressed events on the active view. +---Do nothing by default. +---@param button core.view.mousebutton +---@param x number +---@param y number +---@param clicks integer function RootView.on_view_mouse_pressed(button, x, y, clicks) end +---@param button core.view.mousebutton +---@param x number +---@param y number +---@param clicks integer +---@return boolean function RootView:on_mouse_pressed(button, x, y, clicks) local div = self.root_node:get_divider_overlapping_point(x, y) local node = self.root_node:get_child_overlapping_point(x, y) @@ -162,6 +184,9 @@ function RootView:set_show_overlay(overlay, status) end +---@param button core.view.mousebutton +---@param x number +---@param y number function RootView:on_mouse_released(button, x, y, ...) if self.dragged_divider then self.dragged_divider = nil @@ -220,6 +245,10 @@ local function resize_child_node(node, axis, value, delta) end +---@param x number +---@param y number +---@param dx number +---@param dy number function RootView:on_mouse_moved(x, y, dx, dy) if core.active_view == core.nag_view then core.request_cursor("arrow") @@ -284,6 +313,10 @@ function RootView:on_mouse_left() end +---@param filename string +---@param x number +---@param y number +---@return boolean function RootView:on_file_dropped(filename, x, y) local node = self.root_node:get_child_overlapping_point(x, y) return node and node.active_view:on_file_dropped(filename, x, y) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 542522c7..497e5e2e 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -10,17 +10,18 @@ local View = require "core.view" local Object = require "core.object" ----@alias StatusView.styledtext table +---@alias core.statusview.styledtext table ---A status bar implementation for lite, check core.status_view. ----@class StatusView : View ----@field private items StatusView.Item[] ----@field private active_items StatusView.Item[] ----@field private hovered_item StatusView.Item +---@class core.statusview : core.view +---@field public super core.view +---@field private items core.statusview.item[] +---@field private active_items core.statusview.item[] +---@field private hovered_item core.statusview.item ---@field private message_timeout number ----@field private message StatusView.styledtext +---@field private message core.statusview.styledtext ---@field private tooltip_mode boolean ----@field private tooltip StatusView.styledtext +---@field private tooltip core.statusview.styledtext ---@field private left_width number ---@field private right_width number ---@field private r_left_width number @@ -40,52 +41,52 @@ StatusView.separator = " " ---@type string StatusView.separator2 = " | " ----@alias StatusView.Item.separator ----|>'StatusView.separator' # Space separator ----| 'StatusView.separator2' # Pipe separator +---@alias core.statusview.item.separator +---|>'core.statusview.separator' # Space separator +---| 'core.statusview.separator2' # Pipe separator ----@alias StatusView.Item.predicate fun():boolean ----@alias StatusView.Item.onclick fun(button: string, x: number, y: number) ----@alias StatusView.Item.getitem fun():StatusView.styledtext,StatusView.styledtext ----@alias StatusView.Item.ondraw fun(x, y, h, hovered: boolean, calc_only: boolean):number +---@alias core.statusview.item.predicate fun():boolean +---@alias core.statusview.item.onclick fun(button: string, x: number, y: number) +---@alias core.statusview.item.getitem fun():core.statusview.styledtext,core.statusview.styledtext +---@alias core.statusview.item.ondraw fun(x, y, h, hovered: boolean, calc_only?: boolean):number ----@class StatusView.Item : Object +---@class core.statusview.item : core.object ---@field name string ----@field predicate StatusView.Item.predicate ----@field alignment StatusView.Item.alignment +---@field predicate core.statusview.item.predicate +---@field alignment core.statusview.item.alignment ---@field tooltip string | nil ---@field command string | nil @Command to perform when the item is clicked. ----@field on_click StatusView.Item.onclick | nil @Function called when item is clicked and no command is set. ----@field on_draw StatusView.Item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. +---@field on_click core.statusview.item.onclick | nil @Function called when item is clicked and no command is set. +---@field on_draw core.statusview.item.ondraw | nil @Custom drawing that when passed calc true should return the needed width for drawing and when false should draw. ---@field background_color renderer.color | nil ---@field background_color_hover renderer.color | nil ---@field visible boolean ----@field separator StatusView.Item.separator +---@field separator core.statusview.item.separator ---@field private active boolean ---@field private x number ---@field private w number ----@field private cached_item StatusView.styledtext -StatusView.Item = Object:extend() +---@field private cached_item core.statusview.styledtext +local StatusViewItem = Object:extend() ---Flag to tell the item should me aligned on left side of status bar. ---@type number -StatusView.Item.LEFT = 1 +StatusViewItem.LEFT = 1 ---Flag to tell the item should me aligned on right side of status bar. ---@type number -StatusView.Item.RIGHT = 2 +StatusViewItem.RIGHT = 2 ----@alias StatusView.Item.alignment ----|>'StatusView.Item.LEFT' ----| 'StatusView.Item.RIGHT' +---@alias core.statusview.item.alignment +---|>'core.statusview.item.LEFT' +---| 'core.statusview.item.RIGHT' ---Constructor ----@param predicate string | table | StatusView.Item.predicate +---@param predicate string | table | core.statusview.item.predicate ---@param name string ----@param alignment StatusView.Item.alignment ----@param command string | StatusView.Item.onclick +---@param alignment core.statusview.item.alignment +---@param command string | core.statusview.item.onclick ---@param tooltip? string | nil -function StatusView.Item:new(predicate, name, alignment, command, tooltip) +function StatusViewItem:new(predicate, name, alignment, command, tooltip) self:set_predicate(predicate) self.name = name self.alignment = alignment or StatusView.Item.LEFT @@ -104,25 +105,28 @@ end ---Called by the status bar each time that the item needs to be rendered, ---if on_draw() is set this function is obviated. ----@return StatusView.styledtext -function StatusView.Item:get_item() return {} end +---@return core.statusview.styledtext +function StatusViewItem:get_item() return {} end ---Do not show the item on the status bar. -function StatusView.Item:hide() self.visible = false end +function StatusViewItem:hide() self.visible = false end ---Show the item on the status bar. -function StatusView.Item:show() self.visible = true end +function StatusViewItem:show() self.visible = true end ---A condition to evaluate if the item should be displayed. If a string ---is given it is treated as a require import that should return a valid object ---which is checked against the current active view, the sames applies if a ---table is given. A function that returns a boolean can be used instead to ---perform a custom evaluation, setting to nil means always evaluates to true. ----@param predicate string | table | StatusView.Item.predicate -function StatusView.Item:set_predicate(predicate) +---@param predicate string | table | core.statusview.item.predicate +function StatusViewItem:set_predicate(predicate) self.predicate = command.generate_predicate(predicate) end +---@type core.statusview.item +StatusView.Item = StatusViewItem + ---Predicated used on the default docview widgets. ---@return boolean @@ -267,9 +271,9 @@ end ---Set a position to the best match according to total available items. ----@param self StatusView +---@param self core.statusview ---@param position integer ----@param alignment StatusView.Item.alignment +---@param alignment core.statusview.item.alignment ---@return integer position local function normalize_position(self, position, alignment) local offset = 0 @@ -299,18 +303,18 @@ end ---Adds an item to be rendered in the status bar. ----@param predicate string | table | StatusView.Item.predicate : +---@param predicate string | table | core.statusview.item.predicate : ---A condition to evaluate if the item should be displayed. If a string ---is given it is treated as a require import that should return a valid object ---which is checked against the current active view, the sames applies if a ---table is given. A function that returns a boolean can be used instead to ---perform a custom evaluation, setting to nil means always evaluates to true. ---@param name string A unique name to identify the item on the status bar. ----@param alignment StatusView.Item.alignment ----@param getitem StatusView.Item.getitem : ----A function that should return a StatusView.styledtext element, +---@param alignment core.statusview.item.alignment +---@param getitem core.statusview.item.getitem : +---A function that should return a core.statusview.styledtext element, ---returning empty table is allowed. ----@param command? string | StatusView.Item.onclick : +---@param command? string | core.statusview.item.onclick : ---The name of a valid registered command or a callback function to execute ---when the item is clicked. ---@param pos? integer : @@ -318,10 +322,10 @@ end ---a value of -1 inserts the item at the end which is the default. A value ---of 1 will insert the item at the beggining. ---@param tooltip? string Displayed when mouse hovers the item ----@return StatusView.Item +---@return core.statusview.item function StatusView:add_item(predicate, name, alignment, getitem, command, pos, tooltip) assert(self:get_item(name) == nil, "status item already exists: " .. name) - ---@type StatusView.Item + ---@type core.statusview.item local item = StatusView.Item(predicate, name, alignment, command, tooltip) item.get_item = getitem pos = type(pos) == "nil" and -1 or tonumber(pos) @@ -332,7 +336,7 @@ end ---Get an item object associated to a name or nil if not found. ---@param name string ----@return StatusView.Item | nil +---@return core.statusview.item | nil function StatusView:get_item(name) for _, item in ipairs(self.items) do if item.name == name then return item end @@ -342,8 +346,8 @@ end ---Get a list of items. ----@param alignment? StatusView.Item.alignment ----@return StatusView.Item[] +---@param alignment? core.statusview.item.alignment +---@return core.statusview.item[] function StatusView:get_items_list(alignment) if alignment then local items = {} @@ -361,7 +365,7 @@ end ---Move an item to a different position. ---@param name string ---@param position integer Can be negative value to position in reverse order ----@param alignment? StatusView.Item.alignment +---@param alignment? core.statusview.item.alignment ---@return boolean moved function StatusView:move_item(name, position, alignment) assert(name, "no name provided") @@ -387,7 +391,7 @@ end ---Remove an item from the status view. ---@param name string ----@return StatusView.Item removed_item +---@return core.statusview.item removed_item function StatusView:remove_item(name) local item = nil for pos, it in ipairs(self.items) do @@ -493,8 +497,8 @@ end ---Activates tooltip mode displaying only the given ----text until StatusView:remove_tooltip() is called. ----@param text string | StatusView.styledtext +---text until core.statusview:remove_tooltip() is called. +---@param text string | core.statusview.styledtext function StatusView:show_tooltip(text) self.tooltip = type(text) == "table" and text or { text } self.tooltip_mode = true @@ -508,8 +512,8 @@ end ---Helper function to draw the styled text. ----@param self StatusView ----@param items StatusView.styledtext +---@param self core.statusview +---@param items core.statusview.styledtext ---@param x number ---@param y number ---@param draw_fn fun(font,color,text,align, x,y,w,h):number @@ -542,8 +546,8 @@ end ---Draws a table of styled text on the status bar starting on the left or right. ----@param items StatusView.styledtext ----@param right_align boolean +---@param items core.statusview.styledtext +---@param right_align? boolean ---@param xoffset? number ---@param yoffset? number function StatusView:draw_items(items, right_align, xoffset, yoffset) @@ -562,7 +566,7 @@ end ---Draw the tooltip of a given status bar item. ----@param item StatusView.Item +---@param item core.statusview.item function StatusView:draw_item_tooltip(item) core.root_view:defer_draw(function() local text = item.tooltip @@ -613,10 +617,10 @@ end ---Helper function to copy a styled text table into another. ----@param t1 StatusView.styledtext ----@param t2 StatusView.styledtext +---@param t1 core.statusview.styledtext +---@param t2 core.statusview.styledtext local function table_add(t1, t2) - for i, value in ipairs(t2) do + for _, value in ipairs(t2) do table.insert(t1, value) end end @@ -624,8 +628,8 @@ end ---Helper function to merge deprecated items to a temp items table. ---@param destination table ----@param items StatusView.styledtext ----@param alignment StatusView.Item.alignment +---@param items core.statusview.styledtext +---@param alignment core.statusview.item.alignment local function merge_deprecated_items(destination, items, alignment) local start = true local items_start, items_end = {}, {} @@ -663,13 +667,13 @@ end ---Append a space item into the given items list. ----@param self StatusView ----@param destination StatusView.Item[] +---@param self core.statusview +---@param destination core.statusview.item[] ---@param separator string ----@param alignment StatusView.Item.alignment ----@return StatusView.Item +---@param alignment core.statusview.item.alignment +---@return core.statusview.item local function add_spacing(self, destination, separator, alignment, x) - ---@type StatusView.Item + ---@type core.statusview.item local space = StatusView.Item(nil, "space", alignment) space.cached_item = separator == self.separator and { style.text, separator @@ -686,8 +690,8 @@ end ---Remove starting and ending separators. ----@param self StatusView ----@param styled_text StatusView.styledtext +---@param self core.statusview +---@param styled_text core.statusview.styledtext local function remove_spacing(self, styled_text) if not Object.is(styled_text[1], renderer.font) @@ -725,8 +729,6 @@ end ---of the status bar checking their predicates and performing positioning ---calculations for proper functioning of tooltips and clicks. function StatusView:update_active_items() - local left, right = {}, {} - local x = self:get_content_offset() local rx = x + self.size.x @@ -735,7 +737,7 @@ function StatusView:update_active_items() self.active_items = {} - ---@type StatusView.Item[] + ---@type core.statusview.item[] local combined_items = {} table_add(combined_items, self.items) @@ -749,7 +751,7 @@ function StatusView:update_active_items() -- calculate left and right width for _, item in ipairs(combined_items) do item.cached_item = {} - if item.visible and item.predicate(self) then + if item.visible and item:predicate() then local styled_text = type(item.get_item) == "function" and item.get_item(self) or item.get_item @@ -890,7 +892,7 @@ function StatusView:get_hovered_panel(x, y) end ----@param item StatusView.Item +---@param item core.statusview.item ---@return number x ---@return number w function StatusView:get_item_visible_area(item) @@ -1056,8 +1058,8 @@ end ---Retrieve the hover status and proper background color if any. ----@param self StatusView ----@param item StatusView.Item +---@param self core.statusview +---@param item core.statusview.item ---@return boolean is_hovered ---@return renderer.color | nil color local function get_item_bg_color(self, item) diff --git a/data/core/titleview.lua b/data/core/titleview.lua index 9090861c..f9d7a961 100644 --- a/data/core/titleview.lua +++ b/data/core/titleview.lua @@ -17,6 +17,8 @@ local title_commands = { {symbol = "X", action = function() core.quit() end}, } +---@class core.titleview : core.view +---@field super core.view local TitleView = View:extend() local function title_view_height() diff --git a/data/core/view.lua b/data/core/view.lua index 1f8a9208..04d01230 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -4,7 +4,51 @@ local style = require "core.style" local common = require "core.common" local Object = require "core.object" +---@class core.view.position +---@field x number +---@field y number +---@class core.view.scroll +---@field x number +---@field y number +---@field to core.view.position + +---@class core.view.thumbtrack +---@field thumb number +---@field track number + +---@class core.view.thumbtrackwidth +---@field thumb number +---@field track number +---@field to core.view.thumbtrack + +---@class core.view.scrollbar +---@field x core.view.thumbtrack +---@field y core.view.thumbtrack +---@field w core.view.thumbtrackwidth +---@field h core.view.thumbtrack + +---@class core.view.increment +---@field value number +---@field to number + +---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'" + +---@alias core.view.mousebutton "'left'" | "'right'" + +---@alias core.view.context "'application'" | "'session'" + +---Base view. +---@class core.view : core.object +---@field context core.view.context +---@field super core.object +---@field position core.view.position +---@field size core.view.position +---@field scroll core.view.scroll +---@field cursor core.view.cursor +---@field scrollable boolean +---@field scrollbar core.view.scrollbar +---@field scrollbar_alpha core.view.increment local View = Object:extend() -- context can be "application" or "session". The instance of objects @@ -19,11 +63,11 @@ function View:new() self.cursor = "arrow" self.scrollable = false self.scrollbar = { - x = { thumb = 0, track = 0 }, - y = { thumb = 0, track = 0 }, - w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } }, - h = { thumb = 0, track = 0 }, - } + x = { thumb = 0, track = 0 }, + y = { thumb = 0, track = 0 }, + w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } }, + h = { thumb = 0, track = 0 }, + } self.scrollbar_alpha = { value = 0, to = 0 } end @@ -54,16 +98,22 @@ function View:try_close(do_close) end +---@return string function View:get_name() return "---" end +---@return number function View:get_scrollable_size() return math.huge end +---@return number x +---@return number y +---@return number width +---@return number height function View:get_scrollbar_track_rect() local sz = self:get_scrollable_size() if sz <= self.size.y or sz == math.huge then @@ -81,6 +131,10 @@ function View:get_scrollbar_track_rect() end +---@return number x +---@return number y +---@return number width +---@return number height function View:get_scrollbar_rect() local sz = self:get_scrollable_size() if sz <= self.size.y or sz == math.huge then @@ -99,17 +153,28 @@ function View:get_scrollbar_rect() end +---@param x number +---@param y number +---@return boolean function View:scrollbar_overlaps_point(x, y) local sx, sy, sw, sh = self:get_scrollbar_rect() return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh end +---@param x number +---@param y number +---@return boolean function View:scrollbar_track_overlaps_point(x, y) local sx, sy, sw, sh = self:get_scrollbar_track_rect() return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh end +---@param button core.view.mousebutton +---@param x number +---@param y number +---@param clicks integer +---return boolean function View:on_mouse_pressed(button, x, y, clicks) if self:scrollbar_track_overlaps_point(x, y) then if self:scrollbar_overlaps_point(x, y) then @@ -125,11 +190,18 @@ function View:on_mouse_pressed(button, x, y, clicks) end +---@param button core.view.mousebutton +---@param x number +---@param y number function View:on_mouse_released(button, x, y) self.dragging_scrollbar = false end +---@param x number +---@param y number +---@param dx number +---@param dy number function View:on_mouse_moved(x, y, dx, dy) if self.dragging_scrollbar then local delta = self:get_scrollable_size() / self.size.y * dy @@ -150,15 +222,22 @@ function View:on_mouse_left() end +---@param filename string +---@param x number +---@param y number +---@return boolean function View:on_file_dropped(filename, x, y) return false end +---@param text string function View:on_text_input(text) -- no-op end +---@param y number +---@return boolean function View:on_mouse_wheel(y) end @@ -170,6 +249,8 @@ function View:get_content_bounds() end +---@return number x +---@return number y function View:get_content_offset() local x = common.round(self.position.x - self.scroll.x) local y = common.round(self.position.y - self.scroll.y) @@ -213,6 +294,7 @@ function View:update() end +---@param color renderer.color function View:draw_background(color) local x, y = self.position.x, self.position.y local w, h = self.size.x, self.size.y diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 5bd57783..bdcb0f49 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -766,7 +766,7 @@ command.add(function() return treeitem() ~= nil end, { local text local item = treeitem() if not is_project_folder(item.abs_filename) then - text = treeitem().filename .. PATHSEP + text = item.filename .. PATHSEP end core.command_view:enter("Filename", { text = text, @@ -789,7 +789,7 @@ command.add(function() return treeitem() ~= nil end, { local text local item = treeitem() if not is_project_folder(item.abs_filename) then - text = treeitem().filename .. PATHSEP + text = item.filename .. PATHSEP end core.command_view:enter("Folder Name", { text = text, From 82915bb217c498080888b62ac8b6180b986ddb4d Mon Sep 17 00:00:00 2001 From: jgmdev Date: Tue, 7 Jun 2022 22:29:21 -0400 Subject: [PATCH 316/409] changelog: added links to releases --- changelog.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/changelog.md b/changelog.md index bf61310d..f283ad4e 100644 --- a/changelog.md +++ b/changelog.md @@ -790,3 +790,28 @@ A new global variable `USERDIR` is exposed to point to the user's directory. ## [1.06] - 2020-05-31 - subpixel font rendering with gamma correction + +[2.1.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.1.0 +[2.0.5]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.5 +[2.0.4]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.4 +[2.0.3]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.3 +[2.0.2]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.2 +[2.0.1]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.1 +[2.0]: https://github.com/lite-xl/lite-xl/releases/tag/v2.0.0 +[1.16.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.11 +[1.16.10]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.10 +[1.16.9]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.9 +[1.16.8]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.8 +[1.16.7]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.7 +[1.16.6]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.6 +[1.16.5]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.5 +[1.16.4]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.4 +[1.16.2]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.2-lite-xl +[1.16.1]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.1-lite-xl +[1.16]: https://github.com/lite-xl/lite-xl/releases/tag/v1.16.0-lite-xl +[1.15]: https://github.com/lite-xl/lite-xl/releases/tag/v1.15-lite-xl +[1.14]: https://github.com/lite-xl/lite-xl/releases/tag/v1.14-lite-xl +[1.13]: https://github.com/lite-xl/lite-xl/releases/tag/v1.13-lite-xl +[1.11]: https://github.com/lite-xl/lite-xl/releases/tag/v1.11-lite-xl +[1.08]: https://github.com/lite-xl/lite-xl/releases/tag/v1.08-subpixel +[1.06]: https://github.com/lite-xl/lite-xl/releases/tag/1.06-subpixel-rc1 From ed02a55cc1bad64420f1df6b2a4aaac161525b54 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Fri, 10 Jun 2022 23:55:41 +0200 Subject: [PATCH 317/409] `language_md`: Add math delimiters --- data/plugins/language_md.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index c469855c..93d937b1 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -110,6 +110,9 @@ syntax.add { { pattern = "&#?[a-zA-Z0-9]+;", type = "keyword2" }, ---- Markdown rules + -- math + { pattern = { "%$%$", "%$%$", "\\" }, type = "string", syntax = ".tex"}, + { pattern = { "%$", "%$", "\\" }, type = "string", syntax = ".tex"}, -- code blocks { pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" }, { pattern = { "```cpp", "```" }, type = "string", syntax = ".cpp" }, From 99d328cfd793420bd1890555330b71384616b290 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 11 Jun 2022 05:13:37 +0200 Subject: [PATCH 318/409] Downgrade `StatusView:get_items` deprecation message to warning --- data/core/statusview.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 497e5e2e..32882e75 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -606,7 +606,7 @@ end ---@return table right function StatusView:get_items(nowarn) if not nowarn and not self.get_items_warn then - core.error( + core.warn( "Overriding StatusView:get_items() is deprecated, " .. "use core.status_view:add_item() instead." ) From 685956cbdb1dd4e1b792e6034b94920d90e089e5 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 11 Jun 2022 06:21:55 +0200 Subject: [PATCH 319/409] Add `Highlighter:update_notify` to keep track of retokenized lines This is helpful for plugins that need to know when a line has been retokenized. --- data/core/doc/highlighter.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/data/core/doc/highlighter.lua b/data/core/doc/highlighter.lua index 9ba7b634..888c82aa 100644 --- a/data/core/doc/highlighter.lua +++ b/data/core/doc/highlighter.lua @@ -22,13 +22,21 @@ function Highlighter:new(doc) else local max = math.min(self.first_invalid_line + 40, self.max_wanted_line) + local retokenized_from for i = self.first_invalid_line, max do local state = (i > 1) and self.lines[i - 1].state local line = self.lines[i] if not (line and line.init_state == state and line.text == self.doc.lines[i]) then + retokenized_from = retokenized_from or i self.lines[i] = self:tokenize_line(i, state) + elseif retokenized_from then + self:update_notify(retokenized_from, i - retokenized_from - 1) + retokenized_from = nil end end + if retokenized_from then + self:update_notify(retokenized_from, max - retokenized_from) + end self.first_invalid_line = max + 1 core.redraw = true @@ -71,6 +79,10 @@ function Highlighter:remove_notify(line, n) common.splice(self.lines, line, n) end +function Highlighter:update_notify(line, n) + -- plugins can hook here to be notified that lines have been retokenized +end + function Highlighter:tokenize_line(idx, state) local res = {} @@ -87,6 +99,7 @@ function Highlighter:get_line(idx) local prev = self.lines[idx - 1] line = self:tokenize_line(idx, prev and prev.state) self.lines[idx] = line + self:update_notify(idx, 0) end self.max_wanted_line = math.max(self.max_wanted_line, idx) return line From f38723ea4698bf74b174625aba01731315123854 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sat, 11 Jun 2022 06:30:13 +0200 Subject: [PATCH 320/409] `drawwhitespace`: Cache whitespace location --- data/plugins/drawwhitespace.lua | 174 +++++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 47 deletions(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 6186eac6..46709e20 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -4,6 +4,7 @@ local style = require "core.style" local DocView = require "core.docview" local common = require "core.common" local config = require "core.config" +local Highlighter = require "core.doc.highlighter" config.plugins.drawwhitespace = common.merge({ enabled = true, @@ -95,6 +96,52 @@ config.plugins.drawwhitespace = common.merge({ } }, config.plugins.drawwhitespace) + +local ws_cache = setmetatable({}, { __mode = "k" }) + +-- Move cache to make space for new lines +local prev_insert_notify = Highlighter.insert_notify +function Highlighter:insert_notify(line, n, ...) + prev_insert_notify(self, line, n, ...) + local blanks = { } + if not ws_cache[self] then + ws_cache[self] = {} + end + local to = math.min(line + n, #self.doc.lines) + for i=#self.doc.lines+n,to,-1 do + ws_cache[self][i] = ws_cache[self][i - n] + end + for i=line,to do + ws_cache[self][i] = nil + end +end + +-- Close the cache gap created by removed lines +local prev_remove_notify = Highlighter.remove_notify +function Highlighter:remove_notify(line, n, ...) + prev_remove_notify(self, line, n, ...) + if not ws_cache[self] then + ws_cache[self] = {} + end + local to = math.max(line + n, #self.doc.lines) + for i=line,to do + ws_cache[self][i] = ws_cache[self][i + n] + end +end + +-- Remove changed lines from the cache +local prev_update_notify = Highlighter.update_notify +function Highlighter:update_notify(line, n, ...) + prev_update_notify(self, line, n, ...) + if not ws_cache[self] then + ws_cache[self] = {} + end + for i=line,line+n do + ws_cache[self][i] = nil + end +end + + local function get_option(substitution, option) if substitution[option] == nil then return config.plugins.drawwhitespace[option] @@ -114,66 +161,99 @@ function DocView:draw_line_text(idx, x, y) local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) local ty = y + self:get_line_text_y_offset() - local tx - local text, offset, s, e = self.doc.lines[idx], 1 - local x1, _, x2, _ = self:get_content_bounds() - local _offset = self:get_x_offset_col(idx, x1) - for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do - local char = substitution.char - local sub = substitution.sub - offset = _offset + if + not ws_cache[self.doc.highlighter] + or ws_cache[self.doc.highlighter].font ~= font + then + ws_cache[self.doc.highlighter] = {font = font} + end - local show_leading = get_option(substitution, "show_leading") - local show_middle = get_option(substitution, "show_middle") - local show_trailing = get_option(substitution, "show_trailing") + if not ws_cache[self.doc.highlighter][idx] then -- need to cache line + local cache = {} - local show_middle_min = get_option(substitution, "show_middle_min") + local tx + local text = self.doc.lines[idx] - local base_color = get_option(substitution, "color") - local leading_color = get_option(substitution, "leading_color") or base_color - local middle_color = get_option(substitution, "middle_color") or base_color - local trailing_color = get_option(substitution, "trailing_color") or base_color + for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do + local char = substitution.char + local sub = substitution.sub + local offset = 1 - local pattern = char.."+" - while true do - s, e = text:find(pattern, offset) - if not s then break end + local show_leading = get_option(substitution, "show_leading") + local show_middle = get_option(substitution, "show_middle") + local show_trailing = get_option(substitution, "show_trailing") - tx = self:get_col_x_offset(idx, s) + x + local show_middle_min = get_option(substitution, "show_middle_min") - local color = base_color - local draw = false + local base_color = get_option(substitution, "color") + local leading_color = get_option(substitution, "leading_color") or base_color + local middle_color = get_option(substitution, "middle_color") or base_color + local trailing_color = get_option(substitution, "trailing_color") or base_color - if e == #text - 1 then - draw = show_trailing - color = trailing_color - elseif s == 1 then - draw = show_leading - color = leading_color - else - draw = show_middle and (e - s + 1 >= show_middle_min) - color = middle_color - end + local pattern = char.."+" + while true do + local s, e = text:find(pattern, offset) + if not s then break end - if draw then - -- We need to draw tabs one at a time because they might have a - -- different size than the substituting character. - -- This also applies to any other char if we use non-monospace fonts - -- but we ignore this case for now. - if char == "\t" then - for i = s,e do - tx = self:get_col_x_offset(idx, i) + x - tx = renderer.draw_text(font, sub, tx, ty, color) - end + tx = self:get_col_x_offset(idx, s) + + local color = base_color + local draw = false + + if e == #text - 1 then + draw = show_trailing + color = trailing_color + elseif s == 1 then + draw = show_leading + color = leading_color else - tx = renderer.draw_text(font, string.rep(sub, e - s + 1), tx, ty, color) + draw = show_middle and (e - s + 1 >= show_middle_min) + color = middle_color end - end - if tx > x + x2 then break end - offset = e + 1 + if draw then + local last_cache_idx = #cache + -- We need to draw tabs one at a time because they might have a + -- different size than the substituting character. + -- This also applies to any other char if we use non-monospace fonts + -- but we ignore this case for now. + if char == "\t" then + for i = s,e do + tx = self:get_col_x_offset(idx, i) + cache[last_cache_idx + 1] = sub + cache[last_cache_idx + 2] = tx + cache[last_cache_idx + 3] = font:get_width(sub) + cache[last_cache_idx + 4] = color + last_cache_idx = last_cache_idx + 4 + end + else + cache[last_cache_idx + 1] = string.rep(sub, e - s + 1) + cache[last_cache_idx + 2] = tx + cache[last_cache_idx + 3] = font:get_width(cache[last_cache_idx + 1]) + cache[last_cache_idx + 4] = color + end + end + offset = e + 1 + end end + ws_cache[self.doc.highlighter][idx] = cache + end + + -- draw from cache + local x1, _, x2, _ = self:get_content_bounds() + x1 = x1 + x + x2 = x2 + x + local cache = ws_cache[self.doc.highlighter][idx] + for i=1,#cache,4 do + local sub = cache[i] + local tx = cache[i + 1] + x + local tw = cache[i + 2] + local color = cache[i + 3] + if tx + tw >= x1 then + tx = renderer.draw_text(font, sub, tx, y, color) + end + if tx > x2 then break end end return draw_line_text(self, idx, x, y) From dc2e9621ca9362da7809b25e34d1116093f8dc0a Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sat, 11 Jun 2022 21:01:12 +0800 Subject: [PATCH 321/409] lua patch for utf-8 support --- .gitignore | 2 + resources/windows/001-lua-unicode.diff | 195 +++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 resources/windows/001-lua-unicode.diff diff --git a/.gitignore b/.gitignore index 75dec714..13052b48 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ compile_commands.json error.txt lite-xl* LiteXL* + +!resources/windows/*.diff diff --git a/resources/windows/001-lua-unicode.diff b/resources/windows/001-lua-unicode.diff new file mode 100644 index 00000000..8306d354 --- /dev/null +++ b/resources/windows/001-lua-unicode.diff @@ -0,0 +1,195 @@ +diff -ruN lua-5.4.3/meson.build newlua/meson.build +--- lua-5.4.3/meson.build 2022-05-29 21:04:17.850449500 +0800 ++++ newlua/meson.build 2022-06-10 19:23:55.685139800 +0800 +@@ -82,6 +82,7 @@ + 'src/lutf8lib.c', + 'src/lvm.c', + 'src/lzio.c', ++ 'src/utf8_wrappers.c', + dependencies: lua_lib_deps, + override_options: project_options, + implicit_include_directories: false, +Binary files lua-5.4.3/src/lua54.dll and newlua/src/lua54.dll differ +diff -ruN lua-5.4.3/src/luaconf.h newlua/src/luaconf.h +--- lua-5.4.3/src/luaconf.h 2021-03-15 21:32:52.000000000 +0800 ++++ newlua/src/luaconf.h 2022-06-10 19:15:03.014745300 +0800 +@@ -786,5 +786,15 @@ + + + ++#if defined(lua_c) || defined(luac_c) || (defined(LUA_LIB) && \ ++ (defined(lauxlib_c) || defined(liolib_c) || \ ++ defined(loadlib_c) || defined(loslib_c))) ++#include "utf8_wrappers.h" ++#endif ++ ++ ++ ++ ++ + #endif + +diff -ruN lua-5.4.3/src/Makefile newlua/src/Makefile +--- lua-5.4.3/src/Makefile 2021-02-10 02:47:17.000000000 +0800 ++++ newlua/src/Makefile 2022-06-10 19:22:45.267931400 +0800 +@@ -33,7 +33,7 @@ + PLATS= guess aix bsd c89 freebsd generic linux linux-readline macosx mingw posix solaris + + LUA_A= liblua.a +-CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o ++CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o lundump.o lvm.o lzio.o utf8_wrappers.o + LIB_O= lauxlib.o lbaselib.o lcorolib.o ldblib.o liolib.o lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o linit.o + BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS) + + +diff -ruN lua-5.4.3/src/utf8_wrappers.c newlua/src/utf8_wrappers.c +--- lua-5.4.3/src/utf8_wrappers.c 1970-01-01 07:30:00.000000000 +0730 ++++ newlua/src/utf8_wrappers.c 2022-06-10 19:13:11.904613300 +0800 +@@ -0,0 +1,101 @@ ++/** ++ * Wrappers to provide Unicode (UTF-8) support on Windows. ++ * ++ * Copyright (c) 2018 Peter Wu ++ * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) ++ */ ++ ++#ifdef _WIN32 ++#include /* for MultiByteToWideChar */ ++#include /* for _wrename */ ++#include ++#include ++#include ++ ++// Set a high limit in case long paths are enabled. ++#define MAX_PATH_SIZE 4096 ++#define MAX_MODE_SIZE 128 ++// cmd.exe argument length is reportedly limited to 8192. ++#define MAX_CMD_SIZE 8192 ++ ++FILE *fopen_utf8(const char *pathname, const char *mode) { ++ wchar_t pathname_w[MAX_PATH_SIZE]; ++ wchar_t mode_w[MAX_MODE_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) || ++ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { ++ errno = EINVAL; ++ return NULL; ++ } ++ return _wfopen(pathname_w, mode_w); ++} ++ ++FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream) { ++ wchar_t pathname_w[MAX_PATH_SIZE]; ++ wchar_t mode_w[MAX_MODE_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE) || ++ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { ++ // Close stream as documented for the error case. ++ fclose(stream); ++ errno = EINVAL; ++ return NULL; ++ } ++ return _wfreopen(pathname_w, mode_w, stream); ++} ++ ++int remove_utf8(const char *pathname) { ++ wchar_t pathname_w[MAX_PATH_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pathname, -1, pathname_w, MAX_PATH_SIZE)) { ++ errno = EINVAL; ++ return -1; ++ } ++ return _wremove(pathname_w); ++} ++ ++int rename_utf8(const char *oldpath, const char *newpath) { ++ wchar_t oldpath_w[MAX_PATH_SIZE]; ++ wchar_t newpath_w[MAX_PATH_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, oldpath, -1, oldpath_w, MAX_PATH_SIZE) || ++ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, newpath, -1, newpath_w, MAX_PATH_SIZE)) { ++ errno = EINVAL; ++ return -1; ++ } ++ return _wrename(oldpath_w, newpath_w); ++} ++ ++FILE *popen_utf8(const char *command, const char *mode) { ++ wchar_t command_w[MAX_CMD_SIZE]; ++ wchar_t mode_w[MAX_MODE_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE) || ++ !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, mode_w, MAX_MODE_SIZE)) { ++ errno = EINVAL; ++ return NULL; ++ } ++ return _wpopen(command_w, mode_w); ++} ++ ++int system_utf8(const char *command) { ++ wchar_t command_w[MAX_CMD_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, command, -1, command_w, MAX_CMD_SIZE)) { ++ errno = EINVAL; ++ return -1; ++ } ++ return _wsystem(command_w); ++} ++ ++DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize) { ++ wchar_t filename_w[MAX_PATH + 1]; ++ if (!GetModuleFileNameW(hModule, filename_w, MAX_PATH + 1)) { ++ return 0; ++ } ++ return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, filename_w, -1, lpFilename, nSize, NULL, NULL); ++} ++ ++HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { ++ wchar_t pathname_w[MAX_PATH_SIZE]; ++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, lpLibFileName, -1, pathname_w, MAX_PATH_SIZE)) { ++ SetLastError(ERROR_INVALID_NAME); ++ return NULL; ++ } ++ return LoadLibraryExW(pathname_w, hFile, dwFlags); ++} ++#endif +diff -ruN lua-5.4.3/src/utf8_wrappers.h newlua/src/utf8_wrappers.h +--- lua-5.4.3/src/utf8_wrappers.h 1970-01-01 07:30:00.000000000 +0730 ++++ newlua/src/utf8_wrappers.h 2022-06-10 19:22:53.554879400 +0800 +@@ -0,0 +1,42 @@ ++/** ++ * Wrappers to provide Unicode (UTF-8) support on Windows. ++ * ++ * Copyright (c) 2018 Peter Wu ++ * SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) ++ */ ++ ++#ifdef _WIN32 ++ ++#if defined(loadlib_c) || defined(lauxlib_c) || defined(liolib_c) || defined(luac_c) ++#include /* for loadlib_c */ ++FILE *fopen_utf8(const char *pathname, const char *mode); ++#define fopen fopen_utf8 ++#endif ++ ++#ifdef lauxlib_c ++FILE *freopen_utf8(const char *pathname, const char *mode, FILE *stream); ++#define freopen freopen_utf8 ++#endif ++ ++#ifdef liolib_c ++FILE *popen_utf8(const char *command, const char *mode); ++#define _popen popen_utf8 ++#endif ++ ++#ifdef loslib_c ++int remove_utf8(const char *pathname); ++int rename_utf8(const char *oldpath, const char *newpath); ++int system_utf8(const char *command); ++#define remove remove_utf8 ++#define rename rename_utf8 ++#define system system_utf8 ++#endif ++ ++#ifdef loadlib_c ++#include ++DWORD GetModuleFileNameA_utf8(HMODULE hModule, LPSTR lpFilename, DWORD nSize); ++HMODULE LoadLibraryExA_utf8(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); ++#define GetModuleFileNameA GetModuleFileNameA_utf8 ++#define LoadLibraryExA LoadLibraryExA_utf8 ++#endif ++#endif From 73cd768a195fdbc97b5cb0759b50172e5aa52f81 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Sun, 12 Jun 2022 08:22:01 +0800 Subject: [PATCH 322/409] load space metrics only instead of all metrics of the 1st 256 characters --- src/renderer.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/renderer.c b/src/renderer.c index a2c2ddb9..144e2e46 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -39,7 +39,7 @@ typedef struct { typedef struct { SDL_Surface* surface; - GlyphMetric metrics[MAX_GLYPHSET]; + GlyphMetric metrics[MAX_GLYPHSET]; } GlyphSet; typedef struct RenFont { @@ -71,7 +71,7 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) { } static int font_set_load_options(RenFont* font) { - int load_target = font->antialiasing == FONT_ANTIALIASING_NONE ? FT_LOAD_TARGET_MONO + int load_target = font->antialiasing == FONT_ANTIALIASING_NONE ? FT_LOAD_TARGET_MONO : (font->hinting == FONT_HINTING_SLIGHT ? FT_LOAD_TARGET_LIGHT : FT_LOAD_TARGET_NORMAL); int hinting = font->hinting == FONT_HINTING_NONE ? FT_LOAD_NO_HINTING : FT_LOAD_FORCE_AUTOHINT; return load_target | hinting; @@ -148,7 +148,7 @@ static void font_load_glyphset(RenFont* font, int idx) { FT_GlyphSlot slot = font->face->glyph; font_set_style(&slot->outline, (64 / bitmaps_cached) * j, font->style); if (FT_Render_Glyph(slot, render_option)) - continue; + continue; for (unsigned int line = 0; line < slot->bitmap.rows; ++line) { int target_offset = set->surface->pitch * line + set->metrics[i].x0 * byte_width; int source_offset = line * slot->bitmap.pitch; @@ -206,10 +206,13 @@ RenFont* ren_font_load(const char* path, float size, ERenFontAntialiasing antial font->antialiasing = antialiasing; font->hinting = hinting; font->style = style; - font->space_advance = font_get_glyphset(font, ' ', 0)->metrics[' '].xadvance; + + if (FT_Load_Char(face, ' ', font_set_load_options(font))) + goto failure; + font->space_advance = face->glyph->advance.x / 64.0f; font->tab_advance = font->space_advance * 2; return font; - failure: + failure: FT_Done_Face(face); return NULL; } @@ -234,7 +237,7 @@ void ren_font_free(RenFont* font) { void ren_font_group_set_tab_size(RenFont **fonts, int n) { for (int j = 0; j < FONT_FALLBACK_MAX && fonts[j]; ++j) { - for (int i = 0; i < (fonts[j]->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? SUBPIXEL_BITMAPS_CACHED : 1); ++i) + for (int i = 0; i < (fonts[j]->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? SUBPIXEL_BITMAPS_CACHED : 1); ++i) font_get_glyphset(fonts[j], '\t', i)->metrics['\t'].xadvance = fonts[j]->space_advance * n; } } @@ -281,11 +284,11 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor const char* end = text + strlen(text); uint8_t* destination_pixels = surface->pixels; int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height; - + while (text < end) { unsigned int codepoint, r, g, b; text = utf8_to_codepoint(text, &codepoint); - GlyphSet* set = NULL; GlyphMetric* metric = NULL; + GlyphSet* set = NULL; GlyphMetric* metric = NULL; RenFont* font = font_group_get_glyph(&set, &metric, fonts, codepoint, (int)(fmod(pen_x, 1.0) * SUBPIXEL_BITMAPS_CACHED)); if (!metric) break; From c947e8a4d1a40a04fcd26b3728a5e6a4b3dfcef0 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 12 Jun 2022 02:55:36 +0200 Subject: [PATCH 323/409] Convert more byte offsets to utf-8 pos in regex tokenizer --- data/core/tokenizer.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 555d60b5..14c46bd1 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -175,27 +175,29 @@ function tokenizer.tokenize(incoming_syntax, text, state) res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) } or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) } if p.regex and #res > 0 then -- set correct utf8 len for regex result - res[2] = res[1] + string.ulen(text:sub(res[1], res[2])) - 1 + local char_pos_1 = string.ulen(text:sub(1, res[1])) + local char_pos_2 = char_pos_1 + string.ulen(text:sub(res[1], res[2])) - 1 -- `regex.match` returns group results as a series of `begin, end` -- we only want `begin`s if #res >= 3 then - res[3] = res[1] + string.ulen(text:sub(res[1], res[3])) - 1 + res[3] = char_pos_1 + string.ulen(text:sub(res[1], res[3])) - 1 end for i=1,(#res-3) do local curr = i + 3 local from = i * 2 + 3 if from < #res then - res[curr] = res[1] + string.ulen(text:sub(res[1], res[from])) - 1 + res[curr] = char_pos_1 + string.ulen(text:sub(res[1], res[from])) - 1 else res[curr] = nil end end - res[1] = next + res[1] = char_pos_1 + res[2] = char_pos_2 end if res[1] and close and target[3] then local count = 0 for i = res[1] - 1, 1, -1 do - if text:byte(i) ~= target[3]:byte() then break end + if text:ubyte(i) ~= target[3]:ubyte() then break end count = count + 1 end -- Check to see if the escaped character is there, From 5b6b48320f3a5dc581069037358a3cbc4d20d3d9 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Sun, 12 Jun 2022 04:19:05 +0200 Subject: [PATCH 324/409] Check if "open" pattern is escaped Previously this check was only done for "close" patterns. --- data/core/tokenizer.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 555d60b5..9231cce7 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -192,15 +192,21 @@ function tokenizer.tokenize(incoming_syntax, text, state) end res[1] = next end - if res[1] and close and target[3] then + if res[1] and target[3] then + -- Check to see if the escaped character is there, + -- and if it is not itself escaped. local count = 0 for i = res[1] - 1, 1, -1 do if text:byte(i) ~= target[3]:byte() then break end count = count + 1 end - -- Check to see if the escaped character is there, - -- and if it is not itself escaped. - if count % 2 == 0 then break end + if count % 2 == 0 then + -- The match is not escaped, so confirm it + break + elseif not close then + -- The *open* match is escaped, so avoid it + return + end end until not res[1] or not close or not target[3] return table.unpack(res) From 093ae837ee184350e67bd4ae10a999e7f846a8ff Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:04:37 +0800 Subject: [PATCH 325/409] add .cache to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 75dec714..5ef4e966 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ lhelper/ submodules/ subprojects/*/ /appimage* +.cache .ccls-cache .lite-debug.log .run* From 0de90d542b20b908c14f024d6c7da12d4f84028a Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:05:20 +0800 Subject: [PATCH 326/409] omit filename from error message --- src/api/system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/system.c b/src/api/system.c index ff99ce6f..584dd7de 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -769,7 +769,7 @@ static int f_load_native_plugin(lua_State *L) { const char *path = luaL_checkstring(L, 2); void *library = SDL_LoadObject(path); if (!library) - return luaL_error(L, "Unable to load %s: %s", name, SDL_GetError()); + return (lua_pushstring(L, SDL_GetError()), lua_error(L)); lua_getglobal(L, "package"); lua_getfield(L, -1, "native_plugins"); From 42e0028f1c25062753e77cf1f1be41a9291f5945 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 15 Jun 2022 22:56:52 +0800 Subject: [PATCH 327/409] run GC between scale to prevent ram from exploding --- data/plugins/scale.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index cb331f7a..2d638ddc 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -108,10 +108,12 @@ end local function inc_scale() set_scale(current_scale + scale_steps) + collectgarbage "step" end local function dec_scale() set_scale(current_scale - scale_steps) + collectgarbage "step" end From 7ecc17409450a429eeb97fd2db4c9c657f5d84af Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Wed, 15 Jun 2022 23:23:13 +0800 Subject: [PATCH 328/409] fix memory leak and wrong check in font_retrieve --- src/api/renderer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/renderer.c b/src/api/renderer.c index 3ab33b1a..d4df58e3 100644 --- a/src/api/renderer.c +++ b/src/api/renderer.c @@ -60,16 +60,16 @@ static int f_font_load(lua_State *L) { static bool font_retrieve(lua_State* L, RenFont** fonts, int idx) { memset(fonts, 0, sizeof(RenFont*)*FONT_FALLBACK_MAX); - if (lua_type(L, 1) != LUA_TTABLE) { + if (lua_type(L, idx) != LUA_TTABLE) { fonts[0] = *(RenFont**)luaL_checkudata(L, idx, API_TYPE_FONT); return false; } - int i = 0; - do { + int len = luaL_len(L, idx); len = len > FONT_FALLBACK_MAX ? FONT_FALLBACK_MAX : len; + for (int i = 0; i < len; i++) { lua_rawgeti(L, idx, i+1); - fonts[i] = !lua_isnil(L, -1) ? *(RenFont**)luaL_checkudata(L, -1, API_TYPE_FONT) : NULL; + fonts[i] = *(RenFont**) luaL_checkudata(L, -1, API_TYPE_FONT); lua_pop(L, 1); - } while (fonts[i] && i++ < FONT_FALLBACK_MAX); + } return true; } From d8a3987aa41af10e345e54d7f2ce19ae96db9e35 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 15 Jun 2022 12:15:14 -0400 Subject: [PATCH 329/409] changeslog: added performance section and listed PR 1032 --- changelog.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index f283ad4e..add902d0 100644 --- a/changelog.md +++ b/changelog.md @@ -106,6 +106,18 @@ * Config: added new development option to prevent plugin version checking at startup named [skip_plugins_version](https://github.com/lite-xl/lite-xl/pull/879) +### Performance Improvements + +* [Load space metrics only when creating font](https://github.com/lite-xl/lite-xl/pull/1032) + +* [Performance improvement](https://github.com/lite-xl/lite-xl/pull/883) + of detect indent plugin. + +* Improve performance of + [ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/935). + +* Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896). + ### Backward Incompatible Changes * [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which should improve performance, and provide useful extra functionality. It should @@ -237,9 +249,6 @@ * Fixed a bunch of problems relating to [multi-cursor](https://github.com/lite-xl/lite-xl/pull/886). -* [Performance improvement](https://github.com/lite-xl/lite-xl/pull/883) - of detect indent plugin. - * NagView: [support vscroll](https://github.com/lite-xl/lite-xl/pull/876) when message is too long. @@ -270,9 +279,6 @@ * [Autoreload Nagview](https://github.com/lite-xl/lite-xl/pull/942). -* Improve performance of - [ren_draw_rect](https://github.com/lite-xl/lite-xl/pull/935). - * [Enhancements to scrollbar](https://github.com/lite-xl/lite-xl/pull/916). * Set the correct working directory for the @@ -283,8 +289,6 @@ * [Added plugin load-time log](https://github.com/lite-xl/lite-xl/pull/966). -* Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896). - * TreeView improvements for [multi-project](https://github.com/lite-xl/lite-xl/pull/1010). From 5027a0f12bae97c494ff3700d4b5486b86fddfc9 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 15 Jun 2022 19:33:58 +0200 Subject: [PATCH 330/409] Fix malformed pattern check for group patterns in tokenizer If the token type was a simple string (and not a table), the size of the string was used instead of `1`. --- data/core/tokenizer.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index 555d60b5..b785c4ea 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -259,16 +259,16 @@ function tokenizer.tokenize(incoming_syntax, text, state) local matched = false for n, p in ipairs(current_syntax.patterns) do local find_results = { find_text(text, p, i, true, false) } - if #find_results - 1 > #p.type then - if not bad_patterns[current_syntax] then - bad_patterns[current_syntax] = { } - end - if not bad_patterns[current_syntax][n] then - bad_patterns[current_syntax][n] = true - core.error("Malformed pattern #%d in %s language plugin", n, current_syntax.name or "unnamed") - end - end if find_results[1] then + if #find_results - 1 > (type(p.type) == "table" and #p.type or 1) then + if not bad_patterns[current_syntax] then + bad_patterns[current_syntax] = { } + end + if not bad_patterns[current_syntax][n] then + bad_patterns[current_syntax][n] = true + core.error("Malformed pattern #%d in %s language plugin", n, current_syntax.name or "unnamed") + end + end -- matched pattern; make and add tokens push_tokens(res, current_syntax, p, text, find_results) -- update state if this was a start|end pattern pair From 2e37e85a48c453abe6b76196145f8890567c9d18 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 15 Jun 2022 21:28:46 +0200 Subject: [PATCH 331/409] Add helper function to report bad patterns in tokenizer --- data/core/tokenizer.lua | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index b785c4ea..b00d30e5 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -95,6 +95,16 @@ local function retrieve_syntax_state(incoming_syntax, state) return current_syntax, subsyntax_info, current_pattern_idx, current_level end +local function report_bad_pattern(log_fn, syntax, pattern_idx, msg, ...) + if not bad_patterns[syntax] then + bad_patterns[syntax] = { } + end + if bad_patterns[syntax][pattern_idx] then return end + bad_patterns[syntax][pattern_idx] = true + log_fn("Malformed pattern #%d in %s language plugin. " .. msg, + pattern_idx, syntax.name or "unnamed", ...) +end + ---@param incoming_syntax table ---@param text string ---@param state integer @@ -260,14 +270,14 @@ function tokenizer.tokenize(incoming_syntax, text, state) for n, p in ipairs(current_syntax.patterns) do local find_results = { find_text(text, p, i, true, false) } if find_results[1] then - if #find_results - 1 > (type(p.type) == "table" and #p.type or 1) then - if not bad_patterns[current_syntax] then - bad_patterns[current_syntax] = { } - end - if not bad_patterns[current_syntax][n] then - bad_patterns[current_syntax][n] = true - core.error("Malformed pattern #%d in %s language plugin", n, current_syntax.name or "unnamed") - end + local type_is_table = type(p.type) == "table" + local n_types = type_is_table and #p.type or 1 + if #find_results - 1 > n_types then + report_bad_pattern(core.error, current_syntax, n, + "Not enough token types: got %d needed %d.", n_types, #find_results - 1) + elseif #find_results - 1 < n_types then + report_bad_pattern(core.warn, current_syntax, n, + "Too many token types: got %d needed %d.", n_types, #find_results - 1) end -- matched pattern; make and add tokens push_tokens(res, current_syntax, p, text, find_results) From d169619f69189a4cbda8eb806abdc865906e8e58 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Wed, 15 Jun 2022 21:31:16 +0200 Subject: [PATCH 332/409] Warn if token type is a table when not needed --- data/core/tokenizer.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/core/tokenizer.lua b/data/core/tokenizer.lua index b00d30e5..fe826aae 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -272,7 +272,11 @@ function tokenizer.tokenize(incoming_syntax, text, state) if find_results[1] then local type_is_table = type(p.type) == "table" local n_types = type_is_table and #p.type or 1 - if #find_results - 1 > n_types then + if #find_results == 2 and type_is_table then + report_bad_pattern(core.warn, current_syntax, n, + "Token type is a table, but a string was expected.") + p.type = p.type[1] + elseif #find_results - 1 > n_types then report_bad_pattern(core.error, current_syntax, n, "Not enough token types: got %d needed %d.", n_types, #find_results - 1) elseif #find_results - 1 < n_types then From 2d3abd2533b2e57e7f140f34a9a0807098adf68c Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 16 Jun 2022 00:03:25 +0200 Subject: [PATCH 333/409] `drawwhitespace`: Invalidate cache on config changes --- data/plugins/drawwhitespace.lua | 41 ++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index 46709e20..ad84658d 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -97,13 +97,48 @@ config.plugins.drawwhitespace = common.merge({ }, config.plugins.drawwhitespace) -local ws_cache = setmetatable({}, { __mode = "k" }) +local ws_cache +local cached_settings +local function reset_cache() + ws_cache = setmetatable({}, { __mode = "k" }) + local settings = config.plugins.drawwhitespace + cached_settings = { + show_leading = settings.show_leading, + show_trailing = settings.show_trailing, + show_middle = settings.show_middle, + show_middle_min = settings.show_middle_min, + color = settings.color, + leading_color = settings.leading_color, + middle_color = settings.middle_color, + trailing_color = settings.trailing_color, + substitutions = settings.substitutions, + } +end +reset_cache() + +local function reset_cache_if_needed() + local settings = config.plugins.drawwhitespace + if + not ws_cache or + cached_settings.show_leading ~= settings.show_leading + or cached_settings.show_trailing ~= settings.show_trailing + or cached_settings.show_middle ~= settings.show_middle + or cached_settings.show_middle_min ~= settings.show_middle_min + or cached_settings.color ~= settings.color + or cached_settings.leading_color ~= settings.leading_color + or cached_settings.middle_color ~= settings.middle_color + or cached_settings.trailing_color ~= settings.trailing_color + -- we assume that the entire table changes + or cached_settings.substitutions ~= settings.substitutions + then + reset_cache() + end +end -- Move cache to make space for new lines local prev_insert_notify = Highlighter.insert_notify function Highlighter:insert_notify(line, n, ...) prev_insert_notify(self, line, n, ...) - local blanks = { } if not ws_cache[self] then ws_cache[self] = {} end @@ -160,8 +195,8 @@ function DocView:draw_line_text(idx, x, y) end local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) - local ty = y + self:get_line_text_y_offset() + reset_cache_if_needed() if not ws_cache[self.doc.highlighter] or ws_cache[self.doc.highlighter].font ~= font From bccc02743a362036b70412766ee4b01f017e1fc9 Mon Sep 17 00:00:00 2001 From: takase1121 <20792268+takase1121@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:35:38 +0800 Subject: [PATCH 334/409] add option to patch lua with UTF8 support --- scripts/build.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/build.sh b/scripts/build.sh index 75212468..c1df96f7 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -22,6 +22,7 @@ show_help() { echo "-B --bundle Create an App bundle (macOS only)" echo "-P --portable Create a portable binary package." echo "-O --pgo Use profile guided optimizations (pgo)." + echo "-U --windows-lua-utf Use the UTF8 patch for Lua." echo " macOS: disabled when used with --bundle," echo " Windows: Implicit being the only option." echo @@ -35,6 +36,9 @@ main() { local bundle local portable local pgo + local patch_lua + + local lua_subproject_path for i in "$@"; do case $i in @@ -76,6 +80,10 @@ main() { pgo="-Db_pgo=generate" shift ;; + -U|--windows-lua-utf) + patch_lua="true" + shift + ;; *) # unknown option ;; @@ -103,6 +111,11 @@ main() { $pgo \ "${build_dir}" + lua_subproject_path=subprojects/lua-*/ + if [[ $patch_lua == "true" ]] && [[ ! -z $force_fallback ]] && [[ -d $lua_subproject_path ]]; then + patch -d $lua_subproject_path -p1 --forward < resources/windows/001-lua-unicode.diff + fi + meson compile -C "${build_dir}" if [ ! -z ${pgo+x} ]; then From 4e1ce076104a2cc5cef4968ac7830112a37f6ea0 Mon Sep 17 00:00:00 2001 From: Takase <20792268+takase1121@users.noreply.github.com> Date: Fri, 17 Jun 2022 21:31:52 +0800 Subject: [PATCH 335/409] make system.* functions support UTF8 filenames (#1042) * make system.* functions support UTF8 filenames * move utfconv.h into ifdef guard * fix wrong null check --- src/api/system.c | 160 +++++++++++++++++++++++++++++++++++++++-------- src/utfconv.h | 57 +++++++++++++++++ 2 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 src/utfconv.h diff --git a/src/api/system.c b/src/api/system.c index 584dd7de..9dfff725 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -2,9 +2,8 @@ #include #include #include -#include -#include #include +#include #include #include "api.h" #include "../rencache.h" @@ -12,9 +11,16 @@ #include #include #include -#elif __linux__ + #include "../utfconv.h" +#else + +#include +#include + +#ifdef __linux__ #include #endif +#endif extern SDL_Window *window; @@ -125,6 +131,31 @@ static const char *get_key_name(const SDL_Event *e, char *buf) { } } +#ifdef _WIN32 +static char *win32_error(DWORD rc) { + LPSTR message; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + rc, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, + NULL + ); + + return message; +} + +static void push_win32_error(lua_State *L, DWORD rc) { + LPSTR message = win32_error(rc); + lua_pushstring(L, message); + LocalFree(message); +} +#endif + static int f_poll_event(lua_State *L) { char buf[16]; int mx, my, wx, wy; @@ -425,29 +456,14 @@ static int f_rmdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 - int deleted = RemoveDirectoryA(path); - if(deleted > 0) { + LPWSTR wpath = utfconv_utf8towc(path); + int deleted = RemoveDirectoryW(wpath); + free(wpath); + if (deleted > 0) { lua_pushboolean(L, 1); } else { - DWORD error_code = GetLastError(); - LPVOID message; - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &message, - 0, - NULL - ); - lua_pushboolean(L, 0); - lua_pushlstring(L, (LPCTSTR)message, lstrlen((LPCTSTR)message)); - LocalFree(message); - + push_win32_error(L, GetLastError()); return 2; } #else @@ -468,8 +484,15 @@ static int f_rmdir(lua_State *L) { static int f_chdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + LPWSTR wpath = utfconv_utf8towc(path); + if (wpath == NULL) { return luaL_error(L, UTFCONV_ERROR_INVALID_CONVERSION ); } + int err = _wchdir(wpath); + free(wpath); +#else int err = chdir(path); - if (err) { luaL_error(L, "chdir() failed"); } +#endif + if (err) { luaL_error(L, "chdir() failed: %s", strerror(errno)); } return 0; } @@ -477,6 +500,57 @@ static int f_chdir(lua_State *L) { static int f_list_dir(lua_State *L) { const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + lua_settop(L, 1); + if (strchr("\\/", path[strlen(path) - 2]) != NULL) + lua_pushstring(L, "*"); + else + lua_pushstring(L, "/*"); + + lua_concat(L, 2); + path = lua_tostring(L, -1); + + LPWSTR wpath = utfconv_utf8towc(path); + if (wpath == NULL) { + lua_pushnil(L); + lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); + return 2; + } + + WIN32_FIND_DATAW fd; + HANDLE find_handle = FindFirstFileExW(wpath, FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); + free(wpath); + if (find_handle == INVALID_HANDLE_VALUE) { + lua_pushnil(L); + push_win32_error(L, GetLastError()); + return 2; + } + + char mbpath[MAX_PATH * 4]; // utf-8 spans 4 bytes at most + int len, i = 1; + lua_newtable(L); + + do + { + if (wcscmp(fd.cFileName, L".") == 0) { continue; } + if (wcscmp(fd.cFileName, L"..") == 0) { continue; } + + len = WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, mbpath, MAX_PATH * 4, NULL, NULL); + if (len == 0) { break; } + lua_pushlstring(L, mbpath, len - 1); // len includes \0 + lua_rawseti(L, -2, i++); + } while (FindNextFileW(find_handle, &fd)); + + if (GetLastError() != ERROR_NO_MORE_FILES) { + lua_pushnil(L); + push_win32_error(L, GetLastError()); + FindClose(find_handle); + return 2; + } + + FindClose(find_handle); + return 1; +#else DIR *dir = opendir(path); if (!dir) { lua_pushnil(L); @@ -497,17 +571,29 @@ static int f_list_dir(lua_State *L) { closedir(dir); return 1; +#endif } #ifdef _WIN32 - #include - #define realpath(x, y) _fullpath(y, x, MAX_PATH) + #define realpath(x, y) _wfullpath(y, x, MAX_PATH) #endif static int f_absolute_path(lua_State *L) { const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + LPWSTR wpath = utfconv_utf8towc(path); + if (!wpath) { return 0; } + + LPWSTR wfullpath = realpath(wpath, NULL); + free(wpath); + if (!wfullpath) { return 0; } + + char *res = utfconv_wctoutf8(wfullpath); + free(wfullpath); +#else char *res = realpath(path, NULL); +#endif if (!res) { return 0; } lua_pushstring(L, res); free(res); @@ -518,8 +604,20 @@ static int f_absolute_path(lua_State *L) { static int f_get_file_info(lua_State *L) { const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + struct _stat s; + LPWSTR wpath = utfconv_utf8towc(path); + if (wpath == NULL) { + lua_pushnil(L); + lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); + return 2; + } + int err = _wstat(wpath, &s); + free(wpath); +#else struct stat s; int err = stat(path, &s); +#endif if (err < 0) { lua_pushnil(L); lua_pushstring(L, strerror(errno)); @@ -600,7 +698,15 @@ static int f_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); #ifdef _WIN32 - int err = _mkdir(path); + LPWSTR wpath = utfconv_utf8towc(path); + if (wpath == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, UTFCONV_ERROR_INVALID_CONVERSION); + return 2; + } + + int err = _wmkdir(wpath); + free(wpath); #else int err = mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); #endif diff --git a/src/utfconv.h b/src/utfconv.h new file mode 100644 index 00000000..059b3071 --- /dev/null +++ b/src/utfconv.h @@ -0,0 +1,57 @@ +#ifndef MBSEC_H +#define MBSEC_H + +#ifdef _WIN32 + +#include +#include + +#define UTFCONV_ERROR_INVALID_CONVERSION "Input contains invalid byte sequences." + +LPWSTR utfconv_utf8towc(const char *str) { + LPWSTR output; + int len; + + // len includes \0 + len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (len == 0) + return NULL; + + output = (LPWSTR) malloc(sizeof(WCHAR) * len); + if (output == NULL) + return NULL; + + len = MultiByteToWideChar(CP_UTF8, 0, str, -1, output, len); + if (len == 0) { + free(output); + return NULL; + } + + return output; +} + +char *utfconv_wctoutf8(LPCWSTR str) { + char *output; + int len; + + // len includes \0 + len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + if (len == 0) + return NULL; + + output = (char *) malloc(sizeof(char) * len); + if (output == NULL) + return NULL; + + len = WideCharToMultiByte(CP_UTF8, 0, str, -1, output, len, NULL, NULL); + if (len == 0) { + free(output); + return NULL; + } + + return output; +} + +#endif + +#endif From c2befaa83264a572f65e4057b71224a83c63ea74 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 17 Jun 2022 10:15:14 -0400 Subject: [PATCH 336/409] changelog: added latest changes --- changelog.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index add902d0..f965cf0b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ # Changes Log -## [2.1.0] - 2022-06-07 +## [2.1.0] - 202X-XX-XX ### New Features * Make distinction between @@ -118,6 +118,8 @@ * Improved [tokenizer performance](https://github.com/lite-xl/lite-xl/pull/896). +* drawwhitespace: [Cache whitespace location](https://github.com/lite-xl/lite-xl/pull/1030) + ### Backward Incompatible Changes * [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which should improve performance, and provide useful extra functionality. It should @@ -295,6 +297,16 @@ * Open LogView on user/project [module reload error](https://github.com/lite-xl/lite-xl/pull/1022). +* Check if ["open" pattern is escaped](https://github.com/lite-xl/lite-xl/pull/1034) + +* Support [UTF-8 on Windows](https://github.com/lite-xl/lite-xl/pull/1041) (Lua) + +* Make system.* functions support + [UTF8 filenames on windows](https://github.com/lite-xl/lite-xl/pull/1042) + +* [Fix memory leak](https://github.com/lite-xl/lite-xl/pull/1039) and wrong + check in font_retrieve + * Many, many, many more changes that are too numerous to list. ## [2.0.5] - 2022-01-29 From 3c682512e70b7a3f1589f9645261c90106b512ec Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 17 Jun 2022 11:04:53 -0400 Subject: [PATCH 337/409] build script: fix lua subproject expansion --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index c1df96f7..00eb367c 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -111,7 +111,7 @@ main() { $pgo \ "${build_dir}" - lua_subproject_path=subprojects/lua-*/ + lua_subproject_path=$(echo subprojects/lua-*/) if [[ $patch_lua == "true" ]] && [[ ! -z $force_fallback ]] && [[ -d $lua_subproject_path ]]; then patch -d $lua_subproject_path -p1 --forward < resources/windows/001-lua-unicode.diff fi From 173dd3aeb4891267e139dd752fada6c15abbdda9 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Fri, 17 Jun 2022 15:35:23 -0400 Subject: [PATCH 338/409] plugin treeview: fix crash When the max_project_files is set to a higher value than the allowed system maximum file descriptors, and opening a project directory that causes dirmonitor to open a watch on a lot of files or directories, at least on MacOSX it causes all system.* file functions to return nil (for too many opened files) which breaks the project files scan. --- data/plugins/treeview.lua | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index bdcb0f49..cc13ec59 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -153,28 +153,30 @@ function TreeView:each_item() count_lines = count_lines + 1 y = y + h local i = 1 - while i <= #dir.files and dir_cached.expanded do - local item = dir.files[i] - local cached = self:get_cached(dir, item, dir.name) + if dir.files then -- if consumed max sys file descriptors this can be nil + while i <= #dir.files and dir_cached.expanded do + local item = dir.files[i] + local cached = self:get_cached(dir, item, dir.name) - coroutine.yield(cached, ox, y, w, h) - count_lines = count_lines + 1 - y = y + h - i = i + 1 + coroutine.yield(cached, ox, y, w, h) + count_lines = count_lines + 1 + y = y + h + i = i + 1 - if not cached.expanded then - if cached.skip then - i = cached.skip - else - local depth = cached.depth - while i <= #dir.files do - if get_depth(dir.files[i].filename) <= depth then break end - i = i + 1 + if not cached.expanded then + if cached.skip then + i = cached.skip + else + local depth = cached.depth + while i <= #dir.files do + if get_depth(dir.files[i].filename) <= depth then break end + i = i + 1 + end + cached.skip = i end - cached.skip = i end - end - end -- while files + end -- while files + end end -- for directories self.count_lines = count_lines end) From 665c2cdd4d6ce87f2e998219182fc88aaa63e57e Mon Sep 17 00:00:00 2001 From: jgmdev Date: Mon, 20 Jun 2022 10:01:28 -0400 Subject: [PATCH 339/409] CommandView: improve performance by only drawing visible --- data/core/commandview.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index 545455e6..969338d9 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -334,20 +334,20 @@ local function draw_suggestions_box(self) end -- draw suggestion text - local suggestion_offset = math.max(self.suggestion_idx - max_suggestions, 0) + local offset = math.max(self.suggestion_idx - max_suggestions, 0) + local last = math.min(offset + max_suggestions, #self.suggestions) core.push_clip_rect(rx, ry, rw, rh) - local i = 1 + suggestion_offset - while i <= #self.suggestions do + local first = 1 + offset + for i=first, last do local item = self.suggestions[i] local color = (i == self.suggestion_idx) and style.accent or style.text - local y = self.position.y - (i - suggestion_offset) * lh - dh + local y = self.position.y - (i - offset) * lh - dh common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh) if item.info then local w = self.size.x - x - style.padding.x common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh) end - i = i + 1 end core.pop_clip_rect() end From 8fd00b12cfc7f74564b0a30ebadd2bf55da12d99 Mon Sep 17 00:00:00 2001 From: a <48882585+DMClVG@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:12:25 +0200 Subject: [PATCH 340/409] Add .cjs and .mjs for js syntax highlighting --- data/plugins/language_js.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/language_js.lua b/data/plugins/language_js.lua index 40aba817..b811b026 100644 --- a/data/plugins/language_js.lua +++ b/data/plugins/language_js.lua @@ -3,7 +3,7 @@ local syntax = require "core.syntax" syntax.add { name = "JavaScript", - files = { "%.js$", "%.json$", "%.cson$" }, + files = { "%.js$", "%.json$", "%.cson$", "%.mjs$", "%.cjs$" }, comment = "//", block_comment = { "/*", "*/" }, patterns = { From 76c1db97f5f25b6cd2460b4ee683318264333dbf Mon Sep 17 00:00:00 2001 From: Guldoman Date: Mon, 20 Jun 2022 19:56:53 +0200 Subject: [PATCH 341/409] `drawwhitespace`: Use `Docview` vertical line offset --- data/plugins/drawwhitespace.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index ad84658d..83a4943a 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -279,6 +279,7 @@ function DocView:draw_line_text(idx, x, y) local x1, _, x2, _ = self:get_content_bounds() x1 = x1 + x x2 = x2 + x + local ty = y + self:get_line_text_y_offset() local cache = ws_cache[self.doc.highlighter][idx] for i=1,#cache,4 do local sub = cache[i] @@ -286,7 +287,7 @@ function DocView:draw_line_text(idx, x, y) local tw = cache[i + 2] local color = cache[i + 3] if tx + tw >= x1 then - tx = renderer.draw_text(font, sub, tx, y, color) + tx = renderer.draw_text(font, sub, tx, ty, color) end if tx > x2 then break end end From 7b411c3ea95de553a12a94311e5e539e985eeedd Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 22 Jun 2022 00:42:26 -0400 Subject: [PATCH 342/409] treeview: restore ability to disable toolbarview --- data/plugins/treeview.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index cc13ec59..133fe9af 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -854,8 +854,10 @@ config.plugins.treeview.config_spec = { description = "Default treeview width.", path = "size", type = "number", - default = math.ceil(toolbar_view:get_min_width() / SCALE), - min = toolbar_view:get_min_width() / SCALE, + default = toolbar_view and math.ceil(toolbar_view:get_min_width() / SCALE) + or 200, + min = toolbar_view and toolbar_view:get_min_width() / SCALE + or 200, get_value = function(value) return value / SCALE end, @@ -863,7 +865,9 @@ config.plugins.treeview.config_spec = { return value * SCALE end, on_apply = function(value) - view:set_target_size("x", math.max(value, toolbar_view:get_min_width())) + view:set_target_size("x", math.max( + value, toolbar_view and toolbar_view:get_min_width() or 200 + )) end }, { From 0629542cf7965d6d4e2f700f3f86846c97b21159 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 22 Jun 2022 01:09:28 -0400 Subject: [PATCH 343/409] updated changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index f965cf0b..c8731885 100644 --- a/changelog.md +++ b/changelog.md @@ -120,6 +120,9 @@ * drawwhitespace: [Cache whitespace location](https://github.com/lite-xl/lite-xl/pull/1030) +* CommandView: improve performance by + [only drawing visible](https://github.com/lite-xl/lite-xl/pull/1047) + ### Backward Incompatible Changes * [Upgraded Lua to 5.4](https://github.com/lite-xl/lite-xl/pull/781), which should improve performance, and provide useful extra functionality. It should From e0859e1e393dc1512413fba21dc3bb685f3b5099 Mon Sep 17 00:00:00 2001 From: jgmdev Date: Wed, 22 Jun 2022 01:35:10 -0400 Subject: [PATCH 344/409] treeview: scale fallback sizes as pointed out by @Guldoman --- data/plugins/treeview.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 133fe9af..9dad1eb2 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -855,9 +855,9 @@ config.plugins.treeview.config_spec = { path = "size", type = "number", default = toolbar_view and math.ceil(toolbar_view:get_min_width() / SCALE) - or 200, + or 200 * SCALE, min = toolbar_view and toolbar_view:get_min_width() / SCALE - or 200, + or 200 * SCALE, get_value = function(value) return value / SCALE end, @@ -866,7 +866,7 @@ config.plugins.treeview.config_spec = { end, on_apply = function(value) view:set_target_size("x", math.max( - value, toolbar_view and toolbar_view:get_min_width() or 200 + value, toolbar_view and toolbar_view:get_min_width() or 200 * SCALE )) end }, From 522d8a80942d78488dd504f48395fe67e3986492 Mon Sep 17 00:00:00 2001 From: Guldoman Date: Thu, 23 Jun 2022 00:15:20 +0200 Subject: [PATCH 345/409] `findreplace`: Fix number of total replacements --- data/core/commands/findreplace.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index 584d4f67..642c4c0f 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -108,9 +108,13 @@ local function replace(kind, default, fn) submit = function(new) core.status_view:remove_tooltip() insert_unique(core.previous_replace, new) - local n = doc():replace(function(text) + local results = doc():replace(function(text) return fn(text, old, new) end) + local n = 0 + for _,v in pairs(results) do + n = n + v + end core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new) end, suggest = function() return core.previous_replace end, From beefb164697a5aae81c0ee8f24331a44fa8893cf Mon Sep 17 00:00:00 2001 From: Katrina Grace Date: Wed, 22 Jun 2022 20:56:41 -0600 Subject: [PATCH 346/409] language_html: Improve subsyntax highlighting (#1043) * language_html: Improve subsyntax highlighting - Adjusted `