Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clipboard support & feature to copy container id #584

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Container

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: entfernen
<kbd>e</kbd>: hide/show stopped containers
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Containers

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: remove
<kbd>e</kbd>: hide/show stopped containers
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Contenedores

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: borrar
<kbd>e</kbd>: esconder/mostrar contenedores parados
<kbd>p</kbd>: pausa
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Conteneurs

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: supprimer
<kbd>e</kbd>: cacher/montrer les conteneurs arrêtés
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Containers

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: verwijder
<kbd>e</kbd>: verberg gestopte containers
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Kontenery

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: usuń
<kbd>e</kbd>: hide/show stopped containers
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Contêineres

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: remover
<kbd>e</kbd>: ocultar/mostrar contêineres parados
<kbd>p</kbd>: pausar
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_tr.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Konteynerler

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: kaldır
<kbd>e</kbd>: hide/show stopped containers
<kbd>p</kbd>: pause
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 容器

<pre>
<kbd></kbd>: copy container id
<kbd>d</kbd>: 移除
<kbd>e</kbd>: 隐藏/显示已停止的容器
<kbd>p</kbd>: 暂停
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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/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/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/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
Expand Down
10 changes: 10 additions & 0 deletions pkg/commands/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/go-errors/errors"

"github.com/atotto/clipboard"
"github.com/jesseduffield/kill"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils"
Expand Down Expand Up @@ -373,3 +374,12 @@ func (c *OSCommand) Kill(cmd *exec.Cmd) error {
func (c *OSCommand) PrepareForChildren(cmd *exec.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}))

return clipboard.WriteAll(str)
}
17 changes: 17 additions & 0 deletions pkg/gui/app_status_manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gui

import (
"sync"
"time"

"github.com/jesseduffield/gocui"
Expand All @@ -15,10 +16,15 @@ type appStatus struct {

type statusManager struct {
statuses []appStatus
lock *sync.Mutex
}

func (m *statusManager) removeStatus(name string) {
newStatuses := []appStatus{}

m.lock.Lock()
defer m.lock.Unlock()

for _, status := range m.statuses {
if status.name != name {
newStatuses = append(newStatuses, status)
Expand All @@ -28,6 +34,9 @@ func (m *statusManager) removeStatus(name string) {
}

func (m *statusManager) addWaitingStatus(name string) {
m.lock.Lock()
defer m.lock.Unlock()

m.removeStatus(name)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that removeStatus tries to obtain the lock that addWaitingStatus already holds, wouldn't that cause a deadlock?

newStatus := appStatus{
name: name,
Expand All @@ -38,6 +47,9 @@ func (m *statusManager) addWaitingStatus(name string) {
}

func (m *statusManager) getStatusString() string {
m.lock.Lock()
defer m.lock.Unlock()

if len(m.statuses) == 0 {
return ""
}
Expand All @@ -48,6 +60,11 @@ func (m *statusManager) getStatusString() string {
return topStatus.name
}

// WithStaticWaitingStatus shows a waiting status for a specific duration
func (gui *Gui) WithStaticWaitingStatus(name string, duration time.Duration) error {
return gui.WithWaitingStatus(name, func() error { time.Sleep(duration); return nil })
}

// 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 {
go func() {
Expand Down
14 changes: 14 additions & 0 deletions pkg/gui/containers_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,20 @@ 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
}

err = gui.WithStaticWaitingStatus(fmt.Sprintf(gui.Tr.CopyContainerIdStatus, utils.TruncateWithEllipsis(ctr.ID, 10)), time.Second*2)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really a waiting status: it's a toast message. We should update the naming to reflect that (and I haven't tested it locally but it's important we don't show a loading spinner on this)

if err != nil {
return err
}

return gui.OSCommand.CopyToClipboard(ctr.ID)
}

func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
ctr, err := gui.Panels.Containers.GetSelectedItem()
if err != nil {
Expand Down
9 changes: 6 additions & 3 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"os"
"strings"
"sync"
"time"

"github.com/docker/docker/api/types/events"
Expand Down Expand Up @@ -146,9 +147,11 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
State: initialState,
Config: config,
Tr: tr,
statusManager: &statusManager{},
taskManager: tasks.NewTaskManager(log, tr),
ErrorChan: errorChan,
statusManager: &statusManager{
lock: &sync.Mutex{},
},
taskManager: tasks.NewTaskManager(log, tr),
ErrorChan: errorChan,
}

deadlock.Opts.Disable = !gui.Config.Debug
Expand Down
7 changes: 7 additions & 0 deletions pkg/gui/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleDonate,
},
{
ViewName: "containers",
Key: gocui.KeyCtrlO,
Modifier: gocui.ModNone,
Handler: gui.handleCopyContainerId,
Description: gui.Tr.CopyContainerId,
},
{
ViewName: "containers",
Key: 'd',
Expand Down
4 changes: 4 additions & 0 deletions pkg/i18n/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ type TranslationSet struct {
ViewLogs string
UpProject string
DownProject string
CopyContainerId string
CopyContainerIdStatus string
ServicesTitle string
ContainersTitle string
StandaloneContainersTitle string
Expand Down Expand Up @@ -190,6 +192,8 @@ func englishSet() TranslationSet {
ViewLogs: "view logs",
UpProject: "up project",
DownProject: "down project",
CopyContainerId: "copy container id",
CopyContainerIdStatus: "Copied %s to clipboard",
RemoveImage: "remove image",
RemoveVolume: "remove volume",
RemoveNetwork: "remove network",
Expand Down
21 changes: 21 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sort"
"strings"
"time"
"unicode"

"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
Expand Down Expand Up @@ -410,3 +411,23 @@ func marshalIntoFormat(data interface{}, format string) ([]byte, error) {
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 changes: 22 additions & 0 deletions vendor/github.com/atotto/clipboard/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions vendor/github.com/atotto/clipboard/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions vendor/github.com/atotto/clipboard/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions vendor/github.com/atotto/clipboard/clipboard.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading