diff --git a/README.md b/README.md
index ad0ba77..f6d1e7a 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,14 @@ freeze main.go --output out.webp
freeze main.go --output out.{svg,png,webp}
```
+### Copy
+
+Copy the output image to your clipboard, so you can paste it anywhere.
+
+```bash
+freeze main.go --output clipboard
+```
+
### Font
Specify the font family, font size, and font line height of the output image.
diff --git a/configurations/full.json b/configurations/full.json
index 40d5131..36f5fde 100644
--- a/configurations/full.json
+++ b/configurations/full.json
@@ -29,5 +29,6 @@
"size": 14,
"ligatures": true
},
- "line_height": 1.2
-}
\ No newline at end of file
+ "line_height": 1.2,
+ "copy": false
+}
diff --git a/freeze_test.go b/freeze_test.go
index 0443255..716a5b5 100644
--- a/freeze_test.go
+++ b/freeze_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"github.com/aymanbagabas/go-udiff"
+ "golang.design/x/clipboard"
)
const binary = "./test/freeze-test"
@@ -58,6 +59,25 @@ func TestFreezeOutput(t *testing.T) {
}
}
+func TestFreezeCopy(t *testing.T) {
+ output := "clipboard"
+ defer os.Remove(output)
+
+ cmd := exec.Command(binary, "test/input/bubbletea.model", "-o", output, "--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers")
+ err := cmd.Run()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = clipboard.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+ png := clipboard.Read(clipboard.FmtImage)
+ if png == nil {
+ t.Fatal("clipboard is empty")
+ }
+}
+
func TestFreezeHelp(t *testing.T) {
out := bytes.Buffer{}
cmd := exec.Command(binary)
@@ -136,6 +156,11 @@ func TestFreezeConfigurations(t *testing.T) {
flags: []string{"--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers"},
output: "bubbletea",
},
+ {
+ input: "test/input/bubbletea.model",
+ flags: []string{"--language", "go", "--height", "800", "--width", "750", "--config", "full", "--window=false", "--show-line-numbers"},
+ output: "bubbletea-copy",
+ },
// {
// flags: []string{"--execute", "layout", "--height", "800", "--config", "full", "--margin", "50,10"},
// output: "composite-2",
diff --git a/go.mod b/go.mod
index 853cbb8..d574782 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.16
github.com/muesli/reflow v0.3.0
+ golang.design/x/clipboard v0.7.0
)
require (
@@ -47,6 +48,9 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
+ golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842 // indirect
+ golang.org/x/image v0.14.0 // indirect
+ golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.18.0 // indirect
diff --git a/go.sum b/go.sum
index 8853999..8f3043e 100644
--- a/go.sum
+++ b/go.sum
@@ -91,8 +91,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
+golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo=
+golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
+golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842 h1:kEvPiBVeT1JJGw/3THfe1W1zvTAvU1V6pCFV0icZvQs=
+golang.org/x/exp/shiny v0.0.0-20240506185415-9bf2ced13842/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
+golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
+golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/main.go b/main.go
index 3a0919d..7a0f54f 100644
--- a/main.go
+++ b/main.go
@@ -398,6 +398,9 @@ func main() {
istty := isatty.IsTerminal(os.Stdout.Fd())
+ if config.Output == "clipboard" { // convert to png because we can't copy svg to clipboard
+ config.Output = "clipboard.png"
+ }
switch {
case strings.HasSuffix(config.Output, ".png"):
// use libsvg conversion.
@@ -417,6 +420,10 @@ func main() {
default:
// output file specified.
if config.Output != "" {
+ _, err := doc.WriteToBytes()
+ if err != nil {
+ printErrorFatal("Unable to write output", err)
+ }
err = doc.WriteToFile(config.Output)
if err != nil {
printErrorFatal("Unable to write output", err)
diff --git a/png.go b/png.go
index b7d0184..6461120 100644
--- a/png.go
+++ b/png.go
@@ -5,12 +5,24 @@ import (
"context"
"os"
"os/exec"
+ "strings"
"github.com/beevik/etree"
"github.com/charmbracelet/freeze/font"
"github.com/kanrichan/resvg-go"
+ "golang.design/x/clipboard"
)
+func copyToClipboard(img []byte) error {
+ err := clipboard.Init()
+ if err != nil {
+ return err
+ }
+ clipboard.Write(clipboard.FmtImage, img)
+ clipboard.Read(clipboard.FmtImage)
+ return err
+}
+
func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {
_, err := exec.LookPath("rsvg-convert")
if err != nil {
@@ -27,6 +39,17 @@ func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {
rsvgConvert := exec.Command("rsvg-convert", "-o", output)
rsvgConvert.Stdin = bytes.NewReader(svg)
err = rsvgConvert.Run()
+ if err != nil {
+ return err
+ }
+ if strings.HasPrefix(output, "clipboard") {
+ png, err := os.ReadFile(output)
+ defer os.Remove(output) // nolint: errcheck
+ if err != nil {
+ return err
+ }
+ return copyToClipboard(png)
+ }
return err //nolint: wrapcheck
}
@@ -90,9 +113,8 @@ func resvgConvert(doc *etree.Document, w, h float64, output string) error {
return err //nolint: wrapcheck
}
- err = os.WriteFile(output, png, 0o600)
- if err != nil {
- return err //nolint: wrapcheck
+ if output == "clipboard" {
+ return copyToClipboard(png)
}
- return err //nolint: wrapcheck
+ return os.WriteFile(output, png, 0o600)
}
diff --git a/test/golden/svg/bubbletea-copy.svg b/test/golden/svg/bubbletea-copy.svg
new file mode 100644
index 0000000..40807bd
--- /dev/null
+++ b/test/golden/svg/bubbletea-copy.svg
@@ -0,0 +1,49 @@
+
+
+
diff --git a/test/golden/svg/bubbletea.svg b/test/golden/svg/bubbletea.svg
index 304e551..40807bd 100644
--- a/test/golden/svg/bubbletea.svg
+++ b/test/golden/svg/bubbletea.svg
@@ -42,8 +42,8 @@
30 }
31
32 func (m model) View() string {
- 33 return // ...
- 34 }
+ 33 return // ...
+ 34 }
diff --git a/test/golden/svg/overflow-line-numbers.svg b/test/golden/svg/overflow-line-numbers.svg
index b5461c9..1a263f7 100644
--- a/test/golden/svg/overflow-line-numbers.svg
+++ b/test/golden/svg/overflow-line-numbers.svg
@@ -77,7 +77,7 @@
65 {{- if eq .Arch "amd64" }}x86_64
66 {{- else if eq .Arch "386" }}i386
67 {{- else }}{{ .Arch }}{{ end }}
- 68 {{- with .Arm}}v{{ . }}{{ end }}
+ 68 {{- with .Arm}}v{{ . }}{{ end }}
69 wrap_in_directory: true
70 files:
71 - README*
@@ -93,7 +93,7 @@
81 file_name_template: >-
82 {{- trimsuffix .ConventionalFileName .ConventionalExtension -}}
83 {{- if and (eq .Arm "6") (eq .ConventionalExtension ".deb") }}6{{ end -}}
- 84 {{- .ConventionalExtension -}}
+ 84 {{- .ConventionalExtension -}}
85 license: MIT
86 formats:
87 - apk
diff --git a/test/golden/svg/tab.svg b/test/golden/svg/tab.svg
index c44874f..f67a879 100644
--- a/test/golden/svg/tab.svg
+++ b/test/golden/svg/tab.svg
@@ -12,10 +12,10 @@
package main
-// freeze/issues/50
-
-type Config struct { //nolint: revive
- Telegram struct {
+// freeze/issues/50
+
+type Config struct { //nolint: revive
+ Telegram struct {
Token string `env:"TG_TOKEN"`
ChatID string `env:"TG_CHAT"`
OwnerID string `env:"TG_ADMIN"`
@@ -32,8 +32,8 @@
Debug bool
}
-func Load() (*Config, error) { //nolint: revive
- var c Config
+func Load() (*Config, error) { //nolint: revive
+ var c Config
var err error
return &c, err
}
diff --git a/test/golden/svg/wrap.svg b/test/golden/svg/wrap.svg
index cca22cd..badc169 100644
--- a/test/golden/svg/wrap.svg
+++ b/test/golden/svg/wrap.svg
@@ -14,8 +14,8 @@
import "fmt"
-// freeze/issues/14
-
+// freeze/issues/14
+
func main() {
fmt.Println("This is a really long line that is going to go over the 80
character limit. This is a really long line that is going to go over the 80