zyedidia / micro

A modern and intuitive terminal-based text editor
https://micro-editor.github.io
MIT License
24.47k stars 1.16k forks source link

[Feature request]: Duplicate lazy line selection #3110

Closed Gavin-Holt closed 1 month ago

Gavin-Holt commented 6 months ago

Hi

My name is Gavin, and I am a lazy line selector.

I have tried to go straight, but lack the willpower to make pinpoint accurate line selections.

I blame a troubled relationship with the mouse, we never bonded, and I have no love for the little rodent.

My condition is so bad, that sometimes my lazy line selection is backwards - from bottom up!

Thankfully, there is a near perfect solution, the micro editor.

Micro will act upon partially selected lines:

The only irregular command I have discovered so far is DuplicateLine, and it trips me up hourly. With no selection, the current line is duplicated. However, with any text selected - only that text is duplicated (see https://github.com/zyedidia/micro/pull/416).

To duplicate a set of lines, I have to make a pinpoint selection, remembering to include the end-of-line character (not selected with a simple shift end!).

I guess many users will be accustomed to the default behaviour, and it would be unfair for my affliction to inconvenience the majority of law-abiding precision line selectors.

My ideal solution would be a pair of functions DuplicateLinesBelow and DuplicateLinesAbove, which would make copies of the current lazy line selections above or below AND leave my selection (and clipboard) unchanged. e.g.

Before DuplicateLinesBelow :

image

After DuplicateLinesBelow :

image

I am not good at manipulating bp.Cursor.Loc, so a Lua solution is beyond my abilities.

Any help for an incurable lazy line selector would be welcome.

Kind Regards Gavin Holt

OS: Windows 10 Version: 2.0.13 Commit hash: https://github.com/zyedidia/micro/commit/68d88b571de6dca9fb8f03e2a3caafa2287c38d4 Compiled on December 12, 2023

dmaluka commented 6 months ago

I'm using the following in my init.lua for lazy line selection:

function selectLineUp(bp)
    if not bp.Cursor:HasSelection() then
        bp:EndOfLine()
        bp:SelectToStartOfLine()
    else
        bp:SelectUp()
        bp:SelectToStartOfLine()
    end
end

function selectLineDown(bp)
    if not bp.Cursor:HasSelection() then
        bp:StartOfLine()
        bp:SelectDown()
    else
        bp:SelectDown()
    end
end

I have Ctrl-Shift-Up and Ctrl-Shift-Down bound to these functions:

    "CtrlShiftDown": "lua:initlua.selectLineDown",
    "CtrlShiftUp": "lua:initlua.selectLineUp",

So in particular you could use these to quickly select a few whole lines via Ctrl-Shift-Up or Ctrl-Shift-Down (i.e. in whatever direction you like) and then duplicate them via Ctrl-D.

Gavin-Holt commented 6 months ago

Hi,

Many thanks for sharing your functions - taking this as inspiration - and combining with https://github.com/zyedidia/micro/issues/2583, I have written a SelectBlock function:

function editor_SelectBlock(Current)                  
-- Extend the selection to include the whole of any line selected
    if editor.HasSelection(Current)  then
        if Current.Cursor.CurSelection[1]:LessThan(-Current.Cursor.CurSelection[2]) then
            -- forward -> backward
            -- Extend selection to end
            Current:SelectToEndOfLine()
            -- Get EOL Character
            Current:SelectRight()
            -- Swap to end of selection
            editor_SwapAnchor(Current)
            -- Extend selection to begining of line
            Current:SelectToStartOfLine()
        else
            -- backward -> forward
            -- Extend selection to begining of line
            Current:SelectToStartOfLine()
            -- Swap to end of selection
            editor_SwapAnchor(Current)
            -- Extend selection to end
            Current:SelectToEndOfLine()
            -- Get EOL Character
            Current:SelectRight()
        end
    else
        Current.Cursor:SelectLine()
    end
    return true
end

This can be called before duplicateline to achieve block duplication:

    "CtrlD": "lua:initlua.editor_GetBlock, duplicateline"

Unfortunately these manipulations change the current selection (so you can't make multiple copies).

I will leave this open as I still feel duplicateline is an irregular command and better solution should be found.

Kind Regards Gavin Holt

Gavin-Holt commented 2 months ago

I guess we can close this now as there is a work-around.

Kind Regards Gavin Holt

Gavin-Holt commented 2 months ago

Hi,

After downloading the new binary today, my selectblock function no longer works. I suspect this is a result of Fix Deselect() after mouse selection https://github.com/zyedidia/micro/commit/3f810c24d231daaee00239a919feb415e864653f or Fix cursor moving down when selection exist https://github.com/zyedidia/micro/pull/3091

This function is only used to extend the selection before using the DuplicateLine action, to cope with with partly selected lines and I have it bound to Ctrl+D:

    "CtrlD": "lua:initlua.selectblock,DuplicateLine,EndOfLine",

It fails only when the lazy selection if from the bottom upwards!

Generally Micro will act upon partly selected lines (top down or bottom up) for all the following :

DuplicateLine remains an outlier, and my work-around no longer works!

Not wanting to upset the current users of DuplicateLine could we have a DuplicateLines action which acts on all partly selected lines?

Kind Regards Gavin Holt

PS. Naming things is hard: given a free hand I would rename DuplicateLine action to Duplicate (a method to duplicate selection, or current line if no selection, without using the clipboard), and that would allow a more logical use for DuplicateLine (duplicate all partly selected lines or current line if no selection, without using the clipboard).

dmaluka commented 1 month ago

After downloading the new binary today, my selectblock function no longer works. I suspect this is a result of Fix Deselect() after mouse selection 3f810c2 or Fix cursor moving down when selection exist #3091

Yes, we changed the behavior to always ignore the direction of selection. First, to make it at least consistent (and to fix bugs caused by its inconsistency), second, because apparently some users even prefer this ignore-direction-of-selection behavior.

You can use something like this now:

function selectblock(bp)
    if bp.Cursor:HasSelection() then
        local lastY = bp.Cursor.CurSelection[2].Y
        bp:StartOfLine()
        while bp.Cursor.Loc.Y <= lastY do
            bp:SelectDown()
        end
    end
end

PS. Naming things is hard: given a free hand I would rename DuplicateLine action to Duplicate (a method to duplicate selection, or current line if no selection, without using the clipboard), and that would allow a more logical use for DuplicateLine (duplicate all partly selected lines or current line if no selection, without using the clipboard).

I'm thinking about the same. We may clean up and unify things by changing behavior of some action without changing behavior of the default keys bound to them (so that the change would not affect most users): bind Ctrl-c to Copy|CopyLine, Ctrl-x to Cut|CutLine, Ctrl-d to Duplicate|DuplicateLine. The Copy, Cut and Duplicate actions would return false if there is no selection, and the *Line variants would operate on whole lines, not just the selection.

dmaluka commented 1 month ago

I'm thinking about the same. We may clean up and unify things by changing behavior of some action without changing behavior of the default keys bound to them (so that the change would not affect most users): bind Ctrl-c to Copy|CopyLine, Ctrl-x to Cut|CutLine, Ctrl-d to Duplicate|DuplicateLine. The Copy, Cut and Duplicate actions would return false if there is no selection, and the *Line variants would operate on whole lines, not just the selection.

Implemented in #3335. With this PR you will probably not need your selectblock any longer.

Gavin-Holt commented 1 month ago

Hi

I have been giving more thought to duplicate and have written a function to deal with three situations:

  1. Duplicate the current line (if no selection)
  2. Duplicate selected word/phrase (on one line)
  3. Duplicate the whole of any partly selected lines (lazy multi-line selection)

All of these situations leave the cursor position/selection unchanged, and therefore can be repeated for multiple duplication.

My Lua function satisfies my needs, but would be better as a built in action - to work with multiple cursors.

function duplicate(Current)
    if Current.Cursor:HasSelection() then
        if Current.Cursor.CurSelection[1].Y == Current.Cursor.CurSelection[2].Y then
            -- Selection on a single line - therefore:
                -- Duplicate selection with padding
                -- Insert after last character
                -- Maintain original selection
            local sel = Current.Cursor:GetSelection()   -- BYTES
            sel = util.String(sel)                      -- String
            sel = sel.." "                              -- Add right padding as likely a word
            local nextcharLOC = buffer.Loc(Current.Cursor.CurSelection[2].X+1, Current.Cursor.CurSelection[2].Y)
            -- If I don't increment X the inserted text expands the current selection!
            Current.Buf:Insert(nextcharLOC, sel)
            return true
        else
            -- Multiline selection - therefore:
                -- Get all part selected lines
                -- Insert after last line
                -- Maintain original selection
            local block  =  ""
            local top    = math.min(Current.Cursor.CurSelection[1].Y,Current.Cursor.CurSelection[2].Y)
            local bottom = math.max(Current.Cursor.CurSelection[1].Y,Current.Cursor.CurSelection[2].Y)
            for i=top, bottom,1 do
                block = block..Current.Buf:Line(i).."\n"
            end
            local nextlineLOC = buffer.Loc(0,bottom+1)
            Current.Buf:Insert(nextlineLOC, block)
            return true
        end
    else
            -- Nil selection  - therefore:
                -- Duplicate current line
                -- Insert after current line
                -- Maintain cursor position
            local sel = Current.Buf:Line(Current.Cursor.Y).."\n"
            local nextlineLOC = buffer.Loc(0,Current.Cursor.Y+1)
            Current.Buf:Insert(nextlineLOC, sel)
            return true
    end
end

Kind Regards Gavin Holt