From 9615ead41be09c447d6ca26ff52a545f8f31cbb9 Mon Sep 17 00:00:00 2001 From: ThaCuber <70547062+thacuber2a03@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:12:43 -0400 Subject: [PATCH] Text overwriting (#1495) * added text overwriting * rewrote `DocView:draw_caret` to not use the order of draws * forgot to delete some old code in `DocView:draw_overlay` also added a temporary solution to overwriting and added the missing arguments in `DocView:draw_ime_decoration` and fixed `DocView:draw_caret` * accidentally broke the `draw_caret` call in `draw_overlay` in the process * multiline * fixed calling `Doc:get_char` as a function that, in turn, crashed the editor because "can't index a number" * move and rename some stuff * remove unneeded extra check I just had to change the `~=` to `<` in the second condition * overwrite disregards pasting text * disregard overwrite on selections; doc only removes selection * Fixed error where `doc` was used, instead of `self`. --------- Co-authored-by: ThaCuber <70547062+ThaCuber@users.noreply.github.com> Co-authored-by: Adam Harrison --- data/core/commands/doc.lua | 5 +++ data/core/doc/init.lua | 87 +++++++++++++++++--------------------- data/core/docview.lua | 14 ++++-- data/core/keymap.lua | 1 + data/core/statusview.lua | 13 ++++++ 5 files changed, 67 insertions(+), 53 deletions(-) diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index c698d2cc..3d4426c3 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -545,6 +545,11 @@ local commands = { dv.doc.crlf = not dv.doc.crlf end, + ["doc:toggle-overwrite"] = function(dv) + dv.doc.overwrite = not dv.doc.overwrite + core.blink_reset() -- to show the cursor has changed edit modes + end, + ["doc:save-as"] = function(dv) local last_doc = core.last_active_view and core.last_active_view.doc local text diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index c3eeba87..3c0a30d7 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -1,5 +1,6 @@ local Object = require "core.object" local Highlighter = require "core.doc.highlighter" +local translate = require "core.doc.translate" local core = require "core" local syntax = require "core.syntax" local config = require "core.config" @@ -29,7 +30,6 @@ function Doc:new(filename, abs_filename, new_file) end end - function Doc:reset() self.lines = { "\n" } self.selections = { 1, 1, 1, 1 } @@ -38,10 +38,10 @@ function Doc:reset() self.redo_stack = { idx = 1 } self.clean_change_id = 1 self.highlighter = Highlighter(self) + self.overwrite = false self:reset_syntax() end - function Doc:reset_syntax() local header = self:get_text(1, 1, self:position_offset(1, 1, 128)) local path = self.abs_filename @@ -56,16 +56,14 @@ function Doc:reset_syntax() end end - function Doc:set_filename(filename, abs_filename) self.filename = filename self.abs_filename = abs_filename self:reset_syntax() end - function Doc:load(filename) - local fp = assert( io.open(filename, "rb") ) + local fp = assert(io.open(filename, "rb")) self:reset() self.lines = {} local i = 1 @@ -85,7 +83,6 @@ function Doc:load(filename) self:reset_syntax() end - function Doc:reload() if self.filename then local sel = { self:get_selection() } @@ -95,7 +92,6 @@ function Doc:reload() end end - function Doc:save(filename, abs_filename) if not filename then assert(self.filename, "no filename set to default to") @@ -104,7 +100,7 @@ function Doc:save(filename, abs_filename) else assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path") end - local fp = assert( io.open(filename, "wb") ) + local fp = assert(io.open(filename, "wb")) for _, line in ipairs(self.lines) do if self.crlf then line = line:gsub("\n", "\r\n") end fp:write(line) @@ -115,12 +111,10 @@ function Doc:save(filename, abs_filename) self:clean() end - function Doc:get_name() return self.filename or "unsaved" end - function Doc:is_dirty() if self.new_file then if self.filename then return true end @@ -130,20 +124,17 @@ function Doc:is_dirty() end end - function Doc:clean() self.clean_change_id = self:get_change_id() end - function Doc:get_indent_info() if not self.indent_info then return config.tab_type, config.indent_size, false end return self.indent_info.type or config.tab_type, - self.indent_info.size or config.indent_size, - self.indent_info.confirmed + self.indent_info.size or config.indent_size, + self.indent_info.confirmed end - function Doc:get_change_id() return self.undo_stack.idx end @@ -167,13 +158,14 @@ function Doc:get_selection(sort) return line1, col1, line2, col2, swap end - ---Get the selection specified by `idx` ---@param idx integer @the index of the selection to retrieve ---@param sort? boolean @whether to sort the selection returned ---@return integer,integer,integer,integer,boolean? @line1, col1, line2, col2, was the selection sorted function Doc:get_selection_idx(idx, sort) - local line1, col1, line2, col2 = self.selections[idx*4-3], self.selections[idx*4-2], self.selections[idx*4-1], self.selections[idx*4] + local line1, col1, line2, col2 = self.selections[idx * 4 - 3], self.selections[idx * 4 - 2], + self.selections[idx * 4 - 1], + self.selections[idx * 4] if line1 and sort then return sort_positions(line1, col1, line2, col2) else @@ -217,7 +209,7 @@ function Doc:set_selections(idx, line1, col1, line2, col2, swap, rm) 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, rm == nil and 4 or rm, { 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) @@ -233,7 +225,6 @@ function Doc:add_selection(line1, col1, line2, col2, swap) self.last_selection = target end - function Doc:remove_selection(idx) if self.last_selection >= idx then self.last_selection = self.last_selection - 1 @@ -241,7 +232,6 @@ function Doc:remove_selection(idx) common.splice(self.selections, (idx - 1) * 4 + 1, 4) end - function Doc:set_selection(line1, col1, line2, col2, swap) self.selections = {} self:set_selections(1, line1, col1, line2, col2, swap) @@ -252,24 +242,24 @@ 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) - if self.last_selection >= (i+3)/4 then - self.last_selection = self.last_selection - 1 - end - break + self.selections[i + 1] == self.selections[j + 1] then + common.splice(self.selections, i, 4) + if self.last_selection >= (i + 3) / 4 then + self.last_selection = self.last_selection - 1 + end + break end end end end local function selection_iterator(invariant, idx) - local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1) + 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(table.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), table.unpack(invariant[1], target, target+4) + return idx + (invariant[3] and -1 or 1), table.unpack(invariant[1], target, target + 4) end end @@ -277,8 +267,9 @@ end -- 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) + idx_reverse == true and ((#self.selections / 4) + 1) or ((idx_reverse or -1) + 1) end + -- End of cursor seciton. function Doc:sanitize_position(line, col) @@ -291,7 +282,6 @@ function Doc:sanitize_position(line, col) return line, common.clamp(col, 1, #self.lines[line]) end - local function position_offset_func(self, line, col, fn, ...) line, col = self:sanitize_position(line, col) return fn(self, line, col, ...) @@ -330,7 +320,6 @@ function Doc:position_offset(line, col, ...) end end - function Doc:get_text(line1, col1, line2, col2) line1, col1 = self:sanitize_position(line1, col1) line2, col2 = self:sanitize_position(line2, col2) @@ -346,13 +335,11 @@ function Doc:get_text(line1, col1, line2, col2) return table.concat(lines) end - function Doc:get_char(line, col) line, col = self:sanitize_position(line, col) return self.lines[line]:sub(col, col) end - local function push_undo(undo_stack, time, type, ...) undo_stack[undo_stack.idx] = { type = type, time = time, ... } undo_stack[undo_stack.idx - config.max_undos] = nil @@ -413,7 +400,8 @@ function Doc:raw_insert(line, col, text, undo_stack, time) if cline1 < line then break end local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0 local column_addition = line == cline1 and ccol1 > col and len or 0 - self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition) + self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, + ccol2 + column_addition) end -- push undo @@ -426,7 +414,6 @@ function Doc:raw_insert(line, col, text, undo_stack, time) self:sanitize_selection() end - function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) -- push undo local text = self:get_text(line1, col1, line2, col2) @@ -485,7 +472,6 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time) self:sanitize_selection() end - function Doc:insert(line, col, text) self.redo_stack = { idx = 1 } -- Reset the clean id when we're pushing something new before it @@ -497,7 +483,6 @@ function Doc:insert(line, col, text) self:on_text_change("insert") end - function Doc:remove(line1, col1, line2, col2) self.redo_stack = { idx = 1 } line1, col1 = self:sanitize_position(line1, col1) @@ -507,28 +492,34 @@ function Doc:remove(line1, col1, line2, col2) self:on_text_change("remove") end - function Doc:undo() pop_undo(self, self.undo_stack, self.redo_stack, false) end - function Doc:redo() pop_undo(self, self.redo_stack, self.undo_stack, false) end - function Doc:text_input(text, idx) for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do + local had_selection = false if line1 ~= line2 or col1 ~= col2 then self:delete_to_cursor(sidx) + had_selection = true end + + if self.overwrite + and not had_selection + and col1 < #self.lines[line1] + and text:ulen() == 1 then + self:remove(line1, col1, translate.next_char(self, line1, col1)) + end + self:insert(line1, col1, text) self:move_to_cursor(sidx, #text) end end - function Doc:ime_text_editing(text, start, length, idx) for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do if line1 ~= line2 or col1 ~= col2 then @@ -539,7 +530,6 @@ function Doc:ime_text_editing(text, start, length, idx) end end - function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) local old_text = self:get_text(line1, col1, line2, col2) local new_text, res = fn(old_text) @@ -555,7 +545,7 @@ function Doc:replace_cursor(idx, line1, col1, line2, col2, fn) end function Doc:replace(fn) - local has_selection, results = false, { } + local has_selection, results = false, {} for idx, line1, col1, line2, col2 in self:get_selections(true) do if line1 ~= line2 or col1 ~= col2 then results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn) @@ -569,7 +559,6 @@ function Doc:replace(fn) return results end - function Doc:delete_to_cursor(idx, ...) for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do if line1 ~= line2 or col1 ~= col2 then @@ -583,6 +572,7 @@ function Doc:delete_to_cursor(idx, ...) end self:merge_cursors(idx) end + function Doc:delete_to(...) return self:delete_to_cursor(nil, ...) end function Doc:move_to_cursor(idx, ...) @@ -591,8 +581,8 @@ function Doc:move_to_cursor(idx, ...) end self:merge_cursors(idx) end -function Doc:move_to(...) return self:move_to_cursor(nil, ...) end +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(false, idx) do @@ -601,8 +591,8 @@ function Doc:select_to_cursor(idx, ...) end self:merge_cursors(idx) end -function Doc:select_to(...) return self:select_to_cursor(nil, ...) end +function Doc:select_to(...) return self:select_to_cursor(nil, ...) end function Doc:get_indent_string() local indent_type, indent_size = self:get_indent_info() @@ -625,7 +615,7 @@ function Doc:get_line_indent(line, rnd_up) local indent = e and line:sub(1, e):gsub("\t", soft_tab) or "" local number = #indent / #soft_tab return e, indent:sub(1, - (rnd_up and math.ceil(number) or math.floor(number))*#soft_tab) + (rnd_up and math.ceil(number) or math.floor(number)) * #soft_tab) end end @@ -674,5 +664,4 @@ function Doc:on_close() core.log_quiet("Closed doc \"%s\"", self:get_name()) end - return Doc diff --git a/data/core/docview.lua b/data/core/docview.lua index e2da6dfc..c2502040 100644 --- a/data/core/docview.lua +++ b/data/core/docview.lua @@ -460,9 +460,14 @@ function DocView:draw_line_text(line, x, y) return self:get_line_height() end -function DocView:draw_caret(x, y) - local lh = self:get_line_height() +function DocView:draw_caret(x, y, line, col) + local lh = self:get_line_height() + if self.doc.overwrite then + local w = self:get_font():get_width(self.doc:get_char(line, col)) + renderer.draw_rect(x, y + lh, w, style.caret_width * 2, style.caret) + else renderer.draw_rect(x, y, style.caret_width, lh, style.caret) + end end function DocView:draw_line_body(line, x, y) @@ -542,7 +547,7 @@ function DocView:draw_ime_decoration(line1, col1, line2, col2) line_size = style.caret_width renderer.draw_rect(x + math.min(x1, x2), y + lh - line_size, math.abs(x1 - x2), line_size, style.caret) end - self:draw_caret(x + x1, y) + self:draw_caret(x + x1, y, line1, col) end @@ -559,7 +564,8 @@ function DocView:draw_overlay() else if config.disable_blink or (core.blink_timer - core.blink_start) % T < T / 2 then - self:draw_caret(self:get_line_screen_position(line1, col1)) + local x, y = self:get_line_screen_position(line1, col1) + self:draw_caret(x, y, line1, col1) end end end diff --git a/data/core/keymap.lua b/data/core/keymap.lua index 74e270d2..9f19cfb7 100644 --- a/data/core/keymap.lua +++ b/data/core/keymap.lua @@ -341,6 +341,7 @@ keymap.add_direct { ["ctrl+x"] = "doc:cut", ["ctrl+c"] = "doc:copy", ["ctrl+v"] = "doc:paste", + ["insert"] = "doc:toggle-overwrite", ["ctrl+insert"] = "doc:copy", ["shift+insert"] = "doc:paste", ["escape"] = { "command:escape", "doc:select-none", "dialog:select-no" }, diff --git a/data/core/statusview.lua b/data/core/statusview.lua index 3ef40d82..74124768 100644 --- a/data/core/statusview.lua +++ b/data/core/statusview.lua @@ -319,6 +319,19 @@ function StatusView:register_docview_items() end, command = "doc:toggle-line-ending" }) + + self:add_item { + predicate = predicate_docview, + name = "doc:overwrite-mode", + alignment = StatusView.Item.RIGHT, + get_item = function() + return { + style.text, core.active_view.doc.overwrite and "OVR" or "INS" + } + end, + command = "doc:toggle-overwrite", + separator = StatusView.separator2 + } end