From 76216f87e6545296113f65b155341de467c7f1d0 Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Sun, 25 Jun 2023 03:06:54 +0200 Subject: [PATCH 1/6] stage one: non sticky basics work - need to add caret while dragging so user knows perfectly where text will be inserted - need to add sticky mode properly - test that holding two buttons and releasing right doesn't mess things up --- plugins/dragdropselected.lua | 386 +++++++++++++++++++++++++++-------- 1 file changed, 301 insertions(+), 85 deletions(-) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index a2ce67f0..ce3311e3 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -1,112 +1,328 @@ --[[ - dragdropselected.lua - provides basic drag and drop of selected text (in same document) - version: 20200627_133351 - originally by SwissalpS - - TODO: use OS drag and drop events - TODO: change mouse cursor when duplicating - TODO: add dragging image + dragdropselected.lua + provides basic drag and drop of selected text (in same document) + - LMB+drag selected text to move it elsewhere + - or LMB on selection and then LMB at destination (if sticky is enabled) + - hold ctrl on release to copy selection + - press escape to abort + version: 20230616_094245 by SwissalpS + original: 20200627_133351 by SwissalpS + + TODO: add dragging image + TODO: use OS drag and drop events + TODO: change mouse cursor when duplicating (requires change in cpp/SDL2) --]] -local DocView = require "core.docview" local core = require "core" +local style = require "core.style" +local common = require "core.common" +local config = require "core.config" +local DocView = require "core.docview" local keymap = require "core.keymap" +local command = require "core.command" --- helper function for on_mouse_pressed to determine if mouse down is in selection --- iLine is line number where mouse down happened --- iCol is column where mouse down happened --- iSelLine1 is line number where selection starts --- iSelCol1 is column where selection starts --- iSelLine2 is line number where selection ends --- iSelCol2 is column where selection ends -local function isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) - if iLine < iSelLine1 then return false end - if iLine > iSelLine2 then return false end - if (iLine == iSelLine1) and (iCol < iSelCol1) then return false end - if (iLine == iSelLine2) and (iCol > iSelCol2) then return false end - return true -end -- isInSelection +local dnd = {} + +if not config.dragdropselected then + config.dragdropselected = { + enabled = true, + useSticky = false + } +end + + +-- helper function to determine if mouse is in selection +-- iLine is line number where mouse is +-- iCol is column where mouse is +-- iLine1 is line number where selection starts +-- iCol1 is column where selection starts +-- iLine2 is line number where selection ends +-- iCol2 is column where selection ends +function dnd.isInSelection(iLine, iCol, iLine1, iCol1, iLine2, iCol2) + if iLine < iLine1 then return false end + if iLine > iLine2 then return false end + if (iLine == iLine1) and (iCol < iCol1) then return false end + if (iLine == iLine2) and (iCol > iCol2) then return false end + return true +end -- dnd.isInSelection + + +function DocView:dnd_collectSelection() + self.dnd_lSelection = nil + local iLine1, iCol1, iLine2, iCol2, bSwap = self.doc:get_selection(true) + -- skip empty selection (jic Doc didn't skip them) + if iLine1 ~= iLine2 or iCol1 ~= iCol2 then + self.dnd_lSelection = { iLine1, iCol1, iLine2, iCol2, bSwap } + end + return self.dnd_lSelection +end -- DocView:dnd_collectSelection + + +function dnd.getSelectedText(doc) + local iLine1, iCol1, iLine2, iCol2, bSwap = doc:get_selection(true) + -- skip empty markers + if iLine1 ~= iLine2 or iCol1 ~= iCol2 then + return doc:get_text(iLine1, iCol1, iLine2, iCol2) + end + + return '' +end -- dnd.getSelectedText --- override DocView:on_mouse_moved -local on_mouse_moved = DocView.on_mouse_moved -function DocView:on_mouse_moved(x, y, ...) - local sCursor = nil +-- checks whether given coordinates are in the selection +-- iLine, iCol are position of mouse +-- bDuplicating triggers 'exclusive' check making checked area smaller +function DocView:dnd_isInSelection(iX, iY, bDuplicating) + self.dnd_lSelection = self.dnd_lSelection or self:dnd_collectSelection() + if not self.dnd_lSelection then return nil end - -- make sure we only act if previously on_mouse_pressed was in selection - if self.bClickedIntoSelection then + local iLine, iCol = self:resolve_screen_position(iX, iY) + if config.dragdropselected.useSticky and not self.dnd_bDragging then + -- allow user to clear selection in sticky mode by clicking in empty area + -- to the right of selection + local iX2 = self:get_line_screen_position(iLine, #self.doc.lines[iLine]) + -- this does not exactly corespond with the graphical selected area + -- it means selection can't be grabbed by the "\n" at the end + if iX2 < iX then return nil end + end - -- show that we are dragging something - sCursor = 'hand' + local iLine1, iCol1, iLine2, iCol2, bSwap + iLine1, iCol1, iLine2, iCol2, bSwap = table.unpack(self.dnd_lSelection) + if bDuplicating then + -- adjust boundries for duplication actions + -- this allows users to duplicate selection adjacent to selection + iCol1 = iCol1 + 1 + if #self.doc.lines[iLine1] < iCol1 then + iCol1 = 1 + iLine1 = iLine1 + 1 + end + iCol2 = iCol2 - 1 + if 0 == iCol2 then + iLine2 = iLine2 - 1 + iCol2 = #self.doc.lines[iLine2] + end + end -- if duplicating + + if dnd.isInSelection(iLine, iCol, iLine1, iCol1, iLine2, iCol2) then + return self.dnd_lSelection + end + return nil +end -- DocView:dnd_isInSelection + + +-- restore selection that existed when DnD was initiated +function DocView:dnd_setSelection() + if not self.dnd_lSelection then + return + end + self.doc:set_selection(table.unpack(self.dnd_lSelection)) +end -- DocView:dnd_setSelections + + +-- unset stashes and flag, reset cursor +-- helper for on_mouse_released and +-- when escape is pressed during drag (or not, not worth checking) +function dnd.reset(oDocView) + if not oDocView then + oDocView = core.active_view + if not oDocView:is(DocView) then return end + end - -- check for modifier to duplicate - -- (may want to set a flag as this only needs to be done once) - -- TODO: make image to drag with and/or hand over to OS dnd event - if not keymap.modkeys['ctrl'] then - -- TODO: maybe check if moved at all and only delete then or - -- as some editors do, only when dropped. I do like it going - -- instantly as that reduces the travel-distance. - self.doc:delete_to(0) - --sCursor = 'arrowWithPlus' -- 'handWithPlus' - end + if nil ~= oDocView.dnd_bBlink then + config.disable_blink = oDocView.dnd_bBlink + end + oDocView.dnd_lSelection = nil + oDocView.dnd_bDragging = nil + oDocView.dnd_bBlink = nil + oDocView.cursor = 'ibeam' + oDocView.dnd_sText = nil +end -- dnd.reset - -- calculate line and column for current mouse position - local iLine, iCol = self:resolve_screen_position(x, y) - -- move text cursor - self.doc:set_selection(iLine, iCol) - -- update scroll position - self:scroll_to_line(iLine, true) - end -- if previously clicked into selection +local on_mouse_moved = DocView.on_mouse_moved +function DocView:on_mouse_moved(x, y, ...) + if not config.dragdropselected.enabled or not self.dnd_sText then + -- there is nothing to do -> hand off to original on_mouse_moved() + return on_mouse_moved(self, x, y, ...) + end - -- hand off to 'old' on_mouse_moved() - on_mouse_moved(self, x, y, ...) - -- override cursor as needed - if sCursor then self.cursor = sCursor end + -- not sure we need to do this or if we better not + DocView.super.on_mouse_moved(self, x, y, ...) + if self.dnd_bDragging then + -- remove last caret showing insert location + --self.doc:remove_selection(self.doc.last_selection) + else + self.dnd_bDragging = true + -- show that we are dragging something + self.cursor = 'hand' + -- make sure selection is marked + self:dnd_setSelection() + end + -- calculate line and column for current mouse position + local iLine, iCol = self:resolve_screen_position(x, y) + -- TODO: show insert location + --self.doc:add_selection(iLine, iCol) + -- update scroll position, if needed + self:scroll_to_line(iLine, true) + return true end -- DocView:on_mouse_moved --- override DocView:on_mouse_pressed + local on_mouse_pressed = DocView.on_mouse_pressed 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 caught + end - -- no need to proceed if not left button or has no selection - if ('left' ~= button) - or (not self.doc:has_selection()) - or (1 < clicks) then - return on_mouse_pressed(self, button, x, y, clicks) - end - -- convert pixel coordinates to line and column coordinates - local iLine, iCol = self:resolve_screen_position(x, y) - -- get selection coordinates - local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = self.doc:get_selection(true) - -- set flag for on_mouse_released and on_mouse_moved() methods to detect dragging - self.bClickedIntoSelection = isInSelection(iLine, iCol, iSelLine1, iSelCol1, - iSelLine2, iSelCol2) - if self.bClickedIntoSelection then - -- stash selection for inserting later - self.sDraggedText = self.doc:get_text(self.doc:get_selection()) - else - -- let 'old' on_mouse_pressed() do whatever it needs to do - on_mouse_pressed(self, button, x, y, clicks) - end + -- sticky mode support + if self.dnd_bDragging then + return true + end + -- no need to proceed if: not enabled, not left button, no selection + -- or if this is a multi-click event + if not config.dragdropselected.enabled + or 'left' ~= button + or 1 < clicks + or not self:dnd_isInSelection(x, y) + then + dnd.reset(self) + -- let 'old' on_mouse_pressed() do whatever it needs to do + return on_mouse_pressed(self, button, x, y, clicks) + end + + -- stash selection for inserting later + self.dnd_sText = dnd.getSelectedText(self.doc) + -- disable blinking caret and stash user setting + self.dnd_bBlink = config.disable_blink + config.disable_blink = true + return true end -- DocView:on_mouse_pressed --- override DocView:on_mouse_released() + local on_mouse_released = DocView.on_mouse_released -function DocView:on_mouse_released(button) - - if self.bClickedIntoSelection then - -- insert stashed selected text at current position - self.doc:text_input(self.sDraggedText) - -- unset stash and flag(s) TODO: - self.sDraggedText = '' - self.bClickedIntoSelection = nil - end +function DocView:on_mouse_released(button, x, y) + -- for some reason, lite triggers two on_mouse_released events + -- one seems to be the suggestion box, we debounce that here + if self.state and self.suggestions then + return on_mouse_released(self, button, x, y) + end + -- nothing to do if: not enabled or never clicked into selection + if not config.dragdropselected.enabled or not self.dnd_sText then + return on_mouse_released(self, button, x, y) + end - -- hand over to old handler - on_mouse_released(self, button) + local iLine, iCol = self:resolve_screen_position(x, y) + if not self.dnd_sText then--bDragging then + if not config.dragdropselected.useSticky then + -- not using sticky -> clear selection + self.doc:set_selection(iLine, iCol) + dnd.reset(self) + end + return on_mouse_released(self, button, x, y) + end + local bDuplicating = keymap.modkeys['ctrl'] + if self:dnd_isInSelection(x, y, bDuplicating) then + -- drag aborted by releasing mouse inside selection + --self.doc:remove_selection(self.doc.last_selection) + --self.doc:set_selection(iLine, iCol) + else + -- do some calculations for selecting inserted text + local iAdditionalLines, sLast = -1, '' + for s in (self.dnd_sText .. "\n"):gmatch("(.-)\n") do + iAdditionalLines = iAdditionalLines + 1 + sLast = s + end + local iLastLength = #sLast + -- have doc handle selection updates + self.doc:insert(iLine, iCol, self.dnd_sText) + -- add a marker so we know where to start selecting pasted text + --self.doc:add_selection(iLine, iCol) + if not bDuplicating then + self.doc:delete_to(0) + end + -- get new location of inserted text + --iLine, iCol = self.doc:get_selection_idx(self.doc.last_selection, true) + local iLine2, iCol2 = iLine + iAdditionalLines + if iLine == iLine2 then + iCol2 = iCol + iLastLength + else + iCol2 = iLastLength + 1 + end + -- finally select inserted text + self.doc:set_selection(iLine, iCol, iLine2, iCol2) + end + -- unset stashes and flag + dnd.reset(self) + return on_mouse_released(self, button, x, y) end -- DocView:on_mouse_released +--[[ +local draw_caret = DocView.draw_caret +function DocView:draw_caret(x, y) + if self.dnd_sText and config.dragdropselected.enabled then + -- don't show carets inside selections + if self:dnd_isInSelection(x, y, true) then + return + end + end + return draw_caret(self, x, y) +end -- DocView:draw_caret() +--]] + +-- disable text_input during drag operations +local on_text_input = DocView.on_text_input +function DocView:on_text_input(text) + if self.dnd_bDragging then + return true + end + return on_text_input(self, text) +end -- DocView:on_text_input + + +function dnd.abort(oDocView) + if not config.dragdropselected.enabled then return end + + oDocView = oDocView or core.active_view + if oDocView.dnd_bDragging then + oDocView:dnd_setSelection() + end + dnd.reset(oDocView) +end -- dnd.abort + + +function dnd.predicate() + if not config.dragdropselected.enabled + or not core.active_view:is(DocView) + or not core.active_view.dnd_bDragging + then return false end + + return true, core.active_view +end -- dnd.predicate + + +function dnd.toggleEnabled() + config.dragdropselected.enabled = not config.dragdropselected.enabled + core.status_view:show_message('i', style.text, 'Drag n\' Drop is ' + .. (config.dragdropselected.enabled and 'en' or 'dis') .. 'abled') +end + + +function dnd.toggleSticky() + config.dragdropselected.useSticky = not config.dragdropselected.useSticky + core.status_view:show_message('i', style.text, 'Sticky mode is ' + .. (config.dragdropselected.useSticky and 'en' or 'dis') .. 'abled') +end + +command.add('core.docview', { + ['dragdropselected:toggleEnabled'] = dnd.toggleEnabled, + ['dragdropselected:toggleSticky'] = dnd.toggleSticky +}) +command.add(dnd.predicate, { ['dragdropselected:abort'] = dnd.abort }) +keymap.add({ ['escape'] = 'dragdropselected:abort' }) + + +return dnd + From f4347ecdee9da2a8c43cbd7c4cf6198e5cfcb3fc Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Sun, 25 Jun 2023 04:26:35 +0200 Subject: [PATCH 2/6] trivial changes mainly adapting to same wording as used in lite-xl version --- plugins/dragdropselected.lua | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index ce3311e3..29b3af1e 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -13,12 +13,12 @@ TODO: change mouse cursor when duplicating (requires change in cpp/SDL2) --]] local core = require "core" -local style = require "core.style" +local command = require "core.command" local common = require "core.common" local config = require "core.config" local DocView = require "core.docview" local keymap = require "core.keymap" -local command = require "core.command" +local style = require "core.style" local dnd = {} @@ -287,40 +287,52 @@ function dnd.abort(oDocView) oDocView = oDocView or core.active_view if oDocView.dnd_bDragging then + -- ensure the selection is set as it was oDocView:dnd_setSelection() end dnd.reset(oDocView) end -- dnd.abort -function dnd.predicate() +function dnd.abortPredicate() if not config.dragdropselected.enabled or not core.active_view:is(DocView) or not core.active_view.dnd_bDragging then return false end return true, core.active_view -end -- dnd.predicate +end -- dnd.abortPredicate + + +function dnd.showStatus(s) + if not core.status_view then return end + + core.status_view:show_message('i', style.text, s) +end -- dnd.showStatus function dnd.toggleEnabled() config.dragdropselected.enabled = not config.dragdropselected.enabled - core.status_view:show_message('i', style.text, 'Drag n\' Drop is ' + dnd.showStatus('Drag n\' Drop is ' .. (config.dragdropselected.enabled and 'en' or 'dis') .. 'abled') -end + +end -- dnd.toggleEnabled function dnd.toggleSticky() config.dragdropselected.useSticky = not config.dragdropselected.useSticky - core.status_view:show_message('i', style.text, 'Sticky mode is ' + dnd.showStatus('Sticky mode is ' .. (config.dragdropselected.useSticky and 'en' or 'dis') .. 'abled') -end -command.add('core.docview', { - ['dragdropselected:toggleEnabled'] = dnd.toggleEnabled, - ['dragdropselected:toggleSticky'] = dnd.toggleSticky +end -- dnd.toggleSticky + + +command.add(nil, { + ['dragdropselected:toggle-enabled'] = dnd.toggleEnabled, + ['dragdropselected:toggle-sticky'] = dnd.toggleSticky }) -command.add(dnd.predicate, { ['dragdropselected:abort'] = dnd.abort }) + +command.add(dnd.abortPredicate, { ['dragdropselected:abort'] = dnd.abort }) keymap.add({ ['escape'] = 'dragdropselected:abort' }) From fadad51a69d4c9bff4e722d561366fdfcf3574fb Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Mon, 26 Jun 2023 04:11:50 +0200 Subject: [PATCH 3/6] stage two: move and copy now work in both modes selection is set after operation --- plugins/dragdropselected.lua | 68 +++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index 29b3af1e..dfa1ea89 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -6,8 +6,15 @@ - hold ctrl on release to copy selection - press escape to abort version: 20230616_094245 by SwissalpS + changes: - (regretably) dragged selection is no longer deleted when drag starts + this follows procedure that most editors use. + - ctrl is checked on release, previously on drag-start/mouse-down + - added sticky mode to help against finger strain from holding mouse + button during entire drag procedure + - moved/copied portion is now selected after operation original: 20200627_133351 by SwissalpS + TODO: write work-around for https://github.com/rxi/lite/issues/313 TODO: add dragging image TODO: use OS drag and drop events TODO: change mouse cursor when duplicating (requires change in cpp/SDL2) @@ -76,6 +83,7 @@ function DocView:dnd_isInSelection(iX, iY, bDuplicating) if not self.dnd_lSelection then return nil end local iLine, iCol = self:resolve_screen_position(iX, iY) +--[[ we can't have this behaviour yet, see: https://github.com/rxi/lite/issues/313 if config.dragdropselected.useSticky and not self.dnd_bDragging then -- allow user to clear selection in sticky mode by clicking in empty area -- to the right of selection @@ -84,7 +92,7 @@ function DocView:dnd_isInSelection(iX, iY, bDuplicating) -- it means selection can't be grabbed by the "\n" at the end if iX2 < iX then return nil end end - +--]] local iLine1, iCol1, iLine2, iCol2, bSwap iLine1, iCol1, iLine2, iCol2, bSwap = table.unpack(self.dnd_lSelection) if bDuplicating then @@ -209,12 +217,15 @@ function DocView:on_mouse_released(button, x, y) return on_mouse_released(self, button, x, y) end -- nothing to do if: not enabled or never clicked into selection - if not config.dragdropselected.enabled or not self.dnd_sText then + if not config.dragdropselected.enabled + or not self.dnd_sText + or 'left' ~= button + then return on_mouse_released(self, button, x, y) end local iLine, iCol = self:resolve_screen_position(x, y) - if not self.dnd_sText then--bDragging then + if not self.dnd_bDragging then if not config.dragdropselected.useSticky then -- not using sticky -> clear selection self.doc:set_selection(iLine, iCol) @@ -229,30 +240,37 @@ function DocView:on_mouse_released(button, x, y) --self.doc:remove_selection(self.doc.last_selection) --self.doc:set_selection(iLine, iCol) else - -- do some calculations for selecting inserted text - local iAdditionalLines, sLast = -1, '' - for s in (self.dnd_sText .. "\n"):gmatch("(.-)\n") do - iAdditionalLines = iAdditionalLines + 1 - sLast = s - end - local iLastLength = #sLast - -- have doc handle selection updates - self.doc:insert(iLine, iCol, self.dnd_sText) - -- add a marker so we know where to start selecting pasted text - --self.doc:add_selection(iLine, iCol) - if not bDuplicating then - self.doc:delete_to(0) - end - -- get new location of inserted text - --iLine, iCol = self.doc:get_selection_idx(self.doc.last_selection, true) - local iLine2, iCol2 = iLine + iAdditionalLines - if iLine == iLine2 then - iCol2 = iCol + iLastLength + local iLength = #self.dnd_sText + local iLine1, iCol1, iLine2, iCol2 = table.unpack(self.dnd_lSelection) + if iLine < iLine1 or (iLine == iLine1 and iCol < iCol1) then + -- insertion point is before selection --> delete first + if not bDuplicating then + self.doc:remove(iLine1, iCol1, iLine2, iCol2) + end + self.doc:insert(iLine, iCol, self.dnd_sText) + self.doc:set_selection(iLine, iCol, + self.doc:position_offset(iLine, iCol, iLength)) + else - iCol2 = iLastLength + 1 + -- insertion point is after selection --> insert first + -- cache offset in case insertion is on same line as last selected line + local iRest = #self.doc.lines[iLine2]:sub(iCol2, iCol - 1) + self.doc:insert(iLine, iCol, self.dnd_sText) + if bDuplicating then + self.doc:set_selection(iLine, iCol, + self.doc:position_offset(iLine, iCol, iLength)) + + else + self.doc:remove(iLine1, iCol1, iLine2, iCol2) + if iLine == iLine2 then + iCol = iCol1 + iRest + end + iLine = iLine - iLine2 + iLine1 + self.doc:set_selection(iLine, iCol, + self.doc:position_offset(iLine, iCol, iLength)) + + end end - -- finally select inserted text - self.doc:set_selection(iLine, iCol, iLine2, iCol2) end -- unset stashes and flag dnd.reset(self) From 10dd429c16c0a76fe46c59bf81d0dfe5888e8cd9 Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Mon, 26 Jun 2023 05:24:14 +0200 Subject: [PATCH 4/6] stage three: add insertion caret during drag --- plugins/dragdropselected.lua | 43 ++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index dfa1ea89..6498fc7f 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -12,6 +12,8 @@ - added sticky mode to help against finger strain from holding mouse button during entire drag procedure - moved/copied portion is now selected after operation + - during drag operation an extra caret is shown to indicate exact + insertion location and real caret is not blinking original: 20200627_133351 by SwissalpS TODO: write work-around for https://github.com/rxi/lite/issues/313 @@ -141,6 +143,7 @@ function dnd.reset(oDocView) oDocView.dnd_lSelection = nil oDocView.dnd_bDragging = nil oDocView.dnd_bBlink = nil + oDocView.dnd_lCaret = nil oDocView.cursor = 'ibeam' oDocView.dnd_sText = nil end -- dnd.reset @@ -166,10 +169,9 @@ function DocView:on_mouse_moved(x, y, ...) -- make sure selection is marked self:dnd_setSelection() end - -- calculate line and column for current mouse position + -- show insert location, if not in selection local iLine, iCol = self:resolve_screen_position(x, y) - -- TODO: show insert location - --self.doc:add_selection(iLine, iCol) + self.dnd_lCaret = not self:dnd_isInSelection(x, y) and { iLine, iCol } or nil -- update scroll position, if needed self:scroll_to_line(iLine, true) return true @@ -202,9 +204,6 @@ function DocView:on_mouse_pressed(button, x, y, clicks) -- stash selection for inserting later self.dnd_sText = dnd.getSelectedText(self.doc) - -- disable blinking caret and stash user setting - self.dnd_bBlink = config.disable_blink - config.disable_blink = true return true end -- DocView:on_mouse_pressed @@ -277,18 +276,28 @@ function DocView:on_mouse_released(button, x, y) return on_mouse_released(self, button, x, y) end -- DocView:on_mouse_released ---[[ -local draw_caret = DocView.draw_caret -function DocView:draw_caret(x, y) - if self.dnd_sText and config.dragdropselected.enabled then - -- don't show carets inside selections - if self:dnd_isInSelection(x, y, true) then - return - end + +-- draw extra caret when dragging to indicate exact insert location +local draw_line_body = DocView.draw_line_body +function DocView:draw_line_body(idx, x, y) + -- disable blinking caret during drag operation + if self.dnd_bDragging then + self.blink_timer = 0 end - return draw_caret(self, x, y) -end -- DocView:draw_caret() ---]] + -- draw everything normally (except for caret blink) + draw_line_body(self, idx, x, y) + if not self.dnd_lCaret then return end + + -- check that current line is the line with insertion caret + local iLine, iCol = table.unpack(self.dnd_lCaret) + if idx ~= iLine then return end + + -- draw insertion caret + local iH = self:get_line_height() + local iX = x + self:get_col_x_offset(iLine, iCol) + renderer.draw_rect(iX, y, style.caret_width, iH, style.caret) +end -- DocView:draw_line_body + -- disable text_input during drag operations local on_text_input = DocView.on_text_input From d2a6b0b89fdd1e8815fa2b0f21be764502f0ef20 Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Mon, 26 Jun 2023 05:26:35 +0200 Subject: [PATCH 5/6] stage four: cleanup --- plugins/dragdropselected.lua | 50 +++++++++++++----------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index 6498fc7f..efe1883b 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -1,6 +1,6 @@ --[[ dragdropselected.lua - provides basic drag and drop of selected text (in same document) + provides drag and drop of selected text (in same document) - LMB+drag selected text to move it elsewhere - or LMB on selection and then LMB at destination (if sticky is enabled) - hold ctrl on release to copy selection @@ -67,7 +67,7 @@ end -- DocView:dnd_collectSelection function dnd.getSelectedText(doc) - local iLine1, iCol1, iLine2, iCol2, bSwap = doc:get_selection(true) + local iLine1, iCol1, iLine2, iCol2 = doc:get_selection(true) -- skip empty markers if iLine1 ~= iLine2 or iCol1 ~= iCol2 then return doc:get_text(iLine1, iCol1, iLine2, iCol2) @@ -95,8 +95,8 @@ function DocView:dnd_isInSelection(iX, iY, bDuplicating) if iX2 < iX then return nil end end --]] - local iLine1, iCol1, iLine2, iCol2, bSwap - iLine1, iCol1, iLine2, iCol2, bSwap = table.unpack(self.dnd_lSelection) + local iLine1, iCol1, iLine2, iCol2 + iLine1, iCol1, iLine2, iCol2 = table.unpack(self.dnd_lSelection) if bDuplicating then -- adjust boundries for duplication actions -- this allows users to duplicate selection adjacent to selection @@ -115,6 +115,7 @@ function DocView:dnd_isInSelection(iX, iY, bDuplicating) if dnd.isInSelection(iLine, iCol, iLine1, iCol1, iLine2, iCol2) then return self.dnd_lSelection end + return nil end -- DocView:dnd_isInSelection @@ -125,7 +126,7 @@ function DocView:dnd_setSelection() return end self.doc:set_selection(table.unpack(self.dnd_lSelection)) -end -- DocView:dnd_setSelections +end -- DocView:dnd_setSelection -- unset stashes and flag, reset cursor @@ -142,7 +143,6 @@ function dnd.reset(oDocView) end oDocView.dnd_lSelection = nil oDocView.dnd_bDragging = nil - oDocView.dnd_bBlink = nil oDocView.dnd_lCaret = nil oDocView.cursor = 'ibeam' oDocView.dnd_sText = nil @@ -152,17 +152,13 @@ end -- dnd.reset local on_mouse_moved = DocView.on_mouse_moved function DocView:on_mouse_moved(x, y, ...) if not config.dragdropselected.enabled or not self.dnd_sText then - -- there is nothing to do -> hand off to original on_mouse_moved() return on_mouse_moved(self, x, y, ...) end -- not sure we need to do this or if we better not DocView.super.on_mouse_moved(self, x, y, ...) - if self.dnd_bDragging then - -- remove last caret showing insert location - --self.doc:remove_selection(self.doc.last_selection) - else + if not self.dnd_bDragging then self.dnd_bDragging = true -- show that we are dragging something self.cursor = 'hand' @@ -198,7 +194,6 @@ function DocView:on_mouse_pressed(button, x, y, clicks) or not self:dnd_isInSelection(x, y) then dnd.reset(self) - -- let 'old' on_mouse_pressed() do whatever it needs to do return on_mouse_pressed(self, button, x, y, clicks) end @@ -215,10 +210,12 @@ function DocView:on_mouse_released(button, x, y) if self.state and self.suggestions then return on_mouse_released(self, button, x, y) end - -- nothing to do if: not enabled or never clicked into selection + + -- nothing to do if: not enabled, + -- never clicked into selection or not left button if not config.dragdropselected.enabled - or not self.dnd_sText or 'left' ~= button + or not self.dnd_sText then return on_mouse_released(self, button, x, y) end @@ -234,12 +231,7 @@ function DocView:on_mouse_released(button, x, y) end local bDuplicating = keymap.modkeys['ctrl'] - if self:dnd_isInSelection(x, y, bDuplicating) then - -- drag aborted by releasing mouse inside selection - --self.doc:remove_selection(self.doc.last_selection) - --self.doc:set_selection(iLine, iCol) - else - local iLength = #self.dnd_sText + if not self:dnd_isInSelection(x, y, bDuplicating) then local iLine1, iCol1, iLine2, iCol2 = table.unpack(self.dnd_lSelection) if iLine < iLine1 or (iLine == iLine1 and iCol < iCol1) then -- insertion point is before selection --> delete first @@ -247,29 +239,23 @@ function DocView:on_mouse_released(button, x, y) self.doc:remove(iLine1, iCol1, iLine2, iCol2) end self.doc:insert(iLine, iCol, self.dnd_sText) - self.doc:set_selection(iLine, iCol, - self.doc:position_offset(iLine, iCol, iLength)) - else -- insertion point is after selection --> insert first -- cache offset in case insertion is on same line as last selected line local iRest = #self.doc.lines[iLine2]:sub(iCol2, iCol - 1) self.doc:insert(iLine, iCol, self.dnd_sText) - if bDuplicating then - self.doc:set_selection(iLine, iCol, - self.doc:position_offset(iLine, iCol, iLength)) - - else + if not bDuplicating then self.doc:remove(iLine1, iCol1, iLine2, iCol2) + -- adjust new selection if iLine == iLine2 then iCol = iCol1 + iRest end iLine = iLine - iLine2 + iLine1 - self.doc:set_selection(iLine, iCol, - self.doc:position_offset(iLine, iCol, iLength)) - end end + -- finally select inserted text + self.doc:set_selection(iLine, iCol, + self.doc:position_offset(iLine, iCol, #self.dnd_sText)) end -- unset stashes and flag dnd.reset(self) @@ -340,7 +326,7 @@ end -- dnd.showStatus function dnd.toggleEnabled() config.dragdropselected.enabled = not config.dragdropselected.enabled - dnd.showStatus('Drag n\' Drop is ' + dnd.showStatus("Drag n' Drop is " .. (config.dragdropselected.enabled and 'en' or 'dis') .. 'abled') end -- dnd.toggleEnabled From 74390c7510a0a7bd5a270866c255110d4e0556dc Mon Sep 17 00:00:00 2001 From: Luke aka SwissalpS Date: Mon, 26 Jun 2023 06:00:03 +0200 Subject: [PATCH 6/6] credits --- plugins/dragdropselected.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua index efe1883b..e092a729 100644 --- a/plugins/dragdropselected.lua +++ b/plugins/dragdropselected.lua @@ -14,6 +14,9 @@ - moved/copied portion is now selected after operation - during drag operation an extra caret is shown to indicate exact insertion location and real caret is not blinking + thanks to: github.com/Guldoman for his valuable inputs while I was re-writing + this plugin for lite-xl. The plugin turned out a lot better and + thus the re-write for lite also turned out better. original: 20200627_133351 by SwissalpS TODO: write work-around for https://github.com/rxi/lite/issues/313