Skip to content

Commit

Permalink
Merge pull request #3335 from dmaluka/line-actions-cleanup
Browse files Browse the repository at this point in the history
Improve and unify `CopyLine`, `CutLine`, `DeleteLine`, `DuplicateLine` actions
  • Loading branch information
dmaluka committed Sep 16, 2024
2 parents 1539da7 + bf65847 commit ca60120
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 67 deletions.
172 changes: 125 additions & 47 deletions internal/action/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1238,101 +1238,179 @@ func (h *BufPane) Redo() bool {
return true
}

func (h *BufPane) selectLines() int {
if h.Cursor.HasSelection() {
start := h.Cursor.CurSelection[0]
end := h.Cursor.CurSelection[1]
if start.GreaterThan(end) {
start, end = end, start
}
if end.X == 0 {
end = end.Move(-1, h.Buf)
}

h.Cursor.Deselect(true)
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
} else {
h.Cursor.SelectLine()
}
return h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y
}

// Copy the selection to the system clipboard
func (h *BufPane) Copy() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied selection")
if !h.Cursor.HasSelection() {
return false
}
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = false
InfoBar.Message("Copied selection")
h.Relocate()
return true
}

// CopyLine copies the current line to the clipboard
// CopyLine copies the current line to the clipboard. If there is a selection,
// CopyLine copies all the lines that are (fully or partially) in the selection.
func (h *BufPane) CopyLine() bool {
if h.Cursor.HasSelection() {
origLoc := h.Cursor.Loc
origLastVisualX := h.Cursor.LastVisualX
origSelection := h.Cursor.CurSelection

nlines := h.selectLines()
if nlines == 0 {
return false
}
origLoc := h.Cursor.Loc
h.Cursor.SelectLine()
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true
InfoBar.Message("Copied line")
h.freshClip = false
if nlines > 1 {
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
} else {
InfoBar.Message("Copied line")
}

h.Cursor.Deselect(true)
h.Cursor.Loc = origLoc
h.Cursor.LastVisualX = origLastVisualX
h.Cursor.CurSelection = origSelection
h.Relocate()
return true
}

// CutLine cuts the current line to the clipboard
func (h *BufPane) CutLine() bool {
h.Cursor.SelectLine()
// Cut the selection to the system clipboard
func (h *BufPane) Cut() bool {
if !h.Cursor.HasSelection() {
return false
}
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = false
InfoBar.Message("Cut selection")

h.Relocate()
return true
}

// CutLine cuts the current line to the clipboard. If there is a selection,
// CutLine cuts all the lines that are (fully or partially) in the selection.
func (h *BufPane) CutLine() bool {
nlines := h.selectLines()
if nlines == 0 {
return false
}
totalLines := nlines
if h.freshClip {
if h.Cursor.HasSelection() {
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
} else {
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
}
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
InfoBar.Error(err)
return false
} else {
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
totalLines = strings.Count(clip, "\n") + nlines
}
} else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
h.Copy()
} else {
h.Cursor.CopySelection(clipboard.ClipboardReg)
}
h.freshClip = true
h.lastCutTime = time.Now()
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Cut line")
h.Cursor.StoreVisualX()
if totalLines > 1 {
InfoBar.Message(fmt.Sprintf("Cut %d lines", totalLines))
} else {
InfoBar.Message("Cut line")
}
h.Relocate()
return true
}

// Cut the selection to the system clipboard
func (h *BufPane) Cut() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
h.freshClip = true
InfoBar.Message("Cut selection")

h.Relocate()
return true
// Duplicate the selection
func (h *BufPane) Duplicate() bool {
if !h.Cursor.HasSelection() {
return false
}
return h.CutLine()
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
InfoBar.Message("Duplicated selection")
h.Relocate()
return true
}

// DuplicateLine duplicates the current line or selection
// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine
// duplicates all the lines that are (fully or partially) in the selection.
func (h *BufPane) DuplicateLine() bool {
var infoMessage = "Duplicated line"
if h.Cursor.HasSelection() {
infoMessage = "Duplicated selection"
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
origLoc := h.Cursor.Loc
origLastVisualX := h.Cursor.LastVisualX
origSelection := h.Cursor.CurSelection

start := h.Cursor.CurSelection[0]
end := h.Cursor.CurSelection[1]
if start.GreaterThan(end) {
start, end = end, start
}
if end.X == 0 {
end = end.Move(-1, h.Buf)
}

h.Cursor.Deselect(true)
h.Cursor.Loc = end
h.Cursor.End()
for y := start.Y; y <= end.Y; y++ {
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(y)))
}

h.Cursor.Loc = origLoc
h.Cursor.LastVisualX = origLastVisualX
h.Cursor.CurSelection = origSelection

if start.Y < end.Y {
InfoBar.Message(fmt.Sprintf("Duplicated %d lines", end.Y-start.Y+1))
} else {
InfoBar.Message("Duplicated line")
}
} else {
h.Cursor.End()
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
// h.Cursor.Right()
InfoBar.Message("Duplicated line")
}

InfoBar.Message(infoMessage)
h.Relocate()
return true
}

// DeleteLine deletes the current line
// DeleteLine deletes the current line. If there is a selection, DeleteLine
// deletes all the lines that are (fully or partially) in the selection.
func (h *BufPane) DeleteLine() bool {
h.Cursor.SelectLine()
if !h.Cursor.HasSelection() {
nlines := h.selectLines()
if nlines == 0 {
return false
}
h.Cursor.DeleteSelection()
h.Cursor.ResetSelection()
InfoBar.Message("Deleted line")
h.Cursor.StoreVisualX()
if nlines > 1 {
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
} else {
InfoBar.Message("Deleted line")
}
h.Relocate()
return true
}
Expand Down
9 changes: 4 additions & 5 deletions internal/action/bufpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,8 @@ type BufPane struct {
lastClickTime time.Time
lastLoc buffer.Loc

// lastCutTime stores when the last ctrl+k was issued.
// It is used for clearing the clipboard to replace it with fresh cut lines.
lastCutTime time.Time

// freshClip returns true if the clipboard has never been pasted.
// freshClip returns true if one or more lines have been cut to the clipboard
// and have never been pasted yet.
freshClip bool

// Was the last mouse event actually a double click?
Expand Down Expand Up @@ -780,6 +777,7 @@ var BufKeyActions = map[string]BufKeyAction{
"CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"Duplicate": (*BufPane).Duplicate,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
Expand Down Expand Up @@ -907,6 +905,7 @@ var MultiActions = map[string]bool{
"Copy": true,
"Cut": true,
"CutLine": true,
"Duplicate": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
Expand Down
10 changes: 5 additions & 5 deletions internal/action/defaults_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ var bufdefaults = map[string]string{
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
Expand Down Expand Up @@ -146,8 +146,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
"Home": "StartOfTextToggle",
Expand Down
10 changes: 5 additions & 5 deletions internal/action/defaults_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ var bufdefaults = map[string]string{
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
Expand Down Expand Up @@ -149,8 +149,8 @@ var infodefaults = map[string]string{
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
"Home": "StartOfTextToggle",
Expand Down
18 changes: 13 additions & 5 deletions runtime/help/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ Autocomplete
The `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between
jumping to the start of the text (first) and start of the line.

The `CutLine` action cuts the current line and adds it to the previously cut
lines in the clipboard since the last paste (rather than just replaces the
clipboard contents with this line). So you can cut multiple, not necessarily
consecutive lines to the clipboard just by pressing `Ctrl-k` multiple times,
without selecting them. If you want the more traditional behavior i.e. just
rewrite the clipboard every time, you can use `CopyLine,DeleteLine` action
instead of `CutLine`.

You can also bind some mouse actions (these must be bound to mouse buttons)

```
Expand Down Expand Up @@ -495,10 +503,10 @@ conventions for text editing defaults.
"Alt-]": "DiffNext|CursorEnd",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-d": "Duplicate|DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
Expand Down Expand Up @@ -621,8 +629,8 @@ are given below:
"Backtab": "CycleAutocompleteBack",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-c": "Copy|CopyLine",
"Ctrl-x": "Cut|CutLine",
"Ctrl-k": "CutLine",
"Ctrl-v": "Paste",
"Home": "StartOfTextToggle",
Expand Down

0 comments on commit ca60120

Please sign in to comment.