From 37a3884ee29823252d84c5abf1fa22f4e0f25508 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 15:22:56 -0400 Subject: [PATCH 01/22] Initial commit of multicursor. Next step is to investigate how multicursor works on various other IDEs and ape those. --- data/core/commands/doc.lua | 245 +++++++++++++++++++++---------------- data/core/doc/init.lua | 124 +++++++++++++------ data/core/docview.lua | 69 +++++++---- 3 files changed, 270 insertions(+), 168 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index c8c27509..ae67d5e1 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -24,13 +24,19 @@ local function get_indent_string() end -local function doc_multiline_selection(sort) - local line1, col1, line2, col2, swap = doc():get_selection(sort) - if line2 > line1 and col2 == 1 then - line2 = line2 - 1 - col2 = #doc().lines[line2] +local function doc_multiline_selections(sort) + local iter = doc():get_selections(sort) + return function() + local idx, line1, col1, line2, col2 = iter() + if not idx then + return + end + if line2 > line1 and col2 == 1 then + line2 = line2 - 1 + col2 = #doc().lines[line2] + end + return idx, line1, col1, line2, col2 end - return line1, col1, line2, col2, swap end local function append_line_if_last_line(line) @@ -76,46 +82,51 @@ local commands = { end, ["doc:newline"] = function() - local line, col = doc():get_selection() - local indent = doc().lines[line]:match("^[\t ]*") - if col <= #indent then - indent = indent:sub(#indent + 2 - col) + for idx, line, col in doc():get_selections() do + local indent = doc().lines[line]:match("^[\t ]*") + if col <= #indent then + indent = indent:sub(#indent + 2 - col) + end + doc():text_input("\n" .. indent, idx) end - doc():text_input("\n" .. indent) end, ["doc:newline-below"] = function() - local line = doc():get_selection() - local indent = doc().lines[line]:match("^[\t ]*") - doc():insert(line, math.huge, "\n" .. indent) - doc():set_selection(line + 1, math.huge) + for idx, line in doc():get_selections() do + local indent = doc().lines[line]:match("^[\t ]*") + doc():insert(line, math.huge, "\n" .. indent) + doc():set_selections(idx, line + 1, math.huge) + end end, ["doc:newline-above"] = function() - local line = doc():get_selection() - local indent = doc().lines[line]:match("^[\t ]*") - doc():insert(line, 1, indent .. "\n") - doc():set_selection(line, math.huge) + for idx, line in doc():get_selections() do + local indent = doc().lines[line]:match("^[\t ]*") + doc():insert(line, 1, indent .. "\n") + doc():set_selections(idx, line, math.huge) + end end, ["doc:delete"] = function() - local line, col = doc():get_selection() - if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then - doc():remove(line, col, line, math.huge) + for idx, line, col in doc():get_selections() do + if not doc():has_selection(idx) and doc().lines[line]:find("^%s*$", col) then + doc():remove(line, col, line, math.huge) + end + doc():delete_to(idx, translate.next_char) end - doc():delete_to(translate.next_char) end, ["doc:backspace"] = function() - local line, col = doc():get_selection() - if not doc():has_selection() then - local text = doc():get_text(line, 1, line, col) - if #text >= config.indent_size and text:find("^ *$") then - doc():delete_to(0, -config.indent_size) - return + for idx, line, col in doc():get_selections() do + if not doc():has_selection(idx) then + local text = doc():get_text(line, 1, line, col) + if #text >= config.indent_size and text:find("^ *$") then + doc():delete_to(idx, 0, -config.indent_size) + return + end end + doc():delete_to(idx, translate.previous_char) end - doc():delete_to(translate.previous_char) end, ["doc:select-all"] = function() @@ -128,75 +139,92 @@ local commands = { end, ["doc:select-lines"] = function() - local line1, _, line2, _, swap = doc():get_selection(true) - append_line_if_last_line(line2) - doc():set_selection(line1, 1, line2 + 1, 1, swap) + for idx, line1, _, line2 in doc():get_selections(true) do + append_line_if_last_line(line2) + doc():set_selections(idx, line1, 1, line2 + 1, 1, swap) + end end, ["doc:select-word"] = function() - local line1, col1 = doc():get_selection(true) - local line1, col1 = translate.start_of_word(doc(), line1, col1) - local line2, col2 = translate.end_of_word(doc(), line1, col1) - doc():set_selection(line2, col2, line1, col1) + for idx, line1, col1 in doc():get_selections(true) do + local line1, col1 = translate.start_of_word(doc(), line1, col1) + local line2, col2 = translate.end_of_word(doc(), line1, col1) + doc():set_selections(idx, line2, col2, line1, col1) + end end, ["doc:join-lines"] = function() - local line1, _, line2 = doc():get_selection(true) - if line1 == line2 then line2 = line2 + 1 end - local text = doc():get_text(line1, 1, line2, math.huge) - text = text:gsub("(.-)\n[\t ]*", function(x) - return x:find("^%s*$") and x or x .. " " - end) - doc():insert(line1, 1, text) - doc():remove(line1, #text + 1, line2, math.huge) - if doc():has_selection() then - doc():set_selection(line1, math.huge) + for idx, line1, _, line2 in doc():get_selections(true) do + if line1 == line2 then line2 = line2 + 1 end + local text = doc():get_text(line1, 1, line2, math.huge) + text = text:gsub("(.-)\n[\t ]*", function(x) + return x:find("^%s*$") and x or x .. " " + end) + doc():insert(line1, 1, text) + doc():remove(line1, #text + 1, line2, math.huge) + if doc():has_selection(idx) then + doc():set_selections(idx, line1, math.huge) + end end end, ["doc:indent"] = function() - doc():indent_text(false, doc_multiline_selection(true)) + 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 not l1 then + doc():set_selections(idx, l1, c1, l2, c2) + end + end end, ["doc:unindent"] = function() - doc():indent_text(true, doc_multiline_selection(true)) + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2) + if not l1 then + doc():set_selections(idx, l1, c1, l2, c2) + end + end end, ["doc:duplicate-lines"] = function() - local line1, col1, line2, col2, swap = doc_multiline_selection(true) - append_line_if_last_line(line2) - local text = doc():get_text(line1, 1, line2 + 1, 1) - doc():insert(line2 + 1, 1, text) - local n = line2 - line1 + 1 - doc():set_selection(line1 + n, col1, line2 + n, col2, swap) + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + append_line_if_last_line(line2) + local text = doc():get_text(line1, 1, line2 + 1, 1) + doc():insert(line2 + 1, 1, text) + local n = line2 - line1 + 1 + doc():set_selections(idx, line1 + n, col1, line2 + n, col2, swap) + end end, ["doc:delete-lines"] = function() - local line1, col1, line2 = doc_multiline_selection(true) - append_line_if_last_line(line2) - doc():remove(line1, 1, line2 + 1, 1) - doc():set_selection(line1, col1) + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + append_line_if_last_line(line2) + doc():remove(line1, 1, line2 + 1, 1) + doc():set_selections(idx, line1, col1) + end end, ["doc:move-lines-up"] = function() - local line1, col1, line2, col2, swap = doc_multiline_selection(true) - append_line_if_last_line(line2) - if line1 > 1 then - local text = doc().lines[line1 - 1] - doc():insert(line2 + 1, 1, text) - doc():remove(line1 - 1, 1, line1, 1) - doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap) + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + append_line_if_last_line(line2) + if line1 > 1 then + local text = doc().lines[line1 - 1] + doc():insert(line2 + 1, 1, text) + doc():remove(line1 - 1, 1, line1, 1) + doc():set_selections(idx, line1 - 1, col1, line2 - 1, col2) + end end end, ["doc:move-lines-down"] = function() - local line1, col1, line2, col2, swap = doc_multiline_selection(true) - append_line_if_last_line(line2 + 1) - if line2 < #doc().lines then - local text = doc().lines[line2 + 1] - doc():remove(line2 + 1, 1, line2 + 2, 1) - doc():insert(line1, 1, text) - doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap) + for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + append_line_if_last_line(line2 + 1) + if line2 < #doc().lines then + local text = doc().lines[line2 + 1] + doc():remove(line2 + 1, 1, line2 + 2, 1) + doc():insert(line1, 1, text) + doc():set_selections(idx, line1 + 1, col1, line2 + 1, col2) + end end end, @@ -205,28 +233,29 @@ local commands = { if not comment then return end local indentation = get_indent_string() local comment_text = comment .. " " - local line1, _, line2 = doc_multiline_selection(true) - local uncomment = true - local start_offset = math.huge - for line = line1, line2 do - local text = doc().lines[line] - local s = text:find("%S") - local cs, ce = text:find(comment_text, s, true) - if s and cs ~= s then - uncomment = false - start_offset = math.min(start_offset, s) - end - end - for line = line1, line2 do - local text = doc().lines[line] - local s = text:find("%S") - if uncomment then + for idx, line1, _, line2 in doc_multiline_selections(true) do + local uncomment = true + local start_offset = math.huge + for line = line1, line2 do + local text = doc().lines[line] + local s = text:find("%S") local cs, ce = text:find(comment_text, s, true) - if ce then - doc():remove(line, cs, line, ce + 1) + if s and cs ~= s then + uncomment = false + start_offset = math.min(start_offset, s) + end + end + for line = line1, line2 do + local text = doc().lines[line] + local s = text:find("%S") + if uncomment then + local cs, ce = text:find(comment_text, s, true) + if ce then + doc():remove(line, cs, line, ce + 1) + end + elseif s then + doc():insert(line, start_offset, comment_text) end - elseif s then - doc():insert(line, start_offset, comment_text) end end end, @@ -350,26 +379,30 @@ local translations = { } for name, fn in pairs(translations) do - commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end - commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end - commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end + commands["doc:move-to-" .. name] = function() doc():move_to(nil, fn, dv()) end + commands["doc:select-to-" .. name] = function() doc():select_to(nil, fn, dv()) end + commands["doc:delete-to-" .. name] = function() doc():delete_to(nil, fn, dv()) end end commands["doc:move-to-previous-char"] = function() - if doc():has_selection() then - local line, col = doc():get_selection(true) - doc():set_selection(line, col) - else - doc():move_to(translate.previous_char) + for idx, line, col in doc():get_selections(true) do + if doc():has_selection(idx) then + doc():set_selections(idx, line, col) + end + end + if not doc():has_selection() then + doc():move_to(nil, translate.previous_char) end end commands["doc:move-to-next-char"] = function() - if doc():has_selection() then - local _, _, line, col = doc():get_selection(true) - doc():set_selection(line, col) - else - doc():move_to(translate.next_char) + for idx, _, _, line, col in doc():get_selections(true) do + if doc():has_selection(idx) then + doc():set_selections(idx, line, col) + end + end + if not doc():has_selection() then + doc():move_to(nil, translate.next_char) end end diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 88f63a68..63a4225b 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -46,7 +46,7 @@ end function Doc:reset() self.lines = { "\n" } - self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } } + self.selections = { 1, 1, 1, 1 } self.undo_stack = { idx = 1 } self.redo_stack = { idx = 1 } self.clean_change_id = 1 @@ -131,9 +131,20 @@ function Doc:set_selection(line1, col1, line2, col2, swap) assert(not line2 == not col2, "expected 2 or 4 arguments") if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end line1, col1 = self:sanitize_position(line1, col1) + line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) + self.selections = { line1, col1, line2, col2 } +end + +function Doc:set_selections(idx, line1, col1, line2, col2, swap) + assert(not line2 == not col2, "expected 3 or 5 arguments") + if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end + line1, col1 = self:sanitize_position(line1, col1) line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) - self.selection.a.line, self.selection.a.col = line1, col1 - self.selection.b.line, self.selection.b.col = line2, col2 + local target = (idx - 1)*4 + 1 + self.selections[target] = line1 + self.selections[target+1] = col1 + self.selections[target+2] = line2 + self.selections[target+3] = col2 end @@ -147,22 +158,53 @@ end function Doc:get_selection(sort) - local a, b = self.selection.a, self.selection.b if sort then - return sort_positions(a.line, a.col, b.line, b.col) + return sort_positions(self.selections[1], self.selections[2], self.selections[3], self.selections[4]) + end + return self.selections[1], self.selections[2], self.selections[3], self.selections[4] +end + +function Doc:get_selection_count() + return #self.selections / 4 +end + +function Doc:get_selections(sort) + local idx = 1 + return function() + if idx >= #self.selections then + return + end + idx = idx + 4 + if sort then + return ((idx - 5) / 4) + 1, sort_positions(self.selections[idx - 4], self.selections[idx - 3], self.selections[idx - 2], self.selections[idx - 1]) + else + return ((idx - 5) / 4) + 1, self.selections[idx - 4], self.selections[idx - 3], self.selections[idx - 2], self.selections[idx - 1] + end end - return a.line, a.col, b.line, b.col end -function Doc:has_selection() - local a, b = self.selection.a, self.selection.b - return not (a.line == b.line and a.col == b.col) +function Doc:has_selection(idx) + if idx then + local target = (idx-1)*4+1 + return + self.selections[target] ~= self.selections[target+2] or + self.selections[target+1] ~= self.selections[target+3] + end + for target = 1, #self.selections, 4 do + if self.selections[target] ~= self.selections[target+2] or + self.selections[target+1] ~= self.selections[target+3] then + return true + end + end + return false end function Doc:sanitize_selection() - self:set_selection(self:get_selection()) + for idx, line1, col1, line2, col2 in self:get_selections() do + self:set_selections(idx, line1, col1, line2, col2) + end end @@ -348,13 +390,16 @@ function Doc:redo() end -function Doc:text_input(text) - if self:has_selection() then - self:delete_to() +function Doc:text_input(text, idx) + for sidx, line, col in self:get_selections() do + if not idx or idx == sidx then + if self:has_selection(sidx) then + self:delete_to(sidx) + end + self:insert(line, col, text) + self:move_to(sidx, #text) + end end - local line, col = self:get_selection() - self:insert(line, col, text) - self:move_to(#text) end @@ -380,29 +425,38 @@ function Doc:replace(fn) end -function Doc:delete_to(...) - local line, col = self:get_selection(true) - if self:has_selection() then - self:remove(self:get_selection()) - else - local line2, col2 = self:position_offset(line, col, ...) - self:remove(line, col, line2, col2) - line, col = sort_positions(line, col, line2, col2) +function Doc:delete_to(idx, ...) + for sidx, line1, col1, line2, col2 in self:get_selections(true) do + if not idx or sidx == idx then + if self:has_selection(sidx) then + self:remove(line1, col1, line2, col2) + else + local l2, c2 = self:position_offset(line, col, ...) + self:remove(line1, col1, l2, c2) + line1, col1 = sort_positions(line1, col1, l2, c2) + end + self:set_selections(sidx, line1, col1) + end end - self:set_selection(line, col) end -function Doc:move_to(...) - local line, col = self:get_selection() - self:set_selection(self:position_offset(line, col, ...)) +function Doc:move_to(idx, ...) + for sidx, line, col in self:get_selections() do + if not idx or sidx == idx then + self:set_selections(sidx, self:position_offset(line, col, ...)) + end + end end -function Doc:select_to(...) - local line, col, line2, col2 = self:get_selection() - line, col = self:position_offset(line, col, ...) - self:set_selection(line, col, line2, col2) +function Doc:select_to(idx, ...) + for sidx, line, col, line2, col2 in self:get_selections() do + if not idx or idx == sidx then + line, col = self:position_offset(line, col, ...) + self:set_selections(sidx, line, col, line2, col2) + end + end end @@ -439,7 +493,7 @@ end -- inserts the appropriate whitespace, as if you typed them normally. -- * if you are unindenting, the cursor will jump to the start of the line, -- and remove the appropriate amount of spaces (or a tab). -function Doc:indent_text(unindent, line1, col1, line2, col2, swap) +function Doc:indent_text(unindent, line1, col1, line2, col2) local text = get_indent_string() local _, se = self.lines[line1]:find("^[ \t]+") local in_beginning_whitespace = col1 == 1 or (se and col1 <= se + 1) @@ -455,9 +509,9 @@ function Doc:indent_text(unindent, line1, col1, line2, col2, swap) l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d if (unindent or in_beginning_whitespace) and not self:has_selection() then local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1]) - self:set_selection(line1, start_cursor, line2, start_cursor, swap) + return line1, start_cursor, line2, start_cursor else - self:set_selection(line1, col1 + l1d, line2, col2 + l2d, swap) + return line1, col1 + l1d, line2, col2 + l2d end else self:text_input(text) diff --git a/data/core/docview.lua b/data/core/docview.lua index 070ee0c4..edde4c55 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -276,7 +276,18 @@ function DocView:on_mouse_moved(x, y, ...) local l1, c1 = self:resolve_screen_position(x, y) local l2, c2 = table.unpack(self.mouse_selecting) local clicks = self.mouse_selecting.clicks - self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2)) + if keymap.modkeys["ctrl"] then + if l1 > l2 then + l2 = l1 + end + local idx = 1 + for i = l1, l2 do + idx = idx + 1 + self.doc:set_selections(idx, i, math.min(c1, #self.doc.lines[i]), i, math.min(c1, #self.doc.lines[i])) + end + else + self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2)) + end end end @@ -340,46 +351,50 @@ end function DocView:draw_line_body(idx, x, y) - local line, col = self.doc:get_selection() - -- draw selection if it overlaps this line - local line1, col1, line2, col2 = self.doc:get_selection(true) - if idx >= line1 and idx <= line2 then - local text = self.doc.lines[idx] - if line1 ~= idx then col1 = 1 end - if line2 ~= idx then col2 = #text + 1 end - local x1 = x + self:get_col_x_offset(idx, col1) - local x2 = x + self:get_col_x_offset(idx, col2) - local lh = self:get_line_height() - renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) + for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do + if idx >= line1 and idx <= line2 then + local text = self.doc.lines[idx] + if line1 ~= idx then col1 = 1 end + if line2 ~= idx then col2 = #text + 1 end + local x1 = x + self:get_col_x_offset(idx, col1) + local x2 = x + self:get_col_x_offset(idx, col2) + local lh = self:get_line_height() + renderer.draw_rect(x1, y, x2 - x1, lh, style.selection) + end end - - -- draw line highlight if caret is on this line - if config.highlight_current_line and not self.doc:has_selection() - and line == idx and core.active_view == self then - self:draw_line_highlight(x + self.scroll.x, y) + 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 not self.doc:has_selection(lidx) + and line1 == idx and core.active_view == self then + self:draw_line_highlight(x + self.scroll.x, y) + end end - + -- draw line's text self:draw_line_text(idx, x, y) -- draw caret if it overlaps this line local T = config.blink_period - if line == idx and core.active_view == self - and (core.blink_timer - core.blink_start) % T < T / 2 - and system.window_has_focus() then - local lh = self:get_line_height() - local x1 = x + self:get_col_x_offset(line, col) - renderer.draw_rect(x1, y, style.caret_width, lh, style.caret) + for _, line, col in self.doc:get_selections() do + if line == idx and core.active_view == self + and (core.blink_timer - core.blink_start) % T < T / 2 + and system.window_has_focus() then + local lh = self:get_line_height() + local x1 = x + self:get_col_x_offset(line, col) + renderer.draw_rect(x1, y, style.caret_width, lh, style.caret) + end end end function DocView:draw_line_gutter(idx, x, y) local color = style.line_number - local line1, _, line2, _ = self.doc:get_selection(true) - if idx >= line1 and idx <= line2 then - color = style.line_number2 + for _, line1, _, line2 in self.doc:get_selections(true) do + if idx >= line1 and idx <= line2 then + color = style.line_number2 + break + end end local yoffset = self:get_line_text_y_offset() x = x + style.padding.x From 93670a314d1c34e4d8f0973b0e159fa976f36e79 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 15:36:26 -0400 Subject: [PATCH 02/22] Changed iterator behaviour to avoid allocating a closure each time. --- data/core/doc/init.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 63a4225b..21ee3f5c 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -169,18 +169,17 @@ function Doc:get_selection_count() end function Doc:get_selections(sort) - local idx = 1 - return function() - if idx >= #self.selections then + return function(selections, idx) + local target = idx*4 + 1 + if target >= #selections then return end - idx = idx + 4 if sort then - return ((idx - 5) / 4) + 1, sort_positions(self.selections[idx - 4], self.selections[idx - 3], self.selections[idx - 2], self.selections[idx - 1]) + return idx+1, sort_positions(selections[target], selections[target+1], selections[target+2], selections[target+3]) else - return ((idx - 5) / 4) + 1, self.selections[idx - 4], self.selections[idx - 3], self.selections[idx - 2], self.selections[idx - 1] + return idx+1, selections[target], selections[target+1], selections[target+2], selections[target+3] end - end + end, self.selections, 0 end From a7f39017ffd8dae6b94a7100466f9860fef556ba Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 16:06:21 -0400 Subject: [PATCH 03/22] Fixed undo stack. --- data/core/doc/init.lua | 16 ++++++++++------ data/core/docview.lua | 5 ++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 21ee3f5c..7dc34c4c 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -159,9 +159,16 @@ end function Doc:get_selection(sort) if sort then - return sort_positions(self.selections[1], self.selections[2], self.selections[3], self.selections[4]) + local result = {} + for sidx, line1, col1, line2, col2 in self:get_selections(true) do + table.insert(result, line1) + table.insert(result, col1) + table.insert(result, line2) + table.insert(result, col2) + end + return result end - return self.selections[1], self.selections[2], self.selections[3], self.selections[4] + return unpack(self.selections) end function Doc:get_selection_count() @@ -292,14 +299,11 @@ local function pop_undo(self, undo_stack, redo_stack, modified) if cmd.type == "insert" then local line, col, text = table.unpack(cmd) self:raw_insert(line, col, text, redo_stack, cmd.time) - elseif cmd.type == "remove" then 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.selection.a.line, self.selection.a.col = cmd[1], cmd[2] - self.selection.b.line, self.selection.b.col = cmd[3], cmd[4] + self.selections = { unpack(cmd) } end modified = modified or (cmd.type ~= "selection") diff --git a/data/core/docview.lua b/data/core/docview.lua index edde4c55..477cee48 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -278,12 +278,11 @@ function DocView:on_mouse_moved(x, y, ...) local clicks = self.mouse_selecting.clicks if keymap.modkeys["ctrl"] then if l1 > l2 then - l2 = l1 + l1, l2 = l2, l1 end local idx = 1 for i = l1, l2 do - idx = idx + 1 - self.doc:set_selections(idx, i, math.min(c1, #self.doc.lines[i]), i, math.min(c1, #self.doc.lines[i])) + self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c1, #self.doc.lines[i])) end else self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2)) From 08ab6cba05133576abc78cfb02597f6fd104af58 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 16:37:45 -0400 Subject: [PATCH 04/22] Added in multiple clipboard line buffers. --- data/core/commands/doc.lua | 34 +++++++++++++++++++++++++--------- data/core/doc/init.lua | 10 +++++++++- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index ae67d5e1..edba731e 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -45,7 +45,6 @@ local function append_line_if_last_line(line) end end - local function save(filename) doc():save(filename and core.normalize_to_project_dir(filename)) local saved_filename = doc().filename @@ -63,22 +62,39 @@ local commands = { end, ["doc:cut"] = function() - if doc():has_selection() then - local text = doc():get_text(doc():get_selection()) - system.set_clipboard(text) - doc():delete_to(0) + local full_text = "" + for idx, line1, col1, line2, col2 in doc():get_selections() do + if line1 ~= line2 or col1 ~= col2 then + local text = doc():get_text(line1, col1, line2, col2) + doc():delete_to(idx, 0) + full_text = full_text == "" and text or (full_text .. "\n" .. text) + doc():set_cursor_clipboard(idx, text) + else + doc():set_cursor_clipboard(idx, "") + end end + system.set_clipboard(full_text) end, ["doc:copy"] = function() - if doc():has_selection() then - local text = doc():get_text(doc():get_selection()) - system.set_clipboard(text) + local full_text = "" + for idx, line1, col1, line2, col2 in doc():get_selections() do + if line1 ~= line2 or col1 ~= col2 then + local text = doc():get_text(line1, col1, line2, col2) + full_text = full_text == "" and text or (full_text .. "\n" .. text) + doc():set_cursor_clipboard(idx, text) + else + doc():set_cursor_clipboard(idx, "") + end end + system.set_clipboard(full_text) end, ["doc:paste"] = function() - doc():text_input(system.get_clipboard():gsub("\r", "")) + for idx, line1, col1, line2, col2 in doc():get_selections() do + local value = doc():get_cursor_clipboard(idx) or system.get_clipboard() + doc():text_input(value:gsub("\r", ""), idx) + end end, ["doc:newline"] = function() diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 7dc34c4c..9fa22351 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -47,6 +47,7 @@ end function Doc:reset() self.lines = { "\n" } self.selections = { 1, 1, 1, 1 } + self.cursor_clipboard = {} self.undo_stack = { idx = 1 } self.redo_stack = { idx = 1 } self.clean_change_id = 1 @@ -126,6 +127,12 @@ function Doc:get_change_id() return self.undo_stack.idx end +function Doc:get_cursor_clipboard(idx) + return self.cursor_clipboard[idx] +end +function Doc:set_cursor_clipboard(idx, value) + self.cursor_clipboard[idx] = value +end function Doc:set_selection(line1, col1, line2, col2, swap) assert(not line2 == not col2, "expected 2 or 4 arguments") @@ -133,6 +140,7 @@ function Doc:set_selection(line1, col1, line2, col2, swap) line1, col1 = self:sanitize_position(line1, col1) line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) self.selections = { line1, col1, line2, col2 } + self.cursor_clipboard = {} end function Doc:set_selections(idx, line1, col1, line2, col2, swap) @@ -171,7 +179,7 @@ function Doc:get_selection(sort) return unpack(self.selections) end -function Doc:get_selection_count() +function Doc:get_cursor_count() return #self.selections / 4 end From 6c0d12441088414fa4beca1900789f3132fb00c3 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 21:18:05 -0400 Subject: [PATCH 05/22] Allows for rectangular selections with ctrl; also fixed tabbing. --- data/core/commands/doc.lua | 51 ++++++++++++++++++-------------------- data/core/doc/init.lua | 4 +-- data/core/docview.lua | 7 ++---- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index edba731e..1331901b 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -25,9 +25,10 @@ end local function doc_multiline_selections(sort) - local iter = doc():get_selections(sort) + local iter, state, idx = doc():get_selections(sort) return function() - local idx, line1, col1, line2, col2 = iter() + local line1, col1, line2, col2 + idx, line1, col1, line2, col2 = iter(state, idx) if not idx then return end @@ -52,6 +53,23 @@ local function save(filename) core.log("Saved \"%s\"", saved_filename) end +local function cut_or_copy(delete) + local full_text = "" + for idx, line1, col1, line2, col2 in doc():get_selections() do + if line1 ~= line2 or col1 ~= col2 then + local text = doc():get_text(line1, col1, line2, col2) + if delete then + doc():delete_to(idx, 0) + end + full_text = full_text == "" and text or (full_text .. "\n" .. text) + doc():set_cursor_clipboard(idx, text) + else + doc():set_cursor_clipboard(idx, "") + end + end + system.set_clipboard(full_text) +end + local commands = { ["doc:undo"] = function() doc():undo() @@ -62,32 +80,11 @@ local commands = { end, ["doc:cut"] = function() - local full_text = "" - for idx, line1, col1, line2, col2 in doc():get_selections() do - if line1 ~= line2 or col1 ~= col2 then - local text = doc():get_text(line1, col1, line2, col2) - doc():delete_to(idx, 0) - full_text = full_text == "" and text or (full_text .. "\n" .. text) - doc():set_cursor_clipboard(idx, text) - else - doc():set_cursor_clipboard(idx, "") - end - end - system.set_clipboard(full_text) + cut_or_copy(true) end, ["doc:copy"] = function() - local full_text = "" - for idx, line1, col1, line2, col2 in doc():get_selections() do - if line1 ~= line2 or col1 ~= col2 then - local text = doc():get_text(line1, col1, line2, col2) - full_text = full_text == "" and text or (full_text .. "\n" .. text) - doc():set_cursor_clipboard(idx, text) - else - doc():set_cursor_clipboard(idx, "") - end - end - system.set_clipboard(full_text) + cut_or_copy(false) end, ["doc:paste"] = function() @@ -187,7 +184,7 @@ local commands = { ["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 not l1 then + if l1 then doc():set_selections(idx, l1, c1, l2, c2) end end @@ -196,7 +193,7 @@ local commands = { ["doc:unindent"] = function() for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do local l1, c1, l2, c2 = doc():indent_text(true, line1, col1, line2, col2) - if not l1 then + if l1 then doc():set_selections(idx, l1, c1, l2, c2) end end diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 9fa22351..1be09824 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -518,14 +518,14 @@ function Doc:indent_text(unindent, line1, col1, line2, col2) unindent and rnded:sub(1, #rnded - #text) or rnded .. text) end l1d, l2d = #self.lines[line1] - l1d, #self.lines[line2] - l2d - if (unindent or in_beginning_whitespace) and not self:has_selection() then + if (unindent or in_beginning_whitespace) and not has_selection then local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1]) return line1, start_cursor, line2, start_cursor else return line1, col1 + l1d, line2, col2 + l2d end else - self:text_input(text) + self:insert(line1, col1, text) end end diff --git a/data/core/docview.lua b/data/core/docview.lua index 477cee48..081c7966 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -277,12 +277,9 @@ function DocView:on_mouse_moved(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 - local idx = 1 + if l1 > l2 then l1, l2 = l2, l1 end for i = l1, l2 do - self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c1, #self.doc.lines[i])) + 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)) From 316671e5b7c9b514a397ceb85669f19a2de7c363 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sat, 5 Jun 2021 21:23:24 -0400 Subject: [PATCH 06/22] Fixed tabbing spaces. --- data/core/doc/init.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 1be09824..f5bfbe57 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -526,6 +526,7 @@ function Doc:indent_text(unindent, line1, col1, line2, col2) end else self:insert(line1, col1, text) + return line1, col1 + #text, line1, col1 + #text end end From b42708fe56f8749e46fddf5ea089cbeef51ab6ed Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Sun, 6 Jun 2021 20:30:09 -0400 Subject: [PATCH 07/22] Cleaned up functions. --- data/core/commands/doc.lua | 51 +++++++--------- data/core/common.lua | 20 +++++++ data/core/doc/init.lua | 120 +++++++++---------------------------- data/core/docview.lua | 2 +- 4 files changed, 70 insertions(+), 123 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 1331901b..27da949a 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -25,14 +25,10 @@ end local function doc_multiline_selections(sort) - local iter, state, idx = doc():get_selections(sort) + local iter, state, idx, line1, col1, line2, col2 = doc():get_selections(sort) return function() - local line1, col1, line2, col2 idx, line1, col1, line2, col2 = iter(state, idx) - if not idx then - return - end - if line2 > line1 and col2 == 1 then + if idx and line2 > line1 and col2 == 1 then line2 = line2 - 1 col2 = #doc().lines[line2] end @@ -62,9 +58,9 @@ local function cut_or_copy(delete) doc():delete_to(idx, 0) end full_text = full_text == "" and text or (full_text .. "\n" .. text) - doc():set_cursor_clipboard(idx, text) + doc().cursor_clipboard[idx] = text else - doc():set_cursor_clipboard(idx, "") + doc().cursor_clipboard[idx] = "" end end system.set_clipboard(full_text) @@ -89,7 +85,7 @@ local commands = { ["doc:paste"] = function() for idx, line1, col1, line2, col2 in doc():get_selections() do - local value = doc():get_cursor_clipboard(idx) or system.get_clipboard() + local value = doc().cursor_clipboard[idx] or system.get_clipboard() doc():text_input(value:gsub("\r", ""), idx) end end, @@ -121,18 +117,18 @@ local commands = { end, ["doc:delete"] = function() - for idx, line, col in doc():get_selections() do - if not doc():has_selection(idx) and doc().lines[line]:find("^%s*$", col) then - doc():remove(line, col, line, math.huge) + for idx, line1, col1, line2, col2 in doc():get_selections() do + if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then + doc():remove(line1, col1, line1, math.huge) end doc():delete_to(idx, translate.next_char) end end, ["doc:backspace"] = function() - for idx, line, col in doc():get_selections() do - if not doc():has_selection(idx) then - local text = doc():get_text(line, 1, line, col) + for idx, line1, col1, line2, col2 in doc():get_selections() do + if line1 == line2 and col1 == col2 then + local text = doc():get_text(line1, 1, line1, col1) if #text >= config.indent_size and text:find("^ *$") then doc():delete_to(idx, 0, -config.indent_size) return @@ -167,7 +163,7 @@ local commands = { end, ["doc:join-lines"] = function() - for idx, line1, _, line2 in doc():get_selections(true) do + for idx, line1, col1, line2, col2 in doc():get_selections(true) do if line1 == line2 then line2 = line2 + 1 end local text = doc():get_text(line1, 1, line2, math.huge) text = text:gsub("(.-)\n[\t ]*", function(x) @@ -175,7 +171,7 @@ local commands = { end) doc():insert(line1, 1, text) doc():remove(line1, #text + 1, line2, math.huge) - if doc():has_selection(idx) then + if line1 ~= line2 or col1 ~= col2 then doc():set_selections(idx, line1, math.huge) end end @@ -183,6 +179,7 @@ local commands = { ["doc:indent"] = function() for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do + print("LINE", line1, col1, line2, col2) local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2) if l1 then doc():set_selections(idx, l1, c1, l2, c2) @@ -398,25 +395,21 @@ for name, fn in pairs(translations) do end commands["doc:move-to-previous-char"] = function() - for idx, line, col in doc():get_selections(true) do - if doc():has_selection(idx) then - doc():set_selections(idx, line, col) + for idx, line1, col1, line2, col2 in doc():get_selections(true) do + if line1 ~= line2 or col1 ~= col2 then + doc():set_selections(idx, line1, col1) end end - if not doc():has_selection() then - doc():move_to(nil, translate.previous_char) - end + doc():move_to(nil, translate.previous_char) end commands["doc:move-to-next-char"] = function() - for idx, _, _, line, col in doc():get_selections(true) do - if doc():has_selection(idx) then - doc():set_selections(idx, line, col) + for idx, line1, col1, line2, col2 in doc():get_selections(true) do + if line1 ~= line2 or col1 ~= col2 then + doc():set_selections(idx, line2, col2) end end - if not doc():has_selection() then - doc():move_to(nil, translate.next_char) - end + doc():move_to(nil, translate.next_char) end command.add("core.docview", commands) diff --git a/data/core/common.lua b/data/core/common.lua index b7dd61db..548b5faa 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -61,6 +61,26 @@ function common.color(str) end +function common.splice(t, at, remove, insert) + insert = insert or {} + local offset = #insert - remove + local old_len = #t + if offset < 0 then + for i = at - offset, old_len - offset do + t[i + offset] = t[i] + end + elseif offset > 0 then + for i = old_len, at, -1 do + t[i + offset] = t[i] + end + end + for i, item in ipairs(insert) do + t[at + i - 1] = item + end +end + + + local function compare_score(a, b) return a.score > b.score end diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index f5bfbe57..80c8e5de 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -16,26 +16,6 @@ local function split_lines(text) return res end - -local function splice(t, at, remove, insert) - insert = insert or {} - local offset = #insert - remove - local old_len = #t - if offset < 0 then - for i = at - offset, old_len - offset do - t[i + offset] = t[i] - end - elseif offset > 0 then - for i = old_len, at, -1 do - t[i + offset] = t[i] - end - end - for i, item in ipairs(insert) do - t[at + i - 1] = item - end -end - - function Doc:new(filename) self:reset() if filename then @@ -127,91 +107,48 @@ function Doc:get_change_id() return self.undo_stack.idx end -function Doc:get_cursor_clipboard(idx) - return self.cursor_clipboard[idx] -end -function Doc:set_cursor_clipboard(idx, value) - self.cursor_clipboard[idx] = value -end - -function Doc:set_selection(line1, col1, line2, col2, swap) - assert(not line2 == not col2, "expected 2 or 4 arguments") - if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end - line1, col1 = self:sanitize_position(line1, col1) - line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) - self.selections = { line1, col1, line2, col2 } - self.cursor_clipboard = {} -end - function Doc:set_selections(idx, line1, col1, line2, col2, swap) assert(not line2 == not col2, "expected 3 or 5 arguments") if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end line1, col1 = self:sanitize_position(line1, col1) line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) - local target = (idx - 1)*4 + 1 - self.selections[target] = line1 - self.selections[target+1] = col1 - self.selections[target+2] = line2 - self.selections[target+3] = col2 + common.splice(self.selections, (idx - 1)*4 + 1, 4, { line1, col1, line2, col2 }) end +function Doc:set_selection(line1, col1, line2, col2, swap) + self.selections = {} + self:set_selections(1, line1, col1, line2, col2, swap) + self.cursor_clipboard = {} +end local function sort_positions(line1, col1, line2, col2) - if line1 > line2 - or line1 == line2 and col1 > col2 then - return line2, col2, line1, col1, true + if line1 > line2 or line1 == line2 and col1 > col2 then + return line2, col2, line1, col1 end - return line1, col1, line2, col2, false + return line1, col1, line2, col2 end - function Doc:get_selection(sort) - if sort then - local result = {} - for sidx, line1, col1, line2, col2 in self:get_selections(true) do - table.insert(result, line1) - table.insert(result, col1) - table.insert(result, line2) - table.insert(result, col2) - end - return result - end - return unpack(self.selections) -end - -function Doc:get_cursor_count() - return #self.selections / 4 + local idx, line1, col1, line2, col2 = self:get_selections(sort)(self.selections, 0) + return line1, col1, line2, col2 end function Doc:get_selections(sort) return function(selections, idx) local target = idx*4 + 1 - if target >= #selections then - return - end + if target >= #selections then return end if sort then - return idx+1, sort_positions(selections[target], selections[target+1], selections[target+2], selections[target+3]) + return idx+1, sort_positions(unpack(selections, target, target+4)) else - return idx+1, selections[target], selections[target+1], selections[target+2], selections[target+3] + return idx+1, unpack(selections, target, target+4) end end, self.selections, 0 end -function Doc:has_selection(idx) - if idx then - local target = (idx-1)*4+1 - return - self.selections[target] ~= self.selections[target+2] or - self.selections[target+1] ~= self.selections[target+3] - end - for target = 1, #self.selections, 4 do - if self.selections[target] ~= self.selections[target+2] or - self.selections[target+1] ~= self.selections[target+3] then - return true - end - end - return false +function Doc:has_selection() + local line1, col1, line2, col2 = self:get_selection(false) + return line1 ~= line2 or col1 ~= col2 end @@ -341,7 +278,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time) lines[#lines] = lines[#lines] .. after -- splice lines into line array - splice(self.lines, line, 1, lines) + common.splice(self.lines, line, 1, lines) -- push undo local line2, col2 = self:position_offset(line, col, #text) @@ -365,7 +302,7 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) local after = self.lines[line2]:sub(col2) -- splice line into line array - splice(self.lines, line1, line2 - line1 + 1, { before .. after }) + common.splice(self.lines, line1, line2 - line1 + 1, { before .. after }) -- update highlighter and assure selection is in bounds self.highlighter:invalidate(line1) @@ -402,12 +339,12 @@ end function Doc:text_input(text, idx) - for sidx, line, col in self:get_selections() do + for sidx, line1, col1, line2, col2 in self:get_selections() do if not idx or idx == sidx then - if self:has_selection(sidx) then + if line1 ~= line2 or col1 ~= col2 then self:delete_to(sidx) end - self:insert(line, col, text) + self:insert(line1, col1, text) self:move_to(sidx, #text) end end @@ -415,11 +352,8 @@ end function Doc:replace(fn) - local line1, col1, line2, col2, swap - local had_selection = self:has_selection() - if had_selection then - line1, col1, line2, col2, swap = self:get_selection(true) - else + 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 local old_text = self:get_text(line1, col1, line2, col2) @@ -429,7 +363,7 @@ function Doc:replace(fn) self:remove(line1, col1, line2, col2) if had_selection then line2, col2 = self:position_offset(line1, col1, #new_text) - self:set_selection(line1, col1, line2, col2, swap) + self:set_selection(line1, col1, line2, col2) end end return n @@ -439,10 +373,10 @@ end function Doc:delete_to(idx, ...) for sidx, line1, col1, line2, col2 in self:get_selections(true) do if not idx or sidx == idx then - if self:has_selection(sidx) then + if line1 ~= line2 or col1 ~= col2 then self:remove(line1, col1, line2, col2) else - local l2, c2 = self:position_offset(line, col, ...) + local l2, c2 = self:position_offset(line1, col1, ...) self:remove(line1, col1, l2, c2) line1, col1 = sort_positions(line1, col1, l2, c2) end diff --git a/data/core/docview.lua b/data/core/docview.lua index 081c7966..15ecb838 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -361,7 +361,7 @@ function DocView:draw_line_body(idx, x, y) 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 not self.doc:has_selection(lidx) + 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) end From 2475b1624f6becb052f023658f9732490b6c0ea2 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Jun 2021 08:25:10 -0400 Subject: [PATCH 08/22] Fixed error. --- data/core/doc/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 80c8e5de..09816e4d 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -361,7 +361,7 @@ function Doc:replace(fn) if old_text ~= new_text then self:insert(line2, col2, new_text) self:remove(line1, col1, line2, col2) - if had_selection then + if line1 == line2 and col1 == col2 then line2, col2 = self:position_offset(line1, col1, #new_text) self:set_selection(line1, col1, line2, col2) end From 75658b4f3f3ffb0b98cff88118032bd4e2c02e80 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 7 Jun 2021 08:26:49 -0400 Subject: [PATCH 09/22] Removed unecessary elses. --- data/core/doc/init.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 09816e4d..b448bbd4 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -455,13 +455,11 @@ function Doc:indent_text(unindent, line1, col1, line2, col2) if (unindent or in_beginning_whitespace) and not has_selection then local start_cursor = (se and se + 1 or 1) + l1d or #(self.lines[line1]) return line1, start_cursor, line2, start_cursor - else - return line1, col1 + l1d, line2, col2 + l2d end - else - self:insert(line1, col1, text) - return line1, col1 + #text, line1, col1 + #text + return line1, col1 + l1d, line2, col2 + l2d end + self:insert(line1, col1, text) + return line1, col1 + #text, line1, col1 + #text end -- For plugins to add custom actions of document change From 858f7a2a5070f7838ddf4d976b3612d6100a2928 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Tue, 8 Jun 2021 23:09:09 -0400 Subject: [PATCH 10/22] Added in missing boolean. --- data/core/commands/doc.lua | 56 ++++++++++++++++++++++++++++++++------ data/core/doc/init.lua | 16 ++++++----- data/core/keymap-macos.lua | 2 ++ data/core/keymap.lua | 2 ++ 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 27da949a..ca293393 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -55,7 +55,7 @@ local function cut_or_copy(delete) if line1 ~= line2 or col1 ~= col2 then local text = doc():get_text(line1, col1, line2, col2) if delete then - doc():delete_to(idx, 0) + doc():delete_to(0) end full_text = full_text == "" and text or (full_text .. "\n" .. text) doc().cursor_clipboard[idx] = text @@ -66,6 +66,25 @@ local function cut_or_copy(delete) system.set_clipboard(full_text) end +local function split_cursor(direction) + local new_cursors = {} + for _, line1, col1 in doc():get_selections() do + local exists = false + for _, line2, col2 in doc():get_selections() do + if line1+direction == line2 and col1 == col2 then + exists = true + break + end + end + if not exists and line1 > 1 and line1 < #doc().lines then + table.insert(new_cursors, { line1 - 1, col1 }) + end + end + for i,v in ipairs(new_cursors) do + doc():set_selections(#doc().selections/4 + 1, v[1], v[2]) + end +end + local commands = { ["doc:undo"] = function() doc():undo() @@ -121,7 +140,7 @@ local commands = { if line1 == line2 and col1 == col2 and doc().lines[line1]:find("^%s*$", col1) then doc():remove(line1, col1, line1, math.huge) end - doc():delete_to(idx, translate.next_char) + doc():delete_to_cursor(idx, translate.next_char) end end, @@ -130,11 +149,11 @@ local commands = { if line1 == line2 and col1 == col2 then local text = doc():get_text(line1, 1, line1, col1) if #text >= config.indent_size and text:find("^ *$") then - doc():delete_to(idx, 0, -config.indent_size) + doc():delete_to_cursor(idx, 0, -config.indent_size) return end end - doc():delete_to(idx, translate.previous_char) + doc():delete_to_cursor(idx, translate.previous_char) end end, @@ -146,6 +165,16 @@ local commands = { local line, col = doc():get_selection() 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 @@ -366,6 +395,15 @@ local commands = { os.remove(filename) core.log("Removed \"%s\"", filename) end + + ["doc:create-cursor-previous-line"] = function() + split_cursor(-1) + end, + + ["doc:create-cursor-next-line"] = function() + split_cursor(1) + end, + } @@ -389,9 +427,9 @@ local translations = { } for name, fn in pairs(translations) do - commands["doc:move-to-" .. name] = function() doc():move_to(nil, fn, dv()) end - commands["doc:select-to-" .. name] = function() doc():select_to(nil, fn, dv()) end - commands["doc:delete-to-" .. name] = function() doc():delete_to(nil, fn, dv()) end + commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end + commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end + commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end end commands["doc:move-to-previous-char"] = function() @@ -400,7 +438,7 @@ commands["doc:move-to-previous-char"] = function() doc():set_selections(idx, line1, col1) end end - doc():move_to(nil, translate.previous_char) + doc():move_to(translate.previous_char) end commands["doc:move-to-next-char"] = function() @@ -409,7 +447,7 @@ commands["doc:move-to-next-char"] = function() doc():set_selections(idx, line2, col2) end end - doc():move_to(nil, translate.next_char) + doc():move_to(translate.next_char) end command.add("core.docview", commands) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index b448bbd4..ef9e1f2e 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -130,7 +130,7 @@ end function Doc:get_selection(sort) local idx, line1, col1, line2, col2 = self:get_selections(sort)(self.selections, 0) - return line1, col1, line2, col2 + return line1, col1, line2, col2, sort end function Doc:get_selections(sort) @@ -342,10 +342,10 @@ function Doc:text_input(text, idx) for sidx, line1, col1, line2, col2 in self:get_selections() do if not idx or idx == sidx then if line1 ~= line2 or col1 ~= col2 then - self:delete_to(sidx) + self:delete_to_cursor(sidx) end self:insert(line1, col1, text) - self:move_to(sidx, #text) + self:move_to_cursor(sidx, #text) end end end @@ -370,7 +370,7 @@ function Doc:replace(fn) end -function Doc:delete_to(idx, ...) +function Doc:delete_to_cursor(idx, ...) for sidx, line1, col1, line2, col2 in self:get_selections(true) do if not idx or sidx == idx then if line1 ~= line2 or col1 ~= col2 then @@ -384,18 +384,19 @@ function Doc:delete_to(idx, ...) end end end +function Doc:delete_to(...) return self:delete_to(nil, ...) end - -function Doc:move_to(idx, ...) +function Doc:move_to_cursor(idx, ...) for sidx, line, col in self:get_selections() do if not idx or sidx == idx then self:set_selections(sidx, self:position_offset(line, col, ...)) end end end +function Doc:move_to(...) return self:move_to_cursor(nil, ...) end -function Doc:select_to(idx, ...) +function Doc:select_to_cursor(idx, ...) for sidx, line, col, line2, col2 in self:get_selections() do if not idx or idx == sidx then line, col = self:position_offset(line, col, ...) @@ -403,6 +404,7 @@ function Doc:select_to(idx, ...) end end end +function Doc:select_to(...) return self:select_to_cursor(nil, ...) end local function get_indent_string() diff --git a/data/core/keymap-macos.lua b/data/core/keymap-macos.lua index ccbb102a..3db4d4f7 100644 --- a/data/core/keymap-macos.lua +++ b/data/core/keymap-macos.lua @@ -101,6 +101,8 @@ local function keymap_macos(keymap) ["cmd+shift+end"] = "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" } end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 84bcd770..0bd153b8 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -202,6 +202,8 @@ keymap.add_direct { ["ctrl+shift+end"] = "doc:select-to-end-of-doc", ["shift+pageup"] = "doc:select-to-previous-page", ["shift+pagedown"] = "doc:select-to-next-page", + ["ctrl+shift+up"] = "doc:create-cursor-previous-line", + ["ctrl+shift+down"] = "doc:create-cursor-next-line" } return keymap From c6f7e473f0ed26ccab57630c546e2e0505e0aa23 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 9 Jun 2021 17:14:34 -0400 Subject: [PATCH 11/22] That's what I get for not testing after a 'simple' refactor. --- data/core/commands/doc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index ca293393..fff081a9 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -77,7 +77,7 @@ local function split_cursor(direction) end end if not exists and line1 > 1 and line1 < #doc().lines then - table.insert(new_cursors, { line1 - 1, col1 }) + table.insert(new_cursors, { line1 + direction, col1 }) end end for i,v in ipairs(new_cursors) do From d3b3f263169b05db1ea49c390b30b7f67362f9f1 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 9 Jun 2021 17:59:19 -0400 Subject: [PATCH 12/22] Added in cursor merging behaviour. --- data/core/doc/init.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index ef9e1f2e..4c326f95 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -121,6 +121,18 @@ function Doc:set_selection(line1, col1, line2, col2, swap) self.cursor_clipboard = {} end +function Doc:merge_cursors(idx) + for i = (idx or (#self.selections - 3)), (idx or 5), -4 do + for j = 1, i - 4, 4 do + if self.selections[i] == self.selections[j] and + self.selections[i+1] == self.selections[j+1] then + common.splice(self.selections, i, 4) + break + end + end + end +end + local function sort_positions(line1, col1, line2, col2) if line1 > line2 or line1 == line2 and col1 > col2 then return line2, col2, line1, col1 @@ -383,6 +395,7 @@ function Doc:delete_to_cursor(idx, ...) self:set_selections(sidx, line1, col1) end end + self:merge_cursors(idx) end function Doc:delete_to(...) return self:delete_to(nil, ...) end @@ -392,6 +405,7 @@ function Doc:move_to_cursor(idx, ...) self:set_selections(sidx, self:position_offset(line, col, ...)) end end + self:merge_cursors(idx) end function Doc:move_to(...) return self:move_to_cursor(nil, ...) end @@ -403,6 +417,7 @@ function Doc:select_to_cursor(idx, ...) self:set_selections(sidx, line, col, line2, col2) end end + self:merge_cursors(idx) end function Doc:select_to(...) return self:select_to_cursor(nil, ...) end From c494d52caf48406b109576ff602247c9582e8830 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 9 Jun 2021 18:08:22 -0400 Subject: [PATCH 13/22] Used an inappropriate function in a selection loop. --- data/core/commands/doc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index fff081a9..0157962c 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -55,7 +55,7 @@ local function cut_or_copy(delete) if line1 ~= line2 or col1 ~= col2 then local text = doc():get_text(line1, col1, line2, col2) if delete then - doc():delete_to(0) + doc():delete_to_cursor(idx, 0) end full_text = full_text == "" and text or (full_text .. "\n" .. text) doc().cursor_clipboard[idx] = text From 0f229b039d6425ef9db6d9ffa8cd6c6c1eca3691 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 10 Jun 2021 11:26:39 -0400 Subject: [PATCH 14/22] Fixed merging. Fixed selection undo stack. --- data/core/commands/doc.lua | 2 ++ data/core/doc/init.lua | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 0157962c..7644e11b 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -398,10 +398,12 @@ local commands = { ["doc:create-cursor-previous-line"] = function() split_cursor(-1) + doc():merge_cursors() end, ["doc:create-cursor-next-line"] = function() split_cursor(1) + doc():merge_cursors() end, } diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 4c326f95..b9cfca5b 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -294,7 +294,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", self:get_selection()) + push_undo(undo_stack, time, "selection", unpack(self.selections)) push_undo(undo_stack, time, "remove", line, col, line2, col2) -- update highlighter and assure selection is in bounds @@ -306,7 +306,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", self:get_selection()) + push_undo(undo_stack, time, "selection", unpack(self.selections)) push_undo(undo_stack, time, "insert", line1, col1, text) -- get line content before/after removed text From b065b52067694195a60eb68e4b6091d4ee7ccd38 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 10 Jun 2021 11:29:21 -0400 Subject: [PATCH 15/22] Ensured that textual inputs sort cursors so that we don't get *real* weird behaviour. --- data/core/doc/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index b9cfca5b..c8b541e9 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -351,7 +351,7 @@ end function Doc:text_input(text, idx) - for sidx, line1, col1, line2, col2 in self:get_selections() do + for sidx, line1, col1, line2, col2 in self:get_selections(true) do if not idx or idx == sidx then if line1 ~= line2 or col1 ~= col2 then self:delete_to_cursor(sidx) From 6915d86d5901ba5d5f332cd6877f88a92ba6c199 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 10 Jun 2021 12:29:55 -0400 Subject: [PATCH 16/22] Introduced the constraint that all cursors must be in order. --- data/core/commands/doc.lua | 3 +- data/core/doc/init.lua | 91 ++++++++++++++++++++++---------------- data/core/docview.lua | 1 + 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 7644e11b..b4036e69 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -110,7 +110,7 @@ local commands = { end, ["doc:newline"] = function() - for idx, line, col in doc():get_selections() do + for idx, line, col in doc():get_selections(false, true) do local indent = doc().lines[line]:match("^[\t ]*") if col <= #indent then indent = indent:sub(#indent + 2 - col) @@ -208,7 +208,6 @@ local commands = { ["doc:indent"] = function() for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do - print("LINE", line1, col1, line2, col2) local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2) if l1 then doc():set_selections(idx, l1, c1, l2, c2) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index c8b541e9..f44b0ebf 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -107,18 +107,56 @@ function Doc:get_change_id() return self.undo_stack.idx end -function Doc:set_selections(idx, line1, col1, line2, col2, swap) +-- Cursor section. Cursor indices are *only* valid during a get_selections() call. +-- Cursors will always be iterated in order from top to bottom. Through normal operation +-- curors can never swap positions; only merge or split, or change their position in cursor +-- order. +function Doc:get_selection(sort) + local idx, line1, col1, line2, col2 = self:get_selections(sort)({ self.selections, sort }, 0) + return line1, col1, line2, col2, sort +end + +function Doc:has_selection() + local line1, col1, line2, col2 = self:get_selection(false) + return line1 ~= line2 or col1 ~= col2 +end + +function Doc:sanitize_selection() + for idx, line1, col1, line2, col2 in self:get_selections() do + self:set_selections(idx, line1, col1, line2, col2) + end +end + +local function sort_positions(line1, col1, line2, col2) + if line1 > line2 or line1 == line2 and col1 > col2 then + return line2, col2, line1, col1 + end + return line1, col1, line2, col2 +end + +function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) assert(not line2 == not col2, "expected 3 or 5 arguments") if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end line1, col1 = self:sanitize_position(line1, col1) line2, col2 = self:sanitize_position(line2 or line1, col2 or col1) - common.splice(self.selections, (idx - 1)*4 + 1, 4, { line1, col1, line2, col2 }) + common.splice(self.selections, (idx - 1)*4 + 1, rm == nil and 4 or rm, { line1, col1, line2, col2 }) +end + +function Doc:add_selection(line1, col1, line2, col2, swap) + local l1, c1 = sort_positions(line1, col1, line2, col2) + local target = #self.selections / 4 + 1 + for idx, tl1, tc1 in self:get_selections(true) do + if l1 < tl1 or l1 == tl1 and c1 < tc1 then + target = idx + break + end + end + self:set_selections(target, line1, col1, line2, col2, swap, 0) end function Doc:set_selection(line1, col1, line2, col2, swap) - self.selections = {} + self.selections, self.cursor_clipboard = {}, {} self:set_selections(1, line1, col1, line2, col2, swap) - self.cursor_clipboard = {} end function Doc:merge_cursors(idx) @@ -133,43 +171,20 @@ function Doc:merge_cursors(idx) end end -local function sort_positions(line1, col1, line2, col2) - if line1 > line2 or line1 == line2 and col1 > col2 then - return line2, col2, line1, col1 - end - return line1, col1, line2, col2 -end - -function Doc:get_selection(sort) - local idx, line1, col1, line2, col2 = self:get_selections(sort)(self.selections, 0) - return line1, col1, line2, col2, sort -end - -function Doc:get_selections(sort) - return function(selections, idx) - local target = idx*4 + 1 - if target >= #selections then return end - if sort then - return idx+1, sort_positions(unpack(selections, target, target+4)) - else - return idx+1, unpack(selections, target, target+4) - end - end, self.selections, 0 -end - - -function Doc:has_selection() - local line1, col1, line2, col2 = self:get_selection(false) - return line1 ~= line2 or col1 ~= col2 -end - - -function Doc:sanitize_selection() - for idx, line1, col1, line2, col2 in self:get_selections() do - self:set_selections(idx, line1, col1, line2, col2) +local function selection_iterator(invariant, idx) + local target = invariant[3] and (#invariant[1] - 3 - idx * 4) or (idx*4 + 1) + if idx * 4 >= #invariant[1] then return end + if invariant[2] then + return idx+1, sort_positions(unpack(invariant[1], target, target+4)) + else + return idx+1, unpack(invariant[1], target, target+4) end end +function Doc:get_selections(sort_intra, reverse) + return selection_iterator, { self.selections, sort_intra, reverse }, 0 +end +-- End of cursor seciton. function Doc:sanitize_position(line, col) line = common.clamp(line, 1, #self.lines) diff --git a/data/core/docview.lua b/data/core/docview.lua index 15ecb838..1cccb486 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -278,6 +278,7 @@ function DocView:on_mouse_moved(x, y, ...) local clicks = self.mouse_selecting.clicks if keymap.modkeys["ctrl"] then if l1 > l2 then l1, l2 = l2, l1 end + self.doc.selections = { } for i = l1, l2 do self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i])) end From dfc57bd8848d4a20d572b6d38d0c2730b2c5e80a Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 10 Jun 2021 12:32:33 -0400 Subject: [PATCH 17/22] Used routine to add cursors in order correctly. --- data/core/commands/doc.lua | 4 +--- data/core/doc/init.lua | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index b4036e69..b67a1e7a 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -80,9 +80,7 @@ local function split_cursor(direction) table.insert(new_cursors, { line1 + direction, col1 }) end end - for i,v in ipairs(new_cursors) do - doc():set_selections(#doc().selections/4 + 1, v[1], v[2]) - end + for i,v in ipairs(new_cursors) do doc():add_selection(v[1], v[2]) end end local commands = { diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index f44b0ebf..4091482a 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -143,7 +143,7 @@ function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) end function Doc:add_selection(line1, col1, line2, col2, swap) - local l1, c1 = sort_positions(line1, col1, line2, col2) + local l1, c1 = sort_positions(line1, col1, line2 or line1, col2 or col1) local target = #self.selections / 4 + 1 for idx, tl1, tc1 in self:get_selections(true) do if l1 < tl1 or l1 == tl1 and c1 < tc1 then From b5cbe3a2fb3f8b3c806d240ee4725b784320b722 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 11 Jun 2021 18:14:24 -0400 Subject: [PATCH 18/22] Added in the ability to iterate through cursors backwards. --- data/core/commands/doc.lua | 4 ++-- data/core/doc/init.lua | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index b67a1e7a..02f7551a 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -118,7 +118,7 @@ local commands = { end, ["doc:newline-below"] = function() - for idx, line in doc():get_selections() do + for idx, line in doc():get_selections(false, true) do local indent = doc().lines[line]:match("^[\t ]*") doc():insert(line, math.huge, "\n" .. indent) doc():set_selections(idx, line + 1, math.huge) @@ -126,7 +126,7 @@ local commands = { end, ["doc:newline-above"] = function() - for idx, line in doc():get_selections() do + for idx, line in doc():get_selections(false, true) do local indent = doc().lines[line]:match("^[\t ]*") doc():insert(line, 1, indent .. "\n") doc():set_selections(idx, line, math.huge) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 4091482a..17a82d88 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -172,17 +172,17 @@ function Doc:merge_cursors(idx) end local function selection_iterator(invariant, idx) - local target = invariant[3] and (#invariant[1] - 3 - idx * 4) or (idx*4 + 1) - if idx * 4 >= #invariant[1] then return end + local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1) + if target > #invariant[1] or target <= 0 then return end if invariant[2] then - return idx+1, sort_positions(unpack(invariant[1], target, target+4)) + return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4)) else - return idx+1, unpack(invariant[1], target, target+4) + return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4) end end function Doc:get_selections(sort_intra, reverse) - return selection_iterator, { self.selections, sort_intra, reverse }, 0 + return selection_iterator, { self.selections, sort_intra, reverse }, reverse and (#self.selections / 4) + 1 or 0 end -- End of cursor seciton. From 02102645529d5ca256985a0708a61866c1a49895 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 11 Jun 2021 18:51:14 -0400 Subject: [PATCH 19/22] Made get_selections a bit more flexible. --- data/core/doc/init.lua | 53 +++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 17a82d88..836d9370 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -173,7 +173,7 @@ end 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 then return end + 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)) else @@ -181,8 +181,11 @@ local function selection_iterator(invariant, idx) end end -function Doc:get_selections(sort_intra, reverse) - return selection_iterator, { self.selections, sort_intra, reverse }, reverse and (#self.selections / 4) + 1 or 0 +-- If idx_reverse is true, it'll reverse iterate. If nil, or false, regular iterate. +-- If a number, runs for exactly that iteration. +function Doc:get_selections(sort_intra, idx_reverse) + return selection_iterator, { self.selections, sort_intra, idx_reverse }, + idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1)+1) end -- End of cursor seciton. @@ -366,14 +369,12 @@ end function Doc:text_input(text, idx) - for sidx, line1, col1, line2, col2 in self:get_selections(true) do - if not idx or idx == sidx then - if line1 ~= line2 or col1 ~= col2 then - self:delete_to_cursor(sidx) - end - self:insert(line1, col1, text) - self:move_to_cursor(sidx, #text) + for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do + if line1 ~= line2 or col1 ~= col2 then + self:delete_to_cursor(sidx) end + self:insert(line1, col1, text) + self:move_to_cursor(sidx, #text) end end @@ -398,27 +399,23 @@ end function Doc:delete_to_cursor(idx, ...) - for sidx, line1, col1, line2, col2 in self:get_selections(true) do - if not idx or sidx == idx then - if line1 ~= line2 or col1 ~= col2 then - self:remove(line1, col1, line2, col2) - else - local l2, c2 = self:position_offset(line1, col1, ...) - self:remove(line1, col1, l2, c2) - line1, col1 = sort_positions(line1, col1, l2, c2) - end - self:set_selections(sidx, line1, col1) + for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do + if line1 ~= line2 or col1 ~= col2 then + self:remove(line1, col1, line2, col2) + else + local l2, c2 = self:position_offset(line1, col1, ...) + self:remove(line1, col1, l2, c2) + line1, col1 = sort_positions(line1, col1, l2, c2) end + self:set_selections(sidx, line1, col1) end self:merge_cursors(idx) end function Doc:delete_to(...) return self:delete_to(nil, ...) end function Doc:move_to_cursor(idx, ...) - for sidx, line, col in self:get_selections() do - if not idx or sidx == idx then - self:set_selections(sidx, self:position_offset(line, col, ...)) - end + for sidx, line, col in self:get_selections(false, idx) do + self:set_selections(sidx, self:position_offset(line, col, ...)) end self:merge_cursors(idx) end @@ -426,11 +423,9 @@ function Doc:move_to(...) return self:move_to_cursor(nil, ...) end function Doc:select_to_cursor(idx, ...) - for sidx, line, col, line2, col2 in self:get_selections() do - if not idx or idx == sidx then - line, col = self:position_offset(line, col, ...) - self:set_selections(sidx, line, col, line2, col2) - end + for sidx, line, col, line2, col2 in self:get_selections(false, idx) do + line, col = self:position_offset(line, col, ...) + self:set_selections(sidx, line, col, line2, col2) end self:merge_cursors(idx) end From 3541ab4aa1cec6ee77c4be8f8a39c189e62bae56 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Fri, 11 Jun 2021 19:01:43 -0400 Subject: [PATCH 20/22] Removed unecessary check. --- data/core/commands/doc.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 02f7551a..d54eb059 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -69,14 +69,7 @@ end local function split_cursor(direction) local new_cursors = {} for _, line1, col1 in doc():get_selections() do - local exists = false - for _, line2, col2 in doc():get_selections() do - if line1+direction == line2 and col1 == col2 then - exists = true - break - end - end - if not exists and line1 > 1 and line1 < #doc().lines then + if line1 > 1 and line1 < #doc().lines then table.insert(new_cursors, { line1 + direction, col1 }) end end From 292c98935cd4b44e7e66232cc4aa2f207acd3918 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Mon, 14 Jun 2021 20:11:40 -0400 Subject: [PATCH 21/22] Fixed recursion error. --- data/core/doc/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 836d9370..0c5ba8fb 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -411,7 +411,7 @@ function Doc:delete_to_cursor(idx, ...) end self:merge_cursors(idx) end -function Doc:delete_to(...) return self:delete_to(nil, ...) end +function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end function Doc:move_to_cursor(idx, ...) for sidx, line, col in self:get_selections(false, idx) do From 704e04396fe809d3f4f1f9de3e2a05c84e6a2b9f Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 17 Jun 2021 19:17:24 -0400 Subject: [PATCH 22/22] Rebased, and added the ability for ctrl to just create new cursors. --- data/core/commands/doc.lua | 4 ++-- data/core/docview.lua | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index d54eb059..b428cc74 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -384,7 +384,7 @@ local commands = { end os.remove(filename) core.log("Removed \"%s\"", filename) - end + end, ["doc:create-cursor-previous-line"] = function() split_cursor(-1) @@ -394,7 +394,7 @@ local commands = { ["doc:create-cursor-next-line"] = function() split_cursor(1) doc():merge_cursors() - end, + end } diff --git a/data/core/docview.lua b/data/core/docview.lua index 1cccb486..446cf574 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -256,7 +256,11 @@ function DocView:on_mouse_pressed(button, x, y, clicks) end else local line, col = self:resolve_screen_position(x, y) - self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col)) + 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()