Add support for copy-paste via OSC 52

Ref #1754
This commit is contained in:
Zachary Yedidia 2020-07-04 20:00:39 -04:00
parent 67355337b3
commit f143418267
16 changed files with 403 additions and 61 deletions

View file

@ -16,6 +16,7 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/action" "github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer" "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/config"
ulua "github.com/zyedidia/micro/v2/internal/lua" ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
@ -269,6 +270,9 @@ func main() {
os.Exit(1) os.Exit(1)
} }
m := clipboard.SetMethod(config.GetGlobalOption("clipboard").(string))
clipErr := clipboard.Initialize(m)
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
screen.Screen.Fini() screen.Screen.Fini()
@ -313,6 +317,10 @@ func main() {
screen.TermMessage(err) screen.TermMessage(err)
} }
if clipErr != nil {
action.InfoBar.Error(clipErr, " or change 'clipboard' option")
}
events = make(chan tcell.Event) events = make(chan tcell.Event)
// Here is the event loop which runs in a separate thread // Here is the event loop which runs in a separate thread

4
go.mod
View file

@ -12,12 +12,12 @@ require (
github.com/sergi/go-diff v1.1.0 github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb 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/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect 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 github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2 golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect

6
go.sum
View file

@ -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/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 h1:DNsDMRXdptfdp4f6gSnSNJpWadAgI8UCm26elADCYak=
github.com/zyedidia/clipboard v1.0.1/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA= 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 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c= 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= 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/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 h1:s4zYGOyCNDK4cdrgNVME0SxGizuT/oKY3OyB4Ls2Qpg=
github.com/zyedidia/tcell v1.4.8/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA= 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 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E= 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= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -7,8 +7,8 @@ import (
"time" "time"
shellquote "github.com/kballard/go-shellquote" shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/v2/internal/buffer" "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/config"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell" "github.com/zyedidia/micro/v2/internal/shell"
@ -59,7 +59,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.doubleClick = false h.doubleClick = false
h.Cursor.SelectLine() h.Cursor.SelectLine()
h.Cursor.CopySelection("primary") h.Cursor.CopySelection(clipboard.PrimaryReg)
} else { } else {
// Double click // Double click
h.lastClickTime = time.Now() h.lastClickTime = time.Now()
@ -68,7 +68,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.tripleClick = false h.tripleClick = false
h.Cursor.SelectWord() h.Cursor.SelectWord()
h.Cursor.CopySelection("primary") h.Cursor.CopySelection(clipboard.PrimaryReg)
} }
} else { } else {
h.doubleClick = false h.doubleClick = false
@ -961,13 +961,9 @@ func (h *BufPane) Redo() bool {
// Copy the selection to the system clipboard // Copy the selection to the system clipboard
func (h *BufPane) Copy() bool { func (h *BufPane) Copy() bool {
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
h.Cursor.CopySelection("clipboard") h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true h.freshClip = true
if clipboard.Unsupported { InfoBar.Message("Copied selection")
InfoBar.Message("Copied selection (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied selection")
}
} }
h.Relocate() h.Relocate()
return true return true
@ -979,13 +975,9 @@ func (h *BufPane) CopyLine() bool {
return false return false
} else { } else {
h.Cursor.SelectLine() h.Cursor.SelectLine()
h.Cursor.CopySelection("clipboard") h.Cursor.CopySelection(clipboard.ClipboardReg)
h.freshClip = true h.freshClip = true
if clipboard.Unsupported { InfoBar.Message("Copied line")
InfoBar.Message("Copied line (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied line")
}
} }
h.Cursor.Deselect(true) h.Cursor.Deselect(true)
h.Relocate() h.Relocate()
@ -1000,10 +992,10 @@ func (h *BufPane) CutLine() bool {
} }
if h.freshClip == true { if h.freshClip == true {
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
if clip, err := clipboard.ReadAll("clipboard"); err != nil { if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
// messenger.Error(err) InfoBar.Error(err)
} else { } 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 { } 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 // Cut the selection to the system clipboard
func (h *BufPane) Cut() bool { func (h *BufPane) Cut() bool {
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
h.Cursor.CopySelection("clipboard") h.Cursor.CopySelection(clipboard.ClipboardReg)
h.Cursor.DeleteSelection() h.Cursor.DeleteSelection()
h.Cursor.ResetSelection() h.Cursor.ResetSelection()
h.freshClip = true h.freshClip = true
@ -1147,7 +1139,10 @@ func (h *BufPane) MoveLinesDown() bool {
// Paste whatever is in the system clipboard into the buffer // Paste whatever is in the system clipboard into the buffer
// Delete and paste if the user has a selection // Delete and paste if the user has a selection
func (h *BufPane) Paste() bool { 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.paste(clip)
h.Relocate() h.Relocate()
return true return true
@ -1155,7 +1150,10 @@ func (h *BufPane) Paste() bool {
// PastePrimary pastes from the primary clipboard (only use on linux) // PastePrimary pastes from the primary clipboard (only use on linux)
func (h *BufPane) PastePrimary() bool { 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.paste(clip)
h.Relocate() h.Relocate()
return true return true
@ -1177,11 +1175,7 @@ func (h *BufPane) paste(clip string) {
h.Buf.Insert(h.Cursor.Loc, clip) h.Buf.Insert(h.Cursor.Loc, clip)
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf) // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
h.freshClip = false h.freshClip = false
if clipboard.Unsupported { InfoBar.Message("Pasted clipboard")
InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
} else {
InfoBar.Message("Pasted clipboard")
}
} }
// JumpToMatchingBrace moves the cursor to the matching brace if it is // JumpToMatchingBrace moves the cursor to the matching brace if it is

View file

@ -8,6 +8,7 @@ import (
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/buffer" "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/config"
"github.com/zyedidia/micro/v2/internal/display" "github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua" 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) // h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// } // }
if h.Cursor.HasSelection() { if h.Cursor.HasSelection() {
h.Cursor.CopySelection("primary") h.Cursor.CopySelection(clipboard.PrimaryReg)
} }
h.mouseReleased = true h.mouseReleased = true
} }

View file

@ -13,6 +13,7 @@ import (
shellquote "github.com/kballard/go-shellquote" shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/buffer" "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/config"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell" "github.com/zyedidia/micro/v2/internal/shell"
@ -505,6 +506,12 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
} }
} else if option == "paste" { } else if option == "paste" {
screen.Screen.SetPaste(nativeValue.(bool)) screen.Screen.SetPaste(nativeValue.(bool))
} else if option == "clipboard" {
m := clipboard.SetMethod(nativeValue.(string))
err := clipboard.Initialize(m)
if err != nil {
return err
}
} else { } else {
for _, pl := range config.Plugins { for _, pl := range config.Plugins {
if option == pl.Name { if option == pl.Name {

View file

@ -186,6 +186,16 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
if strings.HasPrefix("doas", input) { if strings.HasPrefix("doas", input) {
suggestions = append(suggestions, "doas") 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) sort.Strings(suggestions)

View file

@ -4,7 +4,7 @@ import (
"errors" "errors"
"runtime" "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/display"
"github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell" "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() { 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") InfoBar.Message("Copied selection to clipboard")
} else if t.Status != shell.TTDone { } else if t.Status != shell.TTDone {
t.WriteString(event.EscSeq()) t.WriteString(event.EscSeq())

View file

@ -1,7 +1,7 @@
package buffer package buffer
import ( import (
"github.com/zyedidia/clipboard" "github.com/zyedidia/micro/v2/internal/clipboard"
"github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/micro/v2/internal/util"
) )
@ -125,10 +125,10 @@ func (c *Cursor) End() {
// CopySelection copies the user's selection to either "primary" // CopySelection copies the user's selection to either "primary"
// or "clipboard" // or "clipboard"
func (c *Cursor) CopySelection(target string) { func (c *Cursor) CopySelection(target clipboard.Register) {
if c.HasSelection() { if c.HasSelection() {
if target != "primary" || c.buf.Settings["useprimary"].(bool) { if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) {
clipboard.WriteAll(string(c.GetSelection()), target) clipboard.Write(string(c.GetSelection()), target)
} }
} }
} }

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -43,6 +43,7 @@ func init() {
// Options with validators // Options with validators
var optionValidators = map[string]optionValidator{ var optionValidators = map[string]optionValidator{
"autosave": validateNonNegativeValue, "autosave": validateNonNegativeValue,
"clipboard": validateClipboard,
"tabsize": validatePositiveValue, "tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue, "scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue, "scrollspeed": validateNonNegativeValue,
@ -322,6 +323,7 @@ func DefaultCommonSettings() map[string]interface{} {
// default values // default values
var DefaultGlobalOnlySettings = map[string]interface{}{ var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0), "autosave": float64(0),
"clipboard": "external",
"colorscheme": "default", "colorscheme": "default",
"divchars": "|-", "divchars": "|-",
"divreverse": true, "divreverse": true,
@ -450,6 +452,22 @@ func validateColorscheme(option string, value interface{}) error {
return nil 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 { func validateLineEnding(option string, value interface{}) error {
endingType, ok := value.(string) endingType, ok := value.(string)

View 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, the various methods for copying and pasting, how they work,
and the best methods for doing so over SSH. 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 # 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 paste events
Micro is an application that runs within the terminal. This means 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 for paste will access your local clipboard and send the text over
the network as a paste event, which is what you want. 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 The recommended method of copying is the following:
by default) to perform pastes. If on Linux, install `xclip` or
`xsel` beforehand. * 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 * If you are working over SSH, use the terminal keybinding
(Ctrl-Shift-v or Command-v) to perform pastes. If your terminal (Ctrl-Shift-c or Command-c) to perform copies. You must first disable
does not support bracketed paste, when performing a paste first the `mouse` option to perform a terminal selection, and you may wish
enable the `paste` option, and when finished disable the option. to disable line numbers and diff indicators (`ruler` and `diffgutter`
options) and close other splits. This method will only be able to copy
# Copying 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. Copying follows a similar discussion to the one above about pasting.
The primary difference is before performing a copy, the application 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` should first disable line numbers and diff indicators (turn off the `ruler`
and `diffgutter` options), otherwise they might be part of your selection and `diffgutter` options), otherwise they might be part of your selection
and copied. 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).

View file

@ -54,6 +54,25 @@ Here are the available options:
default value: `false` 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 * `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 specified column. This is useful if you want column 80 to be highlighted
special for example. special for example.