]> git.lizzy.rs Git - micro.git/commitdiff
Add support for copy-paste via OSC 52
authorZachary Yedidia <zyedidia@gmail.com>
Sun, 5 Jul 2020 00:00:39 +0000 (20:00 -0400)
committerZachary Yedidia <zyedidia@gmail.com>
Sun, 5 Jul 2020 00:00:39 +0000 (20:00 -0400)
Ref #1754

16 files changed:
cmd/micro/micro.go
go.mod
go.sum
internal/action/actions.go
internal/action/bufpane.go
internal/action/command.go
internal/action/infocomplete.go
internal/action/termpane.go
internal/buffer/cursor.go
internal/clipboard/clipboard.go [new file with mode: 0644]
internal/clipboard/internal.go [new file with mode: 0644]
internal/clipboard/multi.go [new file with mode: 0644]
internal/clipboard/terminal.go [new file with mode: 0644]
internal/config/settings.go
runtime/help/copypaste.md
runtime/help/options.md

index a4d22b70e5db8f95e194c532e0499c33957b49cd..723e11b73cec77c01dcdd68b4add08f9f7a4a5b0 100644 (file)
@@ -16,6 +16,7 @@ import (
        lua "github.com/yuin/gopher-lua"
        "github.com/zyedidia/micro/v2/internal/action"
        "github.com/zyedidia/micro/v2/internal/buffer"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/config"
        ulua "github.com/zyedidia/micro/v2/internal/lua"
        "github.com/zyedidia/micro/v2/internal/screen"
@@ -269,6 +270,9 @@ func main() {
                os.Exit(1)
        }
 
+       m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
+       clipErr := clipboard.Initialize(m)
+
        defer func() {
                if err := recover(); err != nil {
                        screen.Screen.Fini()
@@ -313,6 +317,10 @@ func main() {
                screen.TermMessage(err)
        }
 
+       if clipErr != nil {
+               action.InfoBar.Error(clipErr, " or change 'clipboard' option")
+       }
+
        events = make(chan tcell.Event)
 
        // Here is the event loop which runs in a separate thread
diff --git a/go.mod b/go.mod
index 3a0256378c2f288fc357cdf578b4a46082423724..c66f4fbd1caace98a16f0a987df005aa410c1d48 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -12,12 +12,12 @@ require (
        github.com/sergi/go-diff v1.1.0
        github.com/stretchr/testify v1.4.0
        github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
-       github.com/zyedidia/clipboard v1.0.1
+       github.com/zyedidia/clipboard v1.0.3
        github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
        github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
        github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
        github.com/zyedidia/pty v2.0.0+incompatible // indirect
-       github.com/zyedidia/tcell v1.4.8
+       github.com/zyedidia/tcell v1.4.9
        github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
        golang.org/x/text v0.3.2
        gopkg.in/sourcemap.v1 v1.0.5 // indirect
diff --git a/go.sum b/go.sum
index cc537641b86de7fac0661765380eb033e73772a1..bbdcca7864bccf25cd94ddf0a736e986b99768cb 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -40,6 +40,10 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
 github.com/zyedidia/clipboard v1.0.1 h1:DNsDMRXdptfdp4f6gSnSNJpWadAgI8UCm26elADCYak=
 github.com/zyedidia/clipboard v1.0.1/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
+github.com/zyedidia/clipboard v1.0.2 h1:Mh5t+hjEf9I5pcveVxVAFJsuOhd2ByavDgIkflK3Qps=
+github.com/zyedidia/clipboard v1.0.2/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
+github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
+github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
 github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
 github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
 github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
@@ -54,6 +58,8 @@ github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpy
 github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
 github.com/zyedidia/tcell v1.4.8 h1:s4zYGOyCNDK4cdrgNVME0SxGizuT/oKY3OyB4Ls2Qpg=
 github.com/zyedidia/tcell v1.4.8/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
+github.com/zyedidia/tcell v1.4.9 h1:DYUrd8gCmSxUlbFd280sT6tzEqhxfyWMbZNNnup6iQ0=
+github.com/zyedidia/tcell v1.4.9/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
 github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
 github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
index ce2d86c7c6169971a40c29e0fd812cdf2e2d2ccc..5d7b31e0238b9f0dd70ab9ca2a4515791a2eefea 100644 (file)
@@ -7,8 +7,8 @@ import (
        "time"
 
        shellquote "github.com/kballard/go-shellquote"
-       "github.com/zyedidia/clipboard"
        "github.com/zyedidia/micro/v2/internal/buffer"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/config"
        "github.com/zyedidia/micro/v2/internal/screen"
        "github.com/zyedidia/micro/v2/internal/shell"
@@ -59,7 +59,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
                                h.doubleClick = false
 
                                h.Cursor.SelectLine()
-                               h.Cursor.CopySelection("primary")
+                               h.Cursor.CopySelection(clipboard.PrimaryReg)
                        } else {
                                // Double click
                                h.lastClickTime = time.Now()
@@ -68,7 +68,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
                                h.tripleClick = false
 
                                h.Cursor.SelectWord()
-                               h.Cursor.CopySelection("primary")
+                               h.Cursor.CopySelection(clipboard.PrimaryReg)
                        }
                } else {
                        h.doubleClick = false
@@ -961,13 +961,9 @@ func (h *BufPane) Redo() bool {
 // Copy the selection to the system clipboard
 func (h *BufPane) Copy() bool {
        if h.Cursor.HasSelection() {
-               h.Cursor.CopySelection("clipboard")
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
                h.freshClip = true
-               if clipboard.Unsupported {
-                       InfoBar.Message("Copied selection (install xclip for external clipboard)")
-               } else {
-                       InfoBar.Message("Copied selection")
-               }
+               InfoBar.Message("Copied selection")
        }
        h.Relocate()
        return true
@@ -979,13 +975,9 @@ func (h *BufPane) CopyLine() bool {
                return false
        } else {
                h.Cursor.SelectLine()
-               h.Cursor.CopySelection("clipboard")
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
                h.freshClip = true
-               if clipboard.Unsupported {
-                       InfoBar.Message("Copied line (install xclip for external clipboard)")
-               } else {
-                       InfoBar.Message("Copied line")
-               }
+               InfoBar.Message("Copied line")
        }
        h.Cursor.Deselect(true)
        h.Relocate()
@@ -1000,10 +992,10 @@ func (h *BufPane) CutLine() bool {
        }
        if h.freshClip == true {
                if h.Cursor.HasSelection() {
-                       if clip, err := clipboard.ReadAll("clipboard"); err != nil {
-                               // messenger.Error(err)
+                       if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
+                               InfoBar.Error(err)
                        } else {
-                               clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
+                               clipboard.Write(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg)
                        }
                }
        } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
@@ -1021,7 +1013,7 @@ func (h *BufPane) CutLine() bool {
 // Cut the selection to the system clipboard
 func (h *BufPane) Cut() bool {
        if h.Cursor.HasSelection() {
-               h.Cursor.CopySelection("clipboard")
+               h.Cursor.CopySelection(clipboard.ClipboardReg)
                h.Cursor.DeleteSelection()
                h.Cursor.ResetSelection()
                h.freshClip = true
@@ -1147,7 +1139,10 @@ func (h *BufPane) MoveLinesDown() bool {
 // Paste whatever is in the system clipboard into the buffer
 // Delete and paste if the user has a selection
 func (h *BufPane) Paste() bool {
-       clip, _ := clipboard.ReadAll("clipboard")
+       clip, err := clipboard.Read(clipboard.ClipboardReg)
+       if err != nil {
+               InfoBar.Error(err)
+       }
        h.paste(clip)
        h.Relocate()
        return true
@@ -1155,7 +1150,10 @@ func (h *BufPane) Paste() bool {
 
 // PastePrimary pastes from the primary clipboard (only use on linux)
 func (h *BufPane) PastePrimary() bool {
-       clip, _ := clipboard.ReadAll("primary")
+       clip, err := clipboard.Read(clipboard.PrimaryReg)
+       if err != nil {
+               InfoBar.Error(err)
+       }
        h.paste(clip)
        h.Relocate()
        return true
@@ -1177,11 +1175,7 @@ func (h *BufPane) paste(clip string) {
        h.Buf.Insert(h.Cursor.Loc, clip)
        // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
        h.freshClip = false
-       if clipboard.Unsupported {
-               InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
-       } else {
-               InfoBar.Message("Pasted clipboard")
-       }
+       InfoBar.Message("Pasted clipboard")
 }
 
 // JumpToMatchingBrace moves the cursor to the matching brace if it is
index cc365d0a3337ce93523233ef47995f70f11f7305..10280abfd497d61c1a5fd64119f1fa627a71a17b 100644 (file)
@@ -8,6 +8,7 @@ import (
 
        lua "github.com/yuin/gopher-lua"
        "github.com/zyedidia/micro/v2/internal/buffer"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/config"
        "github.com/zyedidia/micro/v2/internal/display"
        ulua "github.com/zyedidia/micro/v2/internal/lua"
@@ -360,7 +361,7 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
                                //      h.Cursor.SetSelectionEnd(h.Cursor.Loc)
                                // }
                                if h.Cursor.HasSelection() {
-                                       h.Cursor.CopySelection("primary")
+                                       h.Cursor.CopySelection(clipboard.PrimaryReg)
                                }
                                h.mouseReleased = true
                        }
index cff833743ef7415b9ede7b581f17468ec35d42a0..f3f5db483f78002fb618545e981540e270b23626 100644 (file)
@@ -13,6 +13,7 @@ import (
 
        shellquote "github.com/kballard/go-shellquote"
        "github.com/zyedidia/micro/v2/internal/buffer"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/config"
        "github.com/zyedidia/micro/v2/internal/screen"
        "github.com/zyedidia/micro/v2/internal/shell"
@@ -505,6 +506,12 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
                        }
                } else if option == "paste" {
                        screen.Screen.SetPaste(nativeValue.(bool))
+               } else if option == "clipboard" {
+                       m := clipboard.SetMethod(nativeValue.(string))
+                       err := clipboard.Initialize(m)
+                       if err != nil {
+                               return err
+                       }
                } else {
                        for _, pl := range config.Plugins {
                                if option == pl.Name {
index f39dfbda2a0f99a4d8100e54488e63b32af6b376..12f7844b5d74f26dcc19ca3fc3e7395f972c2909 100644 (file)
@@ -186,6 +186,16 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
                        if strings.HasPrefix("doas", input) {
                                suggestions = append(suggestions, "doas")
                        }
+               case "clipboard":
+                       if strings.HasPrefix("external", input) {
+                               suggestions = append(suggestions, "external")
+                       }
+                       if strings.HasPrefix("internal", input) {
+                               suggestions = append(suggestions, "internal")
+                       }
+                       if strings.HasPrefix("terminal", input) {
+                               suggestions = append(suggestions, "terminal")
+                       }
                }
        }
        sort.Strings(suggestions)
index 8c75d04bbd361a35fbe57ba0628f941a199c7ee1..860dc192ceabd08e9b5b7377c66757c3da56d033 100644 (file)
@@ -4,7 +4,7 @@ import (
        "errors"
        "runtime"
 
-       "github.com/zyedidia/clipboard"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/display"
        "github.com/zyedidia/micro/v2/internal/screen"
        "github.com/zyedidia/micro/v2/internal/shell"
@@ -90,7 +90,7 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
                        }
                }
                if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
-                       clipboard.WriteAll(t.GetSelection(t.GetView().Width), "clipboard")
+                       clipboard.Write(t.GetSelection(t.GetView().Width), clipboard.ClipboardReg)
                        InfoBar.Message("Copied selection to clipboard")
                } else if t.Status != shell.TTDone {
                        t.WriteString(event.EscSeq())
index 488a14c4bdde598b6cd821ff7283b42b1fa2aea4..0521ebc7cbf0a56f72b04183966bce7eaa958900 100644 (file)
@@ -1,7 +1,7 @@
 package buffer
 
 import (
-       "github.com/zyedidia/clipboard"
+       "github.com/zyedidia/micro/v2/internal/clipboard"
        "github.com/zyedidia/micro/v2/internal/util"
 )
 
@@ -125,10 +125,10 @@ func (c *Cursor) End() {
 
 // CopySelection copies the user's selection to either "primary"
 // or "clipboard"
-func (c *Cursor) CopySelection(target string) {
+func (c *Cursor) CopySelection(target clipboard.Register) {
        if c.HasSelection() {
-               if target != "primary" || c.buf.Settings["useprimary"].(bool) {
-                       clipboard.WriteAll(string(c.GetSelection()), target)
+               if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) {
+                       clipboard.Write(string(c.GetSelection()), target)
                }
        }
 }
diff --git a/internal/clipboard/clipboard.go b/internal/clipboard/clipboard.go
new file mode 100644 (file)
index 0000000..3a7dd00
--- /dev/null
@@ -0,0 +1,151 @@
+package clipboard
+
+import (
+       "errors"
+
+       "github.com/zyedidia/clipboard"
+)
+
+type Method int
+
+const (
+       // External relies on external tools for accessing the clipboard
+       // These include xclip, xsel, wl-clipboard for linux, pbcopy/pbpaste on Mac,
+       // and Syscalls on Windows.
+       External Method = iota
+       // Terminal uses the terminal to manage the clipboard via OSC 52. Many
+       // terminals do not support OSC 52, in which case this method won't work.
+       Terminal
+       // Internal just manages the clipboard with an internal buffer and doesn't
+       // attempt to interface with the system clipboard
+       Internal
+)
+
+// CurrentMethod is the method used to store clipboard information
+var CurrentMethod Method = Internal
+
+// A Register is a buffer used to store text. The system clipboard has the 'clipboard'
+// and 'primary' (linux-only) registers, but other registers may be used internal to micro.
+type Register int
+
+const (
+       // ClipboardReg is the main system clipboard
+       ClipboardReg Register = -1
+       // PrimaryReg is the system primary clipboard (linux only)
+       PrimaryReg = -2
+)
+
+// Initialize attempts to initialize the clipboard using the given method
+func Initialize(m Method) error {
+       var err error
+       switch m {
+       case External:
+               err = clipboard.Initialize()
+       }
+       return err
+}
+
+// SetMethod changes the clipboard access method
+func SetMethod(m string) Method {
+       switch m {
+       case "internal":
+               CurrentMethod = Internal
+       case "external":
+               CurrentMethod = External
+       case "terminal":
+               CurrentMethod = Terminal
+       }
+       return CurrentMethod
+}
+
+// Read reads from a clipboard register
+func Read(r Register) (string, error) {
+       return read(r, CurrentMethod)
+}
+
+// Write writes text to a clipboard register
+func Write(text string, r Register) error {
+       return write(text, r, CurrentMethod)
+}
+
+// ReadMulti reads text from a clipboard register for a certain multi-cursor
+func ReadMulti(r Register, num int) (string, error) {
+       s := multi.getText(r, num)
+       return s, nil
+}
+
+// WriteMulti writes text to a clipboard register for a certain multi-cursor
+func WriteMulti(text string, r Register, num int) error {
+       return writeMulti(text, r, num, CurrentMethod)
+}
+
+// ValidMulti checks if the internal multi-clipboard is valid and up-to-date
+// with the system clipboard
+func ValidMulti(r Register, ncursors int) bool {
+       clip, err := Read(r)
+       if err != nil {
+               return false
+       }
+       return multi.isValid(r, ncursors, clip)
+}
+
+func writeMulti(text string, r Register, num int, m Method) error {
+       multi.writeText(text, r, num)
+       return write(multi.getAllText(r), r, m)
+}
+
+func read(r Register, m Method) (string, error) {
+       switch m {
+       case External:
+               switch r {
+               case ClipboardReg:
+                       return clipboard.ReadAll("clipboard")
+               case PrimaryReg:
+                       return clipboard.ReadAll("primary")
+               default:
+                       return internal.read(r), nil
+               }
+       case Internal:
+               return internal.read(r), nil
+       case Terminal:
+               switch r {
+               case ClipboardReg:
+                       // terminal paste works by sending an esc sequence to the
+                       // terminal to trigger a paste event
+                       err := terminal.read("clipboard")
+                       return "", err
+               case PrimaryReg:
+                       err := terminal.read("primary")
+                       return "", err
+               default:
+                       return internal.read(r), nil
+               }
+       }
+       return "", errors.New("Invalid clipboard method")
+}
+
+func write(text string, r Register, m Method) error {
+       switch m {
+       case External:
+               switch r {
+               case ClipboardReg:
+                       return clipboard.WriteAll(text, "clipboard")
+               case PrimaryReg:
+                       return clipboard.WriteAll(text, "primary")
+               default:
+                       internal.write(text, r)
+               }
+       case Internal:
+               internal.write(text, r)
+       case Terminal:
+               switch r {
+               case ClipboardReg:
+                       return terminal.write(text, "c")
+               case PrimaryReg:
+                       return terminal.write(text, "p")
+               default:
+                       internal.write(text, r)
+               }
+       }
+       return nil
+}
diff --git a/internal/clipboard/internal.go b/internal/clipboard/internal.go
new file mode 100644 (file)
index 0000000..b178b3c
--- /dev/null
@@ -0,0 +1,17 @@
+package clipboard
+
+type internalClipboard map[Register]string
+
+var internal internalClipboard
+
+func init() {
+       internal = make(internalClipboard)
+}
+
+func (c internalClipboard) read(r Register) string {
+       return c[r]
+}
+
+func (c internalClipboard) write(text string, r Register) {
+       c[r] = text
+}
diff --git a/internal/clipboard/multi.go b/internal/clipboard/multi.go
new file mode 100644 (file)
index 0000000..25a1932
--- /dev/null
@@ -0,0 +1,66 @@
+package clipboard
+
+import (
+       "bytes"
+       "hash/fnv"
+)
+
+// For storing multi cursor clipboard contents
+type multiClipboard map[Register][]string
+
+var multi multiClipboard
+
+func (c multiClipboard) getAllText(r Register) string {
+       content := c[r]
+       if content == nil {
+               return ""
+       }
+
+       buf := &bytes.Buffer{}
+       for _, s := range content {
+               buf.WriteString(s)
+               buf.WriteByte('\n')
+       }
+       return buf.String()
+}
+
+func (c multiClipboard) getText(r Register, num int) string {
+       content := c[r]
+       if content == nil || len(content) <= num {
+               return ""
+       }
+
+       return content[num]
+}
+
+func hash(s string) uint32 {
+       h := fnv.New32a()
+       h.Write([]byte(s))
+       return h.Sum32()
+}
+
+// isValid checks if the text stored in this multi-clipboard is the same as the
+// text stored in the system clipboard (provided as an argument), and therefore
+// if it is safe to use the multi-clipboard for pasting instead of the system
+// clipboard.
+func (c multiClipboard) isValid(r Register, ncursors int, clipboard string) bool {
+       content := c[r]
+       if content == nil || len(content) != ncursors {
+               return false
+       }
+
+       return hash(clipboard) == hash(c.getAllText(r))
+}
+
+func (c multiClipboard) writeText(text string, r Register, num int) {
+       content := c[r]
+       if content == nil || num >= cap(content) {
+               content = make([]string, num+1, num+1)
+       }
+
+       content[num] = text
+}
+
+func init() {
+       multi = make(multiClipboard)
+}
diff --git a/internal/clipboard/terminal.go b/internal/clipboard/terminal.go
new file mode 100644 (file)
index 0000000..37b37dc
--- /dev/null
@@ -0,0 +1,15 @@
+package clipboard
+
+import "github.com/zyedidia/micro/v2/internal/screen"
+
+type terminalClipboard struct{}
+
+var terminal terminalClipboard
+
+func (t terminalClipboard) read(reg string) error {
+       return screen.Screen.GetClipboard(reg)
+}
+
+func (t terminalClipboard) write(text, reg string) error {
+       return screen.Screen.SetClipboard(text, reg)
+}
index f298ae04f49d4fa7e2191bfeeaa5fd19a82c459b..efbc671ad23610b283e45a2d4b41aec8c76f02e2 100644 (file)
@@ -43,6 +43,7 @@ func init() {
 // Options with validators
 var optionValidators = map[string]optionValidator{
        "autosave":     validateNonNegativeValue,
+       "clipboard":    validateClipboard,
        "tabsize":      validatePositiveValue,
        "scrollmargin": validateNonNegativeValue,
        "scrollspeed":  validateNonNegativeValue,
@@ -322,6 +323,7 @@ func DefaultCommonSettings() map[string]interface{} {
 // default values
 var DefaultGlobalOnlySettings = map[string]interface{}{
        "autosave":       float64(0),
+       "clipboard":      "external",
        "colorscheme":    "default",
        "divchars":       "|-",
        "divreverse":     true,
@@ -450,6 +452,22 @@ func validateColorscheme(option string, value interface{}) error {
        return nil
 }
 
+func validateClipboard(option string, value interface{}) error {
+       val, ok := value.(string)
+
+       if !ok {
+               return errors.New("Expected string type for clipboard")
+       }
+
+       switch val {
+       case "internal", "external", "terminal":
+       default:
+               return errors.New(option + " must be 'internal', 'external', or 'terminal'")
+       }
+
+       return nil
+}
+
 func validateLineEnding(option string, value interface{}) error {
        endingType, ok := value.(string)
 
index 57e6a7cd043fff3562e9db4a9476c851a899fd8b..1fbf52a5886fd703567e3e18a2969d2413bbc6d9 100644 (file)
@@ -4,8 +4,51 @@ because there are multiple methods. This help document will explain
 the various methods for copying and pasting, how they work,
 and the best methods for doing so over SSH.
 
+# OSC 52 (terminal clipboard)
+
+If possible, setting the `clipboard` option to `terminal` will give
+best results because it will work over SSH and locally. However, there
+is limited support among terminal emulators for the terminal clipboard
+(which uses the OSC 52 protocol to communicate clipboard contents).
+Here is a list of terminal emulators and their status:
+
+* Kitty: supported, but only writing is enabled by default. To enable
+  reading, add `read-primary` and `read-clipboard` to the
+  `clipboard_control` option.
+
+* iTerm2: supported, but must be enabled in
+  `Preferences->General-> Selection->Applications in terminal may access clipboard`.
+
+* `st`: supported.
+
+* `rxvt-unicode`: not natively supported, but there is a Perl extension
+   [here](http://anti.teamidiot.de/static/nei/*/Code/urxvt/).
+
+* `xterm`: supported, but disabled by default. It can be enabled by putting
+   the following in `.Xresources` or `.Xdefaults`:
+   `XTerm*disallowedWindowOps: 20,21,SetXprop`.
+
+* `gnome-terminal`: does not support OSC 52.
+
+**Summary:** If you want copy and paste to work over SSH, then you
+should set `clipboard` to `terminal`, and make sure your terminal
+supports OSC 52.
+
 # Pasting
 
+## Recommendations (TL;DR)
+
+The recommended method of pasting is the following:
+
+* If you are not working over SSH, use the micro keybinding (Ctrl-v
+  by default) to perform pastes. If on Linux, install `xclip` or
+  `xsel` beforehand.
+
+* If you are working over SSH, use the terminal keybinding
+  (Ctrl-Shift-v or Command-v) to perform pastes. If your terminal
+  does not support bracketed paste, when performing a paste first
+  enable the `paste` option, and when finished disable the option.
+
 ## Micro paste events
 
 Micro is an application that runs within the terminal. This means
@@ -56,20 +99,23 @@ machine's clipboard. On the other hand, the terminal keybinding
 for paste will access your local clipboard and send the text over
 the network as a paste event, which is what you want.
 
-## Recommendations
+# Copying
 
-The recommended method of pasting is the following:
+# Recommendations (TL;DR)
 
-* If you are not working over SSH, use the micro keybinding (Ctrl-v
-  by default) to perform pastes. If on Linux, install `xclip` or
-  `xsel` beforehand.
+The recommended method of copying is the following:
 
-* If you are working over SSH, use the terminal keybinding
-  (Ctrl-Shift-v or Command-v) to perform pastes. If your terminal
-  does not support bracketed paste, when performing a paste first
-  enable the `paste` option, and when finished disable the option.
+* If you are not working over SSH, use the micro keybinding (Ctrl-c by
+  default) to perform copies. If on Linux, install `xclip` or `xsel`
+  beforehand.
 
-# Copying
+* If you are working over SSH, use the terminal keybinding
+  (Ctrl-Shift-c or Command-c) to perform copies. You must first disable
+  the `mouse` option to perform a terminal selection, and you may wish
+  to disable line numbers and diff indicators (`ruler` and `diffgutter`
+  options) and close other splits. This method will only be able to copy
+  characters that are displayed on the screen (you will not be able to
+  copy more than one page's worth of characters).
 
 Copying follows a similar discussion to the one above about pasting.
 The primary difference is before performing a copy, the application
@@ -92,19 +138,3 @@ means that for copying multiple lines using the terminal selection, you
 should first disable line numbers and diff indicators (turn off the `ruler`
 and `diffgutter` options), otherwise they might be part of your selection
 and copied.
-
-## Recommendations
-
-The recommended method of copying is the following:
-
-* If you are not working over SSH, use the micro keybinding (Ctrl-c by
-  default) to perform copies. If on Linux, install `xclip` or `xsel`
-  beforehand.
-
-* If you are working over SSH, use the terminal keybinding
-  (Ctrl-Shift-c or Command-c) to perform copies. You must first disable
-  the `mouse` option to perform a terminal selection, and you may wish
-  to disable line numbers and diff indicators (`ruler` and `diffgutter`
-  options) and close other splits. This method will only be able to copy
-  characters that are displayed on the screen (you will not be able to
-  copy more than one page's worth of characters).
index c8b20d0787f96d482208b2fb19c124b0e5bff37a..3fb187a6edfaf7c34ba735e91de70434c59c479d 100644 (file)
@@ -54,6 +54,25 @@ Here are the available options:
 
     default value: `false`
 
+* `clipboard`: specifies how micro should access the system clipboard.
+   Possible values are:
+    * `external`: accesses clipboard via an external tool, such as xclip/xsel
+       or wl-clipboard on Linux, pbcopy/pbpaste on MacOS, and system calls on
+       Windows. On Linux, if you do not have one of the tools installed, or if
+       they are not working, micro will throw an error and use an internal
+       clipboard.
+    * `terminal`: accesses the clipboard via your terminal emulator. Note that
+       there is limited support among terminal emulators for this feature
+       (called OSC 52). Terminals that are known to work are Kitty (enable
+       reading with `clipboard_control` setting), iTerm2 (enable in prefs),
+       st, rxvt-unicode and xterm if enabled (see `> help copypaste` for
+       details). Note that Gnome-terminal does not support this feature. With
+       this setting, copy-paste **will** work over ssh. See `> help copypaste`
+       for details.
+    * `internal`: micro will use an internal clipboard.
+
+    default value: `external`
+
 * `colorcolumn`: if this is not set to 0, it will display a column at the
    specified column. This is useful if you want column 80 to be highlighted
    special for example.