diff options
| author | Takase <20792268+takase1121@users.noreply.github.com> | 2021-11-17 08:42:08 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-17 08:42:08 +0800 |
| commit | 8e6f594790701c499c5aa544f4e90b98e207dcb7 (patch) | |
| tree | 7adc0fa2afa10618ad8e328f0a1fa5bfe6ec61a4 /data | |
| parent | 6d36f2684a376a4698a62f453cf46bba74d4b9bc (diff) | |
| parent | 18959aebefe22e7bcfff80a2affc8ad0fda76328 (diff) | |
| download | lite-xl-8e6f594790701c499c5aa544f4e90b98e207dcb7.tar.gz lite-xl-8e6f594790701c499c5aa544f4e90b98e207dcb7.zip | |
Merge branch 'master' into replace-unpack
Diffstat (limited to 'data')
30 files changed, 927 insertions, 321 deletions
diff --git a/data/core/command.lua b/data/core/command.lua index 7915e16d..2531fb96 100644 --- a/data/core/command.lua +++ b/data/core/command.lua @@ -42,10 +42,10 @@ function command.get_all_valid() end -local function perform(name) +local function perform(name, ...) local cmd = command.map[name] - if cmd and cmd.predicate() then - cmd.perform() + if cmd and cmd.predicate(...) then + cmd.perform(...) return true end return false diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 7ac36976..432ded89 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -148,7 +148,9 @@ command.add(nil, { ["core:change-project-folder"] = function() local dirname = common.dirname(core.project_dir) - core.command_view:set_text(common.home_encode(dirname) .. PATHSEP) + 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)) if text == core.project_dir then return end @@ -162,6 +164,10 @@ command.add(nil, { end, ["core:open-project-folder"] = function() + local dirname = common.dirname(core.project_dir) + 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) local path_stat = system.get_file_info(text) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index fb17e674..ba3d1f0c 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -82,6 +82,16 @@ local function split_cursor(direction) core.blink_reset() end +local function set_cursor(x, y, type) + local line, col = dv():resolve_screen_position(x, y) + doc():set_selection(line, col, line, col) + if type == "word" or type == "lines" then + command.perform("doc:select-" .. type) + end + dv().mouse_selecting = { line, col } + core.blink_reset() +end + local commands = { ["doc:undo"] = function() doc():undo() @@ -168,16 +178,6 @@ local commands = { doc():set_selection(line, col) end, - - ["doc:indent"] = function() - for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do - local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2) - if l1 then - doc():set_selections(idx, l1, c1, l2, c2) - end - end - end, - ["doc:select-lines"] = function() for idx, line1, _, line2 in doc():get_selections(true) do append_line_if_last_line(line2) @@ -398,6 +398,30 @@ local commands = { os.remove(filename) core.log("Removed \"%s\"", filename) end, + + ["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 } + doc():set_selection(line2, col2, line1, col1) + end, + + ["doc:set-cursor"] = function(x, y) + 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") + 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, ["doc:create-cursor-previous-line"] = function() split_cursor(-1) diff --git a/data/core/commands/findreplace.lua b/data/core/commands/findreplace.lua index d5d9f6a3..d1af0d88 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -12,6 +12,7 @@ local last_finds, last_view, last_fn, last_text, last_sel local case_sensitive = config.find_case_sensitive or false local find_regex = config.find_regex or false +local found_expression local function doc() return core.active_view:is(DocView) and core.active_view.doc or last_view.doc @@ -34,24 +35,37 @@ local function update_preview(sel, search_fn, text) if ok and line1 and text ~= "" then last_view.doc:set_selection(line2, col2, line1, col1) last_view:scroll_to_line(line2, true) - return true + found_expression = true else last_view.doc:set_selection(table.unpack(sel)) - return false + found_expression = false end end + +local function insert_unique(t, v) + local n = #t + for i = 1, n do + if t[i] == v then return end + end + t[n + 1] = v +end + + local function find(label, search_fn) last_view, last_sel, last_finds = core.active_view, { core.active_view.doc:get_selection() }, {} - local text, found = last_view.doc:get_text(table.unpack(last_sel)), false + 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, function(text) + 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 then + if found_expression then last_fn, last_text = search_fn, text else core.error("Couldn't find %q", text) @@ -59,8 +73,9 @@ local function find(label, search_fn) last_view:scroll_to_make_visible(table.unpack(last_sel)) end end, function(text) - found = update_preview(last_sel, search_fn, 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 @@ -75,19 +90,24 @@ 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, 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) + 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() end, function() + end, function() return core.previous_replace end, function() core.status_view:remove_tooltip() end) - end, function() end, function() + end, function() return core.previous_find end, function() core.status_view:remove_tooltip() end) end @@ -96,13 +116,60 @@ local function has_selection() return core.active_view:is(DocView) and core.active_view.doc:has_selection() end -command.add(has_selection, { +local function has_unique_selection() + if not core.active_view:is(DocView) then return false end + local text = nil + for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do + if line1 == line2 and col1 == col2 then return false end + local selection = doc():get_text(line1, col1, line2, col2) + if text ~= nil and text ~= selection then return false end + text = selection + end + return text ~= nil +end + +local function is_in_selection(line, col, l1, c1, l2, c2) + if line < l1 or line > l2 then return false end + if line == l1 and col <= c1 then return false end + if line == l2 and col > c2 then return false end + return true +end + +local function is_in_any_selection(line, col) + for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do + if is_in_selection(line, col, l1, c1, l2, c2) then return true end + end + return false +end + +local function select_next(all) + local il1, ic1 = doc():get_selection(true) + for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do + local text = doc():get_text(l1, c1, l2, c2) + repeat + l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true }) + if l1 == il1 and c1 == ic1 then break end + if l2 and (all or not is_in_any_selection(l2, c2)) then + doc():add_selection(l2, c2, l1, c1) + if not all then + core.active_view:scroll_to_make_visible(l2, c2) + return + end + end + until not all or not l2 + if all then break end + end +end + +command.add(has_unique_selection, { ["find-replace:select-next"] = function() local l1, c1, l2, c2 = doc():get_selection(true) local text = doc():get_text(l1, c1, l2, c2) l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true }) if l2 then doc():set_selection(l2, c2, l1, c1) end - end + end, + ["find-replace:select-add-next"] = function() select_next(false) end, + ["find-replace:select-add-all"] = function() select_next(true) end }) command.add("core.docview", { @@ -114,7 +181,9 @@ command.add("core.docview", { end, ["find-replace:replace"] = function() - replace("Text", doc():get_text(doc():get_selection(true)), function(text, old, new) + local l1, c1, l2, c2 = doc():get_selection() + local selected_text = doc():get_text(l1, c1, l2, c2) + replace("Text", l1 == l2 and selected_text or "", function(text, old, new) if not find_regex then return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil) end diff --git a/data/core/commands/root.lua b/data/core/commands/root.lua index e41c723d..8f2536b8 100644 --- a/data/core/commands/root.lua +++ b/data/core/commands/root.lua @@ -3,6 +3,7 @@ local style = require "core.style" local DocView = require "core.docview" local command = require "core.command" local common = require "core.common" +local config = require "core.config" local t = { @@ -63,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) @@ -76,7 +77,7 @@ local t = { local parent = node:get_parent_node(core.root_view.root_node) local n = (parent.a == node) and 0.1 or -0.1 parent.divider = common.clamp(parent.divider + n, 0.1, 0.9) - end, + end } @@ -122,3 +123,14 @@ command.add(function() local node = core.root_view:get_active_node() return not node:get_locked_size() end, t) + +command.add(nil, { + ["root:scroll"] = function(delta) + local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view + if view and view.scrollable then + view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll + return true + end + return false + end +}) diff --git a/data/core/commandview.lua b/data/core/commandview.lua index eb7febc7..b91f1394 100644 --- a/data/core/commandview.lua +++ b/data/core/commandview.lua @@ -34,6 +34,7 @@ function CommandView:new() self.suggestion_idx = 1 self.suggestions = {} self.suggestions_height = 0 + self.show_suggestions = true self.last_change_id = 0 self.gutter_width = 0 self.gutter_text_brightness = 0 @@ -45,6 +46,11 @@ function CommandView:new() end +function CommandView:set_hidden_suggestions() + self.show_suggestions = false +end + + function CommandView:get_name() return View.get_name(self) end @@ -83,10 +89,29 @@ end function CommandView:move_suggestion_idx(dir) - local n = self.suggestion_idx + dir - self.suggestion_idx = common.clamp(n, 1, #self.suggestions) - self:complete() - self.last_change_id = self.doc:get_change_id() + if self.show_suggestions then + local n = self.suggestion_idx + dir + self.suggestion_idx = common.clamp(n, 1, #self.suggestions) + self:complete() + self.last_change_id = self.doc:get_change_id() + else + local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text + local text = self:get_text() + if text == current_suggestion then + local n = self.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:complete() + end + else + self.save_suggestion = text + self:complete() + end + self.last_change_id = self.doc:get_change_id() + self.state.suggest(self:get_text()) + end end @@ -134,6 +159,8 @@ 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 end @@ -187,7 +214,7 @@ function CommandView:update() -- update suggestions box height local lh = self:get_suggestion_line_height() - local dest = math.min(#self.suggestions, max_suggestions) * lh + local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0 self:move_towards("suggestions_height", dest) -- update suggestion cursor offset @@ -256,7 +283,9 @@ end function CommandView:draw() CommandView.super.draw(self) - core.root_view:defer_draw(draw_suggestions_box, self) + if self.show_suggestions then + core.root_view:defer_draw(draw_suggestions_box, self) + end end diff --git a/data/core/common.lua b/data/core/common.lua index 3093a36d..1a1b22cd 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -41,6 +41,11 @@ function common.lerp(a, b, t) end +function common.distance(x1, y1, x2, y2) + return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2)) +end + + function common.color(str) local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)") if r then @@ -276,6 +281,7 @@ end function common.normalize_path(filename) + if not filename then return end if PATHSEP == '\\' then filename = filename:gsub('[/\\]', '\\') local drive, rem = filename:match('^([a-zA-Z])(:.*)') @@ -290,7 +296,8 @@ function common.normalize_path(filename) table.insert(accu, part) end end - return table.concat(accu, PATHSEP) + local npath = table.concat(accu, PATHSEP) + return npath == "" and PATHSEP or npath end diff --git a/data/core/config.lua b/data/core/config.lua index caecdfcd..faffc27e 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -5,6 +5,7 @@ config.fps = 60 config.max_log_items = 80 config.message_timeout = 5 config.mouse_wheel_scroll = 50 * SCALE +config.scroll_past_end = true config.file_size_limit = 10 config.ignore_files = "^%." config.symbol_pattern = "[%a_][%w_]*" @@ -23,9 +24,11 @@ config.max_project_files = 2000 config.transitions = true config.animation_rate = 1.0 config.blink_period = 0.8 +config.disable_blink = false config.draw_whitespace = false 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. @@ -33,5 +36,6 @@ config.plugins = {} config.plugins.trimwhitespace = false config.plugins.lineguide = false +config.plugins.drawwhitespace = false return config diff --git a/data/core/contextmenu.lua b/data/core/contextmenu.lua index 36247597..d6131cdf 100644 --- a/data/core/contextmenu.lua +++ b/data/core/contextmenu.lua @@ -49,7 +49,7 @@ function ContextMenu:register(predicate, items) local width, height = 0, 0 --precalculate the size of context menu for i, item in ipairs(items) do if item ~= DIVIDER then - item.info = keymap.reverse_map[item.command] + item.info = keymap.get_binding(item.command) end local lw, lh = get_item_size(item) width = math.max(width, lw) diff --git a/data/core/doc/highlighter.lua b/data/core/doc/highlighter.lua index e7650d01..4cb703da 100644 --- a/data/core/doc/highlighter.lua +++ b/data/core/doc/highlighter.lua @@ -24,7 +24,7 @@ function Highlighter:new(doc) 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) then + if not (line and line.init_state == state and line.text == self.doc.lines[i]) then self.lines[i] = self:tokenize_line(i, state) end end @@ -44,12 +44,25 @@ function Highlighter:reset() self.max_wanted_line = 0 end - function Highlighter:invalidate(idx) self.first_invalid_line = math.min(self.first_invalid_line, idx) self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines) end +function Highlighter:insert_notify(line, n) + self:invalidate(line) + for i = 1, n do + table.insert(self.lines, line, nil) + end +end + +function Highlighter:remove_notify(line, n) + self:invalidate(line) + for i = 1, n do + table.remove(self.lines, line) + end +end + function Highlighter:tokenize_line(idx, state) local res = {} diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 067cf9e6..640e9fd5 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -102,7 +102,11 @@ end function Doc:is_dirty() - return self.clean_change_id ~= self:get_change_id() or self.new_file + if self.new_file then + return #self.lines > 1 or #self.lines[1] > 1 + else + return self.clean_change_id ~= self:get_change_id() + end end @@ -322,6 +326,7 @@ end function Doc:raw_insert(line, col, text, undo_stack, time) -- split text into lines and merge with line at insertion point local lines = split_lines(text) + local len = #lines[#lines] local before = self.lines[line]:sub(1, col - 1) local after = self.lines[line]:sub(col) for i = 1, #lines - 1 do @@ -332,6 +337,14 @@ 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 + local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0 + local column_addition = line == cline1 and ccol1 > col and len or 0 + self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition) + end -- push undo local line2, col2 = self:position_offset(line, col, #text) @@ -339,7 +352,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time) push_undo(undo_stack, time, "remove", line, col, line2, col2) -- update highlighter and assure selection is in bounds - self.highlighter:invalidate(line) + self.highlighter:insert_notify(line, #lines - 1) self:sanitize_selection() end @@ -356,9 +369,17 @@ 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 + local line_removal = line2 - line1 + local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0 + self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal) + end -- update highlighter and assure selection is in bounds - self.highlighter:invalidate(line1) + self.highlighter:remove_notify(line1, line2 - line1) self:sanitize_selection() end @@ -392,7 +413,7 @@ end function Doc:text_input(text, idx) - for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do + for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do if line1 ~= line2 or col1 ~= col2 then self:delete_to_cursor(sidx) end @@ -401,12 +422,7 @@ function Doc:text_input(text, idx) end end - -function Doc:replace(fn) - local line1, col1, line2, col2 = self:get_selection(true) - if line1 == line2 and col1 == col2 then - line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines] - 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) if old_text ~= new_text then @@ -414,12 +430,27 @@ function Doc:replace(fn) self:remove(line1, col1, line2, col2) if line1 == line2 and col1 == col2 then line2, col2 = self:position_offset(line1, col1, #new_text) - self:set_selection(line1, col1, line2, col2) + self:set_selections(idx, line1, col1, line2, col2) end end return n 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 + n = n + 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) + end + return n +end + function Doc:delete_to_cursor(idx, ...) for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do diff --git a/data/core/docview.lua b/data/core/docview.lua index 17fb534a..07da1cef 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -98,6 +98,9 @@ end function DocView:get_scrollable_size() + if not config.scroll_past_end then + return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2 + end return self:get_line_height() * (#self.doc.lines - 1) + self.size.y end @@ -150,14 +153,14 @@ function DocView:get_col_x_offset(line, col) local font = style.syntax_fonts[type] or default_font for char in common.utf8_chars(text) do if column == col then - return xoffset / font:subpixel_scale() + return xoffset end - xoffset = xoffset + font:get_width_subpixel(char) + xoffset = xoffset + font:get_width(char) column = column + #char end end - return xoffset / default_font:subpixel_scale() + return xoffset end @@ -166,14 +169,12 @@ function DocView:get_x_offset_col(line, x) local xoffset, last_i, i = 0, 1, 1 local default_font = self:get_font() - local subpixel_scale = default_font:subpixel_scale() - local x_subpixel = subpixel_scale * x + subpixel_scale / 2 for _, type, text in self.doc.highlighter:each_token(line) do local font = style.syntax_fonts[type] or default_font for char in common.utf8_chars(text) do - local w = font:get_width_subpixel(char) - if xoffset >= subpixel_scale * x then - return (xoffset - x_subpixel > w / 2) and last_i or i + local w = font:get_width(char) + if xoffset >= x then + return (xoffset - x > w / 2) and last_i or i end xoffset = xoffset + w last_i = i @@ -223,52 +224,6 @@ function DocView:scroll_to_make_visible(line, col) end end - -local function mouse_selection(doc, clicks, line1, col1, line2, col2) - local swap = line2 < line1 or line2 == line1 and col2 <= col1 - if swap then - line1, col1, line2, col2 = line2, col2, line1, col1 - end - if clicks % 4 == 2 then - line1, col1 = translate.start_of_word(doc, line1, col1) - line2, col2 = translate.end_of_word(doc, line2, col2) - elseif clicks % 4 == 3 then - if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then - doc:insert(math.huge, math.huge, "\n") - end - line1, col1, line2, col2 = line1, 1, line2 + 1, 1 - end - if swap then - return line2, col2, line1, col1 - end - return line1, col1, line2, col2 -end - - -function DocView:on_mouse_pressed(button, x, y, clicks) - local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks) - if caught then - return - end - if keymap.modkeys["shift"] then - if clicks % 2 == 1 then - local line1, col1 = select(3, self.doc:get_selection()) - local line2, col2 = self:resolve_screen_position(x, y) - self.doc:set_selection(line2, col2, line1, col1) - end - else - local line, col = self:resolve_screen_position(x, y) - if keymap.modkeys["ctrl"] then - self.doc:add_selection(mouse_selection(self.doc, clicks, line, col, line, col)) - else - self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col)) - end - self.mouse_selecting = { line, col, clicks = clicks } - end - core.blink_reset() -end - - function DocView:on_mouse_moved(x, y, ...) DocView.super.on_mouse_moved(self, x, y, ...) @@ -281,7 +236,6 @@ function DocView:on_mouse_moved(x, y, ...) if self.mouse_selecting then local l1, c1 = self:resolve_screen_position(x, y) local l2, c2 = table.unpack(self.mouse_selecting) - local clicks = self.mouse_selecting.clicks if keymap.modkeys["ctrl"] then if l1 > l2 then l1, l2 = l2, l1 end self.doc.selections = { } @@ -289,7 +243,7 @@ function DocView:on_mouse_moved(x, y, ...) self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i])) end else - self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2)) + self.doc:set_selection(l1, c1, l2, c2) end end end @@ -339,16 +293,11 @@ end function DocView:draw_line_text(idx, x, y) local default_font = self:get_font() - local subpixel_scale = default_font:subpixel_scale() - local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset() + local tx, ty = x, y + self:get_line_text_y_offset() for _, type, text in self.doc.highlighter:each_token(idx) do local color = style.syntax[type] local font = style.syntax_fonts[type] or default_font - if config.draw_whitespace then - tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment) - else - tx = renderer.draw_text_subpixel(font, text, tx, ty, color) - end + tx = renderer.draw_text(font, text, tx, ty, color) end end @@ -358,6 +307,18 @@ function DocView:draw_caret(x, y) 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 + end + end + if draw_highlight and config.highlight_current_line and core.active_view == self then + self:draw_line_highlight(x + self.scroll.x, y) + end + -- draw selection if it overlaps this line for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do if idx >= line1 and idx <= line2 then @@ -367,14 +328,9 @@ function DocView:draw_line_body(idx, x, y) 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() - renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) - end - end - for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do - -- draw line highlight if caret is on this line - if config.highlight_current_line and (line1 == line2 and col1 == col2) - and line1 == idx and core.active_view == self then - self:draw_line_highlight(x + self.scroll.x, y) + if x1 ~= x2 then + renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) + end end end @@ -404,10 +360,12 @@ function DocView:draw_overlay() local T = config.blink_period for _, line, col in self.doc:get_selections() do if line >= minline and line <= maxline - and (core.blink_timer - core.blink_start) % T < T / 2 and system.window_has_focus() then - local x, y = self:get_line_screen_position(line) - self:draw_caret(x + self:get_col_x_offset(line, col), y) + 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) + end end end end diff --git a/data/core/init.lua b/data/core/init.lua index 702e31fd..d07f1cbc 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -17,10 +17,7 @@ local core = {} local function load_session() local ok, t = pcall(dofile, USERDIR .. "/session.lua") - if ok then - return t.recents, t.window, t.window_mode - end - return {} + return ok and t or {} end @@ -30,6 +27,8 @@ local function save_session() fp:write("return {recents=", common.serialize(core.recent_projects), ", window=", common.serialize(table.pack(system.get_window_size())), ", window_mode=", common.serialize(system.get_window_mode()), + ", previous_find=", common.serialize(core.previous_find), + ", previous_replace=", common.serialize(core.previous_replace), "}\n") fp:close() end @@ -80,6 +79,9 @@ 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() update_recents_project("add", dir_path_abs) + if not core.load_project_module() then + command.perform("core:open-log") + end core.on_enter_project(dir_path_abs) end end @@ -320,8 +322,8 @@ local style = require "core.style" ------------------------------- Fonts ---------------------------------------- -- customize fonts: --- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE) --- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE) +-- 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) -- -- font names used by lite: -- style.font : user interface @@ -394,15 +396,6 @@ function core.remove_project_directory(path) return false end - -local function whitespace_replacements() - local r = renderer.replacements.new() - r:add(" ", "·") - r:add("\t", "»") - return r -end - - local function reload_on_user_module_save() -- auto-realod style when user's module is saved by overriding Doc:Save() local doc_save = Doc.save @@ -435,13 +428,15 @@ function core.init() end do - local recent_projects, window_position, window_mode = load_session() - if window_mode == "normal" then - system.set_window_size(table.unpack(window_position)) - elseif window_mode == "maximized" then + local session = load_session() + if session.window_mode == "normal" then + system.set_window_size(table.unpack(session.window)) + elseif session.window_mode == "maximized" then system.set_window_mode("maximized") end - core.recent_projects = recent_projects + core.recent_projects = session.recents or {} + core.previous_find = session.previous_find or {} + core.previous_replace = session.previous_replace or {} end local project_dir = core.recent_projects[1] or "." @@ -461,7 +456,10 @@ function core.init() project_dir = arg_filename project_dir_explicit = true else - delayed_error = string.format("error: invalid file or directory %q", ARGS[i]) + -- 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]) + end end end @@ -495,7 +493,6 @@ function core.init() core.visited_files = {} core.restart_request = false core.quit_request = false - core.replacements = whitespace_replacements() core.root_view = RootView() core.command_view = CommandView() @@ -678,21 +675,23 @@ function core.load_plugins() userdir = {dir = USERDIR, plugins = {}}, datadir = {dir = DATADIR, plugins = {}}, } - local files = {} + local files, ordered = {}, {} 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 end end + table.sort(ordered) - for filename, plugin_dir in pairs(files) do - local basename = filename:match("(.-)%.lua$") or filename + 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 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 and 'userdir' or 'datadir'].plugins + 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 @@ -809,7 +808,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) then + if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then return filename else return core.project_dir .. PATHSEP .. filename @@ -923,11 +922,15 @@ function core.on_event(type, ...) elseif type == "mousemoved" then core.root_view:on_mouse_moved(...) elseif type == "mousepressed" then - core.root_view:on_mouse_pressed(...) + if not core.root_view:on_mouse_pressed(...) then + did_keymap = keymap.on_mouse_pressed(...) + end elseif type == "mousereleased" then core.root_view:on_mouse_released(...) elseif type == "mousewheel" then - core.root_view:on_mouse_wheel(...) + if not core.root_view:on_mouse_wheel(...) then + did_keymap = keymap.on_mouse_wheel(...) + end elseif type == "resized" then core.window_mode = system.get_window_mode() elseif type == "minimized" or type == "maximized" or type == "restored" then diff --git a/data/core/keymap-macos.lua b/data/core/keymap-macos.lua index 647cb132..b0bd41a5 100644 --- a/data/core/keymap-macos.lua +++ b/data/core/keymap-macos.lua @@ -32,6 +32,8 @@ local function keymap_macos(keymap) ["cmd+7"] = "root:switch-to-tab-7", ["cmd+8"] = "root:switch-to-tab-8", ["cmd+9"] = "root:switch-to-tab-9", + ["wheel"] = "root:scroll", + ["cmd+f"] = "find-replace:find", ["cmd+r"] = "find-replace:replace", ["f3"] = "find-replace:repeat-find", @@ -52,23 +54,27 @@ local function keymap_macos(keymap) ["shift+tab"] = "doc:unindent", ["backspace"] = "doc:backspace", ["shift+backspace"] = "doc:backspace", - ["cmd+backspace"] = "doc:delete-to-previous-word-start", + ["option+backspace"] = "doc:delete-to-previous-word-start", ["cmd+shift+backspace"] = "doc:delete-to-previous-word-start", + ["cmd+backspace"] = "doc:delete-to-start-of-indentation", ["delete"] = "doc:delete", ["shift+delete"] = "doc:delete", - ["cmd+delete"] = "doc:delete-to-next-word-end", + ["option+delete"] = "doc:delete-to-next-word-end", ["cmd+shift+delete"] = "doc:delete-to-next-word-end", + ["cmd+delete"] = "doc:delete-to-end-of-line", ["return"] = { "command:submit", "doc:newline", "dialog:select" }, ["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" }, ["cmd+return"] = "doc:newline-below", ["cmd+shift+return"] = "doc:newline-above", ["cmd+j"] = "doc:join-lines", ["cmd+a"] = "doc:select-all", - ["cmd+d"] = { "find-replace:select-next", "doc:select-word" }, + ["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" }, + ["cmd+f3"] = "find-replace:select-next", ["cmd+l"] = "doc:select-lines", + ["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" }, ["cmd+/"] = "doc:toggle-line-comments", - ["cmd+up"] = "doc:move-lines-up", - ["cmd+down"] = "doc:move-lines-down", + ["option+up"] = "doc:move-lines-up", + ["option+down"] = "doc:move-lines-down", ["cmd+shift+d"] = "doc:duplicate-lines", ["cmd+shift+k"] = "doc:delete-lines", @@ -76,33 +82,42 @@ local function keymap_macos(keymap) ["right"] = { "doc:move-to-next-char", "dialog:next-entry"}, ["up"] = { "command:select-previous", "doc:move-to-previous-line" }, ["down"] = { "command:select-next", "doc:move-to-next-line" }, - ["cmd+left"] = "doc:move-to-previous-word-start", - ["cmd+right"] = "doc:move-to-next-word-end", + ["option+left"] = "doc:move-to-previous-word-start", + ["option+right"] = "doc:move-to-next-word-end", + ["cmd+left"] = "doc:move-to-start-of-indentation", + ["cmd+right"] = "doc:move-to-end-of-line", ["cmd+["] = "doc:move-to-previous-block-start", ["cmd+]"] = "doc:move-to-next-block-end", ["home"] = "doc:move-to-start-of-indentation", ["end"] = "doc:move-to-end-of-line", - ["cmd+home"] = "doc:move-to-start-of-doc", - ["cmd+end"] = "doc:move-to-end-of-doc", + ["cmd+up"] = "doc:move-to-start-of-doc", + ["cmd+down"] = "doc:move-to-end-of-doc", ["pageup"] = "doc:move-to-previous-page", ["pagedown"] = "doc:move-to-next-page", + ["shift+1lclick"] = "doc:select-to-cursor", + ["ctrl+1lclick"] = "doc:split-cursor", + ["1lclick"] = "doc:set-cursor", + ["2lclick"] = "doc:set-cursor-word", + ["3lclick"] = "doc:set-cursor-line", ["shift+left"] = "doc:select-to-previous-char", ["shift+right"] = "doc:select-to-next-char", ["shift+up"] = "doc:select-to-previous-line", ["shift+down"] = "doc:select-to-next-line", - ["cmd+shift+left"] = "doc:select-to-previous-word-start", - ["cmd+shift+right"] = "doc:select-to-next-word-end", + ["option+shift+left"] = "doc:select-to-previous-word-start", + ["option+shift+right"] = "doc:select-to-next-word-end", + ["cmd+shift+left"] = "doc:select-to-start-of-indentation", + ["cmd+shift+right"] = "doc:select-to-end-of-line", ["cmd+shift+["] = "doc:select-to-previous-block-start", ["cmd+shift+]"] = "doc:select-to-next-block-end", ["shift+home"] = "doc:select-to-start-of-indentation", ["shift+end"] = "doc:select-to-end-of-line", - ["cmd+shift+home"] = "doc:select-to-start-of-doc", - ["cmd+shift+end"] = "doc:select-to-end-of-doc", + ["cmd+shift+up"] = "doc:select-to-start-of-doc", + ["cmd+shift+down"] = "doc:select-to-end-of-doc", ["shift+pageup"] = "doc:select-to-previous-page", ["shift+pagedown"] = "doc:select-to-next-page", - ["cmd+shift+up"] = "doc:create-cursor-previous-line", - ["cmd+shift+down"] = "doc:create-cursor-next-line" + ["cmd+option+up"] = "doc:create-cursor-previous-line", + ["cmd+option+down"] = "doc:create-cursor-next-line" } end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 0b08259d..fd552f19 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -1,11 +1,12 @@ local command = require "core.command" +local config = require "core.config" local keymap = {} keymap.modkeys = {} keymap.map = {} keymap.reverse_map = {} -local macos = rawget(_G, "MACOS") +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")) @@ -30,7 +31,8 @@ function keymap.add_direct(map) end keymap.map[stroke] = commands for _, cmd in ipairs(commands) do - keymap.reverse_map[cmd] = stroke + keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {} + table.insert(keymap.reverse_map[cmd], stroke) end end end @@ -52,18 +54,43 @@ function keymap.add(map, overwrite) end end for _, cmd in ipairs(commands) do - keymap.reverse_map[cmd] = stroke + keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {} + table.insert(keymap.reverse_map[cmd], stroke) end end 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) +end + + function keymap.get_binding(cmd) - return keymap.reverse_map[cmd] + return table.unpack(keymap.reverse_map[cmd] or {}) end -function keymap.on_key_pressed(k) +function keymap.on_key_pressed(k, ...) local mk = modkey_map[k] if mk then keymap.modkeys[mk] = true @@ -73,18 +100,30 @@ function keymap.on_key_pressed(k) end else local stroke = key_to_stroke(k) - local commands = keymap.map[stroke] + local commands, performed = keymap.map[stroke] if commands then for _, cmd in ipairs(commands) do - local performed = command.perform(cmd) + performed = command.perform(cmd, ...) if performed then break end end - return true + return performed end end return false end +function keymap.on_mouse_wheel(delta, ...) + return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...) + or keymap.on_key_pressed("wheel", delta, ...)) +end + +function keymap.on_mouse_pressed(button, x, y, clicks) + local click_number = (((clicks - 1) % config.max_clicks) + 1) + return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or + keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or + keymap.on_key_pressed(click_number .. "click", x, y, clicks) or + keymap.on_key_pressed("click", x, y, clicks)) +end function keymap.on_key_released(k) local mk = modkey_map[k] @@ -133,6 +172,7 @@ keymap.add_direct { ["alt+7"] = "root:switch-to-tab-7", ["alt+8"] = "root:switch-to-tab-8", ["alt+9"] = "root:switch-to-tab-9", + ["wheel"] = "root:scroll", ["ctrl+f"] = "find-replace:find", ["ctrl+r"] = "find-replace:replace", @@ -168,8 +208,10 @@ keymap.add_direct { ["ctrl+shift+return"] = "doc:newline-above", ["ctrl+j"] = "doc:join-lines", ["ctrl+a"] = "doc:select-all", - ["ctrl+d"] = { "find-replace:select-next", "doc:select-word" }, + ["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" }, + ["ctrl+f3"] = "find-replace:select-next", ["ctrl+l"] = "doc:select-lines", + ["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" }, ["ctrl+/"] = "doc:toggle-line-comments", ["ctrl+up"] = "doc:move-lines-up", ["ctrl+down"] = "doc:move-lines-down", @@ -191,6 +233,11 @@ keymap.add_direct { ["pageup"] = "doc:move-to-previous-page", ["pagedown"] = "doc:move-to-next-page", + ["shift+1lclick"] = "doc:select-to-cursor", + ["ctrl+1lclick"] = "doc:split-cursor", + ["1lclick"] = "doc:set-cursor", + ["2lclick"] = "doc:set-cursor-word", + ["3lclick"] = "doc:set-cursor-line", ["shift+left"] = "doc:select-to-previous-char", ["shift+right"] = "doc:select-to-next-char", ["shift+up"] = "doc:select-to-previous-line", diff --git a/data/core/regex.lua b/data/core/regex.lua index 19306e04..69203cbd 100644 --- a/data/core/regex.lua +++ b/data/core/regex.lua @@ -23,7 +23,7 @@ end -- Moves to the end of the identified character. local function end_character(str, index) local byte = string.byte(str, index + 1) - while byte >= 128 and byte < 192 do + while byte and byte >= 128 and byte < 192 do index = index + 1 byte = string.byte(str, index + 1) end diff --git a/data/core/rootview.lua b/data/core/rootview.lua index 9d017268..07f8b7bf 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -142,7 +142,14 @@ function Node:remove_view(root, view) local parent = self:get_parent_node(root) local is_a = (parent.a == self) local other = parent[is_a and "b" or "a"] - if other:get_locked_size() then + local locked_size_x, locked_size_y = other:get_locked_size() + local locked_size + if parent.type == "hsplit" then + locked_size = locked_size_x + else + locked_size = locked_size_y + end + if self.is_primary_node or locked_size then self.views = {} self:add_view(EmptyView()) else @@ -273,7 +280,10 @@ end function Node:should_show_tabs() if self.locked then return false end - if #self.views > 1 then return true + local dn = core.root_view.dragged_node + if #self.views > 1 + or (dn and dn.dragging) then -- show tabs while dragging + return true elseif config.always_show_tabs then return not self.views[1]:is(EmptyView) end @@ -506,6 +516,53 @@ function Node:update() end end +function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone) + 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) + if standalone then + renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2) + end + 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) + 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) + 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) + core.pop_clip_rect() +end function Node:draw_tabs() local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1) @@ -530,47 +587,9 @@ 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) - local text = view:get_name() - 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) - if view == self.active_view 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) - local show_close_button = ((view == self.active_view or i == self.hovered_tab) and config.tab_close_button) - if show_close_button then - local close_style = self.hovered_close == i and style.text or style.dim - common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h) - end - if i == self.hovered_tab 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) - core.pop_clip_rect() + self:draw_tab(view:get_name(), view == self.active_view, + i == self.hovered_tab, i == self.hovered_close, + x, y, w, h) end core.pop_clip_rect() @@ -696,6 +715,62 @@ function Node:resize(axis, value) end +function Node:get_split_type(mouse_x, mouse_y) + local x, y = self.position.x, self.position.y + local w, h = self.size.x, self.size.y + local _, _, _, tab_h = self:get_scroll_button_rect(1) + y = y + tab_h + h = h - tab_h + + local local_mouse_x = mouse_x - x + local local_mouse_y = mouse_y - y + + if local_mouse_y < 0 then + return "tab" + else + local left_pct = local_mouse_x * 100 / w + local top_pct = local_mouse_y * 100 / h + if left_pct <= 30 then + return "left" + elseif left_pct >= 70 then + return "right" + elseif top_pct <= 30 then + return "up" + elseif top_pct >= 70 then + return "down" + end + return "middle" + end +end + + +function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index) + local tab_index = self:get_tab_overlapping_point(x, y) + if not tab_index then + local first_tab_x = self:get_tab_rect(1) + if x < first_tab_x then + -- mouse before first visible tab + tab_index = self.tab_offset or 1 + else + -- mouse after last visible tab + tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0) + end + end + local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index) + if x > tab_x + tab_w / 2 and tab_index <= #self.views then + -- use next tab + tab_x = tab_x + tab_w + tab_index = tab_index + 1 + end + if self == dragged_node and dragged_index and tab_index > dragged_index then + -- the tab we are moving is counted in tab_index + tab_index = tab_index - 1 + tab_x = tab_x - tab_w + end + return tab_index, tab_x, tab_y, tab_w, tab_h +end + + local RootView = View:extend() function RootView:new() @@ -703,6 +778,14 @@ function RootView:new() self.root_node = Node() self.deferred_draws = {} self.mouse = { x = 0, y = 0 } + self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0, + base_color = style.drag_overlay, + color = { table.unpack(style.drag_overlay) } } + self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 } + self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0, + base_color = style.drag_overlay_tab, + color = { table.unpack(style.drag_overlay_tab) } } + self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 } end @@ -774,38 +857,101 @@ function RootView:on_mouse_pressed(button, x, y, clicks) local div = self.root_node:get_divider_overlapping_point(x, y) if div then self.dragged_divider = div - return + return true end local node = self.root_node:get_child_overlapping_point(x, y) if node.hovered_scroll_button > 0 then node:scroll_tabs(node.hovered_scroll_button) - return + return true end local idx = node:get_tab_overlapping_point(x, y) if idx then if button == "middle" or node.hovered_close == idx then node:close_view(self.root_node, node.views[idx]) + return true else - self.dragged_node = { node, idx } + if button == "left" then + self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y} + end node:set_active_view(node.views[idx]) + return true end - else + elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs core.set_active_view(node.active_view) if not self.on_view_mouse_pressed(button, x, y, clicks) then - node.active_view:on_mouse_pressed(button, x, y, clicks) + return node.active_view:on_mouse_pressed(button, x, y, clicks) end end end -function RootView:on_mouse_released(...) +function RootView:get_overlay_base_color(overlay) + if overlay == self.drag_overlay then + return style.drag_overlay + else + return style.drag_overlay_tab + end +end + + +function RootView:set_show_overlay(overlay, status) + overlay.visible = status + if status then -- reset colors + -- reload base_color + overlay.base_color = self:get_overlay_base_color(overlay) + overlay.color[1] = overlay.base_color[1] + overlay.color[2] = overlay.base_color[2] + overlay.color[3] = overlay.base_color[3] + overlay.color[4] = overlay.base_color[4] + overlay.opacity = 0 + end +end + + +function RootView:on_mouse_released(button, x, y, ...) if self.dragged_divider then self.dragged_divider = nil end if self.dragged_node then - self.dragged_node = nil + if button == "left" then + if self.dragged_node.dragging then + local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y) + local dragged_node = self.dragged_node.node + + if node and not node.locked + -- don't do anything if dragging onto own node, with only one view + and (node ~= dragged_node or #node.views > 1) then + local split_type = node:get_split_type(self.mouse.x, self.mouse.y) + local view = dragged_node.views[self.dragged_node.idx] + + if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting + local new_node = node:split(split_type) + self.root_node:get_node_for_view(view):remove_view(self.root_node, view) + new_node:add_view(view) + elseif split_type == "middle" and node ~= dragged_node then -- move to other node + dragged_node:remove_view(self.root_node, view) + node:add_view(view) + self.root_node:get_node_for_view(view):set_active_view(view) + elseif split_type == "tab" then -- move besides other tabs + local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx) + dragged_node:remove_view(self.root_node, view) + node:add_view(view, tab_index) + self.root_node:get_node_for_view(view):set_active_view(view) + end + self.root_node:update_layout() + core.redraw = true + end + end + self:set_show_overlay(self.drag_overlay, false) + self:set_show_overlay(self.drag_overlay_tab, false) + if self.dragged_node and self.dragged_node.dragging then + core.request_cursor("arrow") + end + self.dragged_node = nil + end + else -- avoid sending on_mouse_released events when dragging tabs + self.root_node:on_mouse_released(button, x, y, ...) end - self.root_node:on_mouse_released(...) end @@ -841,37 +987,33 @@ function RootView:on_mouse_moved(x, y, dx, dy) end self.mouse.x, self.mouse.y = x, y + + local dn = self.dragged_node + if dn and not dn.dragging then + -- start dragging only after enough movement + dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05 + if dn.dragging then + core.request_cursor("hand") + end + end + + -- avoid sending on_mouse_moved events when dragging tabs + if dn then return end + self.root_node:on_mouse_moved(x, y, dx, dy) - local node = self.root_node:get_child_overlapping_point(x, y) + 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 = node and node:get_tab_overlapping_point(x, y) - if node and node:get_scroll_button_index(x, y) then + 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 core.request_cursor("arrow") elseif div then core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev") elseif tab_index then core.request_cursor("arrow") - elseif node then - core.request_cursor(node.active_view.cursor) - end - if node and self.dragged_node and (self.dragged_node[1] ~= node or (tab_index and self.dragged_node[2] ~= tab_index)) - and node.type == "leaf" and #node.views > 0 and node.views[1]:is(DocView) then - local tab = self.dragged_node[1].views[self.dragged_node[2]] - if self.dragged_node[1] ~= node then - for i, v in ipairs(node.views) do if v.doc == tab.doc then tab = nil break end end - if tab then - self.dragged_node[1]:remove_view(self.root_node, tab) - node:add_view(tab, tab_index) - self.root_node:update_layout() - self.dragged_node = { node, tab_index or #node.views } - core.redraw = true - end - else - table.remove(self.dragged_node[1].views, self.dragged_node[2]) - table.insert(node.views, tab_index, tab) - self.dragged_node = { node, tab_index } - end + elseif self.overlapping_node then + core.request_cursor(self.overlapping_node.active_view.cursor) end end @@ -879,7 +1021,7 @@ 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) - node.active_view:on_mouse_wheel(...) + return node.active_view:on_mouse_wheel(...) end @@ -893,10 +1035,110 @@ function RootView:on_focus_lost(...) core.redraw = true 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, "opacity", overlay.visible and 100 or 0) + overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100 +end + + function RootView:update() copy_position_and_size(self.root_node, self) self.root_node:update() self.root_node:update_layout() + + self:update_drag_overlay() + self:interpolate_drag_overlay(self.drag_overlay) + self:interpolate_drag_overlay(self.drag_overlay_tab) +end + + +function RootView:set_drag_overlay(overlay, x, y, w, h, immediate) + overlay.to.x = x + overlay.to.y = y + overlay.to.w = w + overlay.to.h = h + if immediate then + overlay.x = x + overlay.y = y + overlay.w = w + overlay.h = h + end + if not overlay.visible then + self:set_show_overlay(overlay, true) + end +end + + +local function get_split_sizes(split_type, x, y, w, h) + if split_type == "left" then + w = w * .5 + elseif split_type == "right" then + x = x + w * .5 + w = w * .5 + elseif split_type == "up" then + h = h * .5 + elseif split_type == "down" then + y = y + h * .5 + h = h * .5 + end + return x, y, w, h +end + + +function RootView:update_drag_overlay() + if not (self.dragged_node and self.dragged_node.dragging) then return end + local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y) + if over and not over.locked then + local _, _, _, tab_h = over:get_scroll_button_rect(1) + local x, y = over.position.x, over.position.y + local w, h = over.size.x, over.size.y + local split_type = over:get_split_type(self.mouse.x, self.mouse.y) + + if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then + local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y) + self:set_drag_overlay(self.drag_overlay_tab, + tab_x + (tab_index and 0 or tab_w), tab_y, + style.caret_width, tab_h, + -- avoid showing tab overlay moving between nodes + over ~= self.drag_overlay_tab.last_over) + self:set_show_overlay(self.drag_overlay, false) + self.drag_overlay_tab.last_over = over + else + if (over ~= self.dragged_node.node or #over.views > 1) then + y = y + tab_h + h = h - tab_h + x, y, w, h = get_split_sizes(split_type, x, y, w, h) + end + self:set_drag_overlay(self.drag_overlay, x, y, w, h) + self:set_show_overlay(self.drag_overlay_tab, false) + end + else + self:set_show_overlay(self.drag_overlay, false) + self:set_show_overlay(self.drag_overlay_tab, false) + end +end + + +function RootView:draw_grabbed_tab() + local dn = self.dragged_node + 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) +end + + +function RootView:draw_drag_overlay(ov) + if ov.opacity > 0 then + renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color) + end end @@ -906,6 +1148,12 @@ function RootView:draw() local t = table.remove(self.deferred_draws) t.fn(table.unpack(t)) end + + self:draw_drag_overlay(self.drag_overlay) + self:draw_drag_overlay(self.drag_overlay_tab) + if self.dragged_node and self.dragged_node.dragging then + self:draw_grabbed_tab() + end if core.cursor_change_req then system.set_cursor(core.cursor_change_req) core.cursor_change_req = nil diff --git a/data/core/start.lua b/data/core/start.lua index 2c47c902..31fed147 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -20,5 +20,14 @@ package.path = DATADIR .. '/?/init.lua;' .. package.path package.path = USERDIR .. '/?.lua;' .. package.path package.path = USERDIR .. '/?/init.lua;' .. package.path +local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so') +package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix +package.native_plugins = {} +package.searchers = { package.searchers[1], package.searchers[2], function(modname) + local path = package.searchpath(modname, package.cpath) + if not path then return nil end + return system.load_native_plugin, path +end } + table.pack = table.pack or pack or function(...) return {...} end table.unpack = table.unpack or unpack diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 58421c31..3342bdb9 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -6,6 +6,7 @@ local style = require "core.style" local DocView = require "core.docview" local LogView = require "core.logview" local View = require "core.view" +local Object = require "core.object" local StatusView = View:extend() @@ -70,7 +71,7 @@ local function draw_items(self, items, x, y, draw_fn) local color = style.text for _, item in ipairs(items) do - if type(item) == "userdata" then + if Object.is(item, renderer.font) then font = item elseif type(item) == "table" then color = item diff --git a/data/core/style.lua b/data/core/style.lua index 7cf16eb1..3b0d9e35 100644 --- a/data/core/style.lua +++ b/data/core/style.lua @@ -21,11 +21,11 @@ style.tab_width = common.round(170 * SCALE) -- -- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead. -- The antialiasing grayscale with full hinting is interesting for crisp font rendering. -style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE) -style.big_font = style.font:copy(40 * SCALE) -style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"}) -style.icon_big_font = style.icon_font:copy(20 * SCALE) -style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE) +style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE) +style.big_font = style.font:copy(46 * SCALE) +style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"}) +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" } style.background2 = { common.color "#252529" } @@ -44,6 +44,8 @@ style.scrollbar2 = { common.color "#4b4b52" } 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.syntax = {} style.syntax["normal"] = { common.color "#e1e1e6" } diff --git a/data/core/view.lua b/data/core/view.lua index d1374ee4..d6d1bcbc 100644 --- a/data/core/view.lua +++ b/data/core/view.lua @@ -102,13 +102,9 @@ function View:on_text_input(text) -- no-op end - function View:on_mouse_wheel(y) - if self.scrollable then - self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll - end -end +end function View:get_content_bounds() local x = self.scroll.x diff --git a/data/plugins/autocomplete.lua b/data/plugins/autocomplete.lua index c41f233d..fde9487e 100644 --- a/data/plugins/autocomplete.lua +++ b/data/plugins/autocomplete.lua @@ -10,9 +10,9 @@ local RootView = require "core.rootview" local DocView = require "core.docview" local Doc = require "core.doc" -config.plugins.autocomplete = { +config.plugins.autocomplete = { -- Amount of characters that need to be written for autocomplete - min_len = 1, + min_len = 3, -- The max amount of visible items max_height = 6, -- The max amount of scrollable items @@ -502,6 +502,11 @@ command.add(predicate, { suggestions_idx = math.min(suggestions_idx + 1, #suggestions) end, + ["autocomplete:cycle"] = function() + local newidx = suggestions_idx + 1 + suggestions_idx = newidx > #suggestions and 1 or newidx + end, + ["autocomplete:cancel"] = function() reset_suggestions() end, diff --git a/data/plugins/detectindent.lua b/data/plugins/detectindent.lua index 45ebaee6..20541c82 100644 --- a/data/plugins/detectindent.lua +++ b/data/plugins/detectindent.lua @@ -102,6 +102,11 @@ end local function update_cache(doc) local type, size, score = detect_indent_stat(doc) local score_threshold = 4 + if score < score_threshold then + -- use default values + type = config.tab_type + size = config.indent_size + end cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) } doc.indent_info = cache[doc] end @@ -111,20 +116,14 @@ local new = Doc.new function Doc:new(...) new(self, ...) update_cache(self) - if not cache[self].confirmed then - core.add_thread(function () - while not cache[self].confirmed do - update_cache(self) - coroutine.yield(1) - end - end, self) - end end local clean = Doc.clean function Doc:clean(...) clean(self, ...) - update_cache(self) + if not cache[self].confirmed then + update_cache(self) + end end @@ -152,3 +151,78 @@ function DocView:draw(...) return with_indent_override(self.doc, draw, self, ...) end + +local function set_indent_type(doc, type) + cache[doc] = {type = type, + size = cache[doc].value or config.indent_size, + confirmed = true} + doc.indent_info = cache[doc] +end + +local function set_indent_type_command() + core.command_view:enter( + "Specify indent style for this file", + function(value) -- submit + local doc = core.active_view.doc + value = value:lower() + set_indent_type(doc, value == "tabs" and "hard" or "soft") + end, + function(text) -- suggest + return common.fuzzy_match({"tabs", "spaces"}, text) + end, + nil, -- cancel + function(text) -- validate + local t = text:lower() + return t == "tabs" or t == "spaces" + end + ) +end + + +local function set_indent_size(doc, size) + cache[doc] = {type = cache[doc].type or config.tab_type, + size = size, + confirmed = true} + doc.indent_info = cache[doc] +end + +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)) + local doc = core.active_view.doc + set_indent_size(doc, value) + end, + nil, -- suggest + nil, -- cancel + function(value) -- validate + local value = tonumber(value) + return value ~= nil and value >= 1 + end + ) +end + + +command.add("core.docview", { + ["indent:set-file-indent-type"] = set_indent_type_command, + ["indent:set-file-indent-size"] = set_indent_size_command +}) + + +command.add(function() + return core.active_view:is(DocView) + 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 +}) + + +command.add(function() + return core.active_view:is(DocView) + 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 +}) diff --git a/data/plugins/drawwhitespace.lua b/data/plugins/drawwhitespace.lua new file mode 100644 index 00000000..da9d1b12 --- /dev/null +++ b/data/plugins/drawwhitespace.lua @@ -0,0 +1,32 @@ +-- mod-version:2 -- lite-xl 2.0 + +local style = require "core.style" +local DocView = require "core.docview" +local common = require "core.common" + +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["comment"]) + local color = style.syntax.comment + local ty, tx = y + self:get_line_text_y_offset() + local text, offset, s, e = self.doc.lines[idx], 1 + 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) + offset = e + 1 + end + offset = 1 + 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) + offset = e + 1 + end + draw_line_text(self, idx, x, y) +end + + diff --git a/data/plugins/language_md.lua b/data/plugins/language_md.lua index 6e6e4255..3c1c329a 100644 --- a/data/plugins/language_md.lua +++ b/data/plugins/language_md.lua @@ -1,22 +1,41 @@ -- mod-version:2 -- lite-xl 2.0 local syntax = require "core.syntax" + + syntax.add { files = { "%.md$", "%.markdown$" }, patterns = { - { pattern = "\\.", type = "normal" }, - { pattern = { "<!%-%-", "%-%->" }, type = "comment" }, - { 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 = "\\.", type = "normal" }, + { pattern = { "<!%-%-", "%-%->" }, type = "comment" }, + { pattern = { "```c", "```" }, type = "string", syntax = ".c" }, + { pattern = { "```c++", "```" }, 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 = { "```html", "```" }, type = "string", syntax = ".html" }, + { 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 = { "```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 = { "```", "```" }, 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" }, }, symbols = { }, } diff --git a/data/plugins/language_python.lua b/data/plugins/language_python.lua index e19caa63..252a0d14 100644 --- a/data/plugins/language_python.lua +++ b/data/plugins/language_python.lua @@ -2,7 +2,7 @@ local syntax = require "core.syntax" syntax.add { - files = { "%.py$", "%.pyw$" }, + files = { "%.py$", "%.pyw$", "%.rpy$" }, headers = "^#!.*[ /]python", comment = "#", patterns = { diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 61debbff..5a17c8ca 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -7,8 +7,7 @@ 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 offset = self:get_font():get_width(ns) local x = self:get_line_screen_position(1) + offset local y = self.position.y local w = math.ceil(SCALE * 1) diff --git a/data/plugins/projectsearch.lua b/data/plugins/projectsearch.lua index 45967697..d0d75d7f 100644 --- a/data/plugins/projectsearch.lua +++ b/data/plugins/projectsearch.lua @@ -92,7 +92,7 @@ end function ResultsView:on_mouse_pressed(...) local caught = ResultsView.super.on_mouse_pressed(self, ...) if not caught then - self:open_selected_result() + return self:open_selected_result() end end @@ -108,6 +108,7 @@ function ResultsView:open_selected_result() dv.doc:set_selection(res.line, res.col) dv:scroll_to_line(res.line, false, true) end) + return true end @@ -171,7 +172,7 @@ function ResultsView:draw() local ox, oy = self:get_content_offset() local x, y = ox + style.padding.x, oy + style.padding.y local files_number = core.project_files_number() - local per = files_number and self.last_file_idx / files_number or 1 + local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1) local text if self.searching then if files_number then diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index 8d16304b..56eabbb0 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -13,9 +13,6 @@ config.plugins.scale = { use_mousewheel = true } -local MINIMUM_SCALE = 0.25; - -local scale_level = 0 local scale_steps = 0.05 local current_scale = SCALE @@ -36,9 +33,6 @@ local function set_scale(scale) local s = scale / current_scale current_scale = scale - -- we set scale_level in case this was called by user - scale_level = (scale - default_scale) / scale_steps - if config.plugins.scale.mode == "ui" then SCALE = scale @@ -50,10 +44,14 @@ local function set_scale(scale) style.tab_width = style.tab_width * s for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do - renderer.font.set_size(style[name], s * style[name]:get_size()) + style[name] = renderer.font.copy(style[name], s * style[name]:get_size()) end else - renderer.font.set_size(style.code_font, s * style.code_font:get_size()) + 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 -- restore scroll positions @@ -69,30 +67,16 @@ local function get_scale() return current_scale end -local on_mouse_wheel = RootView.on_mouse_wheel - -function RootView:on_mouse_wheel(d, ...) - if keymap.modkeys["ctrl"] and config.plugins.scale.use_mousewheel then - if d < 0 then command.perform "scale:decrease" end - if d > 0 then command.perform "scale:increase" end - else - return on_mouse_wheel(self, d, ...) - end -end - local function res_scale() - scale_level = 0 set_scale(default_scale) end local function inc_scale() - scale_level = scale_level + 1 - set_scale(default_scale + scale_level * scale_steps) + set_scale(current_scale + scale_steps) end -local function dec_scale() - scale_level = scale_level - 1 - set_scale(math.max(default_scale + scale_level * scale_steps), MINIMUM_SCALE) +local function dec_scale() + set_scale(current_scale - scale_steps) end @@ -106,6 +90,8 @@ keymap.add { ["ctrl+0"] = "scale:reset", ["ctrl+-"] = "scale:decrease", ["ctrl+="] = "scale:increase", + ["ctrl+wheelup"] = "scale:increase", + ["ctrl+wheeldown"] = "scale:decrease" } return { diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index f9a67aaf..fa3ab53a 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -243,7 +243,7 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) end else core.try(function() - local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename) + local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename) core.root_view:open_doc(core.open_doc(doc_filename)) end) end @@ -437,15 +437,31 @@ menu:register( command.add(nil, { ["treeview:toggle"] = function() view.visible = not view.visible - end, + end}) + +command.add(function() return view.hovered_item ~= nil end, { ["treeview:rename"] = function() local old_filename = view.hovered_item.filename + local old_abs_filename = view.hovered_item.abs_filename core.command_view:set_text(old_filename) core.command_view:enter("Rename", function(filename) - os.rename(old_filename, 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.reschedule_project_scan() - core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) end, common.path_suggest) end, @@ -521,7 +537,7 @@ command.add(nil, { local hovered_item = view.hovered_item if PLATFORM == "Windows" then - system.exec("start " .. hovered_item.abs_filename) + 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 |
