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/doc.lua b/data/core/commands/doc.lua index b8ce2cb5..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() @@ -388,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 f8e8e45a..d1af0d88 100644 --- a/data/core/commands/findreplace.lua +++ b/data/core/commands/findreplace.lua @@ -37,7 +37,7 @@ local function update_preview(sel, search_fn, text) last_view:scroll_to_line(line2, true) found_expression = true else - last_view.doc:set_selection(unpack(sel)) + last_view.doc:set_selection(table.unpack(sel)) found_expression = false end end @@ -55,7 +55,7 @@ end local function find(label, search_fn) last_view, last_sel, last_finds = 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) @@ -69,8 +69,8 @@ local function find(label, search_fn) last_fn, last_text = search_fn, text else core.error("Couldn't find %q", text) - last_view.doc:set_selection(unpack(last_sel)) - last_view:scroll_to_make_visible(unpack(last_sel)) + last_view.doc:set_selection(table.unpack(last_sel)) + last_view:scroll_to_make_visible(table.unpack(last_sel)) end end, function(text) update_preview(last_sel, search_fn, text) @@ -79,8 +79,8 @@ local function find(label, search_fn) end, function(explicit) core.status_view:remove_tooltip() if explicit then - last_view.doc:set_selection(unpack(last_sel)) - last_view:scroll_to_make_visible(unpack(last_sel)) + last_view.doc:set_selection(table.unpack(last_sel)) + last_view:scroll_to_make_visible(table.unpack(last_sel)) end end) 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/config.lua b/data/core/config.lua index 7cf23925..faffc27e 100644 --- a/data/core/config.lua +++ b/data/core/config.lua @@ -28,6 +28,7 @@ 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. 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/init.lua b/data/core/doc/init.lua index 920e2669..640e9fd5 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -202,9 +202,9 @@ local function selection_iterator(invariant, idx) local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1) if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end if invariant[2] then - return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4)) + return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4)) else - return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4) + return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4) end end @@ -305,7 +305,7 @@ local function pop_undo(self, undo_stack, redo_stack, modified) local line1, col1, line2, col2 = table.unpack(cmd) self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time) elseif cmd.type == "selection" then - self.selections = { unpack(cmd) } + self.selections = { table.unpack(cmd) } end modified = modified or (cmd.type ~= "selection") @@ -348,7 +348,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time) -- push undo local line2, col2 = self:position_offset(line, col, #text) - push_undo(undo_stack, time, "selection", unpack(self.selections)) + push_undo(undo_stack, time, "selection", table.unpack(self.selections)) push_undo(undo_stack, time, "remove", line, col, line2, col2) -- update highlighter and assure selection is in bounds @@ -360,7 +360,7 @@ end function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) -- push undo local text = self:get_text(line1, col1, line2, col2) - push_undo(undo_stack, time, "selection", unpack(self.selections)) + push_undo(undo_stack, time, "selection", table.unpack(self.selections)) push_undo(undo_stack, time, "insert", line1, col1, text) -- get line content before/after removed text diff --git a/data/core/docview.lua b/data/core/docview.lua index 6621ef72..07da1cef 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -224,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, ...) @@ -282,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 = { } @@ -290,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 diff --git a/data/core/init.lua b/data/core/init.lua index 13d0907b..d07f1cbc 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -922,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 53a20468..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", @@ -93,6 +95,11 @@ local function keymap_macos(keymap) ["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/keymap.lua b/data/core/keymap.lua index 7f7c0fe2..fd552f19 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -1,4 +1,5 @@ local command = require "core.command" +local config = require "core.config" local keymap = {} keymap.modkeys = {} @@ -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 -function keymap.get_binding(cmd) - return keymap.reverse_map[cmd] +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.on_key_pressed(k) +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 table.unpack(keymap.reverse_map[cmd] or {}) +end + + +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", @@ -193,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/rootview.lua b/data/core/rootview.lua index 0d219474..07f8b7bf 100644 --- a/data/core/rootview.lua +++ b/data/core/rootview.lua @@ -857,27 +857,29 @@ 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 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 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 @@ -1000,17 +1002,18 @@ function RootView:on_mouse_moved(x, y, dx, dy) 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) + elseif self.overlapping_node then + core.request_cursor(self.overlapping_node.active_view.cursor) end end @@ -1018,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 diff --git a/data/core/start.lua b/data/core/start.lua index 330c42f5..31fed147 100644 --- a/data/core/start.lua +++ b/data/core/start.lua @@ -28,3 +28,6 @@ package.searchers = { package.searchers[1], package.searchers[2], function(modna 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/tokenizer.lua b/data/core/tokenizer.lua index a20dba5e..bdf6197b 100644 --- a/data/core/tokenizer.lua +++ b/data/core/tokenizer.lua @@ -155,7 +155,7 @@ function tokenizer.tokenize(incoming_syntax, text, state) if count % 2 == 0 then break end end until not res[1] or not close or not target[3] - return unpack(res) + return table.unpack(res) end while i <= #text do 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/drawwhitespace.lua b/data/plugins/drawwhitespace.lua index da9d1b12..7b7fa011 100644 --- a/data/plugins/drawwhitespace.lua +++ b/data/plugins/drawwhitespace.lua @@ -9,24 +9,28 @@ 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 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 = 1 + 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 end draw_line_text(self, idx, x, y) end - - diff --git a/data/plugins/lineguide.lua b/data/plugins/lineguide.lua index 5a17c8ca..9f2fca4a 100644 --- a/data/plugins/lineguide.lua +++ b/data/plugins/lineguide.lua @@ -6,8 +6,7 @@ local DocView = require "core.docview" local draw_overlay = DocView.draw_overlay function DocView:draw_overlay(...) - local ns = ("n"):rep(config.line_limit) - local offset = self:get_font():get_width(ns) + local offset = self:get_font():get_width("n") * config.line_limit 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 dda3a2d0..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 diff --git a/data/plugins/scale.lua b/data/plugins/scale.lua index b8384609..56eabbb0 100644 --- a/data/plugins/scale.lua +++ b/data/plugins/scale.lua @@ -67,17 +67,6 @@ 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() set_scale(default_scale) end @@ -101,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/src/api/system.c b/src/api/system.c index c12ee067..c343258c 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -20,9 +20,11 @@ extern SDL_Window *window; static const char* button_name(int button) { switch (button) { - case 1 : return "left"; - case 2 : return "middle"; - case 3 : return "right"; + case SDL_BUTTON_LEFT : return "left"; + case SDL_BUTTON_MIDDLE : return "middle"; + case SDL_BUTTON_RIGHT : return "right"; + case SDL_BUTTON_X1 : return "x"; + case SDL_BUTTON_X2 : return "y"; default : return "?"; } }