mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
Merge 70e96c6a49 into f023b42c89
This commit is contained in:
commit
d03334e893
26 changed files with 640 additions and 18 deletions
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Container
|
## Container
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: entfernen
|
<kbd>d</kbd>: entfernen
|
||||||
<kbd>e</kbd>: hide/show stopped containers
|
<kbd>e</kbd>: hide/show stopped containers
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Containers
|
## Containers
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: remove
|
<kbd>d</kbd>: remove
|
||||||
<kbd>e</kbd>: hide/show stopped containers
|
<kbd>e</kbd>: hide/show stopped containers
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Contenedores
|
## Contenedores
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: borrar
|
<kbd>d</kbd>: borrar
|
||||||
<kbd>e</kbd>: esconder/mostrar contenedores parados
|
<kbd>e</kbd>: esconder/mostrar contenedores parados
|
||||||
<kbd>p</kbd>: pausa
|
<kbd>p</kbd>: pausa
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Conteneurs
|
## Conteneurs
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: supprimer
|
<kbd>d</kbd>: supprimer
|
||||||
<kbd>e</kbd>: cacher/montrer les conteneurs arrêtés
|
<kbd>e</kbd>: cacher/montrer les conteneurs arrêtés
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Containers
|
## Containers
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: verwijder
|
<kbd>d</kbd>: verwijder
|
||||||
<kbd>e</kbd>: verberg gestopte containers
|
<kbd>e</kbd>: verberg gestopte containers
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Kontenery
|
## Kontenery
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: usuń
|
<kbd>d</kbd>: usuń
|
||||||
<kbd>e</kbd>: hide/show stopped containers
|
<kbd>e</kbd>: hide/show stopped containers
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Contêineres
|
## Contêineres
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: remover
|
<kbd>d</kbd>: remover
|
||||||
<kbd>e</kbd>: ocultar/mostrar contêineres parados
|
<kbd>e</kbd>: ocultar/mostrar contêineres parados
|
||||||
<kbd>p</kbd>: pausar
|
<kbd>p</kbd>: pausar
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## Konteynerler
|
## Konteynerler
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: kaldır
|
<kbd>d</kbd>: kaldır
|
||||||
<kbd>e</kbd>: hide/show stopped containers
|
<kbd>e</kbd>: hide/show stopped containers
|
||||||
<kbd>p</kbd>: pause
|
<kbd>p</kbd>: pause
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||||
## 容器
|
## 容器
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
|
<kbd>y</kbd>: copy container ID
|
||||||
<kbd>d</kbd>: 移除
|
<kbd>d</kbd>: 移除
|
||||||
<kbd>e</kbd>: 隐藏/显示已停止的容器
|
<kbd>e</kbd>: 隐藏/显示已停止的容器
|
||||||
<kbd>p</kbd>: 暂停
|
<kbd>p</kbd>: 暂停
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -31,6 +31,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E=
|
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E=
|
||||||
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
|
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
|
||||||
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q=
|
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q=
|
||||||
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
|
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
|
||||||
|
"github.com/atotto/clipboard"
|
||||||
"github.com/jesseduffield/kill"
|
"github.com/jesseduffield/kill"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
|
|
@ -373,3 +374,20 @@ func (c *OSCommand) Kill(cmd *exec.Cmd) error {
|
||||||
func (c *OSCommand) PrepareForChildren(cmd *exec.Cmd) {
|
func (c *OSCommand) PrepareForChildren(cmd *exec.Cmd) {
|
||||||
kill.PrepareForChildren(cmd)
|
kill.PrepareForChildren(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OSCommand) CopyToClipboard(str string) error {
|
||||||
|
escaped := strings.Replace(str, "\n", "\\n", -1)
|
||||||
|
truncated := utils.TruncateWithEllipsis(escaped, 40)
|
||||||
|
|
||||||
|
c.Log.Debug(utils.ResolvePlaceholderString("Copying '{{str}}' to clipboard", map[string]string{"str": truncated}))
|
||||||
|
|
||||||
|
// Not needed yet
|
||||||
|
//if c.UserConfig().OS.CopyToClipboardCmd != "" {
|
||||||
|
// cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
|
||||||
|
// "text": c.Cmd.Quote(str),
|
||||||
|
// })
|
||||||
|
// return c.Cmd.NewShell(cmdStr).Run()
|
||||||
|
//}
|
||||||
|
|
||||||
|
return clipboard.WriteAll(str)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ type statusManager struct {
|
||||||
statuses []appStatus
|
statuses []appStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TickIntervalMs = 50
|
||||||
|
)
|
||||||
|
|
||||||
func (m *statusManager) removeStatus(name string) {
|
func (m *statusManager) removeStatus(name string) {
|
||||||
newStatuses := []appStatus{}
|
newStatuses := []appStatus{}
|
||||||
for _, status := range m.statuses {
|
for _, status := range m.statuses {
|
||||||
|
|
@ -27,12 +31,12 @@ func (m *statusManager) removeStatus(name string) {
|
||||||
m.statuses = newStatuses
|
m.statuses = newStatuses
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *statusManager) addWaitingStatus(name string) {
|
func (m *statusManager) addStatus(name string, statusType string, duration int) {
|
||||||
m.removeStatus(name)
|
m.removeStatus(name)
|
||||||
newStatus := appStatus{
|
newStatus := appStatus{
|
||||||
name: name,
|
name: name,
|
||||||
statusType: "waiting",
|
statusType: statusType,
|
||||||
duration: 0,
|
duration: duration,
|
||||||
}
|
}
|
||||||
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
||||||
}
|
}
|
||||||
|
|
@ -41,36 +45,26 @@ func (m *statusManager) getStatusString() string {
|
||||||
if len(m.statuses) == 0 {
|
if len(m.statuses) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
topStatus := m.statuses[0]
|
topStatus := m.statuses[0]
|
||||||
if topStatus.statusType == "waiting" {
|
if topStatus.statusType == "waiting" {
|
||||||
return topStatus.name + " " + utils.Loader()
|
return topStatus.name + " " + utils.Loader()
|
||||||
|
} else if topStatus.statusType == "info" {
|
||||||
|
return topStatus.name
|
||||||
}
|
}
|
||||||
|
|
||||||
return topStatus.name
|
return topStatus.name
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||||
func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
||||||
go func() {
|
go func() {
|
||||||
gui.statusManager.addWaitingStatus(name)
|
go gui.Notify(name, "waiting", 0)()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
gui.statusManager.removeStatus(name)
|
gui.statusManager.removeStatus(name)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(time.Millisecond * 50)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
appStatus := gui.statusManager.getStatusString()
|
|
||||||
if appStatus == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
|
|
||||||
gui.Log.Warn(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := f(); err != nil {
|
if err := f(); err != nil {
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
gui.g.Update(func(g *gocui.Gui) error {
|
||||||
return gui.createErrorPanel(err.Error())
|
return gui.createErrorPanel(err.Error())
|
||||||
|
|
@ -80,3 +74,37 @@ func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify sends static notification to the user.
|
||||||
|
// duration of 0 will disable the self-cleaning of the notification
|
||||||
|
func (gui *Gui) Notify(name string, statusType string, duration int) func() {
|
||||||
|
return func() {
|
||||||
|
gui.statusManager.addStatus(name, statusType, duration)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
gui.statusManager.removeStatus(name)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Millisecond * TickIntervalMs)
|
||||||
|
tickCount := 0
|
||||||
|
endTick := duration * 1000 / TickIntervalMs
|
||||||
|
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
tickCount++
|
||||||
|
// If no duration, don't terminate early
|
||||||
|
if duration > 0 && tickCount >= endTick {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appStatus := gui.statusManager.getStatusString()
|
||||||
|
if appStatus == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gui.renderString(gui.g, "appStatus", appStatus); err != nil {
|
||||||
|
gui.Log.Warn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -252,8 +252,10 @@ func (gui *Gui) refreshContainersAndServices() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep track of current service selected so that we can reposition our cursor if it moves position in the list
|
// keep track of current service selected so that we can reposition our cursor if it moves position in the list
|
||||||
|
gui.DockerCommand.ServiceMutex.Lock()
|
||||||
originalSelectedLineIdx := gui.Panels.Services.SelectedIdx
|
originalSelectedLineIdx := gui.Panels.Services.SelectedIdx
|
||||||
selectedService, isServiceSelected := gui.Panels.Services.List.TryGet(originalSelectedLineIdx)
|
selectedService, isServiceSelected := gui.Panels.Services.List.TryGet(originalSelectedLineIdx)
|
||||||
|
gui.DockerCommand.ServiceMutex.Unlock()
|
||||||
|
|
||||||
containers, services, err := gui.DockerCommand.RefreshContainersAndServices(
|
containers, services, err := gui.DockerCommand.RefreshContainersAndServices(
|
||||||
gui.Panels.Services.List.GetAllItems(),
|
gui.Panels.Services.List.GetAllItems(),
|
||||||
|
|
@ -358,6 +360,18 @@ func (gui *Gui) PauseContainer(container *commands.Container) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleCopyContainerId(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
ctr, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedStatusText := fmt.Sprintf("Copied %s to clipboard", utils.TruncateWithEllipsis(ctr.ID, 10))
|
||||||
|
go gui.Notify(formattedStatusText, "info", 3)()
|
||||||
|
|
||||||
|
return gui.OSCommand.CopyToClipboard(ctr.ID)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
|
||||||
ctr, err := gui.Panels.Containers.GetSelectedItem()
|
ctr, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleDonate,
|
Handler: gui.handleDonate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "containers",
|
||||||
|
Key: 'y',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleCopyContainerId,
|
||||||
|
Description: gui.Tr.CopyContainerId,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "containers",
|
ViewName: "containers",
|
||||||
Key: 'd',
|
Key: 'd',
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ type TranslationSet struct {
|
||||||
FilterList string
|
FilterList string
|
||||||
OpenInBrowser string
|
OpenInBrowser string
|
||||||
SortContainersByState string
|
SortContainersByState string
|
||||||
|
CopyContainerId string
|
||||||
|
|
||||||
LogsTitle string
|
LogsTitle string
|
||||||
ConfigTitle string
|
ConfigTitle string
|
||||||
|
|
@ -216,6 +217,7 @@ func englishSet() TranslationSet {
|
||||||
FilterList: "filter list",
|
FilterList: "filter list",
|
||||||
OpenInBrowser: "open in browser (first port is http)",
|
OpenInBrowser: "open in browser (first port is http)",
|
||||||
SortContainersByState: "sort containers by state",
|
SortContainersByState: "sort containers by state",
|
||||||
|
CopyContainerId: "copy container ID",
|
||||||
|
|
||||||
GlobalTitle: "Global",
|
GlobalTitle: "Global",
|
||||||
MainTitle: "Main",
|
MainTitle: "Main",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
|
@ -410,3 +411,23 @@ func marshalIntoFormat(data interface{}, format string) ([]byte, error) {
|
||||||
return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
|
return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringWidth(s string) int {
|
||||||
|
// We are intentionally not using a range loop here, because that would
|
||||||
|
// convert the characters to runes, which is unnecessary work in this case.
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] > unicode.MaxASCII {
|
||||||
|
return runewidth.StringWidth(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TruncateWithEllipsis returns a string, truncated to a certain length, with an ellipsis
|
||||||
|
func TruncateWithEllipsis(str string, limit int) string {
|
||||||
|
if StringWidth(str) > limit && limit <= 2 {
|
||||||
|
return strings.Repeat(".", limit)
|
||||||
|
}
|
||||||
|
return runewidth.Truncate(str, limit, "…")
|
||||||
|
}
|
||||||
|
|
|
||||||
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
- windows
|
||||||
|
|
||||||
|
go:
|
||||||
|
- go1.13.x
|
||||||
|
- go1.x
|
||||||
|
|
||||||
|
services:
|
||||||
|
- xvfb
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- export DISPLAY=:99.0
|
||||||
|
|
||||||
|
script:
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
|
||||||
|
- go test -v .
|
||||||
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
|
||||||
|
- go test -v .
|
||||||
27
vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
Normal file
27
vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2013 Ato Araki. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of @atotto. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
Normal file
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
[](https://travis-ci.org/atotto/clipboard)
|
||||||
|
|
||||||
|
[](http://godoc.org/github.com/atotto/clipboard)
|
||||||
|
|
||||||
|
# Clipboard for Go
|
||||||
|
|
||||||
|
Provide copying and pasting to the Clipboard for Go.
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
$ go get github.com/atotto/clipboard
|
||||||
|
|
||||||
|
Platforms:
|
||||||
|
|
||||||
|
* OSX
|
||||||
|
* Windows 7 (probably work on other Windows)
|
||||||
|
* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
|
||||||
|
|
||||||
|
|
||||||
|
Document:
|
||||||
|
|
||||||
|
* http://godoc.org/github.com/atotto/clipboard
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
* Text string only
|
||||||
|
* UTF-8 text encoding only (no conversion)
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
* Clipboard watcher(?)
|
||||||
|
|
||||||
|
## Commands:
|
||||||
|
|
||||||
|
paste shell command:
|
||||||
|
|
||||||
|
$ go get github.com/atotto/clipboard/cmd/gopaste
|
||||||
|
$ # example:
|
||||||
|
$ gopaste > document.txt
|
||||||
|
|
||||||
|
copy shell command:
|
||||||
|
|
||||||
|
$ go get github.com/atotto/clipboard/cmd/gocopy
|
||||||
|
$ # example:
|
||||||
|
$ cat document.txt | gocopy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
Normal file
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2013 @atotto. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package clipboard read/write on clipboard
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
// ReadAll read string from clipboard
|
||||||
|
func ReadAll() (string, error) {
|
||||||
|
return readAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteAll write string to clipboard
|
||||||
|
func WriteAll(text string) error {
|
||||||
|
return writeAll(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsupported might be set true during clipboard init, to help callers decide
|
||||||
|
// whether or not to offer clipboard options.
|
||||||
|
var Unsupported bool
|
||||||
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
Normal file
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2013 @atotto. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pasteCmdArgs = "pbpaste"
|
||||||
|
copyCmdArgs = "pbcopy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPasteCommand() *exec.Cmd {
|
||||||
|
return exec.Command(pasteCmdArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCopyCommand() *exec.Cmd {
|
||||||
|
return exec.Command(copyCmdArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAll() (string, error) {
|
||||||
|
pasteCmd := getPasteCommand()
|
||||||
|
out, err := pasteCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAll(text string) error {
|
||||||
|
copyCmd := getCopyCommand()
|
||||||
|
in, err := copyCmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := in.Write([]byte(text)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := in.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return copyCmd.Wait()
|
||||||
|
}
|
||||||
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
Normal file
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2013 @atotto. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build plan9
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readAll() (string, error) {
|
||||||
|
f, err := os.Open("/dev/snarf")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
str, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAll(text string) error {
|
||||||
|
f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Write([]byte(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
Normal file
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright 2013 @atotto. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build freebsd linux netbsd openbsd solaris dragonfly
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xsel = "xsel"
|
||||||
|
xclip = "xclip"
|
||||||
|
powershellExe = "powershell.exe"
|
||||||
|
clipExe = "clip.exe"
|
||||||
|
wlcopy = "wl-copy"
|
||||||
|
wlpaste = "wl-paste"
|
||||||
|
termuxClipboardGet = "termux-clipboard-get"
|
||||||
|
termuxClipboardSet = "termux-clipboard-set"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Primary bool
|
||||||
|
trimDos bool
|
||||||
|
|
||||||
|
pasteCmdArgs []string
|
||||||
|
copyCmdArgs []string
|
||||||
|
|
||||||
|
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
|
||||||
|
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
|
||||||
|
|
||||||
|
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
|
||||||
|
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
|
||||||
|
|
||||||
|
powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
|
||||||
|
clipExeCopyArgs = []string{clipExe}
|
||||||
|
|
||||||
|
wlpasteArgs = []string{wlpaste, "--no-newline"}
|
||||||
|
wlcopyArgs = []string{wlcopy}
|
||||||
|
|
||||||
|
termuxPasteArgs = []string{termuxClipboardGet}
|
||||||
|
termuxCopyArgs = []string{termuxClipboardSet}
|
||||||
|
|
||||||
|
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||||
|
pasteCmdArgs = wlpasteArgs
|
||||||
|
copyCmdArgs = wlcopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(wlcopy); err == nil {
|
||||||
|
if _, err := exec.LookPath(wlpaste); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = xclipPasteArgs
|
||||||
|
copyCmdArgs = xclipCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(xclip); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = xselPasteArgs
|
||||||
|
copyCmdArgs = xselCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(xsel); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = termuxPasteArgs
|
||||||
|
copyCmdArgs = termuxCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
|
||||||
|
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = powershellExePasteArgs
|
||||||
|
copyCmdArgs = clipExeCopyArgs
|
||||||
|
trimDos = true
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(clipExe); err == nil {
|
||||||
|
if _, err := exec.LookPath(powershellExe); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Unsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPasteCommand() *exec.Cmd {
|
||||||
|
if Primary {
|
||||||
|
pasteCmdArgs = pasteCmdArgs[:1]
|
||||||
|
}
|
||||||
|
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCopyCommand() *exec.Cmd {
|
||||||
|
if Primary {
|
||||||
|
copyCmdArgs = copyCmdArgs[:1]
|
||||||
|
}
|
||||||
|
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAll() (string, error) {
|
||||||
|
if Unsupported {
|
||||||
|
return "", missingCommands
|
||||||
|
}
|
||||||
|
pasteCmd := getPasteCommand()
|
||||||
|
out, err := pasteCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result := string(out)
|
||||||
|
if trimDos && len(result) > 1 {
|
||||||
|
result = result[:len(result)-2]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAll(text string) error {
|
||||||
|
if Unsupported {
|
||||||
|
return missingCommands
|
||||||
|
}
|
||||||
|
copyCmd := getCopyCommand()
|
||||||
|
in, err := copyCmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := in.Write([]byte(text)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := in.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return copyCmd.Wait()
|
||||||
|
}
|
||||||
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
Normal file
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2013 @atotto. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cfUnicodetext = 13
|
||||||
|
gmemMoveable = 0x0002
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
user32 = syscall.MustLoadDLL("user32")
|
||||||
|
isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
|
||||||
|
openClipboard = user32.MustFindProc("OpenClipboard")
|
||||||
|
closeClipboard = user32.MustFindProc("CloseClipboard")
|
||||||
|
emptyClipboard = user32.MustFindProc("EmptyClipboard")
|
||||||
|
getClipboardData = user32.MustFindProc("GetClipboardData")
|
||||||
|
setClipboardData = user32.MustFindProc("SetClipboardData")
|
||||||
|
|
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||||
|
globalAlloc = kernel32.NewProc("GlobalAlloc")
|
||||||
|
globalFree = kernel32.NewProc("GlobalFree")
|
||||||
|
globalLock = kernel32.NewProc("GlobalLock")
|
||||||
|
globalUnlock = kernel32.NewProc("GlobalUnlock")
|
||||||
|
lstrcpy = kernel32.NewProc("lstrcpyW")
|
||||||
|
)
|
||||||
|
|
||||||
|
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
||||||
|
func waitOpenClipboard() error {
|
||||||
|
started := time.Now()
|
||||||
|
limit := started.Add(time.Second)
|
||||||
|
var r uintptr
|
||||||
|
var err error
|
||||||
|
for time.Now().Before(limit) {
|
||||||
|
r, _, err = openClipboard.Call(0)
|
||||||
|
if r != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAll() (string, error) {
|
||||||
|
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||||
|
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err := waitOpenClipboard()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
h, _, err := getClipboardData.Call(cfUnicodetext)
|
||||||
|
if h == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
l, _, err := globalLock.Call(h)
|
||||||
|
if l == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
||||||
|
|
||||||
|
r, _, err := globalUnlock.Call(h)
|
||||||
|
if r == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
closed, _, err := closeClipboard.Call()
|
||||||
|
if closed == 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAll(text string) error {
|
||||||
|
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
||||||
|
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
err := waitOpenClipboard()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := emptyClipboard.Call(0)
|
||||||
|
if r == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := syscall.StringToUTF16(text)
|
||||||
|
|
||||||
|
// "If the hMem parameter identifies a memory object, the object must have
|
||||||
|
// been allocated using the function with the GMEM_MOVEABLE flag."
|
||||||
|
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
||||||
|
if h == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if h != 0 {
|
||||||
|
globalFree.Call(h)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
l, _, err := globalLock.Call(h)
|
||||||
|
if l == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
||||||
|
if r == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err = globalUnlock.Call(h)
|
||||||
|
if r == 0 {
|
||||||
|
if err.(syscall.Errno) != 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err = setClipboardData.Call(cfUnicodetext, h)
|
||||||
|
if r == 0 {
|
||||||
|
_, _, _ = closeClipboard.Call()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h = 0 // suppress deferred cleanup
|
||||||
|
closed, _, err := closeClipboard.Call()
|
||||||
|
if closed == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
|
@ -8,6 +8,9 @@ github.com/Microsoft/go-winio/pkg/guid
|
||||||
# github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
|
# github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
|
||||||
## explicit
|
## explicit
|
||||||
github.com/OpenPeeDeeP/xdg
|
github.com/OpenPeeDeeP/xdg
|
||||||
|
# github.com/atotto/clipboard v0.1.4
|
||||||
|
## explicit
|
||||||
|
github.com/atotto/clipboard
|
||||||
# github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
# github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/boz/go-throttle
|
github.com/boz/go-throttle
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue